diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 4271b92c..61ebfd5b 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -195,7 +195,7 @@ jobs: commit_hash: ${{ needs.context.outputs.commit_hash }} git_repo_branch: ${{ needs.context.outputs.git_repo_branch }} gh_auth_token: ${{ secrets.GH_AUTH_TOKEN }} - app_instance: eval + app_instance: dev - name: 'Surface context from executed build step' id: context diff --git a/.gitignore b/.gitignore index 6cdf7358..1177047d 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,9 @@ spotseeker_web # IDE files .idea/ +# vscode files +.vscode/ + # pipenv Pipfile Pipfile.lock \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 00f0db18..1a55804f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -ARG DJANGO_CONTAINER_VERSION=1.3.8 +ARG DJANGO_CONTAINER_VERSION=1.4.1 FROM gcr.io/uwit-mci-axdd/django-container:${DJANGO_CONTAINER_VERSION} as app-container diff --git a/docker-compose.yml b/docker-compose.yml index f03b8ef0..8dbbc03c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,7 +11,7 @@ services: - "${PORT:-8000}:8000" environment: ENV: localdev - AUTH_MODULE: ${AUTH_MODULE:-all_ok} + SPOTSEEKER_OAUTH_ENABLED: ${SPOTSEEKER_OAUTH_ENABLED} SPOTSEEKER_AUTH_ADMINS: ${SPOTSEEKER_AUTH_ADMINS} SPOTSEEKER_WEB_SERVER_HOST: ${SPOTSEEKER_WEB_SERVER_HOST} SPOTSEEKER_WEB_OAUTH_KEY: ${SPOTSEEKER_WEB_OAUTH_KEY} diff --git a/docker/dev-values.yml b/docker/dev-values.yml new file mode 100644 index 00000000..a4cfef1c --- /dev/null +++ b/docker/dev-values.yml @@ -0,0 +1,156 @@ +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 1 +ingress: + enabled: true + tls: + spotseeker: + secretName: dev.api.scout.uw.edu-ingress-cert + hosts: + - dev.api.scout.uw.edu + hosts: + spotseeker: + host: dev.api.scout.uw.edu + paths: + - "/" + annotations: + cert-manager.io/cluster-issuer: letsencrypt + nginx.ingress.kubernetes.io/client-body-buffer-size: "16K" + nginx.ingress.kubernetes.io/proxy-body-size: "20m" + nginx.ingress.kubernetes.io/proxy-read-timeout: "500" + nginx.ingress.kubernetes.io/ssl-ciphers: "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256" +lifecycle: + enabled: true + preStop: + enabled: true +affinity: + podsSpanNodes: true +readiness: + enabled: true +securityPolicy: + enabled: true +externalService: + enabled: true + name: spotseeker-dev-db-service + type: ClusterIP + serviceAddress: 172.18.0.196 + servicePort: 3306 +database: + engine: mysql + name: api_dev + hostname: spotseeker-dev-db-service + secretName: dev.api.scout.uw.edu-sql-secrets +repo: spotseeker +instance: dev +image: + repository: gcr.io/uwit-mci-axdd/spotseeker + tag: IMAGE_TAG +memcached: + enabled: true + replicaCount: 1 + updateStrategy: + type: RollingUpdate +gcsCredentials: + mounted: true + secretName: dev.api.scout.uw.edu-gc-service-credentials +cronjob: + enabled: false + jobs: + - name: sync-techloan + schedule: "0,15,30,45 * * * *" + command: ["/scripts/management_command.sh"] + args: ["sync_techloan"] +daemon: + enabled: false # cronjob is probably more appropriate + daemons: + - name: sync-techloan + replicaCount: 1 + command: ["/scripts/management_daemon.sh"] + args: ["--delay", "300", "sync_techloan"] +environmentVariables: + - name: AUTH_MODULE + value: oauth + - name: CLUSTER_CNAME + value: dev.api.scout.uw.edu + - name: ENV + value: dev + - name: WEBSERVER + value: nginx + - name: CACHE_MAX_ENTRIES + value: "1000" + - name: CACHE_TIMEOUT + value: "86400" +externalSecrets: + enabled: true + secrets: + - name: dev.api.scout.uw.edu-secrets + externalKey: axdd/kv/data/scout/dev-api/secrets + data: + - name: django-secret + property: django-secret + - name: spotseeker-auth-admins + property: spotseeker-auth-admins + - name: storage-bucket-name + property: storage-bucket-name + - name: storage-project-id + property: storage-project-id + - name: spotseeker-techloan-url + property: spotseeker-techloan-url + - name: spotseeker-web-server-host + property: spotseeker-web-server-host + - name: spotseeker-web-oauth-key + property: spotseeker-web-oauth-key + - name: spotseeker-web-oauth-secret + property: spotseeker-web-oauth-secret + - name: spotseeker-web-oauth-user + property: spotseeker-web-oauth-user + - name: dev.api.scout.uw.edu-sql-secrets + externalKey: axdd/kv/data/scout/common/sql-secrets + data: + - name: username + property: username + - name: password + property: password + - name: dev.api.scout.uw.edu-gc-service-credentials + externalKey: axdd/kv/data/scout/common/gc-service-credentials + data: + - name: credentials.json + property: credentials.json +environmentVariablesSecrets: + djangoSecret: + name: DJANGO_SECRET + secretName: dev.api.scout.uw.edu-secrets + secretKey: django-secret + spotseekerAuthAdmins: + name: SPOTSEEKER_AUTH_ADMINS + secretName: dev.api.scout.uw.edu-secrets + secretKey: spotseeker-auth-admins + storageBucketName: + name: STORAGE_BUCKET_NAME + secretName: dev.api.scout.uw.edu-secrets + secretKey: storage-bucket-name + storageProjectId: + name: STORAGE_PROJECT_ID + secretName: dev.api.scout.uw.edu-secrets + secretKey: storage-project-id + spotseekerTechloanUrl: + name: SPOTSEEKER_TECHLOAN_URL + secretName: dev.api.scout.uw.edu-secrets + secretKey: spotseeker-techloan-url + spotseekerWebServerHost: + name: SPOTSEEKER_WEB_SERVER_HOST + secretName: dev.api.scout.uw.edu-secrets + secretKey: spotseeker-web-server-host + spotseekerWebOauthKey: + name: SPOTSEEKER_WEB_OAUTH_KEY + secretName: dev.api.scout.uw.edu-secrets + secretKey: spotseeker-web-oauth-key + spotseekerWebOauthSecret: + name: SPOTSEEKER_WEB_OAUTH_SECRET + secretName: dev.api.scout.uw.edu-secrets + secretKey: spotseeker-web-oauth-secret + spotseekerWebOauthUser: + name: SPOTSEEKER_WEB_OAUTH_USER + secretName: dev.api.scout.uw.edu-secrets + secretKey: spotseeker-web-oauth-user diff --git a/docker/prod-values.yml b/docker/prod-values.yml index 132dfc9e..cc092111 100644 --- a/docker/prod-values.yml +++ b/docker/prod-values.yml @@ -71,8 +71,8 @@ daemon: command: ["/scripts/management_daemon.sh"] args: ["--delay", "300", "sync_techloan"] environmentVariables: - - name: AUTH_MODULE - value: oauth + - name: SPOTSEEKER_OAUTH_ENABLED + value: true - name: CLUSTER_CNAME value: api.scout.uw.edu - name: ENV diff --git a/docker/settings.py b/docker/settings.py index 7d44a69d..c8e0c492 100644 --- a/docker/settings.py +++ b/docker/settings.py @@ -8,15 +8,36 @@ DEBUG = False INSTALLED_APPS += [ - "oauth_provider", "spotseeker_server", + "oauth2_provider", + "corsheaders", "django_extensions", ] MIDDLEWARE += [ "spotseeker_server.logger.oauth.LogMiddleware", + "corsheaders.middleware.CorsMiddleware", ] +AUTH_USER_MODEL = "spotseeker_server.Client" + +AUTHENTICATION_BACKENDS = ( + "oauth2_provider.backends.OAuth2Backend", + "django.contrib.auth.backends.ModelBackend", +) + +CORS_ORIGIN_ALLOW_ALL = DEBUG +if os.getenv("ENV", "localdev") == "prod": + CORS_ALLOWED_ORIGINS = [ + "https://scout.uw.edu", + "https://manager.scout.uw.edu", + ] +else: + CORS_ALLOWED_ORIGINS = [ + "https://test.scout.uw.edu", + "https://test.manager.scout.uw.edu", + ] + # django storages settings if not DEBUG: DEFAULT_FILE_STORAGE = "storages.backends.gcloud.GoogleCloudStorage" @@ -28,9 +49,11 @@ "/gcs/credentials.json" ) -SPOTSEEKER_AUTH_MODULE = "spotseeker_server.auth.{}".format( - os.getenv("AUTH_MODULE", "all_ok") -) +SPOTSEEKER_OAUTH_ENABLED = os.getenv("SPOTSEEKER_OAUTH_ENABLED", not DEBUG) +# convert string to boolean if set in .env +if type(SPOTSEEKER_OAUTH_ENABLED) == str: + SPOTSEEKER_OAUTH_ENABLED = SPOTSEEKER_OAUTH_ENABLED.lower() == "true" + # turn string of auth admins into list SPOTSEEKER_AUTH_ADMINS = ( os.getenv("SPOTSEEKER_AUTH_ADMINS", "").replace(" ", "").split(",") @@ -60,7 +83,8 @@ } } else: - # The various MEMCACHED variables are set in django-container's base_settings/common.py + # The various MEMCACHED variables are set in django-container's + # base_settings/common.py CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', diff --git a/docker/test-values.yml b/docker/test-values.yml index e64f48c4..2f158c68 100644 --- a/docker/test-values.yml +++ b/docker/test-values.yml @@ -65,8 +65,8 @@ daemon: command: ["/scripts/management_daemon.sh"] args: ["--delay", "300", "sync_techloan"] environmentVariables: - - name: AUTH_MODULE - value: oauth + - name: SPOTSEEKER_OAUTH_ENABLED + value: true - name: CLUSTER_CNAME value: test.api.scout.uw.edu - name: ENV diff --git a/docker/urls.py b/docker/urls.py index 4816c0b9..954c31d1 100644 --- a/docker/urls.py +++ b/docker/urls.py @@ -1,34 +1,24 @@ -"""docker URL Configuration +# Copyright 2024 UW-IT, University of Washington +# SPDX-License-Identifier: Apache-2.0 -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/1.11/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') -Including another URLconf - 1. Import the include() function: from django.conf.urls import url, include - 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) -""" +from .base_urls import * from django.conf import settings -from django.conf.urls import url, include +from django.urls import path, include +from django.contrib import admin -urlpatterns = [ - url(r'^auth/', include('oauth_provider.urls')), - url(r'^api/', include('spotseeker_server.urls')), - url(r'^', include('django_prometheus.urls')), # add here for django 1.11 compatibility +urlpatterns += [ + path("api/", include("spotseeker_server.urls")), + path( + "auth/", include("oauth2_provider.urls", namespace="oauth2_provider") + ), ] if settings.DEBUG: - from django.contrib import admin from django.contrib.staticfiles.urls import staticfiles_urlpatterns urlpatterns += [ - url(r'^admin/doc/', include('django.contrib.admindocs.urls')), - url(r'^admin/', include(admin.site.urls)), + path("admin/doc/", include("django.contrib.admindocs.urls")), + path("admin/", admin.site.urls), ] urlpatterns += staticfiles_urlpatterns() diff --git a/requirements.txt b/requirements.txt index 50e7d025..b009075c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ # these two can be upgraded when django gets an update mysqlclient~=1.3 -django-prometheus~=2.0 +# this is temporary for testing migrations in upgrade +django-extensions==2.2.* -e . diff --git a/spotseeker_server/management/commands/resources/building3.jpg b/resources/building3.jpg similarity index 100% rename from spotseeker_server/management/commands/resources/building3.jpg rename to resources/building3.jpg diff --git a/spotseeker_server/management/commands/resources/building4.jpg b/resources/building4.jpg similarity index 100% rename from spotseeker_server/management/commands/resources/building4.jpg rename to resources/building4.jpg diff --git a/spotseeker_server/management/commands/resources/building5.jpg b/resources/building5.jpg similarity index 100% rename from spotseeker_server/management/commands/resources/building5.jpg rename to resources/building5.jpg diff --git a/spotseeker_server/management/commands/resources/building6.jpg b/resources/building6.jpg similarity index 100% rename from spotseeker_server/management/commands/resources/building6.jpg rename to resources/building6.jpg diff --git a/sample.env b/sample.env index a83a617d..f4947571 100644 --- a/sample.env +++ b/sample.env @@ -3,11 +3,13 @@ # Port to connect to with your browser #PORT=8001 -# Whether to use all_ok (default) or oauth auth module -#AUTH_MODULE=oauth - # Comma separated list of admins #SPOTSEEKER_AUTH_ADMINS=javerage,demo_user +# enabled oauth (True or False) +#SPOTSEEKER_OAUTH_ENABLED=True + # URL to sync techloan from sync_techloan management command -#SPOTSEEKER_TECHLOAN_URL=https://EXAMPLE_DOMAIN/techloan/api/v2/type/?embed=availability&embed=class \ No newline at end of file +#SPOTSEEKER_TECHLOAN_URL=https://EXAMPLE_DOMAIN/techloan/api/v2/type/?embed=availability&embed=class + +#ENV=localdev diff --git a/setup.py b/setup.py index e807c5af..fcdf66db 100644 --- a/setup.py +++ b/setup.py @@ -7,10 +7,12 @@ version="1.0", description="REST Backend for SpaceScout", install_requires=[ - "Django==1.11.*", + "Django==2.2.*", "future", "mock<=1.0.1", "oauthlib==3.1.*", + "django-oauth-toolkit", + "django-cors-headers==3.10.*", "requests==2.26.*", "requests-oauthlib==1.3.*", "Pillow", @@ -18,7 +20,7 @@ "pyproj", "pytz", "simplejson>=2.1", - "django-oauth-plus@git+https://github.com/edx-unsupported/django-oauth-plus#egg=2.2.9.edx-4", + "django-oauth-plus@git+https://github.com/abztrakt/django-oauth-plus@spotseeker-changes#egg=2.2.9.edx-4", "django-storages[google]", "schema", "six", diff --git a/spotseeker_server/__init__.py b/spotseeker_server/__init__.py index 0cbef36b..47252182 100644 --- a/spotseeker_server/__init__.py +++ b/spotseeker_server/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 __version__ = "1.0" diff --git a/spotseeker_server/admin.py b/spotseeker_server/admin.py index 22007230..2f22683a 100644 --- a/spotseeker_server/admin.py +++ b/spotseeker_server/admin.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 """ Changes @@ -7,10 +7,8 @@ sbutler1@illinois.edu: use the same forms as used for REST. """ -from importlib import import_module -from django.db import models from django.contrib import admin -from django.conf import settings +from django.contrib.auth.admin import UserAdmin from spotseeker_server.models import * from spotseeker_server.forms.spot import SpotForm, SpotExtendedInfoForm from spotseeker_server.forms.item import ItemForm, ItemExtendedInfoForm @@ -117,7 +115,7 @@ class SpotExtendedInfoAdmin(admin.ModelAdmin): admin.site.register(SpotType) -admin.site.register(TrustedOAuthClient) +admin.site.register(Client, UserAdmin) class ItemAdmin(admin.ModelAdmin): diff --git a/spotseeker_server/auth/__init__.py b/spotseeker_server/auth/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/spotseeker_server/auth/all_ok.py b/spotseeker_server/auth/all_ok.py deleted file mode 100644 index 74b8fccd..00000000 --- a/spotseeker_server/auth/all_ok.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright 2023 UW-IT, University of Washington -# SPDX-License-Identifier: Apache-2.0 - -""" This module will allow all requests, without requiring authentication or -authorization. To use it, add this to your settings.py: - -SPOTSEEKER_AUTH_MODULE = spotseeker_server.auth.all_ok - -By default authenticate_user will use the user demo_user. You can override -this in settings.py: - -SPOTSEEKER_AUTH_ALL_USER = 'other_user' -""" -from django.conf import settings -from django.contrib.auth.models import User - - -def authenticate_application(*args, **kwargs): - """This always allows requests through""" - return - - -def authenticate_user(*args, **kwargs): - """This always allows requests through""" - request = args[1] - username = getattr(settings, "SPOTSEEKER_AUTH_ALL_USER", "demo_user") - - user_obj = User.objects.get_or_create(username=username) - request.META["SS_OAUTH_USER"] = username - return diff --git a/spotseeker_server/auth/fake_oauth.py b/spotseeker_server/auth/fake_oauth.py deleted file mode 100644 index 6632ea24..00000000 --- a/spotseeker_server/auth/fake_oauth.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright 2023 UW-IT, University of Washington -# SPDX-License-Identifier: Apache-2.0 - -""" This module will allow all requests, without requiring authentication or -authorization. To use it, add this to your settings.py: - -SPOTSEEKER_AUTH_MODULE = spotseeker_server.auth.fake_oauth - -You can specify a user with a request header of ??? (Missing at time of commit) -""" - -from django.contrib import auth - - -def authenticate_application(*args, **kwargs): - """This always allows requests through""" - return - - -def authenticate_user(*args, **kwargs): - """This always allows requests through""" - request = args[1] - - input_username = request.META["TESTING_OAUTH_USER"] - user = auth.authenticate(remote_user=input_username) - request.META["SS_OAUTH_USER"] = user.username - return diff --git a/spotseeker_server/auth/oauth.py b/spotseeker_server/auth/oauth.py deleted file mode 100644 index 2310a03f..00000000 --- a/spotseeker_server/auth/oauth.py +++ /dev/null @@ -1,83 +0,0 @@ -# Copyright 2023 UW-IT, University of Washington -# SPDX-License-Identifier: Apache-2.0 - -""" This module uses oauth to allow applications and users access. -Supports 2-legged oauth for application requests, and for trusted -applications accessing user-restricted methods. Supports 3-legged -oauth for non-trusted applications that want to access user methods. - -To use this module, add this to your settings.py: - -SPOTSEEKER_AUTH_MODULE = spotseeker_server.auth.oauth -""" -from django.http import HttpResponse - -from oauth_provider.utils import get_oauth_request, verify_oauth_request -from oauth_provider.store import store, InvalidConsumerError, InvalidTokenError -from spotseeker_server.models import TrustedOAuthClient - -import logging - - -def authenticate_application(*args, **kwargs): - request = args[1] - try: - oauth_request = get_oauth_request(request) - consumer = store.get_consumer( - request, oauth_request, oauth_request["oauth_consumer_key"] - ) - verify_oauth_request(request, oauth_request, consumer) - - request.META["SS_OAUTH_CONSUMER_NAME"] = consumer.name - request.META["SS_OAUTH_CONSUMER_PK"] = consumer.pk - - return - except Exception as e: - response = HttpResponse("Error authorizing application: %s" % e) - response.status_code = 401 - return response - - -def authenticate_user(*args, **kwargs): - request = args[1] - try: - oauth_request = get_oauth_request(request) - consumer = store.get_consumer( - request, oauth_request, oauth_request["oauth_consumer_key"] - ) - verify_oauth_request(request, oauth_request, consumer) - - # Allow a trusted client to either give us a user via header, or do the - # 3-legged oauth - user = None - try: - trusted_client = TrustedOAuthClient.objects.get(consumer=consumer) - if trusted_client and trusted_client.is_trusted: - user = request.META["HTTP_X_OAUTH_USER"] - logging.info( - "user is a trusted client and was set to {}".format(user) - ) - - except Exception as e: - pass - - if not user: - access_token = store.get_access_token( - request, oauth_request, consumer, oauth_request[u"oauth_token"] - ) - user = store.get_user_for_access_token( - request, oauth_request, access_token - ).username - logging.info( - "user was not a trusted client and was set to {}".format(user) - ) - - request.META["SS_OAUTH_CONSUMER_NAME"] = consumer.name - request.META["SS_OAUTH_CONSUMER_PK"] = consumer.pk - request.META["SS_OAUTH_USER"] = user - - return - except Exception as e: - response = HttpResponse("Error authorizing user: %s" % e) - response.status_code = 401 - return response diff --git a/spotseeker_server/default_forms/item.py b/spotseeker_server/default_forms/item.py index 6aea1698..3dd7e23d 100644 --- a/spotseeker_server/default_forms/item.py +++ b/spotseeker_server/default_forms/item.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django import forms diff --git a/spotseeker_server/default_forms/spot.py b/spotseeker_server/default_forms/spot.py index 27da155f..d8817d17 100644 --- a/spotseeker_server/default_forms/spot.py +++ b/spotseeker_server/default_forms/spot.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 """ Changes diff --git a/spotseeker_server/default_forms/spot_search.py b/spotseeker_server/default_forms/spot_search.py index ebd863c0..e1c8a1d4 100644 --- a/spotseeker_server/default_forms/spot_search.py +++ b/spotseeker_server/default_forms/spot_search.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django import forms diff --git a/spotseeker_server/extras/delimage.py b/spotseeker_server/extras/delimage.py index 73b8fbe0..762d954c 100644 --- a/spotseeker_server/extras/delimage.py +++ b/spotseeker_server/extras/delimage.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 import getopt diff --git a/spotseeker_server/extras/uw_server_values.py b/spotseeker_server/extras/uw_server_values.py index b441c064..977c7ec7 100644 --- a/spotseeker_server/extras/uw_server_values.py +++ b/spotseeker_server/extras/uw_server_values.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 # This file contains UW specific values on the server side to be localized. diff --git a/spotseeker_server/fixtures/dummy_oauth.json b/spotseeker_server/fixtures/dummy_oauth.json index 177661c0..d1215591 100644 --- a/spotseeker_server/fixtures/dummy_oauth.json +++ b/spotseeker_server/fixtures/dummy_oauth.json @@ -1,24 +1,11 @@ [ { - "model": "spotseeker_server.trustedoauthclient", - "pk": 1, - "fields": { - "consumer": 1, - "is_trusted": true, - "bypasses_user_authorization": false - } -}, -{ - "model": "oauth_provider.consumer", + "model": "spotseeker_server.client", "pk": 1, "fields": { "name": "javerage", - "description": "", - "key": "dummy", - "secret": "dummy", - "status": 1, - "user": null, - "xauth_allowed": false + "client_id": "dummy", + "client_secret": "dummy" } } ] diff --git a/spotseeker_server/forms/item.py b/spotseeker_server/forms/item.py index 352620e3..75c43783 100644 --- a/spotseeker_server/forms/item.py +++ b/spotseeker_server/forms/item.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from spotseeker_server.default_forms.item import ( diff --git a/spotseeker_server/forms/spot.py b/spotseeker_server/forms/spot.py index f7ea9b7d..9014ded1 100644 --- a/spotseeker_server/forms/spot.py +++ b/spotseeker_server/forms/spot.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 """ Changes diff --git a/spotseeker_server/forms/spot_search.py b/spotseeker_server/forms/spot_search.py index 57d125e7..cea11e3f 100644 --- a/spotseeker_server/forms/spot_search.py +++ b/spotseeker_server/forms/spot_search.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 """ Changes diff --git a/spotseeker_server/load_module.py b/spotseeker_server/load_module.py index 60b737d4..40072aa4 100644 --- a/spotseeker_server/load_module.py +++ b/spotseeker_server/load_module.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from importlib import import_module diff --git a/spotseeker_server/logger/oauth.py b/spotseeker_server/logger/oauth.py index c17a5339..20876860 100644 --- a/spotseeker_server/logger/oauth.py +++ b/spotseeker_server/logger/oauth.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 """ This creates a log along the lines of the apache diff --git a/spotseeker_server/management/commands/create_consumer.py b/spotseeker_server/management/commands/create_consumer.py index 7da25fad..7eff1ce7 100644 --- a/spotseeker_server/management/commands/create_consumer.py +++ b/spotseeker_server/management/commands/create_consumer.py @@ -1,20 +1,18 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 """ This provides a management command to django's manage.py called -create_consumer that will generate a oauth key and secret based on the consumer -name. +create_consumer that will generate a client with a valid credential based on +the consumer name. """ -from optparse import make_option import hashlib import random import string import time -from django.core.management.base import BaseCommand, CommandError -from oauth_provider.models import Consumer +from django.core.management.base import BaseCommand -from spotseeker_server.models import TrustedOAuthClient +from spotseeker_server.models import Client class Command(BaseCommand): @@ -25,22 +23,14 @@ class Command(BaseCommand): def add_arguments(self, parser): parser.add_argument( + "-n", "--name", dest="consumer_name", - default=False, help="A name for the consumer", ) parser.add_argument( - "--trusted", - dest="trusted", - action="store_true", - default=False, - help="Makes this consumer trusted " - "(Adds a TrustedOAuthClient for it)", - ) - - parser.add_argument( + "-s", "--silent", dest="silent", action="store_true", @@ -52,29 +42,24 @@ def handle(self, *args, **options): if options["consumer_name"]: consumer_name = options["consumer_name"] else: - consumer_name = input("Enter consumer name: ") + consumer_name = input("Enter client name: ") + # key and secret can be anything, but we'll try to keep it unique key = hashlib.sha1( "{0} - {1}".format(random.random(), time.time()).encode("utf-8") ).hexdigest() - # django-oauth-plus now wants secrets to be 16 chars secret = "".join( random.choice(string.ascii_letters + string.digits) for _ in range(16) ) - consumer = Consumer.objects.create( - name=consumer_name, key=key, secret=secret + client = Client.objects.create( + username=consumer_name, + name=consumer_name, client_id=key, client_secret=secret ) - - if options["trusted"]: - trusted = TrustedOAuthClient.objects.create( - consumer=consumer, - is_trusted=1, - bypasses_user_authorization=False, - ) + credential = client.get_client_credential() + client.save() if not options["silent"]: - self.stdout.write("Key: %s\n" % key) - self.stdout.write("Secret: %s\n" % secret) + self.stdout.write(f"Credential: {credential}\n") diff --git a/spotseeker_server/management/commands/create_sample_spots.py b/spotseeker_server/management/commands/create_sample_spots.py index 855829ee..76886598 100644 --- a/spotseeker_server/management/commands/create_sample_spots.py +++ b/spotseeker_server/management/commands/create_sample_spots.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 # -*- coding: utf-8 -*- @@ -6,14 +6,10 @@ create_sample_spots that will generate a set of spots for testing. """ from django.core.management.base import BaseCommand, CommandError -from optparse import make_option from spotseeker_server.models import * -from django.contrib.auth.models import User from django.core.files import File -from django.utils import timezone from decimal import * -# from datetime import datetime import os import glob @@ -48,648 +44,664 @@ def handle(self, *args, **options): "I'm only going to run if you're sure " "you want to 'delete my spots'" ) - else: - Spot.objects.all().delete() - SpotExtendedInfo.objects.all().delete() - SpotAvailableHours.objects.all().delete() - - # delete old default images - base_dir = os.path.dirname(os.path.realpath(__file__)) - path = os.path.join(base_dir, "resources") - imgs = glob.glob(path + "/building*_*.jpg") - for img in imgs: - try: - os.remove(img) - except OSError: - print("Could not delete image: " + img) - - lab_space = Spot.objects.create( - name="This is a computer lab", - capacity=200, - longitude=Decimal("-122.306644"), - latitude=Decimal("47.658241"), - building_name="Art Building (ART)", - ) - # get_or_create returns a tuple - production_studio = SpotType.objects.get_or_create(name="studio")[ - 0 - ] - # get_or_create returns a tuple - computer_lab = SpotType.objects.get_or_create(name="computer_lab")[ - 0 - ] - lab_space.spottypes.add(production_studio) - lab_space.spottypes.add(computer_lab) - lab_space.save() - SpotExtendedInfo.objects.create( - key="campus", value="seattle", spot=lab_space - ) - # get_or_create returns a tuple - cafe_type = SpotType.objects.get_or_create(name="cafe")[0] - art = Spot.objects.create( - name="In the Art Building - multiline " "name to test", - capacity=10, - longitude=Decimal("-122.306644"), - latitude=Decimal("47.658241"), - building_name="Art Building (ART)", - ) - art.spottypes.add(cafe_type) - art.save() - art_ada = SpotExtendedInfo.objects.create( - key="location_" "description", - value="This is the " "location of the " "space", - spot=art, - ) - art_ada = SpotExtendedInfo.objects.create( - key="app_type", value="food", spot=art - ) - art_ada = SpotExtendedInfo.objects.create( - key="has_whiteboards", value="true", spot=art - ) - art_ada = SpotExtendedInfo.objects.create( - key="has_outlets", value="true", spot=art - ) - art_ada = SpotExtendedInfo.objects.create( - key="has_displays", value="true", spot=art - ) - art_ada = SpotExtendedInfo.objects.create( - key="has_printing", value="true", spot=art - ) - art_ada = SpotExtendedInfo.objects.create( - key="has_scanner", value="true", spot=art - ) - art_ada = SpotExtendedInfo.objects.create( - key="has_projector", value="true", spot=art - ) - art_ada = SpotExtendedInfo.objects.create( - key="has_computers", value="true", spot=art - ) - art_ada = SpotExtendedInfo.objects.create( - key="reservable", value="true", spot=art - ) - art_ada = SpotExtendedInfo.objects.create( - key="campus", value="seattle", spot=art - ) - SpotExtendedInfo.objects.create( - key="access_notes", - value=" This space reservable " - "outside of TLC hours. To " - "reserve, go to http://www." - "tacoma.uw.edu/library/" - "reserve-group-study-rooms", - spot=art, - ) - SpotExtendedInfo.objects.create( - key="reservation_notes", - value=" This space reservable " - "outside of TLC hours. To " - "reserve, go to http://www." - "tacoma.uw.edu/library/" - "reserve-group-study-rooms", - spot=art, - ) - mgr = SpotExtendedInfo.objects.create( - key="manager", value="ctlt", spot=art - ) - org = SpotExtendedInfo.objects.create( - key="organization", value="Art", spot=art - ) + Spot.objects.all().delete() + SpotExtendedInfo.objects.all().delete() + SpotAvailableHours.objects.all().delete() + + # delete old default images + imgs = glob.glob("resources/building*_*.jpg") + for img in imgs: + try: + os.remove(img) + except OSError: + print("Could not delete image: " + img) + + lab_space = Spot.objects.create( + name="This is a computer lab", + capacity=200, + longitude=Decimal("-122.306644"), + latitude=Decimal("47.658241"), + building_name="Art Building (ART)", + ) - art2 = Spot.objects.create( - name="Also in the Art Building", - capacity=10, - longitude=Decimal("-122.306644"), - latitude=Decimal("47.658241"), - building_name="Art Building (ART)", - ) - art2.spottypes.add(cafe_type) - art2.save() - art_ada = SpotExtendedInfo.objects.create( - key="has_whiteboards", value="true", spot=art2 - ) - art_ada = SpotExtendedInfo.objects.create( - key="has_outlets", value="true", spot=art2 - ) - art_ada = SpotExtendedInfo.objects.create( - key="has_displays", value="true", spot=art2 - ) - art_ada = SpotExtendedInfo.objects.create( - key="campus", value="seattle", spot=art2 - ) - mgr = SpotExtendedInfo.objects.create( - key="manager", value="ctlt", spot=art2 - ) - org = SpotExtendedInfo.objects.create( - key="organization", value="Art", spot=art2 - ) + # get_or_create returns a tuple + production_studio = SpotType.objects.get_or_create(name="studio")[0] - base_dir = os.path.dirname(os.path.realpath(__file__)) - f = open( - os.path.join(base_dir, "resources", "building3.jpg"), "rb" - ) - art_img1 = SpotImage.objects.create( - description="This is one " "building", - spot=art, - display_index=0, - image=File(f), - ) - f = open( - os.path.join(base_dir, "resources", "building4.jpg"), "rb" - ) - art_img2 = SpotImage.objects.create( - description="This is another " "building", - spot=art, - display_index=1, - image=File(f), - ) - f = open( - os.path.join(base_dir, "resources", "building5.jpg"), "rb" - ) - art_img3 = SpotImage.objects.create( - description="This is a third " "art building", - spot=art, - display_index=2, - image=File(f), - ) + # get_or_create returns a tuple + computer_lab = SpotType.objects.get_or_create(name="computer_lab")[0] + lab_space.spottypes.add(production_studio) + lab_space.spottypes.add(computer_lab) + lab_space.save() - f = open( - os.path.join(base_dir, "resources", "building6.jpg"), "rb" - ) - art_img4 = SpotImage.objects.create( - description="This is a third " "art building", - spot=art, - display_index=3, - image=File(f), - ) + SpotExtendedInfo.objects.create( + key="campus", value="seattle", spot=lab_space + ) - study_room_type = SpotType.objects.get_or_create( - name="study_room" - )[0] - tacoma = Spot.objects.create( - name="WCG #1", - capacity=20, - longitude=Decimal("-122.437212"), - latitude=Decimal("47.246213"), - building_name="West Coast Grocery " "(WCG)", - ) - tacoma.spottypes.add(study_room_type) - tacoma.save() - wcg_outlets = SpotExtendedInfo.objects.create( - key="has_outlets", value="true", spot=tacoma - ) - tacoma_outlets = SpotExtendedInfo.objects.create( - key="campus", value="tacoma", spot=tacoma - ) - mgr = SpotExtendedInfo.objects.create( - key="manager", value="ctlt", spot=tacoma - ) - org = SpotExtendedInfo.objects.create( - key="organization", value="Philosophy", spot=tacoma - ) + # get_or_create returns a tuple + cafe_type = SpotType.objects.get_or_create(name="cafe")[0] - tacoma2 = Spot.objects.create( - name="In tacoma - #2", - capacity=20, - longitude=Decimal("-122.437708"), - latitude=Decimal("47.244832"), - building_name="West Coast Grocery " "(WCG)", - ) - tacoma2.spottypes.add(study_room_type) - tacoma2.save() - tacoma_outlets = SpotExtendedInfo.objects.create( - key="has_outlets", value="true", spot=tacoma2 - ) - mgr = SpotExtendedInfo.objects.create( - key="manager", value="ctlt", spot=tacoma2 - ) - mgr = SpotExtendedInfo.objects.create( - key="campus", value="tacoma", spot=tacoma2 - ) - org = SpotExtendedInfo.objects.create( - key="organization", value="Economics", spot=tacoma2 - ) + art = Spot.objects.create( + name="In the Art Building - multiline " "name to test", + capacity=10, + longitude=Decimal("-122.306644"), + latitude=Decimal("47.658241"), + building_name="Art Building (ART)", + ) + art.spottypes.add(cafe_type) + art.save() + art_ada = SpotExtendedInfo.objects.create( + key="location_" "description", + value="This is the " "location of the " "space", + spot=art, + ) + art_ada = SpotExtendedInfo.objects.create( + key="app_type", value="study", spot=art + ) + art_ada = SpotExtendedInfo.objects.create( + key="has_whiteboards", value="true", spot=art + ) + art_ada = SpotExtendedInfo.objects.create( + key="has_outlets", value="true", spot=art + ) + art_ada = SpotExtendedInfo.objects.create( + key="has_displays", value="true", spot=art + ) + art_ada = SpotExtendedInfo.objects.create( + key="has_printing", value="true", spot=art + ) + art_ada = SpotExtendedInfo.objects.create( + key="has_scanner", value="true", spot=art + ) + art_ada = SpotExtendedInfo.objects.create( + key="has_projector", value="true", spot=art + ) + art_ada = SpotExtendedInfo.objects.create( + key="has_computers", value="true", spot=art + ) + art_ada = SpotExtendedInfo.objects.create( + key="reservable", value="true", spot=art + ) + art_ada = SpotExtendedInfo.objects.create( + key="campus", value="seattle", spot=art + ) + SpotExtendedInfo.objects.create( + key="access_notes", + value=" This space reservable " + "outside of TLC hours. To " + "reserve, go to http://www." + "tacoma.uw.edu/library/" + "reserve-group-study-rooms", + spot=art, + ) + SpotExtendedInfo.objects.create( + key="reservation_notes", + value=" This space reservable " + "outside of TLC hours. To " + "reserve, go to http://www." + "tacoma.uw.edu/library/" + "reserve-group-study-rooms", + spot=art, + ) + mgr = SpotExtendedInfo.objects.create( + key="manager", value="ctlt", spot=art + ) + org = SpotExtendedInfo.objects.create( + key="organization", value="Art", spot=art + ) - tacoma3 = Spot.objects.create( - name="In tacoma - #2", - capacity=20, - longitude=Decimal("-122.438368"), - latitude=Decimal("47.245838"), - building_name="West Coast Grocery " "(WCG)", - ) - tacoma3.spottypes.add(study_room_type) - tacoma3.save() - tacoma_outlets = SpotExtendedInfo.objects.create( - key="has_outlets", value="true", spot=tacoma3 - ) - tacoma_outlets = SpotExtendedInfo.objects.create( - key="campus", value="tacoma", spot=tacoma3 - ) - mgr = SpotExtendedInfo.objects.create( - key="manager", value="ctlt", spot=tacoma3 - ) - org = SpotExtendedInfo.objects.create( - key="organization", value="Sociology", spot=tacoma3 - ) + art2 = Spot.objects.create( + name="Also in the Art Building", + capacity=10, + longitude=Decimal("-122.306644"), + latitude=Decimal("47.658241"), + building_name="Art Building (ART)", + ) + art2.spottypes.add(cafe_type) + art2.save() + art_ada = SpotExtendedInfo.objects.create( + key="has_whiteboards", value="true", spot=art2 + ) + art_ada = SpotExtendedInfo.objects.create( + key="has_outlets", value="true", spot=art2 + ) + art_ada = SpotExtendedInfo.objects.create( + key="has_displays", value="true", spot=art2 + ) + art_ada = SpotExtendedInfo.objects.create( + key="campus", value="seattle", spot=art2 + ) + mgr = SpotExtendedInfo.objects.create( + key="manager", value="ctlt", spot=art2 + ) + org = SpotExtendedInfo.objects.create( + key="organization", value="Art", spot=art2 + ) - lounge_type = SpotType.objects.get_or_create(name="lounge")[0] - fish_kitchen = Spot.objects.create( - name="FSH 2nd Floor South Kitchen", - longitude=Decimal("-122.31659"), - latitude=Decimal("47.65296"), - building_name="Fishery Sciences (FSH)", - floor="2nd floor", - room_number="266", - capacity=12, - ) - fish_kitchen.spottypes.add(lounge_type) - fish_kitchen.save() - fish_outlets = SpotExtendedInfo.objects.create( - key="has_outlets", value="true", spot=fish_kitchen - ) - fish_outlets = SpotExtendedInfo.objects.create( - key="campus", value="seattle", spot=fish_kitchen - ) - mgr = SpotExtendedInfo.objects.create( - key="manager", value="ctlt", spot=fish_kitchen - ) - org = SpotExtendedInfo.objects.create( - key="organization", value="Fisheries", spot=fish_kitchen - ) + f = open(os.path.join("resources", "building3.jpg"), "rb") - outdoor_type = SpotType.objects.get_or_create(name="outdoor")[0] - fish_patio = Spot.objects.create( - name="FSH 2nd Floor Patio/Deck", - longitude=Decimal("-122.31659"), - latitude=Decimal("47.65289"), - building_name="Fishery Sciences " "(FSH)", - floor="2nd floor", - capacity=12, - ) - fish_patio.spottypes.add(outdoor_type) - fish_patio.save() - fish_outlets = SpotExtendedInfo.objects.create( - key="has_outlets", value="true", spot=fish_patio - ) - fish_outlets = SpotExtendedInfo.objects.create( - key="campus", value="seattle", spot=fish_patio - ) - mgr = SpotExtendedInfo.objects.create( - key="manager", value="ctlt", spot=fish_patio - ) - org = SpotExtendedInfo.objects.create( - key="organization", value="Fisheries", spot=fish_patio - ) + art_img1 = SpotImage.objects.create( + description="This is one building", + spot=art, + display_index=0, + image=File(f), + ) + f = open( + os.path.join("resources", "building4.jpg"), "rb" + ) + art_img2 = SpotImage.objects.create( + description="This is another building", + spot=art, + display_index=1, + image=File(f), + ) + f = open( + os.path.join("resources", "building5.jpg"), "rb" + ) + art_img3 = SpotImage.objects.create( + description="This is a third art building", + spot=art, + display_index=2, + image=File(f), + ) - for day in ["su", "m", "t", "w", "th", "f", "sa"]: - SpotAvailableHours.objects.create( - spot=lab_space, - day=day, - start_time="00:00", - end_time="23:59", - ) - SpotAvailableHours.objects.create( - spot=art, day=day, start_time="00:00", end_time="23:59" - ) - SpotAvailableHours.objects.create( - spot=art2, day=day, start_time="00:00", end_time="23:59" - ) - SpotAvailableHours.objects.create( - spot=tacoma, day=day, start_time="00:00", end_time="23:59" - ) - SpotAvailableHours.objects.create( - spot=tacoma2, day=day, start_time="00:00", end_time="23:59" - ) - SpotAvailableHours.objects.create( - spot=tacoma3, day=day, start_time="00:00", end_time="23:59" - ) - SpotAvailableHours.objects.create( - spot=fish_kitchen, - day=day, - start_time="00:00", - end_time="23:59", - ) - SpotAvailableHours.objects.create( - spot=fish_patio, - day=day, - start_time="00:00", - end_time="23:59", - ) - - # Create rooms for Selenium testing - # AA Balcony - like EE Patio but with different name/building - aa_balcony = Spot.objects.create( - name="AA Balcony", - longitude=Decimal("-122.306371"), - latitude=Decimal("47.653474"), - building_name="Art Atrium", - ) - aa_balcony.spottypes.add(outdoor_type) - f = open( - os.path.join(base_dir, "resources", "building3.jpg"), "rb" + f = open( + os.path.join("resources", "building6.jpg"), "rb" + ) + art_img4 = SpotImage.objects.create( + description="This is a third art building", + spot=art, + display_index=3, + image=File(f), + ) + + study_room_type = SpotType.objects.get_or_create(name="study_room")[0] + + tacoma = Spot.objects.create( + name="WCG #1", + capacity=20, + longitude=Decimal("-122.437212"), + latitude=Decimal("47.246213"), + building_name="West Coast Grocery (WCG)", + ) + tacoma.spottypes.add(study_room_type) + tacoma.save() + wcg_outlets = SpotExtendedInfo.objects.create( + key="has_outlets", value="true", spot=tacoma + ) + tacoma_outlets = SpotExtendedInfo.objects.create( + key="campus", value="tacoma", spot=tacoma + ) + mgr = SpotExtendedInfo.objects.create( + key="manager", value="ctlt", spot=tacoma + ) + org = SpotExtendedInfo.objects.create( + key="organization", value="Philosophy", spot=tacoma + ) + + tacoma2 = Spot.objects.create( + name="In tacoma - #2", + capacity=20, + longitude=Decimal("-122.437708"), + latitude=Decimal("47.244832"), + building_name="West Coast Grocery (WCG)", + ) + tacoma2.spottypes.add(study_room_type) + tacoma2.save() + tacoma_outlets = SpotExtendedInfo.objects.create( + key="has_outlets", value="true", spot=tacoma2 + ) + mgr = SpotExtendedInfo.objects.create( + key="manager", value="ctlt", spot=tacoma2 + ) + mgr = SpotExtendedInfo.objects.create( + key="campus", value="tacoma", spot=tacoma2 + ) + org = SpotExtendedInfo.objects.create( + key="organization", value="Economics", spot=tacoma2 + ) + + tacoma3 = Spot.objects.create( + name="In tacoma - #2", + capacity=20, + longitude=Decimal("-122.438368"), + latitude=Decimal("47.245838"), + building_name="West Coast Grocery (WCG)", + ) + tacoma3.spottypes.add(study_room_type) + tacoma3.save() + tacoma_outlets = SpotExtendedInfo.objects.create( + key="has_outlets", value="true", spot=tacoma3 + ) + tacoma_outlets = SpotExtendedInfo.objects.create( + key="campus", value="tacoma", spot=tacoma3 + ) + mgr = SpotExtendedInfo.objects.create( + key="manager", value="ctlt", spot=tacoma3 + ) + org = SpotExtendedInfo.objects.create( + key="organization", value="Sociology", spot=tacoma3 + ) + + lounge_type = SpotType.objects.get_or_create(name="lounge")[0] + fish_kitchen = Spot.objects.create( + name="FSH 2nd Floor South Kitchen", + longitude=Decimal("-122.31659"), + latitude=Decimal("47.65296"), + building_name="Fishery Sciences (FSH)", + floor="2nd floor", + room_number="266", + capacity=12, + ) + fish_kitchen.spottypes.add(lounge_type) + fish_kitchen.save() + fish_outlets = SpotExtendedInfo.objects.create( + key="has_outlets", value="true", spot=fish_kitchen + ) + fish_outlets = SpotExtendedInfo.objects.create( + key="campus", value="seattle", spot=fish_kitchen + ) + mgr = SpotExtendedInfo.objects.create( + key="manager", value="ctlt", spot=fish_kitchen + ) + org = SpotExtendedInfo.objects.create( + key="organization", value="Fisheries", spot=fish_kitchen + ) + + outdoor_type = SpotType.objects.get_or_create(name="outdoor")[0] + fish_patio = Spot.objects.create( + name="FSH 2nd Floor Patio/Deck", + longitude=Decimal("-122.31659"), + latitude=Decimal("47.65289"), + building_name="Fishery Sciences (FSH)", + floor="2nd floor", + capacity=12, + ) + fish_patio.spottypes.add(outdoor_type) + fish_patio.save() + fish_outlets = SpotExtendedInfo.objects.create( + key="has_outlets", value="true", spot=fish_patio + ) + fish_outlets = SpotExtendedInfo.objects.create( + key="campus", value="seattle", spot=fish_patio + ) + mgr = SpotExtendedInfo.objects.create( + key="manager", value="ctlt", spot=fish_patio + ) + org = SpotExtendedInfo.objects.create( + key="organization", value="Fisheries", spot=fish_patio + ) + + for day in ["su", "m", "t", "w", "th", "f", "sa"]: + SpotAvailableHours.objects.create( + spot=lab_space, + day=day, + start_time="00:00", + end_time="23:59", ) - f2 = open( - os.path.join(base_dir, "resources", "building4.jpg"), "rb" + SpotAvailableHours.objects.create( + spot=art, day=day, start_time="00:00", end_time="23:59" ) - aa_balcony_img = SpotImage.objects.create( - description="This is one " "building", - spot=aa_balcony, - display_index=0, - image=File(f), + SpotAvailableHours.objects.create( + spot=art2, day=day, start_time="00:00", end_time="23:59" ) - aa_balcony_img2 = SpotImage.objects.create( - description="This is one " "building", - spot=aa_balcony, - display_index=1, - image=File(f2), + SpotAvailableHours.objects.create( + spot=tacoma, day=day, start_time="00:00", end_time="23:59" ) - aa_balcony.save() - - for day in ["su", "m", "t", "w", "th", "f", "sa"]: - SpotAvailableHours.objects.create( - spot=aa_balcony, - day=day, - start_time="00:00", - end_time="23:59", - ) - - SpotExtendedInfo.objects.create( - key="has_natural_light", value="true", spot=aa_balcony + SpotAvailableHours.objects.create( + spot=tacoma2, day=day, start_time="00:00", end_time="23:59" ) - SpotExtendedInfo.objects.create( - key="food_nearby", value="neighboring", spot=aa_balcony + SpotAvailableHours.objects.create( + spot=tacoma3, day=day, start_time="00:00", end_time="23:59" ) - SpotExtendedInfo.objects.create( - key="campus", value="seattle", spot=aa_balcony + SpotAvailableHours.objects.create( + spot=fish_kitchen, + day=day, + start_time="00:00", + end_time="23:59", ) - SpotExtendedInfo.objects.create( - key="location_description", - value="Art Building Atrium", - spot=aa_balcony, + SpotAvailableHours.objects.create( + spot=fish_patio, + day=day, + start_time="00:00", + end_time="23:59", ) - # Study Room 233 - like Study Room 332 but - # with different name/building - study_room_233 = Spot.objects.create( - name="Study Room 233", - capacity=8, - longitude=Decimal("-122.306382"), - latitude=Decimal("47.653477"), - building_name="Odegaard Undergraduate Library (OUGL)", - ) - study_room_233.spottypes.add(study_room_type) - study_room_233.save() + # Create rooms for Selenium testing + # AA Balcony - like EE Patio but with different name/building + aa_balcony = Spot.objects.create( + name="AA Balcony", + longitude=Decimal("-122.306371"), + latitude=Decimal("47.653474"), + building_name="Art Atrium", + ) + aa_balcony.spottypes.add(outdoor_type) + f = open( + os.path.join("resources", "building3.jpg"), "rb" + ) + f2 = open( + os.path.join("resources", "building4.jpg"), "rb" + ) + aa_balcony_img = SpotImage.objects.create( + description="This is one " "building", + spot=aa_balcony, + display_index=0, + image=File(f), + ) + aa_balcony_img2 = SpotImage.objects.create( + description="This is one " "building", + spot=aa_balcony, + display_index=1, + image=File(f2), + ) + aa_balcony.save() + for day in ["su", "m", "t", "w", "th", "f", "sa"]: SpotAvailableHours.objects.create( - spot=study_room_233, - day="f", + spot=aa_balcony, + day=day, start_time="00:00", - end_time="20:00", - ) - SpotAvailableHours.objects.create( - spot=study_room_233, - day="sa", - start_time="12:00", - end_time="20:00", + end_time="23:59", ) + + SpotExtendedInfo.objects.create( + key="has_natural_light", value="true", spot=aa_balcony + ) + SpotExtendedInfo.objects.create( + key="food_nearby", value="neighboring", spot=aa_balcony + ) + SpotExtendedInfo.objects.create( + key="campus", value="seattle", spot=aa_balcony + ) + SpotExtendedInfo.objects.create( + key="location_description", + value="Art Building Atrium", + spot=aa_balcony, + ) + + # Study Room 233 - like Study Room 332 but + # with different name/building + study_room_233 = Spot.objects.create( + name="Study Room 233", + capacity=8, + longitude=Decimal("-122.306382"), + latitude=Decimal("47.653477"), + building_name="Odegaard Undergraduate Library (OUGL)", + ) + study_room_233.spottypes.add(study_room_type) + study_room_233.save() + + SpotAvailableHours.objects.create( + spot=study_room_233, + day="f", + start_time="00:00", + end_time="20:00", + ) + SpotAvailableHours.objects.create( + spot=study_room_233, + day="sa", + start_time="12:00", + end_time="20:00", + ) + SpotAvailableHours.objects.create( + spot=study_room_233, + day="su", + start_time="12:00", + end_time="23:59", + ) + for day in ["m", "t", "w", "th"]: SpotAvailableHours.objects.create( spot=study_room_233, - day="su", - start_time="12:00", + day=day, + start_time="00:00", end_time="23:59", ) - for day in ["m", "t", "w", "th"]: - SpotAvailableHours.objects.create( - spot=study_room_233, - day=day, - start_time="00:00", - end_time="23:59", - ) - - SpotExtendedInfo.objects.create( - key="location_description", - value="Library, 2nd floor", - spot=study_room_233, - ) - SpotExtendedInfo.objects.create( - key="has_outlets", value="true", spot=study_room_233 - ) - SpotExtendedInfo.objects.create( - key="has_printing", value="true", spot=study_room_233 - ) - SpotExtendedInfo.objects.create( - key="has_whiteboards", value="true", spot=study_room_233 - ) - SpotExtendedInfo.objects.create( - key="food_nearby", value="building", spot=study_room_233 - ) - SpotExtendedInfo.objects.create( - key="reservable", value="true", spot=study_room_233 - ) - SpotExtendedInfo.objects.create( - key="campus", value="seattle", spot=study_room_233 - ) - # Room 301 - like Room 201 but with different name/building - room_301 = Spot.objects.create( - name="Room 301", - capacity=10, - longitude=Decimal("-122.437708"), - latitude=Decimal("47.244832"), - building_name="Joy", - ) - room_301.spottypes.add(study_room_type) - room_301.save() - - for day in ["m", "t", "w", "th"]: - SpotAvailableHours.objects.create( - spot=room_301, - day=day, - start_time="07:00", - end_time="22:00", - ) - - for day in ["f", "sa", "su"]: - SpotAvailableHours.objects.create( - spot=room_301, - day=day, - start_time="07:00", - end_time="17:00", - ) - - SpotExtendedInfo.objects.create( - key="location_description", - value="Sad, 3rd floor", + SpotExtendedInfo.objects.create( + key="location_description", + value="Library, 2nd floor", + spot=study_room_233, + ) + SpotExtendedInfo.objects.create( + key="has_outlets", value="true", spot=study_room_233 + ) + SpotExtendedInfo.objects.create( + key="has_printing", value="true", spot=study_room_233 + ) + SpotExtendedInfo.objects.create( + key="has_whiteboards", value="true", spot=study_room_233 + ) + SpotExtendedInfo.objects.create( + key="food_nearby", value="building", spot=study_room_233 + ) + SpotExtendedInfo.objects.create( + key="reservable", value="true", spot=study_room_233 + ) + SpotExtendedInfo.objects.create( + key="campus", value="seattle", spot=study_room_233 + ) + + # Room 301 - like Room 201 but with different name/building + room_301 = Spot.objects.create( + name="Room 301", + capacity=10, + longitude=Decimal("-122.437708"), + latitude=Decimal("47.244832"), + building_name="Joy", + ) + room_301.spottypes.add(study_room_type) + room_301.save() + + for day in ["m", "t", "w", "th"]: + SpotAvailableHours.objects.create( spot=room_301, - ) - SpotExtendedInfo.objects.create( - key="has_outlets", value="true", spot=room_301 - ) - SpotExtendedInfo.objects.create( - key="has_natural_light", value="true", spot=room_301 - ) - SpotExtendedInfo.objects.create( - key="food_nearby", value="building", spot=room_301 - ) - SpotExtendedInfo.objects.create( - key="campus", value="tacoma", spot=room_301 + day=day, + start_time="07:00", + end_time="22:00", ) - food = Spot.objects.create( - name="This is a food spot", - capacity=10, - longitude=Decimal("-122.3101087"), - latitude=Decimal("47.6549552"), - building_name="Food Building", - ) - food.spottypes.add(cafe_type) - food.save() - food_info = SpotExtendedInfo.objects.create( - key="app_type", value="food", spot=food - ) - food_info = SpotExtendedInfo.objects.create( - key="has_outlets", value="true", spot=food + for day in ["f", "sa", "su"]: + SpotAvailableHours.objects.create( + spot=room_301, + day=day, + start_time="07:00", + end_time="17:00", ) - # get_or_create returns a tuple - item_place_type = SpotType.objects.get_or_create(name="checkout")[ - 0 - ] - loan_office = Spot.objects.create( - name="Tech Loan Office", - building_name="Kane Hall (KNE)", - longitude=Decimal("-122.306382"), - latitude=Decimal("47.653477"), - ) - loan_office.spottypes.add(item_place_type) - loan_office.save() - SpotExtendedInfo.objects.create( - key="app_type", value="tech", spot=loan_office - ) - SpotExtendedInfo.objects.create( - key="has_cte_techloan", value="true", spot=loan_office - ) - SpotExtendedInfo.objects.create( - key="cte_techloan_id", value="1", spot=loan_office - ) - SpotExtendedInfo.objects.create( - key="campus", value="seattle", spot=loan_office - ) + SpotExtendedInfo.objects.create( + key="location_description", + value="Sad, 3rd floor", + spot=room_301, + ) + SpotExtendedInfo.objects.create( + key="has_outlets", value="true", spot=room_301 + ) + SpotExtendedInfo.objects.create( + key="has_natural_light", value="true", spot=room_301 + ) + SpotExtendedInfo.objects.create( + key="food_nearby", value="building", spot=room_301 + ) + SpotExtendedInfo.objects.create( + key="campus", value="tacoma", spot=room_301 + ) - macbook = Item.objects.create( - name="Apple Macbook Pro", - spot=loan_office, - item_category="Placeholder Category", - item_subcategory="Laptop Computer", - ) - f = open( - os.path.join(base_dir, "resources", "building5.jpg"), "rb" - ) - ItemImage.objects.create( - item=macbook, - image=File(f), - display_index=0, - description="Macbook Pro", - ) - ItemExtendedInfo.objects.create( - key="i_quantity", value="10", item=macbook - ) - ItemExtendedInfo.objects.create( - key="i_model", value="Macbook Pro", item=macbook - ) - ItemExtendedInfo.objects.create( - key="i_brand", value="Apple", item=macbook - ) - ItemExtendedInfo.objects.create( - key="i_check_out_period", value="7", item=macbook - ) - ItemExtendedInfo.objects.create( - key="i_is_active", value="true", item=macbook - ) + food = Spot.objects.create( + name="This is a food spot", + capacity=10, + longitude=Decimal("-122.3101087"), + latitude=Decimal("47.6549552"), + building_name="Food Building", + ) + food.spottypes.add(cafe_type) + food.save() + food_info = SpotExtendedInfo.objects.create( + key="app_type", value="food", spot=food + ) + food_info = SpotExtendedInfo.objects.create( + key="has_outlets", value="true", spot=food + ) - latitude = Item.objects.create( - name="Dell Latitude E5440", - spot=loan_office, - item_category="Placeholder Category", - item_subcategory="Laptop Computer", - ) - ItemExtendedInfo.objects.create( - key="i_quantity", value="12", item=latitude - ) - ItemExtendedInfo.objects.create( - key="i_model", value="Latitude E5440", item=latitude - ) - ItemExtendedInfo.objects.create( - key="i_brand", value="Dell", item=latitude - ) - ItemExtendedInfo.objects.create( - key="i_check_out_period", value="14", item=latitude - ) - ItemExtendedInfo.objects.create( - key="i_is_active", value="true", item=latitude - ) + # get_or_create returns a tuple + item_place_type = SpotType.objects.get_or_create(name="checkout")[ + 0 + ] + loan_office = Spot.objects.create( + name="Tech Loan Office", + building_name="Kane Hall (KNE)", + longitude=Decimal("-122.306382"), + latitude=Decimal("47.653477"), + ) + loan_office.spottypes.add(item_place_type) + loan_office.save() + SpotExtendedInfo.objects.create( + key="app_type", value="tech", spot=loan_office + ) + SpotExtendedInfo.objects.create( + key="has_cte_techloan", value="true", spot=loan_office + ) + SpotExtendedInfo.objects.create( + key="cte_techloan_id", value="1", spot=loan_office + ) + SpotExtendedInfo.objects.create( + key="campus", value="seattle", spot=loan_office + ) - passport = Item.objects.create( - name="Fender Passport P-150", - spot=loan_office, - item_category="Placeholder Category", - item_subcategory="Portable Audio System", - ) - ItemExtendedInfo.objects.create( - key="i_quantity", value="5", item=passport - ) - ItemExtendedInfo.objects.create( - key="i_model", value="Passport P-150", item=passport - ) - ItemExtendedInfo.objects.create( - key="i_brand", value="Fender", item=passport - ) - ItemExtendedInfo.objects.create( - key="i_check_out_period", value="7", item=passport - ) - ItemExtendedInfo.objects.create( - key="i_is_active", value="true", item=passport - ) + macbook = Item.objects.create( + name="Apple Macbook Pro", + spot=loan_office, + item_category="Placeholder Category", + item_subcategory="Laptop Computer", + ) + f = open( + os.path.join("resources", "building5.jpg"), "rb" + ) + ItemImage.objects.create( + item=macbook, + image=File(f), + display_index=0, + description="Macbook Pro", + ) + ItemExtendedInfo.objects.create( + key="i_quantity", value="10", item=macbook + ) + ItemExtendedInfo.objects.create( + key="i_model", value="Macbook Pro", item=macbook + ) + ItemExtendedInfo.objects.create( + key="i_brand", value="Apple", item=macbook + ) + ItemExtendedInfo.objects.create( + key="i_check_out_period", value="7", item=macbook + ) + ItemExtendedInfo.objects.create( + key="i_is_active", value="true", item=macbook + ) - other_office = Spot.objects.create( - name="Another Loan Office", - building_name="Kane Hall (KNE)", - longitude=Decimal("-122.306382"), - latitude=Decimal("47.653477"), - ) - loan_office.spottypes.add(item_place_type) - loan_office.save() - SpotExtendedInfo.objects.create( - key="app_type", value="tech", spot=other_office - ) - SpotExtendedInfo.objects.create( - key="has_cte_techloan", value="true", spot=other_office - ) - SpotExtendedInfo.objects.create( - key="cte_techloan_id", value="2", spot=other_office - ) - SpotExtendedInfo.objects.create( - key="campus", value="seattle", spot=other_office - ) + latitude = Item.objects.create( + name="Dell Latitude E5440", + spot=loan_office, + item_category="Placeholder Category", + item_subcategory="Laptop Computer", + ) + ItemExtendedInfo.objects.create( + key="i_quantity", value="12", item=latitude + ) + ItemExtendedInfo.objects.create( + key="i_model", value="Latitude E5440", item=latitude + ) + ItemExtendedInfo.objects.create( + key="i_brand", value="Dell", item=latitude + ) + ItemExtendedInfo.objects.create( + key="i_check_out_period", value="14", item=latitude + ) + ItemExtendedInfo.objects.create( + key="i_is_active", value="true", item=latitude + ) - thingy = Item.objects.create( - name="Thingy P-150", - spot=other_office, - item_category="Placeholder Category", - item_subcategory="Portable Audio System", - ) - ItemExtendedInfo.objects.create( - key="i_quantity", value="5", item=thingy - ) - ItemExtendedInfo.objects.create( - key="i_model", value="Passport P-150", item=thingy - ) - ItemExtendedInfo.objects.create( - key="i_brand", value="Fender", item=thingy - ) - ItemExtendedInfo.objects.create( - key="i_check_out_period", value="7", item=thingy - ) - ItemExtendedInfo.objects.create( - key="i_is_active", value="true", item=thingy - ) + passport = Item.objects.create( + name="Fender Passport P-150", + spot=loan_office, + item_category="Placeholder Category", + item_subcategory="Portable Audio System", + ) + ItemExtendedInfo.objects.create( + key="i_quantity", value="5", item=passport + ) + ItemExtendedInfo.objects.create( + key="i_model", value="Passport P-150", item=passport + ) + ItemExtendedInfo.objects.create( + key="i_brand", value="Fender", item=passport + ) + ItemExtendedInfo.objects.create( + key="i_check_out_period", value="7", item=passport + ) + ItemExtendedInfo.objects.create( + key="i_is_active", value="true", item=passport + ) + + other_office = Spot.objects.create( + name="Another Loan Office", + building_name="Kane Hall (KNE)", + longitude=Decimal("-122.306382"), + latitude=Decimal("47.653477"), + ) + loan_office.spottypes.add(item_place_type) + loan_office.save() + SpotExtendedInfo.objects.create( + key="app_type", value="tech", spot=other_office + ) + SpotExtendedInfo.objects.create( + key="has_cte_techloan", value="true", spot=other_office + ) + SpotExtendedInfo.objects.create( + key="cte_techloan_id", value="2", spot=other_office + ) + SpotExtendedInfo.objects.create( + key="campus", value="seattle", spot=other_office + ) + + thingy = Item.objects.create( + name="Thingy P-150", + spot=other_office, + item_category="Placeholder Category", + item_subcategory="Portable Audio System", + ) + ItemExtendedInfo.objects.create( + key="i_quantity", value="5", item=thingy + ) + ItemExtendedInfo.objects.create( + key="i_model", value="Passport P-150", item=thingy + ) + ItemExtendedInfo.objects.create( + key="i_brand", value="Fender", item=thingy + ) + ItemExtendedInfo.objects.create( + key="i_check_out_period", value="7", item=thingy + ) + ItemExtendedInfo.objects.create( + key="i_is_active", value="true", item=thingy + ) + + loan_office = Spot.objects.create( + name="Yet Another Tech Loan Office", + building_name="Kane Hall (KNE)", + longitude=Decimal("-122.306382"), + latitude=Decimal("47.653477"), + ) + loan_office.spottypes.add(item_place_type) + loan_office.save() + SpotExtendedInfo.objects.create( + key="app_type", value="tech", spot=loan_office + ) + SpotExtendedInfo.objects.create( + key="has_cte_techloan", value="true", spot=loan_office + ) + SpotExtendedInfo.objects.create( + key="cte_techloan_id", value="9", spot=loan_office + ) + SpotExtendedInfo.objects.create( + key="campus", value="seattle", spot=loan_office + ) diff --git a/spotseeker_server/management/commands/register_application.py b/spotseeker_server/management/commands/register_application.py new file mode 100644 index 00000000..abccbf87 --- /dev/null +++ b/spotseeker_server/management/commands/register_application.py @@ -0,0 +1,85 @@ +# Copyright 2024 UW-IT, University of Washington +# SPDX-License-Identifier: Apache-2.0 + +import logging +from io import StringIO +import contextlib + +from django.core.management.base import BaseCommand, CommandParser + +from spotseeker_server.models import Client + +from oauth2_provider.management.commands import createapplication +from oauth2_provider.models import Application + + +logger = logging.getLogger(__name__) + + +class Command(BaseCommand): + help = "Register applications with Spotseeker." + + def add_arguments(self, parser: CommandParser) -> None: + parser.add_argument( + '-s', + '--show-credential', + action='store_true', + default=False, + dest='show_credential', + help="Print the credential created, very sensitive info.", + ) + return super().add_arguments(parser) + + def handle(self, *args, **options): + if args and args[0]: + name = args[0] + else: + name = input("Enter application name: ") + + logger.info("Registering application with Spotseeker...") + + # check if application already exists + try: + Application.objects.get(name=name) + logger.info("Application already registered, skipping...") + return + except Application.DoesNotExist: + pass + + output = StringIO() + + with contextlib.redirect_stdout(output): + createapplication.Command().handle( + name=name, + client_type='confidential', + authorization_grant_type='client-credentials', + verbosity=0, + ) + + logger.debug("Getting client secret...") + secret_len = 128 + end_char = output.tell() + output.seek(end_char - secret_len - 1) + client_secret = output.read(secret_len) + output.close() + + logger.debug("Putting application into Client table...") + + app = Application.objects.get(name=name) + Client.objects.create( + username=name, + name=name, + client_id=app.client_id, + client_secret=client_secret, + ) + + logger.debug("Compiling client credentials...") + + client = Client.objects.get(name=name) + credential = client.get_client_credential() + client.save() + + if options['show_credential']: + logger.info("Credential: {}".format(credential)) + + logger.info("Done.") diff --git a/spotseeker_server/management/commands/retrieve_spots.py b/spotseeker_server/management/commands/retrieve_spots.py index b364e468..78c3b08b 100644 --- a/spotseeker_server/management/commands/retrieve_spots.py +++ b/spotseeker_server/management/commands/retrieve_spots.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.core.management.base import BaseCommand, CommandError diff --git a/spotseeker_server/management/commands/sync_techloan.py b/spotseeker_server/management/commands/sync_techloan.py index 7a37235c..aaf71d8c 100644 --- a/spotseeker_server/management/commands/sync_techloan.py +++ b/spotseeker_server/management/commands/sync_techloan.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 import logging diff --git a/spotseeker_server/management/commands/techloan/spotseeker.py b/spotseeker_server/management/commands/techloan/spotseeker.py index 65e0aff9..b45f675b 100644 --- a/spotseeker_server/management/commands/techloan/spotseeker.py +++ b/spotseeker_server/management/commands/techloan/spotseeker.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 import json diff --git a/spotseeker_server/management/commands/techloan/techloan.py b/spotseeker_server/management/commands/techloan/techloan.py index e86a933e..f8221c71 100644 --- a/spotseeker_server/management/commands/techloan/techloan.py +++ b/spotseeker_server/management/commands/techloan/techloan.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 import json diff --git a/spotseeker_server/management/commands/techloan/utils.py b/spotseeker_server/management/commands/techloan/utils.py index 27e0f341..94e33eab 100644 --- a/spotseeker_server/management/commands/techloan/utils.py +++ b/spotseeker_server/management/commands/techloan/utils.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 import re diff --git a/spotseeker_server/middleware/persistent.py b/spotseeker_server/middleware/persistent.py index f42b413c..228ed534 100644 --- a/spotseeker_server/middleware/persistent.py +++ b/spotseeker_server/middleware/persistent.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 # from http://stackoverflow.com/questions/15896217/django-loading-a-page-that- diff --git a/spotseeker_server/migrations/0001_initial.py b/spotseeker_server/migrations/0001_initial.py index 8483e6ba..9233dda6 100644 --- a/spotseeker_server/migrations/0001_initial.py +++ b/spotseeker_server/migrations/0001_initial.py @@ -2,28 +2,22 @@ from __future__ import unicode_literals from django.db import models, migrations +import django.contrib.auth.models +import django.contrib.auth.validators import re from django.conf import settings import django.core.validators +import django.utils.timezone class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('oauth_provider', '0001_initial'), + ('auth', '0011_update_proxy_permissions'), ] operations = [ - migrations.CreateModel( - name='FavoriteSpot', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ], - options={ - }, - bases=(models.Model,), - ), migrations.CreateModel( name='Item', fields=[ @@ -43,7 +37,7 @@ class Migration(migrations.Migration): ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('key', models.CharField(max_length=50)), ('value', models.CharField(max_length=350)), - ('item', models.ForeignKey(blank=True, to='spotseeker_server.Item', null=True)), + ('item', models.ForeignKey(blank=True, to='spotseeker_server.Item', null=True, on_delete=models.CASCADE)), ], options={ 'verbose_name_plural': 'Item extended info', @@ -65,53 +59,7 @@ class Migration(migrations.Migration): ('etag', models.CharField(max_length=40)), ('upload_user', models.CharField(max_length=40)), ('upload_application', models.CharField(max_length=100)), - ('item', models.ForeignKey(to='spotseeker_server.Item')), - ], - options={ - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='SharedSpace', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('user', models.CharField(max_length=16)), - ('sender', models.CharField(max_length=256)), - ], - options={ - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='SharedSpaceRecipient', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('hash_key', models.CharField(max_length=32)), - ('recipient', models.CharField(max_length=256)), - ('user', models.CharField(default=None, max_length=16, null=True, blank=True)), - ('date_shared', models.DateTimeField(auto_now_add=True)), - ('shared_count', models.IntegerField()), - ('date_first_viewed', models.DateTimeField(null=True)), - ('viewed_count', models.IntegerField()), - ('shared_space', models.ForeignKey(to='spotseeker_server.SharedSpace')), - ], - options={ - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='SpaceReview', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('review', models.CharField(default=b'', max_length=1000)), - ('original_review', models.CharField(default=b'', max_length=1000)), - ('rating', models.IntegerField(validators=[django.core.validators.MaxValueValidator(5), django.core.validators.MinValueValidator(1)])), - ('date_submitted', models.DateTimeField(auto_now_add=True)), - ('date_published', models.DateTimeField(null=True)), - ('is_published', models.BooleanField()), - ('is_deleted', models.BooleanField()), - ('published_by', models.ForeignKey(related_name='published_by', to=settings.AUTH_USER_MODEL, null=True)), - ('reviewer', models.ForeignKey(related_name='reviewer', to=settings.AUTH_USER_MODEL)), + ('item', models.ForeignKey(to='spotseeker_server.Item', on_delete=models.CASCADE)), ], options={ }, @@ -147,7 +95,7 @@ class Migration(migrations.Migration): ('day', models.CharField(max_length=3, choices=[(b'm', b'monday'), (b't', b'tuesday'), (b'w', b'wednesday'), (b'th', b'thursday'), (b'f', b'friday'), (b'sa', b'saturday'), (b'su', b'sunday')])), ('start_time', models.TimeField()), ('end_time', models.TimeField()), - ('spot', models.ForeignKey(to='spotseeker_server.Spot')), + ('spot', models.ForeignKey(to='spotseeker_server.Spot', on_delete=models.CASCADE)), ], options={ 'verbose_name_plural': 'Spot available hours', @@ -160,7 +108,7 @@ class Migration(migrations.Migration): ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('key', models.CharField(max_length=50)), ('value', models.CharField(max_length=350)), - ('spot', models.ForeignKey(to='spotseeker_server.Spot')), + ('spot', models.ForeignKey(to='spotseeker_server.Spot', on_delete=models.CASCADE)), ], options={ 'verbose_name_plural': 'Spot extended info', @@ -182,7 +130,7 @@ class Migration(migrations.Migration): ('etag', models.CharField(max_length=40)), ('upload_user', models.CharField(max_length=40)), ('upload_application', models.CharField(max_length=100)), - ('spot', models.ForeignKey(to='spotseeker_server.Spot')), + ('spot', models.ForeignKey(to='spotseeker_server.Spot', on_delete=models.CASCADE)), ], options={ }, @@ -198,13 +146,40 @@ class Migration(migrations.Migration): }, bases=(models.Model,), ), + migrations.CreateModel( + name='Client', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('name', models.CharField(max_length=255)), + ('client_id', models.CharField(max_length=255)), + ('client_secret', models.CharField(max_length=255)), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), + ], + options={ + 'verbose_name_plural': 'OAuth Clients', + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), migrations.CreateModel( name='TrustedOAuthClient', fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('is_trusted', models.BooleanField()), ('bypasses_user_authorization', models.BooleanField()), - ('consumer', models.ForeignKey(to='oauth_provider.Consumer')), + ('consumer', models.ForeignKey(to='spotseeker_server.Client', on_delete=models.CASCADE)), ], options={ 'verbose_name_plural': 'Trusted OAuth clients', @@ -221,18 +196,6 @@ class Migration(migrations.Migration): field=models.ManyToManyField(related_name='spots', max_length=50, null=True, to='spotseeker_server.SpotType', blank=True), preserve_default=True, ), - migrations.AddField( - model_name='spacereview', - name='space', - field=models.ForeignKey(to='spotseeker_server.Spot'), - preserve_default=True, - ), - migrations.AddField( - model_name='sharedspace', - name='space', - field=models.ForeignKey(to='spotseeker_server.Spot'), - preserve_default=True, - ), migrations.AlterUniqueTogether( name='itemextendedinfo', unique_together=set([('item', 'key')]), @@ -240,19 +203,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='item', name='spot', - field=models.ForeignKey(blank=True, to='spotseeker_server.Spot', null=True), - preserve_default=True, - ), - migrations.AddField( - model_name='favoritespot', - name='spot', - field=models.ForeignKey(to='spotseeker_server.Spot'), - preserve_default=True, - ), - migrations.AddField( - model_name='favoritespot', - name='user', - field=models.ForeignKey(to=settings.AUTH_USER_MODEL), + field=models.ForeignKey(blank=True, to='spotseeker_server.Spot', null=True, on_delete=models.CASCADE), preserve_default=True, ), ] diff --git a/spotseeker_server/migrations/0002_auto_20181029_2244.py b/spotseeker_server/migrations/0002_auto_20181029_2244.py index 31696e5f..ecffa936 100644 --- a/spotseeker_server/migrations/0002_auto_20181029_2244.py +++ b/spotseeker_server/migrations/0002_auto_20181029_2244.py @@ -11,18 +11,6 @@ class Migration(migrations.Migration): ] operations = [ - migrations.AlterField( - model_name='spacereview', - name='is_deleted', - field=models.BooleanField(default=False), - preserve_default=True, - ), - migrations.AlterField( - model_name='spacereview', - name='is_published', - field=models.BooleanField(default=False), - preserve_default=True, - ), migrations.AlterField( model_name='trustedoauthclient', name='bypasses_user_authorization', diff --git a/spotseeker_server/migrations/0004_auto_20200702_1932.py b/spotseeker_server/migrations/0004_auto_20200702_1932.py index e050326f..b37a76d8 100644 --- a/spotseeker_server/migrations/0004_auto_20200702_1932.py +++ b/spotseeker_server/migrations/0004_auto_20200702_1932.py @@ -17,16 +17,6 @@ class Migration(migrations.Migration): name='image', field=models.ImageField(upload_to='item_images'), ), - migrations.AlterField( - model_name='spacereview', - name='original_review', - field=models.CharField(default='', max_length=1000), - ), - migrations.AlterField( - model_name='spacereview', - name='review', - field=models.CharField(default='', max_length=1000), - ), migrations.AlterField( model_name='spotavailablehours', name='day', diff --git a/spotseeker_server/migrations/0005_auto_20230406_2328.py b/spotseeker_server/migrations/0005_auto_20230406_2328.py new file mode 100644 index 00000000..6ba9d2d3 --- /dev/null +++ b/spotseeker_server/migrations/0005_auto_20230406_2328.py @@ -0,0 +1,20 @@ +# Generated by Django 2.2.28 on 2023-04-06 23:28 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('spotseeker_server', '0004_auto_20200702_1932'), + ] + + operations = [ + migrations.AlterModelOptions( + name='trustedoauthclient', + options={'verbose_name_plural': 'Trusted OAuth Clients'}, + ), + migrations.DeleteModel( + name="Client", + ), + ] diff --git a/spotseeker_server/migrations/0005_auto_20230407_2345.py b/spotseeker_server/migrations/0005_auto_20230407_2345.py deleted file mode 100644 index a369b102..00000000 --- a/spotseeker_server/migrations/0005_auto_20230407_2345.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.29 on 2023-04-07 23:45 -from __future__ import unicode_literals - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('spotseeker_server', '0004_auto_20200702_1932'), - ] - - operations = [ - migrations.RemoveField( - model_name='favoritespot', - name='spot', - ), - migrations.RemoveField( - model_name='favoritespot', - name='user', - ), - migrations.RemoveField( - model_name='sharedspace', - name='space', - ), - migrations.RemoveField( - model_name='sharedspacerecipient', - name='shared_space', - ), - migrations.RemoveField( - model_name='spacereview', - name='published_by', - ), - migrations.RemoveField( - model_name='spacereview', - name='reviewer', - ), - migrations.RemoveField( - model_name='spacereview', - name='space', - ), - migrations.DeleteModel( - name='FavoriteSpot', - ), - migrations.DeleteModel( - name='SharedSpace', - ), - migrations.DeleteModel( - name='SharedSpaceRecipient', - ), - migrations.DeleteModel( - name='SpaceReview', - ), - ] diff --git a/spotseeker_server/migrations/0006_auto_20230420_2214.py b/spotseeker_server/migrations/0006_auto_20230420_2214.py new file mode 100644 index 00000000..36d6160d --- /dev/null +++ b/spotseeker_server/migrations/0006_auto_20230420_2214.py @@ -0,0 +1,50 @@ +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('auth', '0011_update_proxy_permissions'), + ('spotseeker_server', '0005_auto_20230406_2328'), + ] + + operations = [ + migrations.CreateModel( + name='Client', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('name', models.CharField(max_length=255)), + ('client_id', models.CharField(max_length=255)), + ('client_secret', models.CharField(max_length=255)), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), + ], + options={ + 'verbose_name_plural': 'OAuth Clients', + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + migrations.AddField( + model_name='client', + name='access_token', + field=models.CharField(blank=True, max_length=255), + ), + migrations.AddField( + model_name='client', + name='client_credential', + field=models.CharField(blank=True, max_length=255), + ), + ] diff --git a/spotseeker_server/migrations/0007_remove_client_access_token.py b/spotseeker_server/migrations/0007_remove_client_access_token.py new file mode 100644 index 00000000..2139c1d2 --- /dev/null +++ b/spotseeker_server/migrations/0007_remove_client_access_token.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.28 on 2023-05-05 15:03 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('spotseeker_server', '0006_auto_20230420_2214'), + ] + + operations = [ + migrations.RemoveField( + model_name='client', + name='access_token', + ), + ] diff --git a/spotseeker_server/migrations/0008_delete_trustedoauthclient.py b/spotseeker_server/migrations/0008_delete_trustedoauthclient.py new file mode 100644 index 00000000..75d626c0 --- /dev/null +++ b/spotseeker_server/migrations/0008_delete_trustedoauthclient.py @@ -0,0 +1,16 @@ +# Generated by Django 2.2.28 on 2023-06-29 22:52 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('spotseeker_server', '0007_remove_client_access_token'), + ] + + operations = [ + migrations.DeleteModel( + name='TrustedOAuthClient', + ), + ] diff --git a/spotseeker_server/models/__init__.py b/spotseeker_server/models/__init__.py index c5bc911f..6877a53a 100644 --- a/spotseeker_server/models/__init__.py +++ b/spotseeker_server/models/__init__.py @@ -1,7 +1,7 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 -from .auth import TrustedOAuthClient +from .auth import Client from .item import Item, ItemExtendedInfo, ItemImage from .spot import Spot, SpotAvailableHours, SpotExtendedInfo, \ SpotImage, SpotType diff --git a/spotseeker_server/models/auth.py b/spotseeker_server/models/auth.py index 6b4afb29..b329f02c 100644 --- a/spotseeker_server/models/auth.py +++ b/spotseeker_server/models/auth.py @@ -1,21 +1,31 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.db import models +from django.contrib.auth.models import AbstractUser -import oauth_provider.models +from base64 import b64encode -class TrustedOAuthClient(models.Model): - consumer = models.ForeignKey(oauth_provider.models.Consumer) - is_trusted = models.BooleanField(default=False) - bypasses_user_authorization = models.BooleanField(default=False) +class Client(AbstractUser): + name = models.CharField(max_length=255) + client_id = models.CharField(max_length=255) + client_secret = models.CharField(max_length=255) + client_credential = models.CharField(max_length=255, blank=True) class Meta: - verbose_name_plural = "Trusted OAuth clients" + verbose_name_plural = "OAuth Clients" - def __unicode__(self): - return self.consumer.name + def get_client_credential(self) -> str: + """ + creates a client credential for the client, setting it for the Client + and returning it + """ + + raw_cred = f"{self.client_id}:{self.client_secret}" + self.client_credential = b64encode(raw_cred.encode('utf-8'))\ + .decode('utf-8') + return self.client_credential def __str__(self): - return self.__unicode__() + return self.name diff --git a/spotseeker_server/models/item.py b/spotseeker_server/models/item.py index 558ac3bc..b4b85cd1 100644 --- a/spotseeker_server/models/item.py +++ b/spotseeker_server/models/item.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from PIL import Image @@ -15,7 +15,8 @@ class Item(models.Model): name = models.CharField(max_length=50) slug = models.SlugField(max_length=50, blank=True) - spot = models.ForeignKey(Spot, blank=True, null=True) + spot = models.ForeignKey(Spot, blank=True, null=True, + on_delete=models.CASCADE) # These need to be item_ cat/subcat due to DB issues item_category = models.CharField(max_length=50, null=True) item_subcategory = models.CharField(max_length=50, null=True) @@ -50,7 +51,8 @@ def json_data_structure(self): class ItemExtendedInfo(models.Model): - item = models.ForeignKey(Item, blank=True, null=True) + item = models.ForeignKey(Item, blank=True, null=True, + on_delete=models.CASCADE) key = models.CharField(max_length=50) value = models.CharField(max_length=350) @@ -81,7 +83,7 @@ class ItemImage(models.Model): description = models.CharField(max_length=200, blank=True) display_index = models.PositiveIntegerField(null=True, blank=True) image = models.ImageField(upload_to="item_images") - item = models.ForeignKey(Item) + item = models.ForeignKey(Item, on_delete=models.CASCADE) content_type = models.CharField(max_length=40) width = models.IntegerField() height = models.IntegerField() diff --git a/spotseeker_server/models/spot.py b/spotseeker_server/models/spot.py index 83af0d3f..6c6e8156 100644 --- a/spotseeker_server/models/spot.py +++ b/spotseeker_server/models/spot.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 """ Changes @@ -14,13 +14,11 @@ from PIL import Image -from django.contrib.auth.models import User from django.core.cache import cache from django.core.files.uploadedfile import UploadedFile from django.core.exceptions import ValidationError from django.core.validators import validate_slug from django.db import models -from django.db.models import Sum, Count from django.urls import reverse from .utility import update_etag @@ -188,7 +186,7 @@ class SpotAvailableHours(models.Model): ("su", "sunday"), ) - spot = models.ForeignKey(Spot) + spot = models.ForeignKey(Spot, on_delete=models.CASCADE) day = models.CharField(max_length=3, choices=DAY_CHOICES) start_time = models.TimeField() @@ -244,7 +242,7 @@ class SpotExtendedInfo(models.Model): key = models.CharField(max_length=50) value = models.CharField(max_length=350) - spot = models.ForeignKey(Spot) + spot = models.ForeignKey(Spot, on_delete=models.CASCADE) class Meta: verbose_name_plural = "Spot extended info" @@ -277,7 +275,7 @@ class SpotImage(models.Model): description = models.CharField(max_length=200, blank=True) display_index = models.PositiveIntegerField(null=True, blank=True) image = models.ImageField(upload_to="space_images") - spot = models.ForeignKey(Spot) + spot = models.ForeignKey(Spot, on_delete=models.CASCADE) content_type = models.CharField(max_length=40) width = models.IntegerField() height = models.IntegerField() diff --git a/spotseeker_server/models/utility.py b/spotseeker_server/models/utility.py index d767d3b3..32b9b639 100644 --- a/spotseeker_server/models/utility.py +++ b/spotseeker_server/models/utility.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from functools import wraps diff --git a/spotseeker_server/org_filters/uw_search.py b/spotseeker_server/org_filters/uw_search.py index 9b5dc368..0ac229fd 100644 --- a/spotseeker_server/org_filters/uw_search.py +++ b/spotseeker_server/org_filters/uw_search.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 """ Changes diff --git a/spotseeker_server/org_forms/uiuc_spot.py b/spotseeker_server/org_forms/uiuc_spot.py index 6c0993a2..9f8397ca 100644 --- a/spotseeker_server/org_forms/uiuc_spot.py +++ b/spotseeker_server/org_forms/uiuc_spot.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 """ Changes diff --git a/spotseeker_server/org_forms/uw_spot.py b/spotseeker_server/org_forms/uw_spot.py index 32906509..d007de36 100644 --- a/spotseeker_server/org_forms/uw_spot.py +++ b/spotseeker_server/org_forms/uw_spot.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 """ Changes @@ -11,9 +11,7 @@ from django.dispatch import receiver from spotseeker_server.default_forms.spot import DefaultSpotForm from spotseeker_server.default_forms.spot import DefaultSpotExtendedInfoForm -from spotseeker_server.models import Spot, SpotExtendedInfo from spotseeker_server.dispatch import spot_post_build -import simplejson as json import re import phonenumbers @@ -21,7 +19,7 @@ # dict of all of the uw extended info with values that must be validated # and what all of the possible validated values are, or validated types validated_ei = { - "app_type": ["food", "tech"], + "app_type": ["food", "study", "tech"], "auto_labstats_available": "int", "auto_labstats_total": "int", "campus": ["seattle", "tacoma", "bothell", "south_lake_union"], diff --git a/spotseeker_server/require_auth.py b/spotseeker_server/require_auth.py deleted file mode 100644 index cad47225..00000000 --- a/spotseeker_server/require_auth.py +++ /dev/null @@ -1,94 +0,0 @@ -# Copyright 2023 UW-IT, University of Washington -# SPDX-License-Identifier: Apache-2.0 - -""" Changes - ================================================================= - - sbutler1@illinois.edu: only load the auth modules once on app - initialization. - ^ This is being reverted back to being loaded every time. After - profiling, it didn't seem like there was hardly any speed - difference, and only loading this once on application load - breaks our unit tests. -""" - -from django.http import HttpResponse -from django.core.exceptions import ImproperlyConfigured -import spotseeker_server.auth.all_ok -from spotseeker_server.load_module import load_module_by_name - -from functools import wraps - -from django.conf import settings - - -def get_auth_module(): - try: - mod_name = settings.SPOTSEEKER_AUTH_MODULE - except (NameError, AttributeError): - return spotseeker_server.auth.all_ok - return load_module_by_name(mod_name) - - -def get_auth_method(method_name): - mod = get_auth_module() - try: - return getattr(mod, method_name) - except (AttributeError, NameError): - raise ImproperlyConfigured( - 'Module "%s" does not define a "%s" ' - "method." % (mod, method_name) - ) - - -def check_auth(method_name, func, *args, **kwargs): - method = get_auth_method(method_name) - bad_response = method(*args, **kwargs) - if bad_response: - return bad_response - else: - return func(*args, **kwargs) - - -def app_auth_required(func): - @wraps(func) - def _checkAuth(*args, **kwargs): - return check_auth("authenticate_application", func, *args, **kwargs) - - return _checkAuth - - -def user_auth_required(func): - @wraps(func) - def _checkAuth(*args, **kwargs): - return check_auth("authenticate_user", func, *args, **kwargs) - - return _checkAuth - - -def admin_auth_required(func): - @wraps(func) - def _checkAuth(*args, **kwargs): - ### - # XXX - this needs to change to something else. stop-gap measure - ### - bad_response = HttpResponse("Error - admin access required") - bad_response.status_code = 401 - - if not hasattr(settings, "SPOTSEEKER_AUTH_ADMINS"): - print("Set SPOTSEEKER_AUTH_ADMINS in your settings.py") - return bad_response - - admins = settings.SPOTSEEKER_AUTH_ADMINS - if not isinstance(admins, (list, tuple)): - print("SPOTSEEKER_AUTH_ADMINS must be a list or tuple") - return bad_response - - request = args[1] - username = request.META["SS_OAUTH_USER"] - if username not in admins: - return bad_response - - return func(*args, **kwargs) - - return _checkAuth diff --git a/spotseeker_server/templates/registration/login.html b/spotseeker_server/templates/registration/login.html new file mode 100644 index 00000000..99da5323 --- /dev/null +++ b/spotseeker_server/templates/registration/login.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/spotseeker_server/test/__init__.py b/spotseeker_server/test/__init__.py index bae5c3f5..70438815 100644 --- a/spotseeker_server/test/__init__.py +++ b/spotseeker_server/test/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TestCase diff --git a/spotseeker_server/test/auth/__init__.py b/spotseeker_server/test/auth/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/spotseeker_server/test/auth/all_ok.py b/spotseeker_server/test/auth/all_ok.py deleted file mode 100644 index 7da248bd..00000000 --- a/spotseeker_server/test/auth/all_ok.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright 2023 UW-IT, University of Washington -# SPDX-License-Identifier: Apache-2.0 - -from django.test import TestCase -from django.conf import settings -from spotseeker_server.models import Spot -import simplejson as json -from django.test.utils import override_settings - - -@override_settings(SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok") -class SpotAuthAllOK(TestCase): - """Tests that the all_ok auth module - successfully allows any client access. - """ - - def setUp(self): - spot = Spot.objects.create( - name="This is for testing the all ok auth module", capacity=10 - ) - self.spot = spot - self.url = "/api/v1/spot/%s" % self.spot.pk - - def test_get(self): - c = self.client - response = c.get(self.url) - spot_dict = json.loads(response.content) - returned_spot = Spot.objects.get(pk=spot_dict["id"]) - self.assertEquals(returned_spot, self.spot, "Returns the correct spot") - - @override_settings( - SPOTSEEKER_SPOT_FORM="spotseeker_server.default_forms." - "spot.DefaultSpotForm", - SPOTSEEKER_SPOTEXTENDEDINFO_FORM="spotseeker_server.default_forms." - "spot.DefaultSpotExtendedInfoForm", - SPOTSEEKER_AUTH_ADMINS=("demo_user",), - ) - def test_put(self): - c = self.client - response = c.get(self.url) - etag = response["ETag"] - - spot_dict = json.loads(response.content) - spot_dict["location"] = {"latitude": 55, "longitude": -30} - spot_dict["name"] = "Modifying all ok" - - response = c.put( - self.url, - json.dumps(spot_dict), - content_type="application/json", - If_Match=etag, - ) - self.assertEquals( - response.status_code, 200, "Accepts a valid json string" - ) - - updated_spot = Spot.objects.get(pk=self.spot.pk) - self.assertEquals( - updated_spot.name, - "Modifying all ok", - "a valid PUT changes the name", - ) diff --git a/spotseeker_server/test/auth/oauth.py b/spotseeker_server/test/auth/oauth.py deleted file mode 100644 index 2d946853..00000000 --- a/spotseeker_server/test/auth/oauth.py +++ /dev/null @@ -1,362 +0,0 @@ -# Copyright 2023 UW-IT, University of Washington -# SPDX-License-Identifier: Apache-2.0 - -from django.test import TestCase -from django.conf import settings -from django.core.management import call_command -from spotseeker_server.models import Spot, TrustedOAuthClient -import simplejson as json -import hashlib -import time -import random -from oauth_provider.models import Consumer -from oauthlib import oauth1 -from django.test.utils import override_settings -from unittest.mock import patch, MagicMock -from spotseeker_server import models -from spotseeker_server.require_auth import get_auth_module, get_auth_method -from spotseeker_server.auth import all_ok, oauth, fake_oauth -from django.core.exceptions import ImproperlyConfigured - - -@override_settings(SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.oauth") -class SpotAuthOAuth(TestCase): - def setUp(self): - spot = Spot.objects.create( - name="This is for testing the oauth module", capacity=10 - ) - self.spot = spot - self.url = "/api/v1/spot/%s" % self.spot.pk - - def test_get_no_oauth(self): - c = self.client - response = c.get(self.url) - self.assertEquals( - response.status_code, 401, "No access to GET w/o oauth" - ) - - def test_valid_oauth(self): - consumer_name = "Test consumer" - - key = hashlib.sha1( - "{0} - {1}".format(random.random(), time.time()).encode("utf-8") - ).hexdigest() - secret = hashlib.sha1( - "{0} - {1}".format(random.random(), time.time()).encode("utf-8") - ).hexdigest() - - create_consumer = Consumer.objects.create( - name=consumer_name, key=key, secret=secret - ) - - client = oauth1.Client(key, client_secret=secret) - _, headers, _ = client.sign("http://testserver" + self.url) - - c = self.client - response = c.get(self.url, HTTP_AUTHORIZATION=headers["Authorization"]) - - self.assertEquals( - response.status_code, - 200, - "Got a 200 w/ a proper oauth client connection", - ) - - spot_dict = json.loads(response.content) - - self.assertEquals( - spot_dict["id"], self.spot.pk, "Got the right spot back from oauth" - ) - - def test_invalid_oauth(self): - client = oauth1.Client( - "This is a fake key", client_secret="This is a fake secret" - ) - _, headers, _ = client.sign("http://testserver" + self.url) - - c = self.client - response = c.get(self.url, HTTP_AUTHORIZATION=headers["Authorization"]) - - self.assertEquals( - response.status_code, - 401, - "Got a 401 w/ an invented oauth client id", - ) - - @override_settings( - SPOTSEEKER_SPOT_FORM="spotseeker_server.default_forms.sp" - "ot.DefaultSpotForm" - ) - @override_settings( - SPOTSEEKER_SPOTEXTENDEDINFO_FORM="spotseeker_server.default_forms.sp" - "ot.DefaultSpotExtendedInfoForm" - ) - def test_put_no_oauth(self): - c = self.client - - response = c.get(self.url) - - etag = self.spot.etag - - spot_dict = self.spot.json_data_structure() - spot_dict["name"] = "Failing to modify oauth" - - response = c.put( - self.url, - json.dumps(spot_dict), - content_type="application/json", - If_Match=etag, - ) - self.assertEquals( - response.status_code, 401, "Rejects a PUT w/o oauth info" - ) - - @override_settings( - SPOTSEEKER_SPOT_FORM="spotseeker_server.default_forms.sp" - "ot.DefaultSpotForm" - ) - @override_settings( - SPOTSEEKER_SPOTEXTENDEDINFO_FORM="spotseeker_server.default_forms.sp" - "ot.DefaultSpotExtendedInfoForm" - ) - def test_put_untrusted_oauth(self): - consumer_name = "Untrusted test consumer" - - key = hashlib.sha1( - "{0} - {1}".format(random.random(), time.time()).encode("utf-8") - ).hexdigest() - secret = hashlib.sha1( - "{0} - {1}".format(random.random(), time.time()).encode("utf-8") - ).hexdigest() - - create_consumer = Consumer.objects.create( - name=consumer_name, key=key, secret=secret - ) - - client = oauth1.Client(key, client_secret=secret) - _, headers, _ = client.sign("http://testserver" + self.url) - - c = self.client - response = c.get(self.url, HTTP_AUTHORIZATION=headers["Authorization"]) - etag = response["ETag"] - - spot_dict = json.loads(response.content) - spot_dict["name"] = "Failing to modify oauth" - spot_dict["location"] = {"latitude": 55, "longitude": -30} - - response = c.put( - self.url, - json.dumps(spot_dict), - content_type="application/json", - If_Match=etag, - HTTP_AUTHORIZATION=headers["Authorization"], - ) - self.assertEquals( - response.status_code, - 401, - "Rejects a PUT from a non-trusted oauth client", - ) - - @override_settings( - SPOTSEEKER_SPOT_FORM="spotseeker_server.default_forms.sp" - "ot.DefaultSpotForm" - ) - @override_settings( - SPOTSEEKER_SPOTEXTENDEDINFO_FORM="spotseeker_server.default_forms.sp" - "ot.DefaultSpotExtendedInfoForm" - ) - def test_put_untrusted_oauth_with_user_header(self): - consumer_name = "Untrusted test consumer" - - key = hashlib.sha1( - "{0} - {1}".format(random.random(), time.time()).encode("utf-8") - ).hexdigest() - secret = hashlib.sha1( - "{0} - {1}".format(random.random(), time.time()).encode("utf-8") - ).hexdigest() - - create_consumer = Consumer.objects.create( - name=consumer_name, key=key, secret=secret - ) - - client = oauth1.Client(key, client_secret=secret) - _, headers, _ = client.sign("http://testserver" + self.url) - - c = self.client - response = c.get(self.url, HTTP_AUTHORIZATION=headers["Authorization"]) - etag = response["ETag"] - - spot_dict = json.loads(response.content) - spot_dict["name"] = "Failing to modify oauth" - - response = c.put( - self.url, - json.dumps(spot_dict), - content_type="application/json", - If_Match=etag, - HTTP_AUTHORIZATION=headers["Authorization"], - HTTP_X_OAUTH_USER="pmichaud", - ) - self.assertEquals( - response.status_code, - 401, - "Rejects a PUT from a non-trusted oauth client", - ) - - @override_settings( - SPOTSEEKER_SPOT_FORM="spotseeker_server.default_forms.sp" - "ot.DefaultSpotForm" - ) - @override_settings( - SPOTSEEKER_SPOTEXTENDEDINFO_FORM="spotseeker_server.default_forms.sp" - "ot.DefaultSpotExtendedInfoForm" - ) - @override_settings(SPOTSEEKER_AUTH_ADMINS=("pmichaud",)) - def test_put_trusted_client(self): - consumer_name = "Trusted test consumer" - - key = hashlib.sha1( - "{0} - {1}".format(random.random(), time.time()).encode("utf-8") - ).hexdigest() - secret = hashlib.sha1( - "{0} - {1}".format(random.random(), time.time()).encode("utf-8") - ).hexdigest() - - create_consumer = Consumer.objects.create( - name=consumer_name, key=key, secret=secret - ) - trusted_consumer = TrustedOAuthClient.objects.create( - consumer=create_consumer, - is_trusted=True, - bypasses_user_authorization=False, - ) - - client = oauth1.Client(key, client_secret=secret) - _, headers, _ = client.sign("http://testserver" + self.url) - - c = self.client - response = c.get(self.url, HTTP_AUTHORIZATION=headers["Authorization"]) - etag = response["ETag"] - - spot_dict = json.loads(response.content) - spot_dict["name"] = "Failing to modify oauth" - spot_dict["location"] = {"latitude": 55, "longitude": -30} - - response = c.put( - self.url, - json.dumps(spot_dict), - content_type="application/json", - If_Match=etag, - HTTP_AUTHORIZATION=headers["Authorization"], - HTTP_X_OAUTH_USER="pmichaud", - ) - self.assertEquals( - response.status_code, - 200, - "Accepts a PUT from a trusted oauth client", - ) - - @override_settings( - SPOTSEEKER_SPOT_FORM="spotseeker_server.default_forms.sp" - "ot.DefaultSpotForm" - ) - @override_settings( - SPOTSEEKER_SPOTEXTENDEDINFO_FORM="spotseeker_server.default_forms.sp" - "ot.DefaultSpotExtendedInfoForm" - ) - def test_put_trusted_client_no_user(self): - consumer_name = "Trusted test consumer" - - key = hashlib.sha1( - "{0} - {1}".format(random.random(), time.time()).encode("utf-8") - ).hexdigest() - secret = hashlib.sha1( - "{0} - {1}".format(random.random(), time.time()).encode("utf-8") - ).hexdigest() - - create_consumer = Consumer.objects.create( - name=consumer_name, key=key, secret=secret - ) - trusted_consumer = TrustedOAuthClient.objects.create( - consumer=create_consumer, - is_trusted=True, - bypasses_user_authorization=False, - ) - - client = oauth1.Client(key, client_secret=secret) - _, headers, _ = client.sign("http://testserver" + self.url) - - c = self.client - response = c.get(self.url, HTTP_AUTHORIZATION=headers["Authorization"]) - etag = response["ETag"] - - spot_dict = json.loads(response.content) - spot_dict["name"] = "Failing to modify oauth" - - response = c.put( - self.url, - json.dumps(spot_dict), - content_type="application/json", - If_Match=etag, - HTTP_AUTHORIZATION=headers["Authorization"], - ) - self.assertEquals( - response.status_code, - 401, - "Rejects a PUT from a trusted oauth client w/o a given user", - ) - - def test_create_trusted_client(self): - """Tests to be sure the create_consumer - command can create trusted clients. - """ - consumer_name = "This is for testing create_consumer" - - call_command( - "create_consumer", - consumer_name=consumer_name, - trusted="yes", - silent=True, - ) - - consumer = Consumer.objects.get(name=consumer_name) - - client = TrustedOAuthClient.objects.get(consumer=consumer) - - self.assertIsInstance(client, TrustedOAuthClient) - - @override_settings() - def test_get_auth_module(self): - del settings.SPOTSEEKER_AUTH_MODULE - self.assertEqual(all_ok, get_auth_module()) - - with override_settings(SPOTSEEKER_AUTH_MODULE='' - 'spotseeker_server.auth.all_ok'): - self.assertEqual(all_ok, get_auth_module()) - - with override_settings(SPOTSEEKER_AUTH_MODULE='' - 'spotseeker_server.auth.oauth'): - self.assertEqual(oauth, get_auth_module()) - - with override_settings(SPOTSEEKER_AUTH_MODULE='' - 'spotseeker_server.auth.fake_oauth'): - self.assertEqual(fake_oauth, get_auth_module()) - - @override_settings() - def test_get_auth_method(self): - with override_settings(SPOTSEEKER_AUTH_MODULE='' - 'spotseeker_server.auth.all_ok'): - self.assertEqual(None, - get_auth_method('authenticate_application')()) - - with override_settings(SPOTSEEKER_AUTH_MODULE='' - 'spotseeker_server.auth.fake_oauth'): - self.assertEqual(None, - get_auth_method('authenticate_application')()) - - with override_settings(SPOTSEEKER_AUTH_MODULE='' - 'spotseeker_server.auth.oauth'): - auth_method = get_auth_method('authenticate_application') - response = auth_method('bad arg0', 'bad arg1') - self.assertEqual(401, response.status_code) - - self.assertRaises(ImproperlyConfigured, get_auth_method, 'fake') diff --git a/spotseeker_server/test/auth/oauth_logger.py b/spotseeker_server/test/auth/oauth_logger.py deleted file mode 100644 index 60af9a40..00000000 --- a/spotseeker_server/test/auth/oauth_logger.py +++ /dev/null @@ -1,278 +0,0 @@ -# Copyright 2023 UW-IT, University of Washington -# SPDX-License-Identifier: Apache-2.0 - -try: - from StringIO import StringIO -except ModuleNotFoundError: - from io import StringIO - -from django.test import TestCase -from django.conf import settings -from spotseeker_server.models import Spot, TrustedOAuthClient -from django.test.client import Client -import re -import simplejson as json -import logging - -import hashlib -import time -import random - -from oauth_provider.models import Consumer -from django.test.utils import override_settings -from mock import patch -from spotseeker_server import models - -from oauthlib import oauth1 - - -@override_settings( - SPOTSEEKER_SPOT_FORM="spotseeker_server.default_forms." - "spot.DefaultSpotForm", - SPOTSEEKER_SPOTEXTENDEDINFO_FORM="spotseeker_server.default_forms." - "spot.DefaultSpotExtendedInfoForm", - SPOTSEEKER_AUTH_ADMINS=("pmichaud",), -) -class SpotAuthOAuthLogger(TestCase): - @classmethod - def setUpTestData(self): - spot = Spot.objects.create( - name="This is for testing the oauth module", capacity=10 - ) - self.spot = spot - self.url = "/api/v1/spot/%s" % self.spot.pk - - def setUp(self): - new_middleware = [] - has_logger = False - self.original_middleware = settings.MIDDLEWARE - for middleware in settings.MIDDLEWARE: - new_middleware.append(middleware) - if middleware == "spotseeker_server.logger.oauth.LogMiddleware": - has_logger = True - - if not has_logger: - new_middleware.append( - "spotseeker_server.logger.oauth.LogMiddleware" - ) - settings.MIDDLEWARE = new_middleware - - self.stream = StringIO() - self.handler = logging.StreamHandler(self.stream) - self.log = logging.getLogger("spotseeker_server.logger.oauth") - self.log.setLevel(logging.INFO) - for handler in self.log.handlers: - self.log.removeHandler(handler) - self.log.addHandler(self.handler) - - def test_log_value_2_legged(self): - with self.settings( - SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.oauth" - ): - - consumer_name = "Test consumer" - - key = hashlib.sha1( - "{0} - {1}".format(random.random(), time.time()).encode( - "utf-8" - ) - ).hexdigest() - secret = hashlib.sha1( - "{0} - {1}".format(random.random(), time.time()).encode( - "utf-8" - ) - ).hexdigest() - - create_consumer = Consumer.objects.create( - name=consumer_name, key=key, secret=secret - ) - - client = oauth1.Client(key, client_secret=secret) - _, headers, _ = client.sign( - "http://testserver/api/v1/spot/%s" % self.spot.pk - ) - - response = Client().get( - self.url, HTTP_AUTHORIZATION=headers["Authorization"] - ) - - with self.settings( - SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok" - ): - - self.handler.flush() - log_message = self.stream.getvalue() - - matches = re.search( - r'\[.*?\] ([\d]+)\t"(.*?)"\t-\t"GET /api' - r'/v1/spot/([\d]+)" ([\d]+) ([\d]+)', - log_message, - ) - - consumer_id = int(matches.group(1)) - consumer_name = matches.group(2) - spot_id = int(matches.group(3)) - status_code = int(matches.group(4)) - response_size = int(matches.group(5)) - - self.assertEquals( - consumer_id, create_consumer.pk, "Logging correct consumer PK" - ) - self.assertEquals( - consumer_name, - create_consumer.name, - "Logging correct consumer name", - ) - self.assertEquals(spot_id, self.spot.pk, "Logging correct uri") - self.assertEquals( - status_code, - response.status_code, - "Logging correct status_code", - ) - self.assertEquals( - response_size, - len(response.content.decode()), - "Logging correct content size", - ) - - def test_log_trusted_3_legged(self): - with self.settings( - SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.oauth" - ): - consumer_name = "Trusted test consumer" - - key = hashlib.sha1( - "{0} - {1}".format(random.random(), time.time()).encode( - "utf-8" - ) - ).hexdigest() - secret = hashlib.sha1( - "{0} - {1}".format(random.random(), time.time()).encode( - "utf-8" - ) - ).hexdigest() - - create_consumer = Consumer.objects.create( - name=consumer_name, key=key, secret=secret - ) - trusted_consumer = TrustedOAuthClient.objects.create( - consumer=create_consumer, - is_trusted=True, - bypasses_user_authorization=False, - ) - - client = oauth1.Client(key, client_secret=secret) - _, headers, _ = client.sign( - "http://testserver/api/v1/spot/%s" % self.spot.pk - ) - - c = Client() - response = c.get( - self.url, HTTP_AUTHORIZATION=headers["Authorization"] - ) - etag = response["ETag"] - - spot_dict = json.loads(response.content) - spot_dict["name"] = "Failing to modify oauth" - spot_dict["location"] = {"latitude": 55, "longitude": -30} - - response = c.put( - self.url, - json.dumps(spot_dict), - content_type="application/json", - If_Match=etag, - HTTP_AUTHORIZATION=headers["Authorization"], - HTTP_X_OAUTH_USER="pmichaud", - ) - self.assertEquals( - response.status_code, - 200, - "Accespts a PUT from a trusted oauth client", - ) - - with self.settings( - SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok" - ): - - self.handler.flush() - log_message = self.stream.getvalue() - - matches = re.search( - r'\n\[.*?\] ([\d]+)\t"(.*?)"\t(.*?)\t"PUT /api/v1/spo' - r't/([\d]+)" ([\d]+) ([\d]+)', - log_message, - re.MULTILINE, - ) - - consumer_id = int(matches.group(1)) - consumer_name = matches.group(2) - user_name = matches.group(3) - spot_id = int(matches.group(4)) - status_code = int(matches.group(5)) - response_size = int(matches.group(6)) - - self.assertEquals( - consumer_id, create_consumer.pk, "Logging correct consumer PK" - ) - self.assertEquals( - consumer_name, - create_consumer.name, - "Logging correct consumer name", - ) - self.assertEquals( - user_name, "pmichaud", "Logging correct oauth username" - ) - self.assertEquals(spot_id, self.spot.pk, "Logging correct uri") - self.assertEquals( - status_code, - response.status_code, - "Logging correct status_code", - ) - self.assertEquals( - response_size, - len(response.content.decode()), - "Logging correct content size", - ) - - def test_invalid(self): - with self.settings( - SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.oauth" - ): - - c = Client() - response = c.get(self.url) - - with self.settings( - SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok" - ): - - self.handler.flush() - log_message = self.stream.getvalue() - - matches = re.search( - r'\[.*?\] -\t"-"\t-\t"GET /api/v1/spot' - r'/([\d]+)" ([\d]+) ([\d]+)', - log_message, - ) - - spot_id = int(matches.group(1)) - status_code = int(matches.group(2)) - response_size = int(matches.group(3)) - - self.assertEquals(spot_id, self.spot.pk, "Logging correct uri") - self.assertEquals( - status_code, - response.status_code, - "Logging correct status_code", - ) - self.assertEquals( - response_size, - len(response.content), - "Logging correct content size", - ) - - def tearDown(self): - self.log.removeHandler(self.handler) - self.handler.close() - - settings.MIDDLEWARE = self.original_middleware diff --git a/spotseeker_server/test/buildings.py b/spotseeker_server/test/buildings.py index ec3c33a5..592262fd 100644 --- a/spotseeker_server/test/buildings.py +++ b/spotseeker_server/test/buildings.py @@ -1,18 +1,15 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TestCase -from django.conf import settings from django.test.client import Client -from spotseeker_server.models import Spot, SpotExtendedInfo -from spotseeker_server import models -from mock import patch +from spotseeker_server.models import Spot from django.test.utils import override_settings import simplejson as json @override_settings( - SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok", + SPOTSEEKER_OAUTH_ENABLED=False, SPOTSEEKER_SPOT_FORM="spotseeker_server.default_forms." "spot.DefaultSpotForm", ) diff --git a/spotseeker_server/test/hours/get.py b/spotseeker_server/test/hours/get.py index 54a743c9..31242dab 100644 --- a/spotseeker_server/test/hours/get.py +++ b/spotseeker_server/test/hours/get.py @@ -1,17 +1,14 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TestCase -from django.conf import settings from django.test.client import Client from spotseeker_server.models import Spot, SpotAvailableHours import simplejson as json from django.test.utils import override_settings -from mock import patch -from spotseeker_server import models -@override_settings(SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok") +@override_settings(SPOTSEEKER_OAUTH_ENABLED=False) class SpotHoursGETTest(TestCase): def setUp(self): spot = Spot.objects.create(name="This spot has available hours") diff --git a/spotseeker_server/test/hours/hours_range.py b/spotseeker_server/test/hours/hours_range.py index 7d7b4988..a93a1463 100644 --- a/spotseeker_server/test/hours/hours_range.py +++ b/spotseeker_server/test/hours/hours_range.py @@ -1,17 +1,15 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from datetime import datetime, timedelta from django.test import TestCase from django.test.client import Client from django.test.utils import override_settings -from mock import patch from spotseeker_server import models import json -import time -@override_settings(SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok") +@override_settings(SPOTSEEKER_OAUTH_ENABLED=False) class HoursRangeTest(TestCase): """Tests searches for spots that are open anywhere within a range of hours. diff --git a/spotseeker_server/test/hours/model.py b/spotseeker_server/test/hours/model.py index 40ccc4ee..72ffb518 100644 --- a/spotseeker_server/test/hours/model.py +++ b/spotseeker_server/test/hours/model.py @@ -1,172 +1,149 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TestCase -from django.conf import settings +from django.test.utils import override_settings from django.core.exceptions import ValidationError import datetime from spotseeker_server.models import Spot, SpotAvailableHours +@override_settings(SPOTSEEKER_OAUTH_ENABLED=False) class SpotHoursModelTest(TestCase): """Tests for Spot AvailableHours.""" def test_startMatchesEnd(self): """Tests that a Spot's AvailableHours cannot have zero length.""" - with self.settings( - SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok" - ): - spot = Spot.objects.create(name="testing hours") - with self.assertRaises( - Exception, - msg="Got an error trying to save a time" - + "range with no time in it", - ) as ex: - SpotAvailableHours.objects.create( - day="m", spot=spot, start_time="01:30", end_time="01:30" - ) - self.assertEqual( - ex.exception.args[0], - "Invalid time range - start time must be before end time", + spot = Spot.objects.create(name="testing hours") + with self.assertRaises( + Exception, + msg="Got an error trying to save a time" + + "range with no time in it", + ) as ex: + SpotAvailableHours.objects.create( + day="m", spot=spot, start_time="01:30", end_time="01:30" ) + self.assertEqual( + ex.exception.args[0], + "Invalid time range - start time must be before end time", + ) def test_startAfterEnd(self): """Tests that a Spot's AvailableHours cannot end before they begin.""" - with self.settings( - SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok" - ): - spot = Spot.objects.create(name="testing hours") - with self.assertRaises( - Exception, - msg="Got an error trying to save a time" - + "range with no time in it", - ) as ex: - SpotAvailableHours.objects.create( - day="m", spot=spot, start_time="01:30", end_time="01:30" - ) - self.assertEqual( - ex.exception.args[0], - "Invalid time range - start time must be before end time", + spot = Spot.objects.create(name="testing hours") + with self.assertRaises( + Exception, + msg="Got an error trying to save a time" + + "range with no time in it", + ) as ex: + SpotAvailableHours.objects.create( + day="m", spot=spot, start_time="01:30", end_time="01:30" ) + self.assertEqual( + ex.exception.args[0], + "Invalid time range - start time must be before end time", + ) def test_properRange(self): """Tests that having AvailableHours with a start time before the end time saves properly. """ - with self.settings( - SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok" - ): - spot = Spot.objects.create(name="testing hours") - hours = SpotAvailableHours.objects.create( - day="m", spot=spot, start_time="01:30", end_time="01:40" - ) + spot = Spot.objects.create(name="testing hours") + hours = SpotAvailableHours.objects.create( + day="m", spot=spot, start_time="01:30", end_time="01:40" + ) - self.assertEqual(hours.start_time, datetime.time(1, 30), "ok") - self.assertEqual(hours.end_time, datetime.time(1, 40), "ok") - self.assertEqual(hours.day, "m", "ok") + self.assertEqual(hours.start_time, datetime.time(1, 30), "ok") + self.assertEqual(hours.end_time, datetime.time(1, 40), "ok") + self.assertEqual(hours.day, "m", "ok") def test_missingStart(self): """Tests that AvailableHours cannot be created with no start time.""" - with self.settings( - SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok" - ): - spot = Spot.objects.create(name="testing hours") - has_error = False - try: - hours = SpotAvailableHours.objects.create( - spot=spot, day="m", end_time="01:30" - ) - except ValidationError: - has_error = True - - self.assertEqual( - has_error, - True, - "Doesn't allow hours to be stored without a " "start time", + spot = Spot.objects.create(name="testing hours") + has_error = False + try: + hours = SpotAvailableHours.objects.create( + spot=spot, day="m", end_time="01:30" ) + except ValidationError: + has_error = True + + self.assertEqual( + has_error, + True, + "Doesn't allow hours to be stored without a " "start time", + ) def test_missingEnd(self): """Tests that AvailableHours cannot be created with no end time.""" - with self.settings( - SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok" - ): - spot = Spot.objects.create(name="testing hours") - has_error = False - try: - hours = SpotAvailableHours.objects.create( - spot=spot, day="m", start_time="01:30" - ) - except ValidationError: - has_error = True - - self.assertEqual( - has_error, - True, - "Doesn't allow hours to be stored without " "an end time", + spot = Spot.objects.create(name="testing hours") + has_error = False + try: + hours = SpotAvailableHours.objects.create( + spot=spot, day="m", start_time="01:30" ) + except ValidationError: + has_error = True + + self.assertEqual( + has_error, + True, + "Doesn't allow hours to be stored without " "an end time", + ) def test_missingHours(self): """Tests that AvailableHourse cannot be created with out a time range. """ - with self.settings( - SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok" - ): - spot = Spot.objects.create(name="testing hours") - has_error = False - try: - hours = SpotAvailableHours.objects.create(spot=spot, day="m") - except ValidationError: - has_error = True - - self.assertEqual( - has_error, - True, - "Doesn't allow hours to be stored without hours", - ) + spot = Spot.objects.create(name="testing hours") + has_error = False + try: + hours = SpotAvailableHours.objects.create(spot=spot, day="m") + except ValidationError: + has_error = True + + self.assertEqual( + has_error, + True, + "Doesn't allow hours to be stored without hours", + ) def test_missingDay(self): """ Tests that AvailableHours cannot be \ created without a day of the week. """ - with self.settings( - SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok" - ): - spot = Spot.objects.create(name="testing hours") - has_error = False - try: - hours = SpotAvailableHours.objects.create( - spot=spot, start_time="01:30", end_time="02:30" - ) - except ValidationError: - has_error = True - - self.assertEqual( - has_error, - True, - "Doesn't allow hours to be stored without a day", + spot = Spot.objects.create(name="testing hours") + has_error = False + try: + hours = SpotAvailableHours.objects.create( + spot=spot, start_time="01:30", end_time="02:30" ) + except ValidationError: + has_error = True + + self.assertEqual( + has_error, + True, + "Doesn't allow hours to be stored without a day", + ) def test_invalidDay(self): """Tests that AvailableHours cannot be created with a day that doesn't exist as a day of the week. """ - with self.settings( - SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok" - ): - spot = Spot.objects.create(name="testing hours") - has_error = False - try: - hours = SpotAvailableHours.objects.create( - spot=spot, - day="Fail_day", - start_time="01:30", - end_time="02:30", - ) - except Exception as e: - has_error = True - - self.assertEqual( - has_error, - True, - "Doesn't allow hours to be stored " "with an invalid day", + spot = Spot.objects.create(name="testing hours") + has_error = False + try: + hours = SpotAvailableHours.objects.create( + spot=spot, + day="Fail_day", + start_time="01:30", + end_time="02:30", ) + except Exception as e: + has_error = True + + self.assertEqual( + has_error, + True, + "Doesn't allow hours to be stored " "with an invalid day", + ) diff --git a/spotseeker_server/test/hours/modify.py b/spotseeker_server/test/hours/modify.py index cddc84c9..0d08bc40 100644 --- a/spotseeker_server/test/hours/modify.py +++ b/spotseeker_server/test/hours/modify.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.conf import settings diff --git a/spotseeker_server/test/hours/open_at.py b/spotseeker_server/test/hours/open_at.py index 65aa804f..a0dd81e4 100644 --- a/spotseeker_server/test/hours/open_at.py +++ b/spotseeker_server/test/hours/open_at.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TestCase @@ -6,18 +6,16 @@ from django.test.client import Client import simplejson as json from datetime import datetime, timedelta -import time from django.test.utils import override_settings from mock import patch from spotseeker_server import models -import mock -@override_settings(SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok") +@override_settings(SPOTSEEKER_OAUTH_ENABLED=False) class SpotHoursOpenAtTest(TestCase): """Tests search requests for spots that are open at a particular time.""" - @mock.patch("spotseeker_server.views.search.SearchView.get_datetime") + @patch("spotseeker_server.views.search.SearchView.get_datetime") def test_open_at(self, datetime_mock): # Create a spot that isn't open now but will be in an hour. spot = models.Spot.objects.create(name="This spot is open later") diff --git a/spotseeker_server/test/hours/open_now.py b/spotseeker_server/test/hours/open_now.py index c9bf38bc..87fe59d0 100644 --- a/spotseeker_server/test/hours/open_now.py +++ b/spotseeker_server/test/hours/open_now.py @@ -1,28 +1,24 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TestCase -from django.conf import settings from django.test.client import Client from spotseeker_server.models import Spot, SpotAvailableHours import simplejson as json from datetime import datetime import datetime as alternate_date -import mock - from time import * from django.test.utils import override_settings from mock import patch -from spotseeker_server import models -@override_settings(SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok") +@override_settings(SPOTSEEKER_OAUTH_ENABLED=False) class SpotHoursOpenNowTest(TestCase): """Tests that we can tell if a Spot is available now, based on it's Available Hours. """ - @mock.patch("spotseeker_server.views.search.SearchView.get_datetime") + @patch("spotseeker_server.views.search.SearchView.get_datetime") def test_open_now(self, datetime_mock): open_spot = Spot.objects.create(name="This spot is open now") no_hours_spot = Spot.objects.create(name="This spot has no hours") @@ -84,7 +80,7 @@ def test_open_now(self, datetime_mock): ) self.assertEquals(has_open_spot, True, "Finds the open spot") - @mock.patch("spotseeker_server.views.search.SearchView.get_datetime") + @patch("spotseeker_server.views.search.SearchView.get_datetime") def test_thirty_sec_before_midnight(self, datetime_mock): """Tests when the user makes a request between 23:59 and 00:00.""" open_spot = Spot.objects.create(name="Spot open overnight") diff --git a/spotseeker_server/test/hours/open_now_location.py b/spotseeker_server/test/hours/open_now_location.py index 3616e264..7b112bbe 100644 --- a/spotseeker_server/test/hours/open_now_location.py +++ b/spotseeker_server/test/hours/open_now_location.py @@ -1,29 +1,26 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TestCase -from django.conf import settings from django.test.client import Client from spotseeker_server.models import Spot, SpotAvailableHours import simplejson as json from datetime import datetime import datetime as alternate_date from decimal import * -import mock from time import * from django.test.utils import override_settings from mock import patch -from spotseeker_server import models -@override_settings(SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok") +@override_settings(SPOTSEEKER_OAUTH_ENABLED=False) class SpotHoursOpenNowLocationTest(TestCase): """Tests that available Spots that are in location range are returned but available Spots outside the location range are not returned. """ - @mock.patch("spotseeker_server.views.search.SearchView.get_datetime") + @patch("spotseeker_server.views.search.SearchView.get_datetime") def test_open_now(self, datetime_mock): open_in_range_spot = Spot.objects.create( name="This spot is open now", diff --git a/spotseeker_server/test/hours/open_now_location_attributes.py b/spotseeker_server/test/hours/open_now_location_attributes.py index adcf56d5..4231cc9c 100644 --- a/spotseeker_server/test/hours/open_now_location_attributes.py +++ b/spotseeker_server/test/hours/open_now_location_attributes.py @@ -1,29 +1,26 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TestCase -from django.conf import settings from django.test.client import Client from spotseeker_server.models import Spot, SpotAvailableHours import simplejson as json from datetime import datetime import datetime as alternate_date from decimal import * -import mock +from mock import patch from time import * from django.test.utils import override_settings -from mock import patch -from spotseeker_server import models -@override_settings(SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok") +@override_settings(SPOTSEEKER_OAUTH_ENABLED=False) class SpotHoursOpenNowLocationAttributesTest(TestCase): """Tests that only available Spots with the requested attribute that are in location range are returned. """ - @mock.patch("spotseeker_server.views.search.SearchView.get_datetime") + @patch("spotseeker_server.views.search.SearchView.get_datetime") def test_open_now(self, datetime_mock): open_in_range_matched_spot = Spot.objects.create( name="Find this: Atlantic", diff --git a/spotseeker_server/test/hours/open_until.py b/spotseeker_server/test/hours/open_until.py index 31676a1f..2703860e 100644 --- a/spotseeker_server/test/hours/open_until.py +++ b/spotseeker_server/test/hours/open_until.py @@ -1,24 +1,21 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TestCase from django.test.utils import override_settings from django.test.client import Client -import mock from spotseeker_server.models import Spot, SpotAvailableHours import simplejson as json -from datetime import datetime, timedelta +from datetime import datetime import datetime as alternate_date -import time from mock import patch -from spotseeker_server import models -@override_settings(SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok") +@override_settings(SPOTSEEKER_OAUTH_ENABLED=False) class SpotHoursOpenUntilTest(TestCase): """Tests search requests for spots that are open at a particular time.""" - @mock.patch("spotseeker_server.views.search.SearchView.get_datetime") + @patch("spotseeker_server.views.search.SearchView.get_datetime") def test_open_until(self, datetime_mock): # Create a spot that isn't open now but will be in an hour. spot = Spot.objects.create(name="This spot is open later") diff --git a/spotseeker_server/test/hours/overlap.py b/spotseeker_server/test/hours/overlap.py index 841bc188..5451f992 100644 --- a/spotseeker_server/test/hours/overlap.py +++ b/spotseeker_server/test/hours/overlap.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.conf import settings diff --git a/spotseeker_server/test/hours/post.py b/spotseeker_server/test/hours/post.py index 9f71da26..e6d95b94 100644 --- a/spotseeker_server/test/hours/post.py +++ b/spotseeker_server/test/hours/post.py @@ -1,18 +1,14 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TestCase -from django.conf import settings from django.test.client import Client -from spotseeker_server.models import Spot, SpotAvailableHours import simplejson as json from django.test.utils import override_settings -from mock import patch -from spotseeker_server import models @override_settings( - SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok", + SPOTSEEKER_OAUTH_ENABLED=False, SPOTSEEKER_SPOT_FORM="spotseeker_server.default_forms.spot." "DefaultSpotForm", ) diff --git a/spotseeker_server/test/hours/put.py b/spotseeker_server/test/hours/put.py index 793abc4b..e66d0504 100644 --- a/spotseeker_server/test/hours/put.py +++ b/spotseeker_server/test/hours/put.py @@ -1,19 +1,16 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TestCase -from django.conf import settings from django.test.client import Client -from spotseeker_server.models import Spot, SpotAvailableHours +from spotseeker_server.models import Spot import simplejson as json from django.test.utils import override_settings from django.test.utils import override_settings -from mock import patch -from spotseeker_server import models @override_settings( - SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok", + SPOTSEEKER_OAUTH_ENABLED=False, SPOTSEEKER_SPOT_FORM="spotseeker_server.default_forms.spot." "DefaultSpotForm", ) diff --git a/spotseeker_server/test/images/__init__.py b/spotseeker_server/test/images/__init__.py index da5b319e..eac833e2 100644 --- a/spotseeker_server/test/images/__init__.py +++ b/spotseeker_server/test/images/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from spotseeker_server.test import SpotServerTestCase diff --git a/spotseeker_server/test/images/delete.py b/spotseeker_server/test/images/delete.py index 8a2b1dc6..cad68d6f 100644 --- a/spotseeker_server/test/images/delete.py +++ b/spotseeker_server/test/images/delete.py @@ -1,23 +1,20 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 import shutil import tempfile from django.test import TestCase -from django.conf import settings from django.test.client import Client from django.core.files.uploadedfile import SimpleUploadedFile from spotseeker_server.models import Spot, SpotImage from os.path import abspath, dirname, isfile from django.test.utils import override_settings -from mock import patch -from spotseeker_server import models TEST_ROOT = abspath(dirname(__file__)) -@override_settings(SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok") +@override_settings(SPOTSEEKER_OAUTH_ENABLED=False) @override_settings(SPOTSEEKER_AUTH_ADMINS=("demo_user",)) class SpotImageDELETETest(TestCase): """Tests DELETE of a SpotImage at /api/v1/spot//image/. diff --git a/spotseeker_server/test/images/get.py b/spotseeker_server/test/images/get.py index b8583f52..16a4683e 100644 --- a/spotseeker_server/test/images/get.py +++ b/spotseeker_server/test/images/get.py @@ -1,30 +1,22 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 import shutil import tempfile +from io import BytesIO as IOStream -try: - from cStringIO import StringIO as IOStream -except ModuleNotFoundError: - from io import BytesIO as IOStream - -from django.conf import settings from django.core.files.uploadedfile import SimpleUploadedFile from django.test import TestCase from django.test.client import Client from django.test.utils import override_settings -from mock import patch from os.path import abspath, dirname from PIL import Image from spotseeker_server.models import Spot, SpotImage -from spotseeker_server import models -import random TEST_ROOT = abspath(dirname(__file__)) -@override_settings(SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok") +@override_settings(SPOTSEEKER_OAUTH_ENABLED=False) class SpotImageGETTest(TestCase): def setUp(self): self.TEMP_DIR = tempfile.mkdtemp() diff --git a/spotseeker_server/test/images/oauth_spot_info.py b/spotseeker_server/test/images/oauth_spot_info.py deleted file mode 100644 index 31ef1361..00000000 --- a/spotseeker_server/test/images/oauth_spot_info.py +++ /dev/null @@ -1,259 +0,0 @@ -# Copyright 2023 UW-IT, University of Washington -# SPDX-License-Identifier: Apache-2.0 - -import secrets -import shutil -import tempfile - -from django.test import TestCase -from django.test.utils import override_settings -from django.test.client import Client -from os.path import abspath, dirname, isfile -from spotseeker_server.models import Spot, SpotImage, TrustedOAuthClient -from django.core.files.uploadedfile import SimpleUploadedFile -from django.conf import settings -from PIL import Image -from oauth_provider.models import Consumer -import random -import hashlib -import time -from oauthlib import oauth1 -import simplejson as json -from mock import patch -from spotseeker_server import models - -TEST_ROOT = abspath(dirname(__file__)) - - -@override_settings(SPOTSEEKER_AUTH_ADMINS=("pmichaud",)) -@override_settings(SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.oauth") -class SpotResourceOAuthImageTest(TestCase): - # In these tests the oauth signature for the same url could've been reused - # However, for every single request a new oauth signature is issued - # Emulating a situation, where, oauth signatures are time stamped - def setUp(self): - self.TEMP_DIR = tempfile.mkdtemp() - - # Setting up oauth - consumer_name = "Test consumer" - key = hashlib.sha1( - "{0} - {1}".format(random.random(), time.time()).encode("utf-8") - ).hexdigest() - secret = hashlib.sha1( - "{0} - {1}".format(random.random(), time.time()).encode("utf-8") - ).hexdigest() - - create_consumer = Consumer.objects.create( - name=consumer_name, key=key, secret=secret - ) - self.trusted_consumer = TrustedOAuthClient.objects.create( - consumer=create_consumer, - is_trusted=True, - bypasses_user_authorization=False, - ) - - self.client = oauth1.Client(key, client_secret=secret) - - with self.settings(MEDIA_ROOT=self.TEMP_DIR): - spot_post = Spot.objects.create( - name="""This is to test posting images in the spot resource, - with oauth""" - ) - self.spot_post = spot_post - - spot_put = Spot.objects.create( - name="""This is to test puting images in the spot resource, - with oauth""" - ) - self.spot_put = spot_put - self.put_url = "http://testserver/api/v1/spot/{0}".format( - self.spot_put.pk - ) - - # JPEG for PUT test - jpeg = self.spot_put.spotimage_set.create( - description="This is the JPEG PUT test", - image=SimpleUploadedFile( - "test_jpeg.jpg", - open( - "%s/../resources/test_jpeg.jpg" % TEST_ROOT, "rb" - ).read(), - "image/jpeg", - ), - ) - - self.jpeg = jpeg - self.jpeg_url = "%s/image/%s" % (self.put_url, self.jpeg.pk) - - spot_delete = Spot.objects.create( - name="""This is to test deleting images in the spot resource, - with oauth""" - ) - self.spot_delete = spot_delete - self.delete_url = "http://testserver/api/v1/spot/{0}".format( - self.spot_delete.pk - ) - - # GIF for DELETE test - gif = self.spot_delete.spotimage_set.create( - description="This is the GIF DELETE test", - image=SimpleUploadedFile( - "test_gif.gif", - open( - "%s/../resources/test_gif.gif" % TEST_ROOT, "rb" - ).read(), - "image/gif", - ), - ) - self.gif = gif - self.gif_url = "%s/image/%s" % (self.delete_url, self.gif.pk) - - def tearDown(self): - shutil.rmtree(self.TEMP_DIR) - - # POST a PNG image, ensure that the spot has only 1 image - # and user attributes are correct - def test_oauth_attributes_post(self): - with self.settings(MEDIA_ROOT=self.TEMP_DIR): - _, headers, _ = self.client.sign( - "http://testserver/api/v1/spot/%s" % self.spot_post.pk - ) - c = Client() - response = c.post( - "/api/v1/spot/{0}/image".format(self.spot_post.pk), - { - "description": "This is the PNG POST test", - "image": SimpleUploadedFile( - "test_png.png", - open( - "%s/../resources/test_png.png" % TEST_ROOT, - "rb", - ).read(), - "image/png", - ), - }, - HTTP_AUTHORIZATION=headers["Authorization"], - HTTP_X_OAUTH_USER="pmichaud", - ) - - __, headers2, __ = self.client.sign( - "http://testserver/api/v1/spot/%s" % self.spot_post.pk - ) - - response = c.get( - "/api/v1/spot/{0}".format(self.spot_post.pk), - HTTP_AUTHORIZATION=headers2["Authorization"], - HTTP_X_OAUTH_USER="pmichaud", - ) - - spot_dict = json.loads(response.content) - - self.assertEquals(len(spot_dict["images"]), 1, "Has 1 image") - - self.assertEquals( - spot_dict["images"][0]["upload_application"], - "Test consumer", - "Image has the proper upload application", - ) - self.assertEquals( - spot_dict["images"][0]["upload_user"], - "pmichaud", - "Image has the proper upload user", - ) - - # Get a JPEG image (image1), change it via PUT (image2), make sure that - # PUT request was 200 & image1!=image2 - def test_oauth_attributes_put(self): - with self.settings(MEDIA_ROOT=self.TEMP_DIR): - _, headers, _ = self.client.sign(self.put_url) - c = Client() - response = c.get( - self.put_url, - HTTP_AUTHORIZATION=headers["Authorization"], - HTTP_X_OAUTH_USER="pmichaud", - ) - spot_dict_before = json.loads(response.content) - - ____, get_headers, ____ = self.client.sign(self.jpeg_url) - response = c.get( - self.jpeg_url, - HTTP_AUTHORIZATION=get_headers["Authorization"], - HTTP_X_OAUTH_USER="pmichaud", - ) - etag = response["ETag"] - - new_name = "testing PUT name: {0}".format(random.random()) - - __, put_headers, __ = self.client.sign(self.jpeg_url) - response = c.put( - self.jpeg_url, - files={ - "description": new_name, - "image": SimpleUploadedFile( - "test_jpeg2.jpg", - open( - "%s/../resources/test_jpeg2.jpg" % TEST_ROOT, "rb" - ).read(), - "image/jpeg", - ), - }, - content_type="multipart/form-data; boundary=--aklsjf--", - If_Match=etag, - HTTP_AUTHORIZATION=put_headers["Authorization"], - HTTP_X_OAUTH_USER="pmichaud", - ) - self.assertEquals(response.status_code, 200, "PUT was successful") - - ___, get_headers, ___ = self.client.sign(self.put_url) - response = c.get( - self.put_url, - HTTP_AUTHORIZATION=get_headers["Authorization"], - HTTP_X_OAUTH_USER="pmichaud", - ) - spot_dict_after = json.loads(response.content) - self.assertNotEquals( - spot_dict_before["images"][0], - spot_dict_after["images"][0], - "Images weren't equal after PUT", - ) - - # Delete a GIF image, confirm that DELETE is succesful, - # further GET & DELETE return 404, image no longer exists as a file - def test_oauth_attributes_delete(self): - with self.settings(MEDIA_ROOT=self.TEMP_DIR): - _, headers, _ = self.client.sign(self.gif_url) - c = Client() - response = c.get( - self.gif_url, - HTTP_AUTHORIZATION=headers["Authorization"], - HTTP_X_OAUTH_USER="pmichaud", - ) - etag = response["ETag"] - - __, delete_headers, __ = self.client.sign(self.gif_url) - response = c.delete( - self.gif_url, - If_Match=etag, - HTTP_AUTHORIZATION=delete_headers["Authorization"], - HTTP_X_OAUTH_USER="pmichaud", - ) - self.assertEquals(response.status_code, 200) - - ___, get_headers, ___ = self.client.sign(self.gif_url) - response = c.get( - self.gif_url, - HTTP_AUTHORIZATION=get_headers["Authorization"], - HTTP_X_OAUTH_USER="pmichaud", - ) - self.assertEquals(response.status_code, 404) - - ____, delete2_headers, ____ = self.client.sign(self.gif_url) - response = c.delete( - self.gif_url, - If_Match=etag, - HTTP_AUTHORIZATION=delete2_headers["Authorization"], - HTTP_X_OAUTH_USER="pmichaud", - ) - self.assertEqual(response.status_code, 404) - - self.assertEqual(isfile(self.gif.image.path), False) diff --git a/spotseeker_server/test/images/post.py b/spotseeker_server/test/images/post.py index 29ea1463..c802d4ae 100644 --- a/spotseeker_server/test/images/post.py +++ b/spotseeker_server/test/images/post.py @@ -1,23 +1,19 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 -from django.conf import settings from django.test import TestCase from django.test.client import Client from django.test.utils import override_settings -from mock import patch from os.path import abspath, dirname -from spotseeker_server import models from spotseeker_server.models import Spot import json -import random import shutil import tempfile TEST_ROOT = abspath(dirname(__file__)) -@override_settings(SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok") +@override_settings(SPOTSEEKER_OAUTH_ENABLED=False) @override_settings(SPOTSEEKER_AUTH_ADMINS=("demo_user",)) class SpotImagePOSTTest(TestCase): """Tests POSTing to /api/v1/spot//image.""" diff --git a/spotseeker_server/test/images/put.py b/spotseeker_server/test/images/put.py index 7f9eeac4..8b329e7d 100644 --- a/spotseeker_server/test/images/put.py +++ b/spotseeker_server/test/images/put.py @@ -1,24 +1,21 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TestCase -from django.conf import settings -from django.test.client import Client, encode_multipart +from django.test.client import Client from django.core.files.uploadedfile import SimpleUploadedFile +from django.test.utils import override_settings from spotseeker_server.models import Spot, SpotImage from os.path import abspath, dirname import os import random import tempfile import shutil -from django.test.utils import override_settings -from mock import patch -from spotseeker_server import models TEST_ROOT = abspath(dirname(__file__)) -@override_settings(SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok") +@override_settings(SPOTSEEKER_OAUTH_ENABLED=False) @override_settings(SPOTSEEKER_AUTH_ADMINS=("demo_user",)) class SpotImagePUTTest(TestCase): """Tests updating a SpotImage by PUTting to diff --git a/spotseeker_server/test/images/spot_info.py b/spotseeker_server/test/images/spot_info.py index 9ddc5427..02dc9835 100644 --- a/spotseeker_server/test/images/spot_info.py +++ b/spotseeker_server/test/images/spot_info.py @@ -1,18 +1,15 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 import shutil import tempfile -from django.conf import settings from django.core.files.uploadedfile import SimpleUploadedFile from django.test import TestCase from django.test.client import Client from django.test.utils import override_settings -from mock import patch from os.path import abspath, dirname from PIL import Image -from spotseeker_server import models from spotseeker_server.models import Spot, SpotImage import datetime import simplejson as json @@ -21,7 +18,7 @@ @override_settings( - SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok", + SPOTSEEKER_OAUTH_ENABLED=False, SPOTSEEKER_SPOT_FORM="spotseeker_server.default_forms.spot." "DefaultSpotForm", ) diff --git a/spotseeker_server/test/images/thumb.py b/spotseeker_server/test/images/thumb.py index c13450c1..a2a5ee04 100644 --- a/spotseeker_server/test/images/thumb.py +++ b/spotseeker_server/test/images/thumb.py @@ -1,13 +1,9 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 import shutil import tempfile - -try: - from cStringIO import StringIO as IOStream -except ModuleNotFoundError: - from io import BytesIO as IOStream +from io import BytesIO as IOStream from spotseeker_server.test.images import ImageTestCase from django.core.files.uploadedfile import SimpleUploadedFile @@ -23,7 +19,7 @@ @override_settings( - SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok", + SPOTSEEKER_OAUTH_ENABLED=False, SPOTSEEKER_AUTH_ADMINS=("demo_user",), ) class ImageThumbTest(ImageTestCase): diff --git a/spotseeker_server/test/item/form.py b/spotseeker_server/test/item/form.py index 429e6231..5d68ecee 100644 --- a/spotseeker_server/test/item/form.py +++ b/spotseeker_server/test/item/form.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TestCase @@ -14,7 +14,7 @@ @override_settings( - SPOTSEEKER_AUTH_MODULE='spotseeker_server.auth.all_ok', + SPOTSEEKER_OAUTH_ENABLED=False, SPOTSEEKER_SPOT_FORM=DEFAULT_FORM, SPOTSEEKER_SPOTEXTENDEDINFO_FORM=DEFAULT_EI_FORM) class ItemFormsTest(TestCase): diff --git a/spotseeker_server/test/item/image_delete.py b/spotseeker_server/test/item/image_delete.py index b8f30571..1d6d5544 100644 --- a/spotseeker_server/test/item/image_delete.py +++ b/spotseeker_server/test/item/image_delete.py @@ -1,24 +1,20 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 import shutil import tempfile from django.test import TestCase -from django.conf import settings from django.test.client import Client from django.core.files.uploadedfile import SimpleUploadedFile from spotseeker_server.models import Item, ItemImage, Spot -from os.path import abspath, dirname, isfile +from os.path import abspath, dirname from django.test.utils import override_settings -from mock import patch -from django.core import cache -from spotseeker_server import models TEST_ROOT = abspath(dirname(__file__)) -@override_settings(SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok") +@override_settings(SPOTSEEKER_OAUTH_ENABLED=False) @override_settings(SPOTSEEKER_AUTH_ADMINS=("demo_user",)) class ItemImageDELETETest(TestCase): """Tests DELETE of a ItemImage at /api/v1/item//image/. diff --git a/spotseeker_server/test/item/image_get.py b/spotseeker_server/test/item/image_get.py index 81173350..e4cb3953 100644 --- a/spotseeker_server/test/item/image_get.py +++ b/spotseeker_server/test/item/image_get.py @@ -1,31 +1,22 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 import shutil import tempfile +from io import BytesIO as IOStream -try: - from cStringIO import StringIO as IOStream -except ModuleNotFoundError: - from io import BytesIO as IOStream - -from django.conf import settings -from django.core import cache from django.core.files.uploadedfile import SimpleUploadedFile from django.test import TestCase from django.test.client import Client from django.test.utils import override_settings -from mock import patch from os.path import abspath, dirname from PIL import Image from spotseeker_server.models import Item, ItemImage -from spotseeker_server import models -import random TEST_ROOT = abspath(dirname(__file__)) -@override_settings(SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok") +@override_settings(SPOTSEEKER_OAUTH_ENABLED=False) class ItemImageGETTest(TestCase): dummy_cache_setting = { diff --git a/spotseeker_server/test/item/image_post.py b/spotseeker_server/test/item/image_post.py index fa6997fe..57e411f2 100644 --- a/spotseeker_server/test/item/image_post.py +++ b/spotseeker_server/test/item/image_post.py @@ -1,24 +1,19 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 -from django.conf import settings -from django.core import cache from django.test import TestCase from django.test.client import Client from django.test.utils import override_settings -from mock import patch from os.path import abspath, dirname -from spotseeker_server import models from spotseeker_server.models import Item, Spot import json -import random import shutil import tempfile TEST_ROOT = abspath(dirname(__file__)) -@override_settings(SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok") +@override_settings(SPOTSEEKER_OAUTH_ENABLED=False) @override_settings(SPOTSEEKER_AUTH_ADMINS=("demo_user",)) class ItemImagePOSTTest(TestCase): """Tests POSTing to /api/v1/item//image.""" diff --git a/spotseeker_server/test/item/image_put.py b/spotseeker_server/test/item/image_put.py index 4ccbb9fe..57314efa 100644 --- a/spotseeker_server/test/item/image_put.py +++ b/spotseeker_server/test/item/image_put.py @@ -1,24 +1,21 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TestCase -from django.conf import settings -from django.test.client import Client, encode_multipart +from django.test.client import Client from django.core.files.uploadedfile import SimpleUploadedFile +from django.test.utils import override_settings from spotseeker_server.models import Item, ItemImage, Spot from os.path import abspath, dirname import os import random import tempfile import shutil -from django.test.utils import override_settings -from mock import patch -from spotseeker_server import models TEST_ROOT = abspath(dirname(__file__)) -@override_settings(SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok") +@override_settings(SPOTSEEKER_OAUTH_ENABLED=False) @override_settings(SPOTSEEKER_AUTH_ADMINS=("demo_user",)) class ItemImagePUTTest(TestCase): """Tests updating a ItemImage by PUTting to diff --git a/spotseeker_server/test/item/image_thumbnail.py b/spotseeker_server/test/item/image_thumbnail.py index 51e69886..4ea419f0 100644 --- a/spotseeker_server/test/item/image_thumbnail.py +++ b/spotseeker_server/test/item/image_thumbnail.py @@ -1,30 +1,22 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 import shutil import tempfile - -try: - from cStringIO import StringIO as IOStream -except ModuleNotFoundError: - from io import BytesIO as IOStream +from io import BytesIO as IOStream from django.test import TestCase -from django.conf import settings from django.core.files.uploadedfile import SimpleUploadedFile from django.test.client import Client +from django.test.utils import override_settings from spotseeker_server.models import Item, ItemImage, Spot from PIL import Image from os.path import abspath, dirname -from django.test.utils import override_settings -from mock import patch -from django.core import cache -from spotseeker_server import models TEST_ROOT = abspath(dirname(__file__)) -@override_settings(SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok") +@override_settings(SPOTSEEKER_OAUTH_ENABLED=False) @override_settings(SPOTSEEKER_AUTH_ADMINS=("demo_user",)) class ItemImageThumbTest(TestCase): diff --git a/spotseeker_server/test/item/model.py b/spotseeker_server/test/item/model.py index b0395f94..0356c697 100644 --- a/spotseeker_server/test/item/model.py +++ b/spotseeker_server/test/item/model.py @@ -1,9 +1,8 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from builtins import range -from django.conf import settings from django.test import TestCase from django.test.utils import override_settings import random @@ -13,7 +12,7 @@ @override_settings( - SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok", + SPOTSEEKER_OAUTH_ENABLED=False, SPOTSEEKER_SPOT_FORM="spotseeker_server.default_forms.spot." "DefaultSpotForm", ) diff --git a/spotseeker_server/test/long_message.py b/spotseeker_server/test/long_message.py index 20de4f6f..115f1e03 100644 --- a/spotseeker_server/test/long_message.py +++ b/spotseeker_server/test/long_message.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 ''' diff --git a/spotseeker_server/test/models.py b/spotseeker_server/test/models.py index bf1c2fc4..00598c4a 100644 --- a/spotseeker_server/test/models.py +++ b/spotseeker_server/test/models.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 import shutil diff --git a/spotseeker_server/test/no_rest_methods.py b/spotseeker_server/test/no_rest_methods.py index 8896302e..e797c1b2 100644 --- a/spotseeker_server/test/no_rest_methods.py +++ b/spotseeker_server/test/no_rest_methods.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TestCase diff --git a/spotseeker_server/test/oauth.py b/spotseeker_server/test/oauth.py new file mode 100644 index 00000000..b9c3711e --- /dev/null +++ b/spotseeker_server/test/oauth.py @@ -0,0 +1,203 @@ +# Copyright 2024 UW-IT, University of Washington +# SPDX-License-Identifier: Apache-2.0 + +from django.test import TestCase +from django.test.utils import override_settings +from spotseeker_server.models import Spot, Client +import simplejson as json +from datetime import timedelta +from django.utils import timezone +from oauth2_provider.models import AccessToken, Application + + +@override_settings( + SPOTSEEKER_SPOT_FORM="spotseeker_server.default_forms.spot.DefaultSpotForm" +) +@override_settings( + SPOTSEEKER_SPOTEXTENDEDINFO_FORM="spotseeker_server.default_forms.spot." + "DefaultSpotExtendedInfoForm" +) +@override_settings(SPOTSEEKER_OAUTH_ENABLED=True) +class SpotAuthOAuth(TestCase): + def setUp(self): + spot = Spot.objects.create( + name="This is for testing the oauth module", capacity=10 + ) + self.spot = spot + self.url = "/api/v1/spot/%s" % self.spot.pk + + def tearDown(self) -> None: + AccessToken.objects.all().delete() + Application.objects.all().delete() + Client.objects.all().delete() + Spot.objects.all().delete() + + return super().tearDown() + + def _create_auth_header(self, token: str) -> str: + return "Bearer " + token + + def _create_token(self, client: Client, expiry: int = 300, + scope: str = 'read write') -> AccessToken: + app = Application.objects.create( + client_type=Application.CLIENT_CONFIDENTIAL, + authorization_grant_type=Application.GRANT_CLIENT_CREDENTIALS, + name="Test app", + user=client, + ) + + token = AccessToken.objects.create( + user=client, + scope=scope, + expires=timezone.now() + timedelta(seconds=expiry), + token="testtoken", + application=app, + ) + + return token + + def test_get_no_oauth(self): + c = self.client + response = c.get(self.url) + self.assertEquals( + response.status_code, 401, "No access to GET w/o oauth" + ) + + def test_valid_oauth(self): + consumer_name = "Test consumer" + + auth_client = Client.objects.create( + username=consumer_name, name=consumer_name, + client_id="fake_id", client_secret="fake_secret" + ) + auth_client.get_client_credential() + auth_client.save() + + token = self._create_auth_header( + self._create_token(auth_client).token + ) + + c = self.client + + response = c.get(self.url, HTTP_AUTHORIZATION=token) + + self.assertEquals( + response.status_code, + 200, + "Got a 200 w/ a proper oauth client connection", + ) + + spot_dict = json.loads(response.content) + + self.assertEquals( + spot_dict["id"], self.spot.pk, "Got the right spot back from oauth" + ) + + def test_invalid_oauth(self): + consumer_name = "Test consumer" + + auth_client = Client.objects.create( + username=consumer_name, name=consumer_name, + client_id="fake_id", client_secret="fake_secret" + ) + auth_client.get_client_credential() + auth_client.save() + + token = self._create_auth_header( + self._create_token(auth_client).token + ) + + c = self.client + response = c.get(self.url, HTTP_AUTHORIZATION=token + "BAD") + + self.assertEquals( + response.status_code, + 401, + "Got a 401 w/ an invented oauth client id", + ) + + def test_put_no_oauth(self): + c = self.client + + response = c.get(self.url) + + etag = self.spot.etag + + spot_dict = self.spot.json_data_structure() + spot_dict["name"] = "Failing to modify oauth" + + response = c.put( + self.url, + json.dumps(spot_dict), + content_type="application/json", + If_Match=etag, + ) + self.assertEquals( + response.status_code, 401, "Rejects a PUT w/o oauth info" + ) + + def test_put_expired_oauth(self): + consumer_name = "Expired consumer" + + unauth_client = Client.objects.create( + username=consumer_name, name=consumer_name, + client_id="fake_id", client_secret="fake_secret" + ) + unauth_client.get_client_credential() + unauth_client.save() + + token = self._create_auth_header( + self._create_token(unauth_client, -1).token + ) + + c = self.client + response = c.get(self.url, HTTP_AUTHORIZATION=token) + + self.assertEquals( + response.status_code, + 401, + "Rejects a GET from an expired oauth client", + ) + + def test_put_read_only_oauth(self): + consumer_name = "Read-only consumer" + + unauth_client = Client.objects.create( + username=consumer_name, name=consumer_name, + client_id="fake_id", client_secret="fake_secret" + ) + unauth_client.get_client_credential() + unauth_client.save() + + token = self._create_auth_header( + self._create_token(unauth_client, scope='read').token + ) + + c = self.client + response = c.get(self.url, HTTP_AUTHORIZATION=token) + + self.assertEquals( + response.status_code, + 200, + "Accepts a GET from a read-only oauth client", + ) + + etag = response["ETag"] + + spot_dict = json.loads(response.content) + spot_dict["name"] = "Failing to modify oauth" + spot_dict["location"] = {"latitude": 55, "longitude": -30} + + response = c.put( + self.url, + json.dumps(spot_dict), + content_type="application/json", + If_Match=etag, + HTTP_AUTHORIZATION=token, + ) + + self.assertEquals( + response.status_code, + 403, + "Rejects a PUT from a non-trusted oauth client", + ) diff --git a/spotseeker_server/test/schema.py b/spotseeker_server/test/schema.py index cfe2d935..55b9b6a1 100644 --- a/spotseeker_server/test/schema.py +++ b/spotseeker_server/test/schema.py @@ -1,16 +1,14 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from spotseeker_server.models import * from django.test.client import Client from django.test import TestCase import simplejson as json -from mock import patch -from spotseeker_server import models from django.test.utils import override_settings -@override_settings(SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok") +@override_settings(SPOTSEEKER_OAUTH_ENABLED=False) @override_settings( SPOTSEEKER_SPOT_FORM="spotseeker_server.default_forms.spot." "DefaultSpotForm" diff --git a/spotseeker_server/test/search/buildings.py b/spotseeker_server/test/search/buildings.py index adacadf4..1d4a1fd4 100644 --- a/spotseeker_server/test/search/buildings.py +++ b/spotseeker_server/test/search/buildings.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from unittest import skipIf @@ -8,7 +8,7 @@ import simplejson as json -@override_settings(SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok") +@override_settings(SPOTSEEKER_OAUTH_ENABLED=False) class BuildingSearchTest(SpotServerTestCase): """Tests the /api/v1/buildings interface.""" @@ -73,7 +73,7 @@ def test_content_type(self): def test_get_all_buildings(self): c = self.client - # TODO: Even though this works, we do not recommend using this method. + # Even though this works, we do not recommend using this method. # You should be passing at least one query param. response = c.get("/api/v1/buildings") buildings = json.loads(response.content) diff --git a/spotseeker_server/test/search/capacity.py b/spotseeker_server/test/search/capacity.py index 52e634ed..82f3549f 100644 --- a/spotseeker_server/test/search/capacity.py +++ b/spotseeker_server/test/search/capacity.py @@ -1,16 +1,12 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from spotseeker_server.test import SpotServerTestCase -from django.conf import settings -from spotseeker_server.models import Spot, SpotExtendedInfo, SpotType import simplejson as json from django.test.utils import override_settings -from mock import patch -from spotseeker_server import models -@override_settings(SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok") +@override_settings(SPOTSEEKER_OAUTH_ENABLED=False) class SpotSearchCapacityTest(SpotServerTestCase): def test_capacity(self): diff --git a/spotseeker_server/test/search/distance.py b/spotseeker_server/test/search/distance.py index e780ed38..c0b3644b 100644 --- a/spotseeker_server/test/search/distance.py +++ b/spotseeker_server/test/search/distance.py @@ -1,18 +1,15 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TestCase -from django.conf import settings from django.test.client import Client +from django.test.utils import override_settings from spotseeker_server.models import Spot import simplejson as json from decimal import * -from django.test.utils import override_settings -from mock import patch -from spotseeker_server import models -@override_settings(SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok") +@override_settings(SPOTSEEKER_OAUTH_ENABLED=False) class SpotSearchDistanceTest(TestCase): def test_invalid_latitude(self): c = Client() diff --git a/spotseeker_server/test/search/distance_fields.py b/spotseeker_server/test/search/distance_fields.py index aeb0c1af..4314aec5 100644 --- a/spotseeker_server/test/search/distance_fields.py +++ b/spotseeker_server/test/search/distance_fields.py @@ -1,66 +1,64 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TestCase -from django.conf import settings from django.test.client import Client +from django.test.utils import override_settings from spotseeker_server.models import Spot import simplejson as json from decimal import * +@override_settings(SPOTSEEKER_OAUTH_ENABLED=False) class SpotSearchDistanceFieldTest(TestCase): def test_distances(self): # Spots are in the atlantic to make them less likely # to collide with actual spots - with self.settings( - SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok" - ): - center_lat = 31.000000 - center_long = -41.000000 + center_lat = 31.000000 + center_long = -41.000000 - inner_top = Spot.objects.create( - name="Atlantic Location 1", - latitude=Decimal("31.0000898315"), - longitude=Decimal("-41.0"), - ) - inner_top.save() + inner_top = Spot.objects.create( + name="Atlantic Location 1", + latitude=Decimal("31.0000898315"), + longitude=Decimal("-41.0"), + ) + inner_top.save() - inner_top2 = Spot.objects.create( - name="Alternate name of AL1", - latitude=Decimal("31.0000898315"), - longitude=Decimal("-41.0"), - ) - inner_top2.save() + inner_top2 = Spot.objects.create( + name="Alternate name of AL1", + latitude=Decimal("31.0000898315"), + longitude=Decimal("-41.0"), + ) + inner_top2.save() - mid_top = Spot.objects.create( - name="Atlantic Location 2", - latitude=Decimal("34.0004491576"), - longitude=Decimal("-44.0"), - ) - mid_top.save() + mid_top = Spot.objects.create( + name="Atlantic Location 2", + latitude=Decimal("34.0004491576"), + longitude=Decimal("-44.0"), + ) + mid_top.save() - c = Client() - response = c.get( - "/api/v1/spot", - { - "center_latitude": center_lat, - "center_longitude": center_long, - "distance": 12, - "name": "Atlantic", - }, - ) - self.assertEquals( - response.status_code, 200, "Accepts the distance query" - ) - self.assertEquals( - response["Content-Type"], - "application/json", - "Has the json header", - ) - spots = json.loads(response.content) - self.assertEquals(len(spots), 1, "Returns 1 spot") + c = Client() + response = c.get( + "/api/v1/spot", + { + "center_latitude": center_lat, + "center_longitude": center_long, + "distance": 12, + "name": "Atlantic", + }, + ) + self.assertEquals( + response.status_code, 200, "Accepts the distance query" + ) + self.assertEquals( + response["Content-Type"], + "application/json", + "Has the json header", + ) + spots = json.loads(response.content) + self.assertEquals(len(spots), 1, "Returns 1 spot") - self.assertEquals( - spots[0]["id"], inner_top.pk, "Found the inner Atlantic spot" - ) + self.assertEquals( + spots[0]["id"], inner_top.pk, "Found the inner Atlantic spot" + ) diff --git a/spotseeker_server/test/search/fields.py b/spotseeker_server/test/search/fields.py index 293078a9..b32f49b0 100644 --- a/spotseeker_server/test/search/fields.py +++ b/spotseeker_server/test/search/fields.py @@ -1,17 +1,13 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from spotseeker_server.test import SpotServerTestCase -from django.conf import settings -from django.test.client import Client from spotseeker_server.models import Spot, SpotExtendedInfo, SpotType import simplejson as json from django.test.utils import override_settings -from mock import patch -from spotseeker_server import models -@override_settings(SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok") +@override_settings(SPOTSEEKER_OAUTH_ENABLED=False) class SpotSearchFieldTest(SpotServerTestCase): @classmethod def setUpClass(self): diff --git a/spotseeker_server/test/search/item.py b/spotseeker_server/test/search/item.py index da0dd924..3774a28c 100644 --- a/spotseeker_server/test/search/item.py +++ b/spotseeker_server/test/search/item.py @@ -1,9 +1,7 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TestCase -from django.conf import settings -from django.test.client import Client from django.test.utils import override_settings from spotseeker_server.models import ( Spot, @@ -12,21 +10,9 @@ ItemExtendedInfo, ) import simplejson as json -from mock import patch -from spotseeker_server import models -try: - from unittest import skip -except ImportError: - def skip(*args, **kwargs): - def inner(self): - pass - - return inner - - -@override_settings(SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok") +@override_settings(SPOTSEEKER_OAUTH_ENABLED=False) class SpotSearchItemTest(TestCase): """ Tests on the implemented item filters. Checks whether the new filters work diff --git a/spotseeker_server/test/search/limit.py b/spotseeker_server/test/search/limit.py index e50e2781..a58bd86b 100644 --- a/spotseeker_server/test/search/limit.py +++ b/spotseeker_server/test/search/limit.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TestCase @@ -6,11 +6,9 @@ from spotseeker_server.models import Spot import simplejson as json from django.test.utils import override_settings -from mock import patch -from spotseeker_server import models -@override_settings(SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok") +@override_settings(SPOTSEEKER_OAUTH_ENABLED=False) class SpotSearchLimitTest(TestCase): def setUp(self): num_spots = 25 diff --git a/spotseeker_server/test/search/noise_level.py b/spotseeker_server/test/search/noise_level.py index 0c6dce14..dc1812d5 100644 --- a/spotseeker_server/test/search/noise_level.py +++ b/spotseeker_server/test/search/noise_level.py @@ -1,8 +1,7 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TestCase -from django.test.client import Client from django.test.utils import override_settings import simplejson as json from spotseeker_server.models import Spot, SpotExtendedInfo @@ -16,7 +15,7 @@ def spot_with_noise_level(name, noise_level): return spot -@override_settings(SPOTSEEKER_AUTH_MODULE='spotseeker_server.auth.all_ok') +@override_settings(SPOTSEEKER_OAUTH_ENABLED=False) class NoiseLevelTestCase(TestCase): @classmethod diff --git a/spotseeker_server/test/search/time.py b/spotseeker_server/test/search/time.py index 32537b95..72c1609c 100644 --- a/spotseeker_server/test/search/time.py +++ b/spotseeker_server/test/search/time.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TestCase @@ -17,7 +17,7 @@ def new_hours(spot, day, start, end): ) -@override_settings(SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok") +@override_settings(SPOTSEEKER_OAUTH_ENABLED=False) class SpotSearchTimeTest(TestCase): def test_SameDayTimeInSerial(self): """Simple open hours test with a single date range per spot""" diff --git a/spotseeker_server/test/search/uw_buildings.py b/spotseeker_server/test/search/uw_buildings.py index 20ffdab4..a1de8e52 100644 --- a/spotseeker_server/test/search/uw_buildings.py +++ b/spotseeker_server/test/search/uw_buildings.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 import simplejson as json @@ -9,7 +9,7 @@ @override_settings( - SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok", + SPOTSEEKER_OAUTH_ENABLED=False, SPOTSEEKER_SEARCH_FILTERS=( "spotseeker_server.org_filters.uw_search.Filter", ), diff --git a/spotseeker_server/test/search/uw_noise_level.py b/spotseeker_server/test/search/uw_noise_level.py index 261fc8dd..cbbc20bc 100644 --- a/spotseeker_server/test/search/uw_noise_level.py +++ b/spotseeker_server/test/search/uw_noise_level.py @@ -1,8 +1,7 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TestCase -from django.test.client import Client from django.test.utils import override_settings import simplejson as json from spotseeker_server.models import Spot, SpotExtendedInfo @@ -17,7 +16,7 @@ def spot_with_noise_level(name, noise_level): return spot -@override_settings(SPOTSEEKER_AUTH_MODULE='spotseeker_server.auth.all_ok', +@override_settings(SPOTSEEKER_OAUTH_ENABLED=False, SPOTSEEKER_SEARCH_FILTERS=( 'spotseeker_server.org_filters.uw_search.Filter',)) class UWNoiseLevelTestCase(TestCase): diff --git a/spotseeker_server/test/search/view_methods.py b/spotseeker_server/test/search/view_methods.py index 7a646e25..684b3d9a 100644 --- a/spotseeker_server/test/search/view_methods.py +++ b/spotseeker_server/test/search/view_methods.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TestCase diff --git a/spotseeker_server/test/spot_caching.py b/spotseeker_server/test/spot_caching.py index 5cf59723..a2011707 100644 --- a/spotseeker_server/test/spot_caching.py +++ b/spotseeker_server/test/spot_caching.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TestCase, override_settings diff --git a/spotseeker_server/test/spot_delete.py b/spotseeker_server/test/spot_delete.py index fac6d030..7c5d52f4 100644 --- a/spotseeker_server/test/spot_delete.py +++ b/spotseeker_server/test/spot_delete.py @@ -1,17 +1,14 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TestCase -from django.conf import settings from django.test.client import Client from spotseeker_server.models import Spot from django.test.utils import override_settings -from mock import patch -from spotseeker_server import models @override_settings( - SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok", + SPOTSEEKER_OAUTH_ENABLED=False, SPOTSEEKER_SPOT_FORM="spotseeker_server.default_forms.spot." "DefaultSpotForm", ) diff --git a/spotseeker_server/test/spot_form.py b/spotseeker_server/test/spot_form.py index 1807a4fc..f24142e8 100644 --- a/spotseeker_server/test/spot_form.py +++ b/spotseeker_server/test/spot_form.py @@ -1,15 +1,14 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TestCase from spotseeker_server.default_forms.spot import DefaultSpotForm from spotseeker_server.forms.spot import SpotForm -from django.conf import settings from django.test.utils import override_settings @override_settings( - SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok", + SPOTSEEKER_OAUTH_ENABLED=False, SPOTSEEKER_SPOT_FORM="spotseeker_server.default_forms.spot." "DefaultSpotForm", SPOTSEEKER_SPOTEXTENDEDINFO_FORM="spotseeker_server.default_forms.spot." diff --git a/spotseeker_server/test/spot_get.py b/spotseeker_server/test/spot_get.py index 58794014..bad26e57 100644 --- a/spotseeker_server/test/spot_get.py +++ b/spotseeker_server/test/spot_get.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from builtins import range @@ -15,7 +15,7 @@ @override_settings( - SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok", + SPOTSEEKER_OAUTH_ENABLED=False, SPOTSEEKER_SPOT_FORM="spotseeker_server.default_forms.spot." "DefaultSpotForm", ) diff --git a/spotseeker_server/test/spot_model.py b/spotseeker_server/test/spot_model.py index 2c004552..e3e272a8 100644 --- a/spotseeker_server/test/spot_model.py +++ b/spotseeker_server/test/spot_model.py @@ -1,18 +1,15 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TestCase -from django.conf import settings import random from spotseeker_server.models import Spot from django.test.utils import override_settings -from mock import patch -from spotseeker_server import models @override_settings( - SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok", + SPOTSEEKER_OAUTH_ENABLED=False, SPOTSEEKER_SPOT_FORM="spotseeker_server.default_forms.spot." "DefaultSpotForm", ) diff --git a/spotseeker_server/test/spot_post.py b/spotseeker_server/test/spot_post.py index 5a847c27..88d8f209 100644 --- a/spotseeker_server/test/spot_post.py +++ b/spotseeker_server/test/spot_post.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TestCase @@ -11,19 +11,9 @@ from past.builtins import basestring -try: - from unittest import skip -except ImportError: - - def skip(*args, **kwargs): - def inner(self): - pass - - return inner - @override_settings( - SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok", + SPOTSEEKER_OAUTH_ENABLED=False, SPOTSEEKER_SPOT_FORM="spotseeker_server.default_forms.spot." "DefaultSpotForm", SPOTSEEKER_SPOTEXTENDEDINFO_FORM="spotseeker_server.default_forms.spot." diff --git a/spotseeker_server/test/spot_put.py b/spotseeker_server/test/spot_put.py index a3fab44e..7a29598e 100644 --- a/spotseeker_server/test/spot_put.py +++ b/spotseeker_server/test/spot_put.py @@ -1,14 +1,10 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 -from django.contrib.auth.models import User from django.test import TestCase -from django.conf import settings from django.test.client import Client from spotseeker_server.models import Spot, Item from django.test.utils import override_settings -from mock import patch -from spotseeker_server import models import simplejson as json import random from spotseeker_server.test import utils_test @@ -16,18 +12,8 @@ from past.builtins import basestring -try: - from unittest import skip -except ImportError: - def skip(*args, **kwargs): - def inner(self): - pass - - return inner - - -@override_settings(SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok") +@override_settings(SPOTSEEKER_OAUTH_ENABLED=False) @override_settings( SPOTSEEKER_SPOT_FORM="spotseeker_server.default_forms.spot." "DefaultSpotForm" diff --git a/spotseeker_server/test/techloan/__init__.py b/spotseeker_server/test/techloan/__init__.py index a3d5e32f..a7cf897c 100644 --- a/spotseeker_server/test/techloan/__init__.py +++ b/spotseeker_server/test/techloan/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from spotseeker_server.test import SpotServerTestCase diff --git a/spotseeker_server/test/techloan/sync_techloan.py b/spotseeker_server/test/techloan/sync_techloan.py index 358eb6ff..c08f5e55 100644 --- a/spotseeker_server/test/techloan/sync_techloan.py +++ b/spotseeker_server/test/techloan/sync_techloan.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from io import StringIO diff --git a/spotseeker_server/test/utils_test.py b/spotseeker_server/test/utils_test.py index 675afc7d..67aa7251 100644 --- a/spotseeker_server/test/utils_test.py +++ b/spotseeker_server/test/utils_test.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 """Provide useful methods for testing diff --git a/spotseeker_server/test/uw_spot/schema.py b/spotseeker_server/test/uw_spot/schema.py index 120ddd97..51e2dae3 100644 --- a/spotseeker_server/test/uw_spot/schema.py +++ b/spotseeker_server/test/uw_spot/schema.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test.utils import override_settings @@ -8,12 +8,11 @@ import simplejson as json -ALL_OK = "spotseeker_server.auth.all_ok" UW_SPOT_FORM = "spotseeker_server.org_forms.uw_spot.UWSpotForm" UW_EXT_INFO_FORM = "spotseeker_server.org_forms.uw_spot.UWSpotExtendedInfoForm" -@override_settings(SPOTSEEKER_AUTH_MODULE=ALL_OK) +@override_settings(SPOTSEEKER_OAUTH_ENABLED=False) @override_settings(SPOTSEEKER_SPOT_FORM=UW_SPOT_FORM) @override_settings(SPOTSEEKER_SPOTEXTENDEDINFO_FORM=UW_EXT_INFO_FORM) class UWSpotSchemaTest(TestCase): diff --git a/spotseeker_server/test/uw_spot/spot_form.py b/spotseeker_server/test/uw_spot/spot_form.py index 2518f908..63b3e3b5 100644 --- a/spotseeker_server/test/uw_spot/spot_form.py +++ b/spotseeker_server/test/uw_spot/spot_form.py @@ -1,18 +1,16 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TestCase +from django.test.utils import override_settings from spotseeker_server.org_forms.uw_spot import UWSpotForm from spotseeker_server.org_forms.uw_spot import UWSpotExtendedInfoForm -from django.conf import settings -from django.test.utils import override_settings -ALL_OK = "spotseeker_server.auth.all_ok" UW_SPOT_FORM = "spotseeker_server.org_forms.uw_spot.UWSpotForm" UW_EXT_INFO_FORM = "spotseeker_server.org_forms.uw.spot.UWSpotExtendedInfoForm" -@override_settings(SPOTSEEKER_AUTH_MODULE=ALL_OK) +@override_settings(SPOTSEEKER_OAUTH_ENABLED=False) @override_settings(SPOTSEEKER_SPOT_FORM=UW_SPOT_FORM) @override_settings(SPOTSEEKER_SPOTEXTENDEDINFO_FORM=UW_EXT_INFO_FORM) class UWSpotFormTest(TestCase): diff --git a/spotseeker_server/test/uw_spot/spot_post.py b/spotseeker_server/test/uw_spot/spot_post.py index 34d167f3..6d6acd73 100644 --- a/spotseeker_server/test/uw_spot/spot_post.py +++ b/spotseeker_server/test/uw_spot/spot_post.py @@ -1,23 +1,19 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TransactionTestCase -from django.conf import settings from django.test.client import Client +from django.test.utils import override_settings from spotseeker_server.models import Spot import simplejson as json import random -from django.test.utils import override_settings -from mock import patch -from spotseeker_server import models -ALL_OK = "spotseeker_server.auth.all_ok" UW_SPOT_FORM = "spotseeker_server.org_forms.uw_spot.UWSpotForm" UW_EXT_INFO_FORM = "spotseeker_server.org_forms.uw_spot.UWSpotExtendedInfoForm" -@override_settings(SPOTSEEKER_AUTH_MODULE=ALL_OK) +@override_settings(SPOTSEEKER_OAUTH_ENABLED=False) @override_settings(SPOTSEEKER_SPOT_FORM=UW_SPOT_FORM) @override_settings(SPOTSEEKER_SPOTEXTENDEDINFO_FORM=UW_EXT_INFO_FORM) @override_settings(SPOTSEEKER_AUTH_ADMINS=("demo_user",)) diff --git a/spotseeker_server/test/uw_spot/spot_put.py b/spotseeker_server/test/uw_spot/spot_put.py index 465c5018..c5846649 100644 --- a/spotseeker_server/test/uw_spot/spot_put.py +++ b/spotseeker_server/test/uw_spot/spot_put.py @@ -1,25 +1,21 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django.test import TransactionTestCase -from django.conf import settings from django.test.client import Client +from django.test.utils import override_settings from spotseeker_server.models import Spot, SpotExtendedInfo import simplejson as json import random -from django.test.utils import override_settings -from mock import patch -from spotseeker_server import models from spotseeker_server.test import utils_test from past.builtins import basestring -ALL_OK = "spotseeker_server.auth.all_ok" UW_SPOT_FORM = "spotseeker_server.org_forms.uw_spot.UWSpotForm" UW_EXT_INFO_FORM = "spotseeker_server.org_forms.uw_spot.UWSpotExtendedInfoForm" -@override_settings(SPOTSEEKER_AUTH_MODULE=ALL_OK) +@override_settings(SPOTSEEKER_OAUTH_ENABLED=False) @override_settings(SPOTSEEKER_SPOT_FORM=UW_SPOT_FORM) @override_settings(SPOTSEEKER_SPOTEXTENDEDINFO_FORM=UW_EXT_INFO_FORM) @override_settings(SPOTSEEKER_AUTH_ADMINS=("demo_user",)) @@ -188,103 +184,100 @@ def test_valid_json_outdated_etag(self): ) def test_valid_json_but_invalid_extended_info(self): - with self.settings( - SPOTSEEKER_AUTH_MODULE=ALL_OK, SPOTSEEKER_SPOT_FORM=UW_SPOT_FORM - ): - c = Client() - new_name = "testing PUT name: {0}".format(random.random()) - new_capacity = 20 - - response = c.get(self.url) - etag = response["ETag"] - app_type = "food" - - json_string = ( - '{"name":"%s","capacity":"%s",\ - "location":{"latitude": 55, "longitude": -30},\ - "extended_info":{"location_description":\ - "This is a description",\ - "has_whiteboards":"true",\ - "num_computers": "10",\ - "has_outlets":"true","has_computers":"true",\ - "manager":"Sam","organization":"UW",\ - "app_type":"%s"}}' - % (new_name, new_capacity, app_type) - ) + c = Client() + new_name = "testing PUT name: {0}".format(random.random()) + new_capacity = 20 - response = c.put( - self.url, - json_string, - content_type="application/json", - If_Match=etag, - ) - self.assertEquals( - response.status_code, 200, "Accepts a valid json string" - ) + response = c.get(self.url) + etag = response["ETag"] + app_type = "food" - # test: invalid extended info value - response = c.get(self.url) - etag = response["ETag"] - updated_json_string = ( - '{"name":"%s","capacity":"%s",\ - "location": {"latitude": 55, "longitude": -30},\ - "extended_info":{"has_whiteboards":"true",\ - "location_description": " ",\ - "has_outlets":"wub wub wub wu wu wuhhhh WUB WUB WUBBBBUB", \ - "has_computers":"true", "num_computers":"10","manager":"Sam",\ - "organization":"UW"}}' - % (new_name, new_capacity) - ) + json_string = ( + '{"name":"%s","capacity":"%s",\ + "location":{"latitude": 55, "longitude": -30},\ + "extended_info":{"location_description":\ + "This is a description",\ + "has_whiteboards":"true",\ + "num_computers": "10",\ + "has_outlets":"true","has_computers":"true",\ + "manager":"Sam","organization":"UW",\ + "app_type":"%s"}}' + % (new_name, new_capacity, app_type) + ) - response = c.put( - self.url, - updated_json_string, - content_type="application/json", - If_Match=etag, - ) - self.assertEquals( - response.status_code, - 400, - "Doesn't update spot with invalid extended info", - ) + response = c.put( + self.url, + json_string, + content_type="application/json", + If_Match=etag, + ) + self.assertEquals( + response.status_code, 200, "Accepts a valid json string" + ) - response = c.get(self.url) - self.assertEquals( - json.loads(json_string)["extended_info"], - json.loads(response.content)["extended_info"], - "Doesn't update spot with invalid extended info", - ) + # test: invalid extended info value + response = c.get(self.url) + etag = response["ETag"] + updated_json_string = ( + '{"name":"%s","capacity":"%s",\ + "location": {"latitude": 55, "longitude": -30},\ + "extended_info":{"has_whiteboards":"true",\ + "location_description": " ",\ + "has_outlets":"wub wub wub wu wu wuhhhh WUB WUB WUBBBBUB", \ + "has_computers":"true", "num_computers":"10","manager":"Sam",\ + "organization":"UW"}}' + % (new_name, new_capacity) + ) - # test: invalid int value - invalid_int = "invalid_int" - invalid_int_json_string = ( - '{"name":"%s","capacity":"%s",\ - "location": {"latitude": 55, "longitude": -30},\ - "extended_info":{"has_whiteboards":"true",\ - "location_description": "This is a description",\ - "has_outlets":"true", "has_computers":"true", \ - "num_computers":"%s","manager":"Sam","organization":"UW"}}' - % (new_name, new_capacity, invalid_int) - ) + response = c.put( + self.url, + updated_json_string, + content_type="application/json", + If_Match=etag, + ) + self.assertEquals( + response.status_code, + 400, + "Doesn't update spot with invalid extended info", + ) - response = c.put( - self.url, - invalid_int_json_string, - content_type="application/json", - If_Match=etag, - ) - self.assertEquals( - response.status_code, - 400, - "Doesn't update spot with invalid int value", - ) + response = c.get(self.url) + self.assertEquals( + json.loads(json_string)["extended_info"], + json.loads(response.content)["extended_info"], + "Doesn't update spot with invalid extended info", + ) - response = c.get(self.url) - self.assertEquals( - json.loads(json_string)["extended_info"], - json.loads(response.content)["extended_info"], - "Doesn't update spot with invalid int value", - ) + # test: invalid int value + invalid_int = "invalid_int" + invalid_int_json_string = ( + '{"name":"%s","capacity":"%s",\ + "location": {"latitude": 55, "longitude": -30},\ + "extended_info":{"has_whiteboards":"true",\ + "location_description": "This is a description",\ + "has_outlets":"true", "has_computers":"true", \ + "num_computers":"%s","manager":"Sam","organization":"UW"}}' + % (new_name, new_capacity, invalid_int) + ) + + response = c.put( + self.url, + invalid_int_json_string, + content_type="application/json", + If_Match=etag, + ) + self.assertEquals( + response.status_code, + 400, + "Doesn't update spot with invalid int value", + ) + + response = c.get(self.url) + self.assertEquals( + json.loads(json_string)["extended_info"], + json.loads(response.content)["extended_info"], + "Doesn't update spot with invalid int value", + ) def test_phone_number_validation(self): phone_numbers = ( diff --git a/spotseeker_server/test/uw_spot/uw_search.py b/spotseeker_server/test/uw_spot/uw_search.py index d60701c4..e4f64f4e 100644 --- a/spotseeker_server/test/uw_spot/uw_search.py +++ b/spotseeker_server/test/uw_spot/uw_search.py @@ -1,7 +1,6 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 -from django.conf import settings from django.test import TestCase from django.test.client import Client from django.test.utils import override_settings @@ -11,7 +10,7 @@ @override_settings( - SPOTSEEKER_AUTH_MODULE="spotseeker_server.auth.all_ok", + SPOTSEEKER_OAUTH_ENABLED=False, SPOTSEEKER_SEARCH_FILTERS=( "spotseeker_server.org_filters.uw_search.Filter", ), diff --git a/spotseeker_server/tests.py b/spotseeker_server/tests.py index 257269e5..968f0b9d 100644 --- a/spotseeker_server/tests.py +++ b/spotseeker_server/tests.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 # Use full test failure messages @@ -14,15 +14,13 @@ from spotseeker_server.test.spot_get import SpotGETTest from spotseeker_server.test.no_rest_methods import NoRESTMethodsTest from spotseeker_server.test.schema import SpotSchemaTest +from spotseeker_server.test.oauth import SpotAuthOAuth from spotseeker_server.test.images.get import SpotImageGETTest from spotseeker_server.test.images.post import SpotImagePOSTTest from spotseeker_server.test.images.put import SpotImagePUTTest from spotseeker_server.test.images.delete import SpotImageDELETETest from spotseeker_server.test.images.thumb import ImageThumbTest from spotseeker_server.test.images.spot_info import SpotResourceImageTest -from spotseeker_server.test.images.oauth_spot_info import ( - SpotResourceOAuthImageTest, -) from spotseeker_server.test.item.model import ItemModelTest from spotseeker_server.test.search.item import SpotSearchItemTest from spotseeker_server.test.search.buildings import BuildingSearchTest @@ -56,9 +54,6 @@ ) from spotseeker_server.test.hours.overlap import SpotHoursOverlapTest from spotseeker_server.test.hours.modify import SpotHoursModifyTest -from spotseeker_server.test.auth.all_ok import SpotAuthAllOK -from spotseeker_server.test.auth.oauth import SpotAuthOAuth -from spotseeker_server.test.auth.oauth_logger import SpotAuthOAuthLogger from spotseeker_server.test.uw_spot.spot_form import UWSpotFormTest from spotseeker_server.test.uw_spot.spot_post import UWSpotPOSTTest from spotseeker_server.test.uw_spot.spot_put import UWSpotPUTTest diff --git a/spotseeker_server/urls.py b/spotseeker_server/urls.py index f461caa9..a514570e 100644 --- a/spotseeker_server/urls.py +++ b/spotseeker_server/urls.py @@ -1,15 +1,8 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 -""" Changes - ================================================================= - - sbutler1@illinois.edu: added external_id support; moved some URL - patterns into the ThumbnailView; added names for reverse() - support. -""" - -from django.conf.urls import include, url +from django.urls import path, re_path +from django.conf import settings from django.views.decorators.csrf import csrf_exempt from spotseeker_server.views.buildings import BuildingListView from spotseeker_server.views.spot import SpotView @@ -24,63 +17,123 @@ from spotseeker_server.views.item_image import ItemImageView from spotseeker_server.views.add_item_image import AddItemImageView from spotseeker_server.views.item_thumbnail import ItemThumbnailView +import oauth2_provider.views as oauth2_views + +# OAuth2 provider endpoints +oauth2_endpoint_views = [ + path( + "authorize/", + oauth2_views.AuthorizationView.as_view(), + name="authorize" + ), + path("token/", oauth2_views.TokenView.as_view(), name="token"), + path( + "revoke-token/", + oauth2_views.RevokeTokenView.as_view(), + name="revoke-token" + ), +] + +if settings.DEBUG: + # OAuth2 Application Management endpoints + oauth2_endpoint_views += [ + path( + "applications/", + oauth2_views.ApplicationList.as_view(), + name="list" + ), + path( + "applications/register/", + oauth2_views.ApplicationRegistration.as_view(), + name="register" + ), + path( + "applications//", + oauth2_views.ApplicationDetail.as_view(), + name="detail" + ), + path( + "applications//delete/", + oauth2_views.ApplicationDelete.as_view(), + name="delete" + ), + path( + "applications//update/", + oauth2_views.ApplicationUpdate.as_view(), + name="update" + ), + ] + + # OAuth2 Token Management endpoints + oauth2_endpoint_views += [ + path( + "authorized-tokens/", + oauth2_views.AuthorizedTokensListView.as_view(), + name="authorized-token-list" + ), + path( + "authorized-tokens//delete/", + oauth2_views.AuthorizedTokenDeleteView.as_view(), + name="authorized-token-delete" + ), + ] urlpatterns = [ - url(r"v1/null$", csrf_exempt(NullView().run)), - url( + path("v1/null", csrf_exempt(NullView().run)), + re_path( r"v1/spot/(?P(\d+|external:[\w-]+))$", csrf_exempt(SpotView().run), name="spot", ), - url(r"v1/spot/?$", csrf_exempt(SearchView().run), name="spot-search"), - url(r"v1/spot/all$", csrf_exempt(AllSpotsView().run), name="spots"), - url( + re_path(r"v1/spot/?$", csrf_exempt(SearchView().run), name="spot-search"), + path("v1/spot/all", csrf_exempt(AllSpotsView().run), name="spots"), + re_path( r"v1/buildings/?$", csrf_exempt(BuildingListView().run), name="buildings", ), - url(r"v1/schema$", csrf_exempt(SchemaGenView().run), name="schema"), - url(r"v1/spot/(?P\d+)/image$", csrf_exempt(AddImageView().run)), - url( - r"v1/spot/(?P\d+)/image/" r"(?P\d+)$", + path("v1/schema", csrf_exempt(SchemaGenView().run), name="schema"), + path(r"v1/spot//image", csrf_exempt(AddImageView().run)), + path( + "v1/spot//image/", csrf_exempt(ImageView().run), name="spot-image", ), - url( + re_path( r"v1/spot/(?P\d+)/image/" r"(?P\d+)/thumb/constrain/" "(?P.+)?$", csrf_exempt(ThumbnailView().run), {"constrain": True}, ), - url( + re_path( r"v1/spot/(?P\d+)/image/" r"(?P\d+)/thumb/" r"(?P.+)?$", csrf_exempt(ThumbnailView().run), name="spot-image-thumb", ), - url( + re_path( r"v1/item/(?P\d+)/image$", csrf_exempt(AddItemImageView().run) ), - url( + re_path( r"v1/item/(?P\d+)/image/" r"(?P[\d]+)$", csrf_exempt(ItemImageView().run), name="item-image", ), - url( + re_path( r"v1/item/(?P\d+)/image/" r"(?P\d+)/thumb/constrain/" r"(?P.+)?$", csrf_exempt(ItemThumbnailView().run), {"constrain": True}, ), - url( + re_path( r"v1/item/(?P\d+)/image/" r"(?P\d+)/thumb/" r"(?P.+)?$", csrf_exempt(ItemThumbnailView().run), name="item-image-thumb", ), - url(r"v1/user/me$", csrf_exempt(PersonView().run)), + re_path(r"v1/user/me$", csrf_exempt(PersonView().run)), ] diff --git a/spotseeker_server/views/add_image.py b/spotseeker_server/views/add_image.py index b60091d3..177e508e 100644 --- a/spotseeker_server/views/add_image.py +++ b/spotseeker_server/views/add_image.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 """ Changes @@ -10,18 +10,16 @@ from spotseeker_server.views.rest_dispatch import RESTDispatch, RESTException from spotseeker_server.models import SpotImage, Spot from django.http import HttpResponse -from spotseeker_server.require_auth import * from PIL import Image +from oauth2_provider.views.generic import ReadWriteScopedResourceView -class AddImageView(RESTDispatch): +class AddImageView(RESTDispatch, ReadWriteScopedResourceView): """Saves a SpotImage for a particular Spot on POST to /api/v1/spot//image. """ - @user_auth_required - @admin_auth_required - def POST(self, request, spot_id): + def post(self, request, spot_id): spot = Spot.objects.get(pk=spot_id) if "image" not in request.FILES: diff --git a/spotseeker_server/views/add_item_image.py b/spotseeker_server/views/add_item_image.py index 3dbaa3d8..8f3fd9b7 100644 --- a/spotseeker_server/views/add_item_image.py +++ b/spotseeker_server/views/add_item_image.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 """ Changes @@ -10,18 +10,16 @@ from spotseeker_server.views.rest_dispatch import RESTDispatch, RESTException from spotseeker_server.models import ItemImage, Item from django.http import HttpResponse -from spotseeker_server.require_auth import * from PIL import Image +from oauth2_provider.views.generic import ReadWriteScopedResourceView -class AddItemImageView(RESTDispatch): +class AddItemImageView(RESTDispatch, ReadWriteScopedResourceView): """Saves a ItemImage for a particular Item on POST to /api/v1/item//image. """ - @user_auth_required - @admin_auth_required - def POST(self, request, item_id): + def post(self, request, item_id): item = Item.objects.get(pk=item_id) if "image" not in request.FILES: diff --git a/spotseeker_server/views/all_spots.py b/spotseeker_server/views/all_spots.py index 0633a302..69fad325 100644 --- a/spotseeker_server/views/all_spots.py +++ b/spotseeker_server/views/all_spots.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 """ Changes @@ -9,11 +9,10 @@ from spotseeker_server.views.rest_dispatch import RESTDispatch, JSONResponse from spotseeker_server.models import Spot -from spotseeker_server.require_auth import app_auth_required +from oauth2_provider.views.generic import ReadWriteScopedResourceView -class AllSpotsView(RESTDispatch): - @app_auth_required - def GET(self, request): +class AllSpotsView(RESTDispatch, ReadWriteScopedResourceView): + def get(self, request): spots = [spot.json_data_structure() for spot in Spot.objects.all()] return JSONResponse(spots) diff --git a/spotseeker_server/views/buildings.py b/spotseeker_server/views/buildings.py index 7f5f75cb..7ef0f463 100644 --- a/spotseeker_server/views/buildings.py +++ b/spotseeker_server/views/buildings.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 """ Changes @@ -9,21 +9,20 @@ """ from spotseeker_server.views.rest_dispatch import RESTDispatch, JSONResponse -from spotseeker_server.require_auth import * from spotseeker_server.models import Spot from spotseeker_server.org_filters import SearchFilterChain from spotseeker_server.views.search import SearchView from django.http import HttpResponse from django.core.exceptions import FieldError +from oauth2_provider.views.generic import ReadWriteScopedResourceView -class BuildingListView(RESTDispatch): +class BuildingListView(RESTDispatch, ReadWriteScopedResourceView): """Performs actions on the list of buildings, at /api/v1/buildings. GET returns 200 with a list of buildings. """ - @app_auth_required - def GET(self, request): + def get(self, request): chain = SearchFilterChain(request) search_view = SearchView() spots = SearchView.filter_on_request( diff --git a/spotseeker_server/views/image.py b/spotseeker_server/views/image.py index 31419c88..53cb7ffe 100644 --- a/spotseeker_server/views/image.py +++ b/spotseeker_server/views/image.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 """ Changes @@ -18,19 +18,18 @@ from wsgiref.util import FileWrapper from django.core.exceptions import ValidationError from django.core.files.images import ImageFile -from spotseeker_server.require_auth import * from spotseeker_server.models import * +from oauth2_provider.views.generic import ReadWriteScopedResourceView -class ImageView(RESTDispatch): +class ImageView(RESTDispatch, ReadWriteScopedResourceView): """Handles actions at /api/v1/spot//image/. GET returns 200 with the image. PUT returns 200 and updates the image. DELETE returns 200 and deletes the image. """ - @app_auth_required - def GET(self, request, spot_id, image_id): + def get(self, request, spot_id, image_id): img = SpotImage.objects.get(pk=image_id) spot = img.spot @@ -48,9 +47,7 @@ def GET(self, request, spot_id, image_id): response["Content-type"] = img.content_type return response - @user_auth_required - @admin_auth_required - def PUT(self, request, spot_id, image_id): + def put(self, request, spot_id, image_id): img = SpotImage.objects.get(pk=image_id) spot = img.spot @@ -73,11 +70,9 @@ def PUT(self, request, spot_id, image_id): img.display_index = request.META["files"]["display_index"] img.save() - return self.GET(request, spot_id, image_id) + return self.get(request, spot_id, image_id) - @user_auth_required - @admin_auth_required - def DELETE(self, request, spot_id, image_id): + def delete(self, request, spot_id, image_id): img = SpotImage.objects.get(pk=image_id) spot = img.spot diff --git a/spotseeker_server/views/item_image.py b/spotseeker_server/views/item_image.py index abdbd4f9..f192327f 100644 --- a/spotseeker_server/views/item_image.py +++ b/spotseeker_server/views/item_image.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 """ Changes @@ -18,19 +18,18 @@ from wsgiref.util import FileWrapper from django.core.exceptions import ValidationError from django.core.files.images import ImageFile -from spotseeker_server.require_auth import * from spotseeker_server.models import * +from oauth2_provider.views.generic import ReadWriteScopedResourceView -class ItemImageView(RESTDispatch): +class ItemImageView(RESTDispatch, ReadWriteScopedResourceView): """Handles actions at /api/v1/item//image/. GET returns 200 with the image. PUT returns 200 and updates the image. DELETE returns 200 and deletes the image. """ - @app_auth_required - def GET(self, request, item_id, image_id): + def get(self, request, item_id, image_id): img = ItemImage.objects.get(pk=image_id) item = img.item @@ -48,9 +47,7 @@ def GET(self, request, item_id, image_id): response["Content-type"] = img.content_type return response - @user_auth_required - @admin_auth_required - def PUT(self, request, item_id, image_id): + def put(self, request, item_id, image_id): img = ItemImage.objects.get(pk=image_id) item = img.item @@ -74,11 +71,9 @@ def PUT(self, request, item_id, image_id): img.save() item.spot.save() - return self.GET(request, item_id, image_id) + return self.get(request, item_id, image_id) - @user_auth_required - @admin_auth_required - def DELETE(self, request, item_id, image_id): + def delete(self, request, item_id, image_id): img = ItemImage.objects.get(pk=image_id) item = img.item diff --git a/spotseeker_server/views/item_thumbnail.py b/spotseeker_server/views/item_thumbnail.py index 5b545646..a11e6717 100644 --- a/spotseeker_server/views/item_thumbnail.py +++ b/spotseeker_server/views/item_thumbnail.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 """ Changes @@ -8,37 +8,27 @@ here to simplify the URL patterns; adapt to the new RESTDispatch framework. """ -try: - from cStringIO import StringIO as IOStream -except ModuleNotFoundError: - from io import BytesIO as IOStream +from io import BytesIO as IOStream from spotseeker_server.views.rest_dispatch import RESTDispatch, RESTException from spotseeker_server.models import ItemImage, Item from django.http import HttpResponse from django.utils.http import http_date -from spotseeker_server.require_auth import app_auth_required from PIL import Image import time import re +from oauth2_provider.views.generic import ReadWriteScopedResourceView RE_WIDTH = re.compile(r"width:(\d+)") RE_HEIGHT = re.compile(r"height:(\d+)") RE_WIDTHxHEIGHT = re.compile(r"^(\d+)x(\d+)$") -class ItemThumbnailView(RESTDispatch): +class ItemThumbnailView(RESTDispatch, ReadWriteScopedResourceView): """Returns 200 with a thumbnail of a ItemImage.""" - @app_auth_required - def GET( - self, - request, - item_id, - image_id, - thumb_dimensions=None, - constrain=False, - ): + def get(self, request, item_id, image_id, + thumb_dimensions=None, constrain=False): img = ItemImage.objects.get(pk=image_id) item = img.item diff --git a/spotseeker_server/views/null.py b/spotseeker_server/views/null.py index 03a065f1..23423700 100644 --- a/spotseeker_server/views/null.py +++ b/spotseeker_server/views/null.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 """ This class is just here to help with testing coverage diff --git a/spotseeker_server/views/oauth.py b/spotseeker_server/views/oauth.py deleted file mode 100644 index 594daef2..00000000 --- a/spotseeker_server/views/oauth.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright 2023 UW-IT, University of Washington -# SPDX-License-Identifier: Apache-2.0 - -from django.http import HttpResponse -from django.shortcuts import render_to_response -from django.template import RequestContext -from oauth_provider.models import Token -from spotseeker_server.models import TrustedOAuthClient - - -def authorize(request, token, callback, params): - consumer = token.consumer - - bypasses_auth = False - trusted_client = TrustedOAuthClient.objects.filter(consumer=consumer) - if len(trusted_client) and trusted_client[0].bypasses_user_authorization: - bypasses_auth = True - - request.session["oauth"] = token.key - return render_to_response( - "oauth/authorize.html", - { - "consumer": consumer.name, - "token": request.GET["oauth_token"], - "bypass_auth": bypasses_auth, - }, - RequestContext(request), - ) - - -def callback(request, **kwargs): - if "oauth_token" not in kwargs: - return render_to_response("oauth/declined.html") - - token = Token.objects.get(key=kwargs["oauth_token"]) - - return render_to_response( - "oauth/oob.html", - { - "verifier": token.verifier, - }, - RequestContext(request), - ) diff --git a/spotseeker_server/views/person.py b/spotseeker_server/views/person.py index bb27a2f9..a0f38bbc 100644 --- a/spotseeker_server/views/person.py +++ b/spotseeker_server/views/person.py @@ -1,16 +1,15 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from spotseeker_server.views.rest_dispatch import RESTDispatch, JSONResponse -from spotseeker_server.require_auth import user_auth_required from django.conf import settings +from oauth2_provider.views.generic import ReadWriteScopedResourceView -class PersonView(RESTDispatch): +class PersonView(RESTDispatch, ReadWriteScopedResourceView): """Information (username, email) about a person""" - @user_auth_required - def GET(self, request): + def get(self, request): user = self._get_user(request) data = { diff --git a/spotseeker_server/views/rest_dispatch.py b/spotseeker_server/views/rest_dispatch.py index 7097ed46..cf46fdb1 100644 --- a/spotseeker_server/views/rest_dispatch.py +++ b/spotseeker_server/views/rest_dispatch.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 """ Changes @@ -15,8 +15,15 @@ from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.conf import settings from django.http import HttpResponse +from oauth2_provider.models import get_access_token_model import simplejson as json import traceback +import logging + + +logger = logging.getLogger(__name__) + +AccessToken = get_access_token_model() class JSONResponse(HttpResponse): @@ -69,14 +76,17 @@ def run(self, *args, **named_args): method = request.META["REQUEST_METHOD"] try: - if "GET" == method and hasattr(self, "GET"): - response = self.GET(*args, **named_args) - elif "POST" == method and hasattr(self, "POST"): - response = self.POST(*args, **named_args) - elif "PUT" == method and hasattr(self, "PUT"): - response = self.PUT(*args, **named_args) - elif "DELETE" == method and hasattr(self, "DELETE"): - response = self.DELETE(*args, **named_args) + if settings.SPOTSEEKER_OAUTH_ENABLED: + self.validate_oauth_scope(request, method) + + if "GET" == method and hasattr(self, "get"): + response = self.get(*args, **named_args) + elif "POST" == method and hasattr(self, "post"): + response = self.post(*args, **named_args) + elif "PUT" == method and hasattr(self, "put"): + response = self.put(*args, **named_args) + elif "DELETE" == method and hasattr(self, "delete"): + response = self.delete(*args, **named_args) else: raise RESTException("Method not allowed", 405) @@ -121,9 +131,36 @@ def validate_etag(self, request, obj): if request.META["HTTP_IF_MATCH"] != obj.etag: raise RESTException("Invalid ETag", 409) + def validate_oauth_scope(self, request, method: str) -> None: + if "Authorization" not in request.headers: + raise RESTException("Missing Authorization header", 401) + + access_token = request.headers.get("Authorization").split(" ")[1] + + try: + token = AccessToken.objects.get(token=access_token) + + if token.is_expired(): + raise RESTException("Expired access token", 401) + + except AccessToken.DoesNotExist: + raise RESTException("Invalid access token", 401) + + scope = token.scope + logger.debug(f"Validating scope: {scope}") + + # match scope with request method + if method == "GET": + if "read" not in scope: + raise RESTException(f"Invalid scope for method {method}", 403) + elif method in ("POST", "PUT", "DELETE"): + if "write" not in scope: + raise RESTException(f"Invalid scope for method {method}", 403) + else: + raise RESTException("Method not allowed", 405) + def _get_user(self, request): if "SS_OAUTH_USER" not in request.META: - print(request.META) raise RESTException( "missing oauth user - improper auth backend?", 400 ) diff --git a/spotseeker_server/views/schema_gen.py b/spotseeker_server/views/schema_gen.py index 2240f51e..f6f733ca 100644 --- a/spotseeker_server/views/schema_gen.py +++ b/spotseeker_server/views/schema_gen.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 """ Changes @@ -10,17 +10,15 @@ from django.apps import apps from django.core.exceptions import ImproperlyConfigured -from django.http import HttpResponse from spotseeker_server.forms.spot import SpotForm from spotseeker_server.models import * -from spotseeker_server.require_auth import * from spotseeker_server.views.rest_dispatch import JSONResponse, RESTDispatch +from oauth2_provider.views.generic import ReadWriteScopedResourceView -class SchemaGenView(RESTDispatch): - @app_auth_required - def GET(self, request): +class SchemaGenView(RESTDispatch, ReadWriteScopedResourceView): + def get(self, request): """Json data that should contain every single piece of information that any spot might contain. diff --git a/spotseeker_server/views/search.py b/spotseeker_server/views/search.py index 3c440e11..e9364361 100644 --- a/spotseeker_server/views/search.py +++ b/spotseeker_server/views/search.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 """ Changes @@ -23,7 +23,6 @@ from django.http import HttpResponse, HttpResponseBadRequest from django.db.models import Q from django.utils.datastructures import MultiValueDictKeyError -from spotseeker_server.require_auth import * from spotseeker_server.models import Spot, SpotType from pyproj import Geod from decimal import * @@ -31,20 +30,18 @@ from datetime import datetime import pytz import sys +from oauth2_provider.views.generic import ReadWriteScopedResourceView -class SearchView(RESTDispatch): +class SearchView(RESTDispatch, ReadWriteScopedResourceView): """Handles searching for Spots with particular attributes based on a query string. """ - @user_auth_required - @admin_auth_required - def POST(self, request): + def post(self, request): return SpotView().run(request) - @app_auth_required - def GET(self, request): + def get(self, request): chain = SearchFilterChain(request) spots = self.filter_on_request( request.GET, chain, request.META, "spot" diff --git a/spotseeker_server/views/spot.py b/spotseeker_server/views/spot.py index cca19a2d..a2f1d2a9 100644 --- a/spotseeker_server/views/spot.py +++ b/spotseeker_server/views/spot.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 """ Changes @@ -16,14 +16,13 @@ RESTFormInvalidError, JSONResponse, ) + +# ItemStash import required for Signal receivers, do not remove! +from spotseeker_server.views.spot_item import ItemStash + from spotseeker_server.forms.spot import SpotForm, SpotExtendedInfoForm -from spotseeker_server.default_forms.item import DefaultItemForm as ItemForm -from spotseeker_server.default_forms.item import ( - DefaultItemExtendedInfoForm as ItemExtendedInfoForm, -) from spotseeker_server.models import * from django.http import HttpResponse -from spotseeker_server.require_auth import * from django.db import transaction import simplejson as json import django.dispatch @@ -33,7 +32,7 @@ spot_post_save, spot_post_build, ) -import spotseeker_server.views.spot_item +from oauth2_provider.views.generic import ReadWriteScopedResourceView from past.builtins import basestring @@ -159,7 +158,7 @@ def _save_extended_info(sender, **kwargs): pass -class SpotView(RESTDispatch): +class SpotView(RESTDispatch, ReadWriteScopedResourceView): """Performs actions on a Spot at /api/v1/spot/. GET returns 200 with Spot details. POST to /api/v1/spot with valid JSON returns 200 and creates a new Spot. @@ -167,30 +166,23 @@ class SpotView(RESTDispatch): DELETE returns 200 and deletes the Spot. """ - @app_auth_required - def GET(self, request, spot_id): + def get(self, request, spot_id): spot = Spot.get_with_external(spot_id) response = JSONResponse(spot.json_data_structure()) response["ETag"] = spot.etag return response - @user_auth_required - @admin_auth_required - def POST(self, request): + def post(self, request): return self.build_and_save_from_input(request, None) - @user_auth_required - @admin_auth_required - def PUT(self, request, spot_id): + def put(self, request, spot_id): spot = Spot.get_with_external(spot_id) self.validate_etag(request, spot) return self.build_and_save_from_input(request, spot) - @user_auth_required - @admin_auth_required - def DELETE(self, request, spot_id): + def delete(self, request, spot_id): spot = Spot.get_with_external(spot_id) self.validate_etag(request, spot) @@ -234,7 +226,7 @@ def build_and_save_from_input(self, request, spot): stash=stash, ) - # Remve excluded fields + # Remove excluded fields excludefields = set(SpotForm.implementation().Meta.exclude) for fieldname in excludefields: if fieldname in json_values: diff --git a/spotseeker_server/views/spot_item.py b/spotseeker_server/views/spot_item.py index 4d3445fa..7fdd8b4e 100644 --- a/spotseeker_server/views/spot_item.py +++ b/spotseeker_server/views/spot_item.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 from django import forms @@ -14,10 +14,6 @@ DefaultItemExtendedInfoForm as ItemExtendedInfoForm, ) from spotseeker_server.models import * -from django.http import HttpResponse -from spotseeker_server.require_auth import * -from django.db import transaction -import simplejson as json import django.dispatch from spotseeker_server.dispatch import ( spot_pre_build, diff --git a/spotseeker_server/views/thumbnail.py b/spotseeker_server/views/thumbnail.py index b1132276..9d95bedb 100644 --- a/spotseeker_server/views/thumbnail.py +++ b/spotseeker_server/views/thumbnail.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 """ Changes @@ -17,28 +17,21 @@ from spotseeker_server.models import SpotImage, Spot from django.http import HttpResponse from django.utils.http import http_date -from spotseeker_server.require_auth import app_auth_required from PIL import Image import time import re +from oauth2_provider.views.generic import ReadWriteScopedResourceView RE_WIDTH = re.compile(r"width:(\d+)") RE_HEIGHT = re.compile(r"height:(\d+)") RE_WIDTHxHEIGHT = re.compile(r"^(\d+)x(\d+)$") -class ThumbnailView(RESTDispatch): +class ThumbnailView(RESTDispatch, ReadWriteScopedResourceView): """Returns 200 with a thumbnail of a SpotImage.""" - @app_auth_required - def GET( - self, - request, - spot_id, - image_id, - thumb_dimensions=None, - constrain=False, - ): + def get(self, request, spot_id, image_id, + thumb_dimensions=None, constrain=False): img = SpotImage.objects.get(pk=image_id) spot = img.spot