From 0f0913c13886a7db534f1988ac3a5628f349945f Mon Sep 17 00:00:00 2001 From: ddx-day <115170545+ddx-day@users.noreply.github.com> Date: Wed, 18 Jan 2023 06:34:00 -0800 Subject: [PATCH] Django social account sso (#5059) Issue: https://github.com/opencv/cvat/issues/1217 Currently there are a few proposals for SSO authentication to bypass the current user/password login on the UI. By using Django social accounts it is also possible to use SSO on the API, retrieving the security token by passing the code from the OAuth2 workflow. This is an example using Amazon Cognito, but any other social account could also be added. ### Motivation and context Currently CVAT has no functionality to log in with SSO. Other current proposals bypass the current Django framework to add SSO in the UI only, but still use username and password for the API. Using Django social accounts integrates SSO with the API as well, allowing it to be used as an alternative to the username and password, but can also be used together with other SSO frameworks that are UI only. ### How has this been tested? Unit tests for SSO manager in cvat-core and integration test with cvat-sdk for /auth/cognito endpoint. ### Checklist - [x] I submit my changes into the `develop` branch - [ ] I have added a description of my changes into [CHANGELOG](https://github.com/cvat-ai/cvat/blob/develop/CHANGELOG.md) file - [x] I have updated the [documentation]( https://github.com/cvat-ai/cvat/blob/develop/README.md#documentation) accordingly - [x] I have added tests to cover my changes - [x] I have linked related issues ([read github docs]( https://help.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword)) - [ ] I have increased versions of npm packages if it is necessary ([cvat-canvas](https://github.com/cvat-ai/cvat/tree/develop/cvat-canvas#versioning), [cvat-core](https://github.com/cvat-ai/cvat/tree/develop/cvat-core#versioning), [cvat-data](https://github.com/cvat-ai/cvat/tree/develop/cvat-data#versioning) and [cvat-ui](https://github.com/cvat-ai/cvat/tree/develop/cvat-ui#versioning)) ### License - [x] I submit _my code changes_ under the same [MIT License]( https://github.com/cvat-ai/cvat/blob/develop/LICENSE) that covers the project. Feel free to contact the maintainers if that's a concern. Co-authored-by: Melanie Day Co-authored-by: Maria Khrustaleva Co-authored-by: Nikita Manovich --- .github/workflows/full.yml | 1 + .github/workflows/main.yml | 1 + cvat/apps/iam/adapters.py | 5 ++ .../social-amazon-cognito-logo.svg | 36 +++++++++++++ cvat/apps/iam/urls.py | 12 +++-- cvat/apps/iam/views.py | 51 +++++++++++++++++-- cvat/requirements/base.txt | 2 +- cvat/settings/base.py | 26 +++++++++- cvat/settings/development.py | 1 + docker-compose.yml | 16 ++++-- .../actions_users/issue_1810_login_logout.js | 2 +- tests/python/mock_oauth2/adapters.py | 15 ++++++ tests/python/mock_oauth2/config.json | 24 +++++++++ tests/python/mock_oauth2/docker-compose.yml | 23 +++++++++ tests/python/mock_oauth2/settings.py | 10 ++++ tests/python/rest_api/test_auth.py | 18 ++++++- tests/python/shared/fixtures/init.py | 27 ++++++---- tests/python/shared/utils/config.py | 3 ++ 18 files changed, 245 insertions(+), 28 deletions(-) create mode 100644 cvat/apps/iam/static/social_authentication/social-amazon-cognito-logo.svg create mode 100644 tests/python/mock_oauth2/adapters.py create mode 100644 tests/python/mock_oauth2/config.json create mode 100644 tests/python/mock_oauth2/docker-compose.yml create mode 100644 tests/python/mock_oauth2/settings.py diff --git a/.github/workflows/full.yml b/.github/workflows/full.yml index e5f504fe..30afdc64 100644 --- a/.github/workflows/full.yml +++ b/.github/workflows/full.yml @@ -94,6 +94,7 @@ jobs: run: | docker load --input /tmp/cvat_server/image.tar docker run --rm -v ${PWD}/cvat-sdk/schema/:/transfer \ + -e USE_ALLAUTH_SOCIAL_ACCOUNTS="True" \ --entrypoint /bin/bash -u root cvat/server \ -c 'python manage.py spectacular --file /transfer/schema.yml' pip3 install --user -r cvat-sdk/gen/requirements.txt diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 39fb91ec..4c7b2b5c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -97,6 +97,7 @@ jobs: run: | docker load --input /tmp/cvat_server/image.tar docker run --rm -v ${PWD}/cvat-sdk/schema/:/transfer \ + -e USE_ALLAUTH_SOCIAL_ACCOUNTS="True" \ --entrypoint /bin/bash -u root cvat/server \ -c 'python manage.py spectacular --file /transfer/schema.yml' pip3 install --user -r cvat-sdk/gen/requirements.txt diff --git a/cvat/apps/iam/adapters.py b/cvat/apps/iam/adapters.py index 649bf472..a53ce0c0 100644 --- a/cvat/apps/iam/adapters.py +++ b/cvat/apps/iam/adapters.py @@ -8,6 +8,7 @@ from django.conf import settings from allauth.socialaccount.adapter import DefaultSocialAccountAdapter from allauth.account.adapter import DefaultAccountAdapter +from allauth.socialaccount.providers.amazon_cognito.views import AmazonCognitoOAuth2Adapter from allauth.socialaccount.providers.github.views import GitHubOAuth2Adapter from allauth.socialaccount.providers.google.views import GoogleOAuth2Adapter from allauth.exceptions import ImmediateHttpResponse @@ -48,3 +49,7 @@ class GoogleAdapter(GoogleOAuth2Adapter): def get_callback_url(self, request, app): return settings.GOOGLE_CALLBACK_URL + +class AmazonCognitoOAuth2AdapterEx(AmazonCognitoOAuth2Adapter): + def get_callback_url(self, request, app): + return settings.AMAZON_COGNITO_REDIRECT_URI diff --git a/cvat/apps/iam/static/social_authentication/social-amazon-cognito-logo.svg b/cvat/apps/iam/static/social_authentication/social-amazon-cognito-logo.svg new file mode 100644 index 00000000..6d632fcd --- /dev/null +++ b/cvat/apps/iam/static/social_authentication/social-amazon-cognito-logo.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cvat/apps/iam/urls.py b/cvat/apps/iam/urls.py index ca6161b4..0ad4d31b 100644 --- a/cvat/apps/iam/urls.py +++ b/cvat/apps/iam/urls.py @@ -12,15 +12,14 @@ from dj_rest_auth.views import ( from allauth.account import app_settings as allauth_settings from cvat.apps.iam.views import ( - SigningView, RegisterViewEx, RulesView, ConfirmEmailViewEx, -) -from cvat.apps.iam.views import ( + SigningView, CognitoLogin, RegisterViewEx, RulesView, + ConfirmEmailViewEx, LoginViewEx, GitHubLogin, GoogleLogin, SocialAuthMethods, github_oauth2_login as github_login, github_oauth2_callback as github_callback, google_oauth2_login as google_login, google_oauth2_callback as google_callback, - LoginViewEx, GitHubLogin, GoogleLogin, - SocialAuthMethods, + amazon_cognito_oauth2_login as amazon_cognito_login, + amazon_cognito_oauth2_callback as amazon_cognito_callback, ) urlpatterns = [ @@ -58,6 +57,9 @@ if settings.IAM_TYPE == 'BASIC': path('google/login/', google_login, name='google_login'), path('google/login/callback/', google_callback, name='google_callback'), path('google/login/token', GoogleLogin.as_view()), + path('amazon-cognito/login/', amazon_cognito_login, name='amazon_cognito_login'), + path('amazon-cognito/login/callback/', amazon_cognito_callback, name='amazon_cognito_callback'), + path('amazon-cognito/login/token', CognitoLogin.as_view()), ] urlpatterns = [path('auth/', include(urlpatterns))] diff --git a/cvat/apps/iam/views.py b/cvat/apps/iam/views.py index 3d8a664c..d01bac58 100644 --- a/cvat/apps/iam/views.py +++ b/cvat/apps/iam/views.py @@ -28,13 +28,13 @@ from allauth.socialaccount.models import SocialLogin from allauth.socialaccount.providers.oauth2.views import OAuth2CallbackView, OAuth2LoginView from allauth.socialaccount.providers.oauth2.client import OAuth2Client from allauth.utils import get_request_param + from furl import furl from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiResponse, OpenApiParameter, extend_schema, inline_serializer, extend_schema_view from drf_spectacular.contrib.rest_auth import get_token_serializer_class -from cvat.apps.iam.adapters import GitHubAdapter, GoogleAdapter from .authentication import Signer from cvat.apps.iam.serializers import SocialLoginSerializerEx, SocialAuthMethodSerializer @@ -49,6 +49,12 @@ GoogleAdapter = ( else None ) +AmazonCognitoAdapter = ( + import_callable(settings.SOCIALACCOUNT_AMAZON_COGNITO_ADAPTER) + if settings.USE_ALLAUTH_SOCIAL_ACCOUNTS + else None +) + def get_context(request): from cvat.apps.organizations.models import Organization, Membership @@ -251,8 +257,11 @@ class OAuth2CallbackViewEx(OAuth2CallbackView): if not code: return HttpResponseBadRequest('Parameter code not found in request') + + provider = self.adapter.provider_id.replace('_', '-') + return HttpResponseRedirect( - f'{settings.SOCIAL_APP_LOGIN_REDIRECT_URL}/?provider={self.adapter.provider_id}&code={code}' + f'{settings.SOCIAL_APP_LOGIN_REDIRECT_URL}/?provider={provider}&code={code}' f'&auth_params={state.get("auth_params")}&process={state.get("process")}' f'&scope={state.get("scope")}') @@ -297,6 +306,17 @@ def github_oauth2_callback(*args, **kwargs): def google_oauth2_login(*args, **kwargs): return OAuth2LoginView.adapter_view(GoogleAdapter)(*args, **kwargs) +@extend_schema( + summary="Redirects to Amazon Cognito authentication page", + description="Redirects to the Amazon Cognito authentication page. " + "After successful authentication on the provider side, " + "a redirect to the callback endpoint is performed.", +) +@api_view(["GET"]) +@permission_classes([AllowAny]) +def amazon_cognito_oauth2_login(*args, **kwargs): + return OAuth2LoginView.adapter_view(AmazonCognitoAdapter)(*args, **kwargs) + @extend_schema( summary="Checks the authentication response from Google, redirects to the CVAT client if successful.", description="Accepts a request from Google with code and state query parameters. " @@ -315,6 +335,24 @@ def google_oauth2_callback(*args, **kwargs): return OAuth2CallbackViewEx.adapter_view(GoogleAdapter)(*args, **kwargs) +@extend_schema( + summary="Checks the authentication response from Amazon Cognito, redirects to the CVAT client if successful.", + description="Accepts a request from Amazon Cognito with code and state query parameters. " + "In case of successful authentication on the provider side, it will " + "redirect to the CVAT client", + parameters=[ + OpenApiParameter('code', description='Returned by google', + location=OpenApiParameter.QUERY, type=OpenApiTypes.STR), + OpenApiParameter('state', description='Returned by google', + location=OpenApiParameter.QUERY, type=OpenApiTypes.STR), + ], +) +@api_view(["GET"]) +@permission_classes([AllowAny]) +def amazon_cognito_oauth2_callback(*args, **kwargs): + return OAuth2CallbackViewEx.adapter_view(AmazonCognitoAdapter)(*args, **kwargs) + + class ConfirmEmailViewEx(ConfirmEmailView): template_name = 'account/email/email_confirmation_signup_message.html' @@ -369,6 +407,10 @@ class GoogleLogin(SocialLoginViewEx): client_class = OAuth2Client callback_url = getattr(settings, 'GOOGLE_CALLBACK_URL', None) +class CognitoLogin(SocialLoginViewEx): + adapter_class = AmazonCognitoAdapter + client_class = OAuth2Client + callback_url = getattr(settings, 'AMAZON_COGNITO_REDIRECT_URI', None) @extend_schema_view( get=extend_schema( @@ -379,6 +421,7 @@ class GoogleLogin(SocialLoginViewEx): fields={ 'google': SocialAuthMethodSerializer(), 'github': SocialAuthMethodSerializer(), + 'amazon-cognito': SocialAuthMethodSerializer(), } )), } @@ -401,7 +444,7 @@ class SocialAuthMethods(views.APIView): getattr(settings, f'SOCIAL_AUTH_{provider.upper()}_CLIENT_ID', None) and getattr(settings, f'SOCIAL_AUTH_{provider.upper()}_CLIENT_SECRET', None) ) - icon_path = osp.join(settings.STATIC_ROOT, 'social_authentication', f'social-{provider}-logo.svg') + icon_path = osp.join(settings.STATIC_ROOT, 'social_authentication', f'social-{provider.replace("_", "-")}-logo.svg') if is_enabled and osp.exists(icon_path): with open(icon_path, 'r') as f: icon = f.read() @@ -413,6 +456,6 @@ class SocialAuthMethods(views.APIView): }) serializer.is_valid(raise_exception=True) - response[provider] = serializer.validated_data + response[provider.replace("_", "-")] = serializer.validated_data return Response(response) diff --git a/cvat/requirements/base.txt b/cvat/requirements/base.txt index 501ad395..76dfbd48 100644 --- a/cvat/requirements/base.txt +++ b/cvat/requirements/base.txt @@ -4,6 +4,7 @@ Django==3.2.16 django-appconf==1.0.4 django-auth-ldap==2.2.0 django-compressor==2.4 +dj-rest-auth[with_social]==2.2.5 django-rq==2.3.2 EasyProcess==0.3 Pillow==9.3.0 @@ -31,7 +32,6 @@ Pygments==2.7.4 drf-spectacular==0.22.1 Shapely==1.7.1 pdf2image==1.14.0 -dj-rest-auth[with_social]==2.2.4 opencv-python-headless==4.5.5.62 h5py==3.6.0 django-cors-headers==3.5.0 diff --git a/cvat/settings/base.py b/cvat/settings/base.py index 2c715e90..8db205a7 100644 --- a/cvat/settings/base.py +++ b/cvat/settings/base.py @@ -111,20 +111,21 @@ INSTALLED_APPS = [ 'django_rq', 'compressor', 'django_sendfile', + "dj_rest_auth", + 'dj_rest_auth.registration', 'dj_pagination', 'rest_framework', 'rest_framework.authtoken', 'drf_spectacular', - 'dj_rest_auth', 'django.contrib.sites', 'allauth', 'allauth.account', 'corsheaders', 'allauth.socialaccount', # social providers + 'allauth.socialaccount.providers.amazon_cognito', 'allauth.socialaccount.providers.github', 'allauth.socialaccount.providers.google', - 'dj_rest_auth.registration', 'health_check', 'health_check.db', 'health_check.contrib.migrations', @@ -240,6 +241,7 @@ TEMPLATES = [ 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', + ], }, }, @@ -287,6 +289,7 @@ AUTHENTICATION_BACKENDS = [ # https://github.com/pennersr/django-allauth ACCOUNT_EMAIL_VERIFICATION = 'none' ACCOUNT_AUTHENTICATION_METHOD = 'username_email' + # set UI url to redirect after a successful e-mail confirmation #changed from '/auth/login' to '/auth/email-confirmation' for email confirmation message ACCOUNT_EMAIL_CONFIRMATION_ANONYMOUS_REDIRECT_URL = '/auth/email-confirmation' @@ -639,6 +642,7 @@ if USE_ALLAUTH_SOCIAL_ACCOUNTS: SOCIALACCOUNT_ADAPTER = 'cvat.apps.iam.adapters.SocialAccountAdapterEx' SOCIALACCOUNT_GITHUB_ADAPTER = 'cvat.apps.iam.adapters.GitHubAdapter' SOCIALACCOUNT_GOOGLE_ADAPTER = 'cvat.apps.iam.adapters.GoogleAdapter' + SOCIALACCOUNT_AMAZON_COGNITO_ADAPTER = 'cvat.apps.iam.adapters.AmazonCognitoOAuth2AdapterEx' SOCIALACCOUNT_LOGIN_ON_GET = True # It's required to define email in the case when a user has a private hidden email. # (e.g in github account set keep my email addresses private) @@ -648,6 +652,7 @@ if USE_ALLAUTH_SOCIAL_ACCOUNTS: # custom variable because by default LOGIN_REDIRECT_URL will be used SOCIAL_APP_LOGIN_REDIRECT_URL = f'{CVAT_BASE_URL}/auth/login-with-social-app' + AMAZON_COGNITO_REDIRECT_URI = f'{CVAT_BASE_URL}/api/auth/amazon-cognito/login/callback/' GITHUB_CALLBACK_URL = f'{CVAT_BASE_URL}/api/auth/github/login/callback/' GOOGLE_CALLBACK_URL = f'{CVAT_BASE_URL}/api/auth/google/login/callback/' @@ -656,6 +661,13 @@ if USE_ALLAUTH_SOCIAL_ACCOUNTS: SOCIAL_AUTH_GITHUB_CLIENT_ID = os.getenv('SOCIAL_AUTH_GITHUB_CLIENT_ID') SOCIAL_AUTH_GITHUB_CLIENT_SECRET = os.getenv('SOCIAL_AUTH_GITHUB_CLIENT_SECRET') + + SOCIAL_AUTH_AMAZON_COGNITO_CLIENT_ID = os.getenv('SOCIAL_AUTH_AMAZON_COGNITO_CLIENT_ID') + SOCIAL_AUTH_AMAZON_COGNITO_CLIENT_SECRET = os.getenv('SOCIAL_AUTH_AMAZON_COGNITO_CLIENT_SECRET') + SOCIAL_AUTH_AMAZON_COGNITO_DOMAIN = os.getenv('SOCIAL_AUTH_AMAZON_COGNITO_DOMAIN') + + # Django allauth social account providers + # https://django-allauth.readthedocs.io/en/latest/providers.html SOCIALACCOUNT_PROVIDERS = { 'google': { 'APP': { @@ -681,4 +693,14 @@ if USE_ALLAUTH_SOCIAL_ACCOUNTS: # key with a capital letter will be used 'PUBLIC_NAME': 'GitHub', }, + 'amazon_cognito': { + 'DOMAIN': SOCIAL_AUTH_AMAZON_COGNITO_DOMAIN, + 'SCOPE': [ 'profile', 'email', 'openid'], + 'APP': { + 'client_id': SOCIAL_AUTH_AMAZON_COGNITO_CLIENT_ID, + 'secret': SOCIAL_AUTH_AMAZON_COGNITO_CLIENT_SECRET, + 'key': '' + }, + 'PUBLIC_NAME': 'Amazon Cognito', + } } diff --git a/cvat/settings/development.py b/cvat/settings/development.py index 7ae8a37b..7cfcbdaa 100644 --- a/cvat/settings/development.py +++ b/cvat/settings/development.py @@ -50,5 +50,6 @@ IAM_OPA_DATA_URL = f'{IAM_OPA_HOST}/v1/data' if USE_ALLAUTH_SOCIAL_ACCOUNTS: GITHUB_CALLBACK_URL = f'{UI_URL}/api/auth/github/login/callback/' GOOGLE_CALLBACK_URL = f'{UI_URL}/api/auth/google/login/callback/' + AMAZON_COGNITO_REDIRECT_URI = f'{UI_URL}/api/auth/amazon-cognito/login/callback/' SOCIALACCOUNT_CALLBACK_CANCELLED_URL = f'{UI_URL}/auth/login' SOCIAL_APP_LOGIN_REDIRECT_URL = f'{UI_URL}/auth/login-with-social-app' diff --git a/docker-compose.yml b/docker-compose.yml index c5fa5535..b9dae107 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -40,11 +40,17 @@ services: IAM_OPA_BUNDLE: '1' no_proxy: elasticsearch,kibana,logstash,nuclio,opa,${no_proxy:-} NUMPROCS: 1 - USE_ALLAUTH_SOCIAL_ACCOUNTS: "" - SOCIAL_AUTH_GOOGLE_CLIENT_ID: "" - SOCIAL_AUTH_GOOGLE_CLIENT_SECRET: "" - SOCIAL_AUTH_GITHUB_CLIENT_ID: "" - SOCIAL_AUTH_GITHUB_CLIENT_SECRET: "" + USE_ALLAUTH_SOCIAL_ACCOUNTS: + # Google enviroment variables + SOCIAL_AUTH_GOOGLE_CLIENT_ID: + SOCIAL_AUTH_GOOGLE_CLIENT_SECRET: + # GitHub enviroment variables + SOCIAL_AUTH_GITHUB_CLIENT_ID: + SOCIAL_AUTH_GITHUB_CLIENT_SECRET: + # Amazon Cognito enviroment variables + SOCIAL_AUTH_AMAZON_COGNITO_DOMAIN: + SOCIAL_AUTH_AMAZON_COGNITO_CLIENT_ID: + SOCIAL_AUTH_AMAZON_COGNITO_CLIENT_SECRET: command: -c supervisord/server.conf labels: - traefik.enable=true diff --git a/tests/cypress/integration/actions_users/issue_1810_login_logout.js b/tests/cypress/integration/actions_users/issue_1810_login_logout.js index b0b1d789..d470452c 100644 --- a/tests/cypress/integration/actions_users/issue_1810_login_logout.js +++ b/tests/cypress/integration/actions_users/issue_1810_login_logout.js @@ -103,7 +103,7 @@ context('When clicking on the Logout button, get the user session closed.', () = method: 'GET', url: '/api/auth/social/methods/', }).then((response) => { - socialAuthMethods = Object.keys(response.body); + socialAuthMethods = Object.keys(response.body).filter((item) => response.body[item].is_enabled); expect(socialAuthMethods).length.gt(0); cy.visit('auth/login'); diff --git a/tests/python/mock_oauth2/adapters.py b/tests/python/mock_oauth2/adapters.py new file mode 100644 index 00000000..053cda79 --- /dev/null +++ b/tests/python/mock_oauth2/adapters.py @@ -0,0 +1,15 @@ +# Copyright (C) 2023 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +from allauth.socialaccount.providers.amazon_cognito.views import AmazonCognitoOAuth2Adapter +from django.conf import settings + + +class TestAmazonCognitoOAuth2Adapter(AmazonCognitoOAuth2Adapter): + @property + def profile_url(self): + return super().profile_url.lower() + + def get_callback_url(self, request, app): + return settings.AMAZON_COGNITO_REDIRECT_URI diff --git a/tests/python/mock_oauth2/config.json b/tests/python/mock_oauth2/config.json new file mode 100644 index 00000000..62e8322b --- /dev/null +++ b/tests/python/mock_oauth2/config.json @@ -0,0 +1,24 @@ +{ + "interactiveLogin": true, + "httpServer": "NettyWrapper", + "tokenCallbacks": [ + { + "issuerId": "oauth2", + "tokenExpiry": 120, + "requestMappings": [ + { + "requestParam": "client_id", + "match": "test-client", + "claims": { + "sub": "test-sub", + "aud": [ + "test-aud" + ], + "email": "admin@localhost.company", + "email_verified": true + } + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/python/mock_oauth2/docker-compose.yml b/tests/python/mock_oauth2/docker-compose.yml new file mode 100644 index 00000000..210a359c --- /dev/null +++ b/tests/python/mock_oauth2/docker-compose.yml @@ -0,0 +1,23 @@ +services: + mock_oauth2: + image: ghcr.io/navikt/mock-oauth2-server:0.5.3 + environment: + JSON_CONFIG_PATH: "/devel/config.json" + SERVER_PORT: 9999 + ports: + - 9999:9999 + volumes: + - ./tests/python/mock_oauth2:/devel + networks: + - cvat + + cvat_server: + environment: + USE_ALLAUTH_SOCIAL_ACCOUNTS: "True" + SOCIAL_AUTH_AMAZON_COGNITO_DOMAIN: "http://mock_oauth2:9999" + SOCIAL_AUTH_AMAZON_COGNITO_CLIENT_ID: "test-client" + DJANGO_SETTINGS_MODULE: mock_oauth2.settings + volumes: + - ./tests/python/mock_oauth2:/home/django/mock_oauth2:ro + depends_on: + - mock_oauth2 diff --git a/tests/python/mock_oauth2/settings.py b/tests/python/mock_oauth2/settings.py new file mode 100644 index 00000000..72b80ab5 --- /dev/null +++ b/tests/python/mock_oauth2/settings.py @@ -0,0 +1,10 @@ +# Copyright (C) 2023 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +from cvat.settings.production import * + +ACCOUNT_EMAIL_REQUIRED = True + +if USE_ALLAUTH_SOCIAL_ACCOUNTS: + SOCIALACCOUNT_AMAZON_COGNITO_ADAPTER = "mock_oauth2.adapters.TestAmazonCognitoOAuth2Adapter" diff --git a/tests/python/rest_api/test_auth.py b/tests/python/rest_api/test_auth.py index 05460a8e..222d11e6 100644 --- a/tests/python/rest_api/test_auth.py +++ b/tests/python/rest_api/test_auth.py @@ -8,7 +8,7 @@ from http import HTTPStatus import pytest from cvat_sdk.api_client import ApiClient, Configuration, models -from shared.utils.config import BASE_URL, USER_PASS, make_api_client +from shared.utils.config import BASE_URL, IS_AMAZON_COGNITO_AUTH_ENABLED, USER_PASS, make_api_client @pytest.mark.usefixtures("restore_db_per_class") @@ -21,6 +21,22 @@ class TestBasicAuth: assert response.status == HTTPStatus.OK assert user.username == username + @pytest.mark.with_external_services + @pytest.mark.skipif( + not IS_AMAZON_COGNITO_AUTH_ENABLED, reason="Amazon Cognito authentication is disabled" + ) + def test_can_do_basic_cognito_token_auth(self): + config = Configuration(host=BASE_URL) + with ApiClient(config) as client: + social_login_request = models.SocialLoginSerializerExRequest() + social_login_request.code = "test-code" + (auth, _) = client.auth_api.create_amazon_cognito_login_token( + social_login_serializer_ex_request=social_login_request + ) + assert "sessionid" in client.cookies + assert "csrftoken" in client.cookies + assert auth.key + @pytest.mark.usefixtures("restore_db_per_function") class TestTokenAuth: diff --git a/tests/python/shared/fixtures/init.py b/tests/python/shared/fixtures/init.py index 77c7e702..f175f93e 100644 --- a/tests/python/shared/fixtures/init.py +++ b/tests/python/shared/fixtures/init.py @@ -29,15 +29,24 @@ CONTAINER_NAME_FILES = [ ) ] -DC_FILES = [ - CVAT_ROOT_DIR / dc_file - for dc_file in ( - "docker-compose.dev.yml", - "tests/docker-compose.file_share.yml", - "tests/docker-compose.minio.yml", - "tests/docker-compose.test_servers.yml", - ) -] + CONTAINER_NAME_FILES +# this files contain some configurations that override the default configuration of the main containers +DC_OVERRIDE_FILES = [ + CVAT_ROOT_DIR / "tests/python/mock_oauth2/docker-compose.yml", +] + +DC_FILES = ( + [ + CVAT_ROOT_DIR / dc_file + for dc_file in ( + "docker-compose.dev.yml", + "tests/docker-compose.file_share.yml", + "tests/docker-compose.minio.yml", + "tests/docker-compose.test_servers.yml", + ) + ] + + CONTAINER_NAME_FILES + + DC_OVERRIDE_FILES +) def pytest_addoption(parser): diff --git a/tests/python/shared/utils/config.py b/tests/python/shared/utils/config.py index f5a3206c..5b380ab8 100644 --- a/tests/python/shared/utils/config.py +++ b/tests/python/shared/utils/config.py @@ -20,6 +20,9 @@ MINIO_SECRET_KEY = "minio_secret_key" # nosec MINIO_ENDPOINT_URL = "http://localhost:9000" +IS_AMAZON_COGNITO_AUTH_ENABLED = True + + def _to_query_params(**kwargs): return "&".join([f"{k}={v}" for k, v in kwargs.items()])