From 5d220e82229bd66272cbe3b2338ab929726fe2b1 Mon Sep 17 00:00:00 2001 From: adamscmRH Date: Wed, 23 May 2018 14:04:36 -0400 Subject: [PATCH 1/4] add scope validator to token endpoints --- awx/api/serializers.py | 416 +++++++++----------- awx/main/tests/functional/api/test_oauth.py | 15 +- 2 files changed, 201 insertions(+), 230 deletions(-) diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 69326d265c..77b7316334 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -985,19 +985,12 @@ class UserSerializer(BaseSerializer): return self._validate_ldap_managed_field(value, 'is_superuser') -class UserAuthorizedTokenSerializer(BaseSerializer): - +class BaseOAuth2TokenSerializer(BaseSerializer): + refresh_token = serializers.SerializerMethodField() token = serializers.SerializerMethodField() + ALLOWED_SCOPES = ['read', 'write'] - class Meta: - model = OAuth2AccessToken - fields = ( - '*', '-name', 'description', 'user', 'token', 'refresh_token', - 'expires', 'scope', 'application' - ) - read_only_fields = ('user', 'token', 'expires') - def get_token(self, obj): request = self.context.get('request', None) try: @@ -1006,7 +999,36 @@ class UserAuthorizedTokenSerializer(BaseSerializer): else: return TOKEN_CENSOR except ObjectDoesNotExist: - return '' + return '' + + def _is_valid_scope(self, value): + if not value or (not isinstance(value, six.string_types)): + return False + words = value.split() + for word in words: + if words.count(word) > 1: + return False # do not allow duplicates + if word not in self.ALLOWED_SCOPES: + return False + return True + + def validate_scope(self, value): + if not self._is_valid_scope(value): + raise serializers.ValidationError(_( + 'Must be a simple space-separated string with allowed scopes {}.' + ).format(self.ALLOWED_SCOPES)) + return value + + +class UserAuthorizedTokenSerializer(BaseOAuth2TokenSerializer): + + class Meta: + model = OAuth2AccessToken + fields = ( + '*', '-name', 'description', 'user', 'token', 'refresh_token', + 'expires', 'scope', 'application' + ) + read_only_fields = ('user', 'token', 'expires') def get_refresh_token(self, obj): request = self.context.get('request', None) @@ -1035,7 +1057,162 @@ class UserAuthorizedTokenSerializer(BaseSerializer): access_token=obj ) return obj + + +class OAuth2TokenSerializer(BaseOAuth2TokenSerializer): + + class Meta: + model = OAuth2AccessToken + fields = ( + '*', '-name', 'description', 'user', 'token', 'refresh_token', + 'application', 'expires', 'scope', + ) + read_only_fields = ('user', 'token', 'expires') + extra_kwargs = { + 'scope': {'allow_null': False, 'required': True}, + 'user': {'allow_null': False, 'required': True} + } + + def get_modified(self, obj): + if obj is None: + return None + return obj.updated + + def get_related(self, obj): + ret = super(OAuth2TokenSerializer, self).get_related(obj) + if obj.user: + ret['user'] = self.reverse('api:user_detail', kwargs={'pk': obj.user.pk}) + if obj.application: + ret['application'] = self.reverse( + 'api:o_auth2_application_detail', kwargs={'pk': obj.application.pk} + ) + ret['activity_stream'] = self.reverse( + 'api:o_auth2_token_activity_stream_list', kwargs={'pk': obj.pk} + ) + return ret + + def get_refresh_token(self, obj): + request = self.context.get('request', None) + try: + if request.method == 'POST': + return getattr(obj.refresh_token, 'token', '') + else: + return TOKEN_CENSOR + except ObjectDoesNotExist: + return '' + + def create(self, validated_data): + current_user = self.context['request'].user + validated_data['user'] = current_user + validated_data['token'] = generate_token() + validated_data['expires'] = now() + timedelta( + seconds=settings.OAUTH2_PROVIDER['ACCESS_TOKEN_EXPIRE_SECONDS'] + ) + obj = super(OAuth2TokenSerializer, self).create(validated_data) + if obj.application and obj.application.user: + obj.user = obj.application.user + obj.save() + if obj.application is not None: + RefreshToken.objects.create( + user=current_user, + token=generate_token(), + application=obj.application, + access_token=obj + ) + return obj + +class OAuth2TokenDetailSerializer(OAuth2TokenSerializer): + + class Meta: + read_only_fields = ('*', 'user', 'application') + + +class OAuth2AuthorizedTokenSerializer(BaseOAuth2TokenSerializer): + + class Meta: + model = OAuth2AccessToken + fields = ( + '*', '-name', 'description', '-user', 'token', 'refresh_token', + 'expires', 'scope', 'application', + ) + read_only_fields = ('user', 'token', 'expires') + extra_kwargs = { + 'scope': {'allow_null': False, 'required': True} + } + + def get_refresh_token(self, obj): + request = self.context.get('request', None) + try: + if request.method == 'POST': + return getattr(obj.refresh_token, 'token', '') + else: + return TOKEN_CENSOR + except ObjectDoesNotExist: + return '' + + def create(self, validated_data): + current_user = self.context['request'].user + validated_data['user'] = current_user + validated_data['token'] = generate_token() + validated_data['expires'] = now() + timedelta( + seconds=settings.OAUTH2_PROVIDER['ACCESS_TOKEN_EXPIRE_SECONDS'] + ) + obj = super(OAuth2AuthorizedTokenSerializer, self).create(validated_data) + if obj.application and obj.application.user: + obj.user = obj.application.user + obj.save() + if obj.application is not None: + RefreshToken.objects.create( + user=current_user, + token=generate_token(), + application=obj.application, + access_token=obj + ) + return obj + + +class OAuth2PersonalTokenSerializer(BaseOAuth2TokenSerializer): + + class Meta: + model = OAuth2AccessToken + fields = ( + '*', '-name', 'description', 'user', 'token', 'refresh_token', + 'application', 'expires', 'scope', + ) + read_only_fields = ('user', 'token', 'expires', 'application') + extra_kwargs = { + 'scope': {'allow_null': False, 'required': True} + } + + def get_modified(self, obj): + if obj is None: + return None + return obj.updated + + def get_related(self, obj): + ret = super(OAuth2PersonalTokenSerializer, self).get_related(obj) + if obj.user: + ret['user'] = self.reverse('api:user_detail', kwargs={'pk': obj.user.pk}) + ret['activity_stream'] = self.reverse( + 'api:o_auth2_token_activity_stream_list', kwargs={'pk': obj.pk} + ) + return ret + + def get_refresh_token(self, obj): + return None + + def create(self, validated_data): + validated_data['user'] = self.context['request'].user + validated_data['token'] = generate_token() + validated_data['expires'] = now() + timedelta( + seconds=settings.OAUTH2_PROVIDER['ACCESS_TOKEN_EXPIRE_SECONDS'] + ) + validated_data['application'] = None + obj = super(OAuth2PersonalTokenSerializer, self).create(validated_data) + obj.save() + return obj + class OAuth2ApplicationSerializer(BaseSerializer): @@ -1096,223 +1273,6 @@ class OAuth2ApplicationSerializer(BaseSerializer): return ret -class OAuth2TokenSerializer(BaseSerializer): - - refresh_token = serializers.SerializerMethodField() - token = serializers.SerializerMethodField() - ALLOWED_SCOPES = ['read', 'write'] - - class Meta: - model = OAuth2AccessToken - fields = ( - '*', '-name', 'description', 'user', 'token', 'refresh_token', - 'application', 'expires', 'scope', - ) - read_only_fields = ('user', 'token', 'expires') - extra_kwargs = { - 'scope': {'allow_null': False, 'required': True}, - 'user': {'allow_null': False, 'required': True} - } - - def get_modified(self, obj): - if obj is None: - return None - return obj.updated - - def get_related(self, obj): - ret = super(OAuth2TokenSerializer, self).get_related(obj) - if obj.user: - ret['user'] = self.reverse('api:user_detail', kwargs={'pk': obj.user.pk}) - if obj.application: - ret['application'] = self.reverse( - 'api:o_auth2_application_detail', kwargs={'pk': obj.application.pk} - ) - ret['activity_stream'] = self.reverse( - 'api:o_auth2_token_activity_stream_list', kwargs={'pk': obj.pk} - ) - return ret - - def get_token(self, obj): - request = self.context.get('request', None) - try: - if request.method == 'POST': - return obj.token - else: - return TOKEN_CENSOR - except ObjectDoesNotExist: - return '' - - def get_refresh_token(self, obj): - request = self.context.get('request', None) - try: - if request.method == 'POST': - return getattr(obj.refresh_token, 'token', '') - else: - return TOKEN_CENSOR - except ObjectDoesNotExist: - return '' - - def _is_valid_scope(self, value): - if not value or (not isinstance(value, six.string_types)): - return False - words = value.split() - for word in words: - if words.count(word) > 1: - return False # do not allow duplicates - if word not in self.ALLOWED_SCOPES: - return False - return True - - def validate_scope(self, value): - if not self._is_valid_scope(value): - raise serializers.ValidationError(_( - 'Must be a simple space-separated string with allowed scopes {}.' - ).format(self.ALLOWED_SCOPES)) - return value - - def create(self, validated_data): - current_user = self.context['request'].user - validated_data['user'] = current_user - validated_data['token'] = generate_token() - validated_data['expires'] = now() + timedelta( - seconds=settings.OAUTH2_PROVIDER['ACCESS_TOKEN_EXPIRE_SECONDS'] - ) - obj = super(OAuth2TokenSerializer, self).create(validated_data) - if obj.application and obj.application.user: - obj.user = obj.application.user - obj.save() - if obj.application is not None: - RefreshToken.objects.create( - user=current_user, - token=generate_token(), - application=obj.application, - access_token=obj - ) - return obj - - -class OAuth2TokenDetailSerializer(OAuth2TokenSerializer): - - class Meta: - read_only_fields = ('*', 'user', 'application') - - -class OAuth2AuthorizedTokenSerializer(BaseSerializer): - - refresh_token = serializers.SerializerMethodField() - token = serializers.SerializerMethodField() - - class Meta: - model = OAuth2AccessToken - fields = ( - '*', '-name', 'description', '-user', 'token', 'refresh_token', - 'expires', 'scope', 'application', - ) - read_only_fields = ('user', 'token', 'expires') - extra_kwargs = { - 'scope': {'allow_null': False, 'required': True} - } - - def get_token(self, obj): - request = self.context.get('request', None) - try: - if request.method == 'POST': - return obj.token - else: - return TOKEN_CENSOR - except ObjectDoesNotExist: - return '' - - def get_refresh_token(self, obj): - request = self.context.get('request', None) - try: - if request.method == 'POST': - return getattr(obj.refresh_token, 'token', '') - else: - return TOKEN_CENSOR - except ObjectDoesNotExist: - return '' - - def create(self, validated_data): - current_user = self.context['request'].user - validated_data['user'] = current_user - validated_data['token'] = generate_token() - validated_data['expires'] = now() + timedelta( - seconds=settings.OAUTH2_PROVIDER['ACCESS_TOKEN_EXPIRE_SECONDS'] - ) - obj = super(OAuth2AuthorizedTokenSerializer, self).create(validated_data) - if obj.application and obj.application.user: - obj.user = obj.application.user - obj.save() - if obj.application is not None: - RefreshToken.objects.create( - user=current_user, - token=generate_token(), - application=obj.application, - access_token=obj - ) - return obj - - -class OAuth2PersonalTokenSerializer(BaseSerializer): - - refresh_token = serializers.SerializerMethodField() - token = serializers.SerializerMethodField() - - class Meta: - model = OAuth2AccessToken - fields = ( - '*', '-name', 'description', 'user', 'token', 'refresh_token', - 'application', 'expires', 'scope', - ) - read_only_fields = ('user', 'token', 'expires', 'application') - extra_kwargs = { - 'scope': {'allow_null': False, 'required': True} - } - - def get_modified(self, obj): - if obj is None: - return None - return obj.updated - - def get_related(self, obj): - ret = super(OAuth2PersonalTokenSerializer, self).get_related(obj) - if obj.user: - ret['user'] = self.reverse('api:user_detail', kwargs={'pk': obj.user.pk}) - if obj.application: - ret['application'] = self.reverse( - 'api:o_auth2_application_detail', kwargs={'pk': obj.application.pk} - ) - ret['activity_stream'] = self.reverse( - 'api:o_auth2_token_activity_stream_list', kwargs={'pk': obj.pk} - ) - return ret - - def get_token(self, obj): - request = self.context.get('request', None) - try: - if request.method == 'POST': - return obj.token - else: - return TOKEN_CENSOR - except ObjectDoesNotExist: - return '' - - def get_refresh_token(self, obj): - return None - - def create(self, validated_data): - validated_data['user'] = self.context['request'].user - validated_data['token'] = generate_token() - validated_data['expires'] = now() + timedelta( - seconds=settings.OAUTH2_PROVIDER['ACCESS_TOKEN_EXPIRE_SECONDS'] - ) - validated_data['application'] = None - obj = super(OAuth2PersonalTokenSerializer, self).create(validated_data) - obj.save() - return obj - - class OrganizationSerializer(BaseSerializer): show_capabilities = ['edit', 'delete'] diff --git a/awx/main/tests/functional/api/test_oauth.py b/awx/main/tests/functional/api/test_oauth.py index 7e745213c8..7e8b63eb08 100644 --- a/awx/main/tests/functional/api/test_oauth.py +++ b/awx/main/tests/functional/api/test_oauth.py @@ -29,7 +29,7 @@ def test_personal_access_token_creation(oauth_application, post, alice): @pytest.mark.django_db -def test_oauth_application_create(admin, organization, post): +def test_oauth2_application_create(admin, organization, post): response = post( reverse('api:o_auth2_application_list'), { 'name': 'test app', @@ -47,7 +47,18 @@ def test_oauth_application_create(admin, organization, post): assert created_app.client_type == 'confidential' assert created_app.authorization_grant_type == 'password' assert created_app.organization == organization - + + +@pytest.mark.django_db +def test_oauth2_validator(admin, oauth_application, post): + post( + reverse('api:o_auth2_application_list'), { + 'name': 'Write App Token', + 'application': oauth_application.pk, + 'scope': 'Write', + }, admin, expect=400 + ) + @pytest.mark.django_db def test_oauth_application_update(oauth_application, organization, patch, admin, alice): From be9598af539cfc1b368eebfc1d35b4580fe2e165 Mon Sep 17 00:00:00 2001 From: adamscmRH Date: Thu, 24 May 2018 12:25:27 -0400 Subject: [PATCH 2/4] fix refresh token & refactor --- awx/api/serializers.py | 68 +++++++++++++----------------------------- 1 file changed, 21 insertions(+), 47 deletions(-) diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 77b7316334..c518a4514c 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -991,6 +991,18 @@ class BaseOAuth2TokenSerializer(BaseSerializer): token = serializers.SerializerMethodField() ALLOWED_SCOPES = ['read', 'write'] + class Meta: + model = OAuth2AccessToken + fields = ( + '*', '-name', 'description', 'user', 'token', 'refresh_token', + 'application', 'expires', 'scope', + ) + read_only_fields = ('user', 'token', 'expires', 'refresh_token') + extra_kwargs = { + 'scope': {'allow_null': False, 'required': True}, + 'user': {'allow_null': False, 'required': True} + } + def get_token(self, obj): request = self.context.get('request', None) try: @@ -1000,6 +1012,11 @@ class BaseOAuth2TokenSerializer(BaseSerializer): return TOKEN_CENSOR except ObjectDoesNotExist: return '' + + def get_modified(self, obj): + if obj is None: + return None + return obj.updated def _is_valid_scope(self, value): if not value or (not isinstance(value, six.string_types)): @@ -1020,15 +1037,7 @@ class BaseOAuth2TokenSerializer(BaseSerializer): return value -class UserAuthorizedTokenSerializer(BaseOAuth2TokenSerializer): - - class Meta: - model = OAuth2AccessToken - fields = ( - '*', '-name', 'description', 'user', 'token', 'refresh_token', - 'expires', 'scope', 'application' - ) - read_only_fields = ('user', 'token', 'expires') +class UserAuthorizedTokenSerializer(BaseOAuth2TokenSerializer): def get_refresh_token(self, obj): request = self.context.get('request', None) @@ -1061,18 +1070,6 @@ class UserAuthorizedTokenSerializer(BaseOAuth2TokenSerializer): class OAuth2TokenSerializer(BaseOAuth2TokenSerializer): - class Meta: - model = OAuth2AccessToken - fields = ( - '*', '-name', 'description', 'user', 'token', 'refresh_token', - 'application', 'expires', 'scope', - ) - read_only_fields = ('user', 'token', 'expires') - extra_kwargs = { - 'scope': {'allow_null': False, 'required': True}, - 'user': {'allow_null': False, 'required': True} - } - def get_modified(self, obj): if obj is None: return None @@ -1096,10 +1093,12 @@ class OAuth2TokenSerializer(BaseOAuth2TokenSerializer): try: if request.method == 'POST': return getattr(obj.refresh_token, 'token', '') + elif not obj.refresh_token: + return None else: return TOKEN_CENSOR except ObjectDoesNotExist: - return '' + return None def create(self, validated_data): current_user = self.context['request'].user @@ -1129,17 +1128,6 @@ class OAuth2TokenDetailSerializer(OAuth2TokenSerializer): class OAuth2AuthorizedTokenSerializer(BaseOAuth2TokenSerializer): - - class Meta: - model = OAuth2AccessToken - fields = ( - '*', '-name', 'description', '-user', 'token', 'refresh_token', - 'expires', 'scope', 'application', - ) - read_only_fields = ('user', 'token', 'expires') - extra_kwargs = { - 'scope': {'allow_null': False, 'required': True} - } def get_refresh_token(self, obj): request = self.context.get('request', None) @@ -1175,20 +1163,7 @@ class OAuth2AuthorizedTokenSerializer(BaseOAuth2TokenSerializer): class OAuth2PersonalTokenSerializer(BaseOAuth2TokenSerializer): class Meta: - model = OAuth2AccessToken - fields = ( - '*', '-name', 'description', 'user', 'token', 'refresh_token', - 'application', 'expires', 'scope', - ) read_only_fields = ('user', 'token', 'expires', 'application') - extra_kwargs = { - 'scope': {'allow_null': False, 'required': True} - } - - def get_modified(self, obj): - if obj is None: - return None - return obj.updated def get_related(self, obj): ret = super(OAuth2PersonalTokenSerializer, self).get_related(obj) @@ -1238,7 +1213,6 @@ class OAuth2ApplicationSerializer(BaseSerializer): ret.pop('client_secret', None) return ret - def get_modified(self, obj): if obj is None: return None From 3d5605f4b520714144e0ac2ffb91d4be27efafe0 Mon Sep 17 00:00:00 2001 From: adamscmRH Date: Thu, 24 May 2018 14:33:27 -0400 Subject: [PATCH 3/4] refactor & purge cruft --- awx/api/serializers.py | 136 +++++-------------- awx/api/urls/user.py | 2 +- awx/api/urls/user_oauth.py | 2 +- awx/api/views.py | 19 +-- awx/main/tests/functional/test_rbac_oauth.py | 4 +- 5 files changed, 39 insertions(+), 124 deletions(-) diff --git a/awx/api/serializers.py b/awx/api/serializers.py index c518a4514c..9348a6c37e 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -948,7 +948,7 @@ class UserSerializer(BaseSerializer): access_list = self.reverse('api:user_access_list', kwargs={'pk': obj.pk}), tokens = self.reverse('api:o_auth2_token_list', kwargs={'pk': obj.pk}), authorized_tokens = self.reverse('api:user_authorized_token_list', kwargs={'pk': obj.pk}), - personal_tokens = self.reverse('api:o_auth2_personal_token_list', kwargs={'pk': obj.pk}), + personal_tokens = self.reverse('api:user_personal_token_list', kwargs={'pk': obj.pk}), )) return res @@ -1013,10 +1013,30 @@ class BaseOAuth2TokenSerializer(BaseSerializer): except ObjectDoesNotExist: return '' - def get_modified(self, obj): - if obj is None: + def get_refresh_token(self, obj): + request = self.context.get('request', None) + try: + if not obj.refresh_token: + return None + elif request.method == 'POST': + return getattr(obj.refresh_token, 'token', '') + else: + return TOKEN_CENSOR + except ObjectDoesNotExist: return None - return obj.updated + + def get_related(self, obj): + ret = super(BaseOAuth2TokenSerializer, self).get_related(obj) + if obj.user: + ret['user'] = self.reverse('api:user_detail', kwargs={'pk': obj.user.pk}) + if obj.application: + ret['application'] = self.reverse( + 'api:o_auth2_application_detail', kwargs={'pk': obj.application.pk} + ) + ret['activity_stream'] = self.reverse( + 'api:o_auth2_token_activity_stream_list', kwargs={'pk': obj.pk} + ) + return ret def _is_valid_scope(self, value): if not value or (not isinstance(value, six.string_types)): @@ -1038,16 +1058,13 @@ class BaseOAuth2TokenSerializer(BaseSerializer): class UserAuthorizedTokenSerializer(BaseOAuth2TokenSerializer): - - def get_refresh_token(self, obj): - request = self.context.get('request', None) - try: - if request.method == 'POST': - return getattr(obj.refresh_token, 'token', '') - else: - return TOKEN_CENSOR - except ObjectDoesNotExist: - return '' + + class Meta: + extra_kwargs = { + 'scope': {'allow_null': False, 'required': True}, + 'user': {'allow_null': False, 'required': True}, + 'application': {'allow_null': False, 'required': True} + } def create(self, validated_data): current_user = self.context['request'].user @@ -1070,36 +1087,6 @@ class UserAuthorizedTokenSerializer(BaseOAuth2TokenSerializer): class OAuth2TokenSerializer(BaseOAuth2TokenSerializer): - def get_modified(self, obj): - if obj is None: - return None - return obj.updated - - def get_related(self, obj): - ret = super(OAuth2TokenSerializer, self).get_related(obj) - if obj.user: - ret['user'] = self.reverse('api:user_detail', kwargs={'pk': obj.user.pk}) - if obj.application: - ret['application'] = self.reverse( - 'api:o_auth2_application_detail', kwargs={'pk': obj.application.pk} - ) - ret['activity_stream'] = self.reverse( - 'api:o_auth2_token_activity_stream_list', kwargs={'pk': obj.pk} - ) - return ret - - def get_refresh_token(self, obj): - request = self.context.get('request', None) - try: - if request.method == 'POST': - return getattr(obj.refresh_token, 'token', '') - elif not obj.refresh_token: - return None - else: - return TOKEN_CENSOR - except ObjectDoesNotExist: - return None - def create(self, validated_data): current_user = self.context['request'].user validated_data['user'] = current_user @@ -1127,56 +1114,11 @@ class OAuth2TokenDetailSerializer(OAuth2TokenSerializer): read_only_fields = ('*', 'user', 'application') -class OAuth2AuthorizedTokenSerializer(BaseOAuth2TokenSerializer): - - def get_refresh_token(self, obj): - request = self.context.get('request', None) - try: - if request.method == 'POST': - return getattr(obj.refresh_token, 'token', '') - else: - return TOKEN_CENSOR - except ObjectDoesNotExist: - return '' - - def create(self, validated_data): - current_user = self.context['request'].user - validated_data['user'] = current_user - validated_data['token'] = generate_token() - validated_data['expires'] = now() + timedelta( - seconds=settings.OAUTH2_PROVIDER['ACCESS_TOKEN_EXPIRE_SECONDS'] - ) - obj = super(OAuth2AuthorizedTokenSerializer, self).create(validated_data) - if obj.application and obj.application.user: - obj.user = obj.application.user - obj.save() - if obj.application is not None: - RefreshToken.objects.create( - user=current_user, - token=generate_token(), - application=obj.application, - access_token=obj - ) - return obj - - -class OAuth2PersonalTokenSerializer(BaseOAuth2TokenSerializer): +class UserPersonalTokenSerializer(BaseOAuth2TokenSerializer): class Meta: read_only_fields = ('user', 'token', 'expires', 'application') - def get_related(self, obj): - ret = super(OAuth2PersonalTokenSerializer, self).get_related(obj) - if obj.user: - ret['user'] = self.reverse('api:user_detail', kwargs={'pk': obj.user.pk}) - ret['activity_stream'] = self.reverse( - 'api:o_auth2_token_activity_stream_list', kwargs={'pk': obj.pk} - ) - return ret - - def get_refresh_token(self, obj): - return None - def create(self, validated_data): validated_data['user'] = self.context['request'].user validated_data['token'] = generate_token() @@ -1184,7 +1126,7 @@ class OAuth2PersonalTokenSerializer(BaseOAuth2TokenSerializer): seconds=settings.OAUTH2_PROVIDER['ACCESS_TOKEN_EXPIRE_SECONDS'] ) validated_data['application'] = None - obj = super(OAuth2PersonalTokenSerializer, self).create(validated_data) + obj = super(UserPersonalTokenSerializer, self).create(validated_data) obj.save() return obj @@ -1218,18 +1160,6 @@ class OAuth2ApplicationSerializer(BaseSerializer): return None return obj.updated - def get_related(self, obj): - ret = super(OAuth2ApplicationSerializer, self).get_related(obj) - if obj.user: - ret['user'] = self.reverse('api:user_detail', kwargs={'pk': obj.user.pk}) - ret['tokens'] = self.reverse( - 'api:o_auth2_application_token_list', kwargs={'pk': obj.pk} - ) - ret['activity_stream'] = self.reverse( - 'api:o_auth2_application_activity_stream_list', kwargs={'pk': obj.pk} - ) - return ret - def _summary_field_tokens(self, obj): token_list = [{'id': x.pk, 'token': TOKEN_CENSOR, 'scope': x.scope} for x in obj.oauth2accesstoken_set.all()[:10]] if has_model_field_prefetched(obj, 'oauth2accesstoken_set'): diff --git a/awx/api/urls/user.py b/awx/api/urls/user.py index 9ecebbb044..c3c896af24 100644 --- a/awx/api/urls/user.py +++ b/awx/api/urls/user.py @@ -34,7 +34,7 @@ urls = [ url(r'^(?P[0-9]+)/applications/$', OAuth2ApplicationList.as_view(), name='o_auth2_application_list'), url(r'^(?P[0-9]+)/tokens/$', OAuth2UserTokenList.as_view(), name='o_auth2_token_list'), url(r'^(?P[0-9]+)/authorized_tokens/$', UserAuthorizedTokenList.as_view(), name='user_authorized_token_list'), - url(r'^(?P[0-9]+)/personal_tokens/$', OAuth2PersonalTokenList.as_view(), name='o_auth2_personal_token_list'), + url(r'^(?P[0-9]+)/personal_tokens/$', OAuth2PersonalTokenList.as_view(), name='user_personal_token_list'), ] diff --git a/awx/api/urls/user_oauth.py b/awx/api/urls/user_oauth.py index bec5c4332b..3b290dbf01 100644 --- a/awx/api/urls/user_oauth.py +++ b/awx/api/urls/user_oauth.py @@ -43,7 +43,7 @@ urls = [ OAuth2TokenActivityStreamList.as_view(), name='o_auth2_token_activity_stream_list' ), - url(r'^personal_tokens/$', OAuth2PersonalTokenList.as_view(), name='o_auth2_personal_token_list'), + url(r'^personal_tokens/$', OAuth2PersonalTokenList.as_view(), name='user_personal_token_list'), ] __all__ = ['urls'] diff --git a/awx/api/views.py b/awx/api/views.py index 5f1d8b22af..bd789ef9b7 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -1610,21 +1610,6 @@ class OAuth2UserTokenList(SubListCreateAPIView): relationship = 'main_oauth2accesstoken' parent_key = 'user' swagger_topic = 'Authentication' - - -class OAuth2AuthorizedTokenList(SubListCreateAPIView): - - view_name = _("OAuth2 Authorized Access Tokens") - - model = OAuth2AccessToken - serializer_class = OAuth2AuthorizedTokenSerializer - parent_model = OAuth2Application - relationship = 'oauth2accesstoken_set' - parent_key = 'application' - swagger_topic = 'Authentication' - - def get_queryset(self): - return get_access_token_model().objects.filter(application__isnull=False, user=self.request.user) class UserAuthorizedTokenList(SubListCreateAPIView): @@ -1632,7 +1617,7 @@ class UserAuthorizedTokenList(SubListCreateAPIView): view_name = _("OAuth2 User Authorized Access Tokens") model = OAuth2AccessToken - serializer_class = OAuth2AuthorizedTokenSerializer + serializer_class = UserAuthorizedTokenSerializer parent_model = User relationship = 'oauth2accesstoken_set' parent_key = 'user' @@ -1659,7 +1644,7 @@ class OAuth2PersonalTokenList(SubListCreateAPIView): view_name = _("OAuth2 Personal Access Tokens") model = OAuth2AccessToken - serializer_class = OAuth2PersonalTokenSerializer + serializer_class = UserPersonalTokenSerializer parent_model = User relationship = 'main_oauth2accesstoken' parent_key = 'user' diff --git a/awx/main/tests/functional/test_rbac_oauth.py b/awx/main/tests/functional/test_rbac_oauth.py index 757c55e12b..f076db3689 100644 --- a/awx/main/tests/functional/test_rbac_oauth.py +++ b/awx/main/tests/functional/test_rbac_oauth.py @@ -200,7 +200,7 @@ class TestOAuth2Token: user_list = [admin, org_admin, org_member, alice] can_access_list = [True, False, True, False] response = post( - reverse('api:o_auth2_personal_token_list', kwargs={'pk': org_member.pk}), + reverse('api:user_personal_token_list', kwargs={'pk': org_member.pk}), {'scope': 'read'}, org_member, expect=201 ) token = AccessToken.objects.get(token=response.data['token']) @@ -220,7 +220,7 @@ class TestOAuth2Token: for user, can_access in zip(user_list, can_access_list): response = post( - reverse('api:o_auth2_personal_token_list', kwargs={'pk': user.pk}), + reverse('api:user_personal_token_list', kwargs={'pk': user.pk}), {'scope': 'read', 'application':None}, user, expect=201 ) token = AccessToken.objects.get(token=response.data['token']) From bb6a4f696462b5f50e78ea8b0df20496778f6a91 Mon Sep 17 00:00:00 2001 From: adamscmRH Date: Thu, 24 May 2018 15:28:17 -0400 Subject: [PATCH 4/4] fix oauth urls & rename for clarity --- awx/api/urls/{user_oauth.py => oauth2.py} | 4 +--- awx/api/urls/{oauth.py => oauth2_root.py} | 0 awx/api/urls/urls.py | 8 ++++---- awx/api/urls/user.py | 4 ++-- awx/api/views.py | 2 +- 5 files changed, 8 insertions(+), 10 deletions(-) rename awx/api/urls/{user_oauth.py => oauth2.py} (90%) rename awx/api/urls/{oauth.py => oauth2_root.py} (100%) diff --git a/awx/api/urls/user_oauth.py b/awx/api/urls/oauth2.py similarity index 90% rename from awx/api/urls/user_oauth.py rename to awx/api/urls/oauth2.py index 3b290dbf01..6e9eea3d9f 100644 --- a/awx/api/urls/user_oauth.py +++ b/awx/api/urls/oauth2.py @@ -11,7 +11,6 @@ from awx.api.views import ( OAuth2TokenList, OAuth2TokenDetail, OAuth2TokenActivityStreamList, - OAuth2PersonalTokenList ) @@ -42,8 +41,7 @@ urls = [ r'^tokens/(?P[0-9]+)/activity_stream/$', OAuth2TokenActivityStreamList.as_view(), name='o_auth2_token_activity_stream_list' - ), - url(r'^personal_tokens/$', OAuth2PersonalTokenList.as_view(), name='user_personal_token_list'), + ), ] __all__ = ['urls'] diff --git a/awx/api/urls/oauth.py b/awx/api/urls/oauth2_root.py similarity index 100% rename from awx/api/urls/oauth.py rename to awx/api/urls/oauth2_root.py diff --git a/awx/api/urls/urls.py b/awx/api/urls/urls.py index e282a73e5f..52e9ef1cf0 100644 --- a/awx/api/urls/urls.py +++ b/awx/api/urls/urls.py @@ -67,8 +67,8 @@ from .schedule import urls as schedule_urls from .activity_stream import urls as activity_stream_urls from .instance import urls as instance_urls from .instance_group import urls as instance_group_urls -from .user_oauth import urls as user_oauth_urls -from .oauth import urls as oauth_urls +from .oauth2 import urls as oauth2_urls +from .oauth2_root import urls as oauth2_root_urls v1_urls = [ @@ -130,7 +130,7 @@ v2_urls = [ url(r'^applications/(?P[0-9]+)/$', OAuth2ApplicationDetail.as_view(), name='o_auth2_application_detail'), url(r'^applications/(?P[0-9]+)/tokens/$', ApplicationOAuth2TokenList.as_view(), name='application_o_auth2_token_list'), url(r'^tokens/$', OAuth2TokenList.as_view(), name='o_auth2_token_list'), - url(r'^', include(user_oauth_urls)), + url(r'^', include(oauth2_urls)), ] app_name = 'api' @@ -145,7 +145,7 @@ urlpatterns = [ url(r'^logout/$', LoggedLogoutView.as_view( next_page='/api/', redirect_field_name='next' ), name='logout'), - url(r'^o/', include(oauth_urls)), + url(r'^o/', include(oauth2_root_urls)), ] if settings.SETTINGS_MODULE == 'awx.settings.development': from awx.api.swagger import SwaggerSchemaView diff --git a/awx/api/urls/user.py b/awx/api/urls/user.py index c3c896af24..ca8d531f46 100644 --- a/awx/api/urls/user.py +++ b/awx/api/urls/user.py @@ -16,7 +16,7 @@ from awx.api.views import ( UserAccessList, OAuth2ApplicationList, OAuth2UserTokenList, - OAuth2PersonalTokenList, + UserPersonalTokenList, UserAuthorizedTokenList, ) @@ -34,7 +34,7 @@ urls = [ url(r'^(?P[0-9]+)/applications/$', OAuth2ApplicationList.as_view(), name='o_auth2_application_list'), url(r'^(?P[0-9]+)/tokens/$', OAuth2UserTokenList.as_view(), name='o_auth2_token_list'), url(r'^(?P[0-9]+)/authorized_tokens/$', UserAuthorizedTokenList.as_view(), name='user_authorized_token_list'), - url(r'^(?P[0-9]+)/personal_tokens/$', OAuth2PersonalTokenList.as_view(), name='user_personal_token_list'), + url(r'^(?P[0-9]+)/personal_tokens/$', UserPersonalTokenList.as_view(), name='user_personal_token_list'), ] diff --git a/awx/api/views.py b/awx/api/views.py index bd789ef9b7..d319cb88df 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -1639,7 +1639,7 @@ class OrganizationApplicationList(SubListCreateAPIView): swagger_topic = 'Authentication' -class OAuth2PersonalTokenList(SubListCreateAPIView): +class UserPersonalTokenList(SubListCreateAPIView): view_name = _("OAuth2 Personal Access Tokens")