diff --git a/oauth2_provider/management/commands/createapplication.py b/oauth2_provider/management/commands/createapplication.py index e63d54280..95cb2d865 100644 --- a/oauth2_provider/management/commands/createapplication.py +++ b/oauth2_provider/management/commands/createapplication.py @@ -3,6 +3,7 @@ from oauth2_provider.models import get_application_model + Application = get_application_model() @@ -11,44 +12,44 @@ class Command(BaseCommand): def add_arguments(self, parser): parser.add_argument( - 'client_type', + "client_type", type=str, - help='The client type, can be confidential or public', + help="The client type, can be confidential or public", ) parser.add_argument( - 'authorization_grant_type', + "authorization_grant_type", type=str, - help='The type of authorization grant to be used', + help="The type of authorization grant to be used", ) parser.add_argument( - '--client-id', + "--client-id", type=str, - help='The ID of the new application', + help="The ID of the new application", ) parser.add_argument( - '--user', + "--user", type=str, - help='The user the application belongs to', + help="The user the application belongs to", ) parser.add_argument( - '--redirect-uris', + "--redirect-uris", type=str, - help='The redirect URIs, this must be a space separated string e.g "URI1 URI2', + help="The redirect URIs, this must be a space separated string e.g 'URI1 URI2'", ) parser.add_argument( - '--client-secret', + "--client-secret", type=str, - help='The secret for this application', + help="The secret for this application", ) parser.add_argument( - '--name', + "--name", type=str, - help='The name this application', + help="The name this application", ) parser.add_argument( - '--skip-authorization', - action='store_true', - help='The ID of the new application', + "--skip-authorization", + action="store_true", + help="The ID of the new application", ) def handle(self, *args, **options): @@ -61,8 +62,8 @@ def handle(self, *args, **options): # verbosity and others. Also do not pass any None to the Application # instance so default values will be generated for those fields if key in application_fields and value: - if key == 'user': - application_data.update({'user_id': value}) + if key == "user": + application_data.update({"user_id": value}) else: application_data.update({key: value}) @@ -71,15 +72,15 @@ def handle(self, *args, **options): try: new_application.full_clean() except ValidationError as exc: - errors = "\n ".join(['- ' + err_key + ': ' + str(err_value) for err_key, + errors = "\n ".join(["- " + err_key + ": " + str(err_value) for err_key, err_value in exc.message_dict.items()]) self.stdout.write( self.style.ERROR( - 'Please correct the following errors:\n %s' % errors + "Please correct the following errors:\n %s" % errors ) ) else: new_application.save() self.stdout.write( - self.style.SUCCESS('New application created successfully') + self.style.SUCCESS("New application created successfully") ) diff --git a/oauth2_provider/models.py b/oauth2_provider/models.py index c29faaa83..f87a51691 100644 --- a/oauth2_provider/models.py +++ b/oauth2_provider/models.py @@ -1,6 +1,6 @@ +import logging from datetime import timedelta from urllib.parse import parse_qsl, urlparse -import logging from django.apps import apps from django.conf import settings @@ -15,6 +15,7 @@ from .settings import oauth2_settings from .validators import RedirectURIValidator, WildcardSet + logger = logging.getLogger(__name__) @@ -263,7 +264,7 @@ class AbstractAccessToken(models.Model): Fields: - * :attr:`user` The Django user representing resources' owner + * :attr:`user` The Django user representing resources" owner * :attr:`source_refresh_token` If from a refresh, the consumed RefeshToken * :attr:`token` Access token * :attr:`application` Application instance @@ -323,7 +324,7 @@ def allow_scopes(self, scopes): def revoke(self): """ - Convenience method to uniform tokens' interface, for now + Convenience method to uniform tokens" interface, for now simply remove this token from the database in order to revoke it. """ self.delete() @@ -356,7 +357,7 @@ class AbstractRefreshToken(models.Model): Fields: - * :attr:`user` The Django user representing resources' owner + * :attr:`user` The Django user representing resources" owner * :attr:`token` Token value * :attr:`application` Application instance * :attr:`access_token` AccessToken instance this refresh token is @@ -459,14 +460,14 @@ def clear_expired(): access_token__expires__lt=refresh_expire_at, ) - logger.info('%s Revoked refresh tokens to be deleted', revoked.count()) - logger.info('%s Expired refresh tokens to be deleted', expired.count()) + logger.info("%s Revoked refresh tokens to be deleted", revoked.count()) + logger.info("%s Expired refresh tokens to be deleted", expired.count()) revoked.delete() expired.delete() else: - logger.info('refresh_expire_at is %s. No refresh tokens deleted.', - refresh_expire_at) + logger.info("refresh_expire_at is %s. No refresh tokens deleted.", + refresh_expire_at) access_tokens = access_token_model.objects.filter( refresh_token__isnull=True, @@ -474,8 +475,8 @@ def clear_expired(): ) grants = grant_model.objects.filter(expires__lt=now) - logger.info('%s Expired access tokens to be deleted', access_tokens.count()) - logger.info('%s Expired grant tokens to be deleted', grants.count()) + logger.info("%s Expired access tokens to be deleted", access_tokens.count()) + logger.info("%s Expired grant tokens to be deleted", grants.count()) access_tokens.delete() grants.delete() diff --git a/oauth2_provider/oauth2_validators.py b/oauth2_provider/oauth2_validators.py index 3595d12fc..9027a4841 100644 --- a/oauth2_provider/oauth2_validators.py +++ b/oauth2_provider/oauth2_validators.py @@ -480,7 +480,7 @@ def save_bearer_token(self, token, request, *args, **kwargs): # expires_in is passed to Server on initialization # custom server class can have logic to override this expires = timezone.now() + timedelta(seconds=token.get( - 'expires_in', oauth2_settings.ACCESS_TOKEN_EXPIRE_SECONDS, + "expires_in", oauth2_settings.ACCESS_TOKEN_EXPIRE_SECONDS, )) if request.grant_type == "client_credentials": diff --git a/oauth2_provider/settings.py b/oauth2_provider/settings.py index 2e513928c..858efdbe7 100644 --- a/oauth2_provider/settings.py +++ b/oauth2_provider/settings.py @@ -188,18 +188,19 @@ def server_kwargs(self): processing, callables have to be assigned directly. For the likes of signed_token_generator it means something like - {'token_generator': signed_token_generator(privkey, **kwargs)} + {"token_generator": signed_token_generator(privkey, **kwargs)} """ kwargs = { key: getattr(self, value) for key, value in [ - ('token_expires_in', 'ACCESS_TOKEN_EXPIRE_SECONDS'), - ('refresh_token_expires_in', 'REFRESH_TOKEN_EXPIRE_SECONDS'), - ('token_generator', 'ACCESS_TOKEN_GENERATOR'), - ('refresh_token_generator', 'REFRESH_TOKEN_GENERATOR'), + ("token_expires_in", "ACCESS_TOKEN_EXPIRE_SECONDS"), + ("refresh_token_expires_in", "REFRESH_TOKEN_EXPIRE_SECONDS"), + ("token_generator", "ACCESS_TOKEN_GENERATOR"), + ("refresh_token_generator", "REFRESH_TOKEN_GENERATOR"), ] } kwargs.update(self.EXTRA_SERVER_KWARGS) return kwargs + oauth2_settings = OAuth2ProviderSettings(USER_SETTINGS, DEFAULTS, IMPORT_STRINGS, MANDATORY) diff --git a/oauth2_provider/validators.py b/oauth2_provider/validators.py index a6f3a33b6..f3f82102c 100644 --- a/oauth2_provider/validators.py +++ b/oauth2_provider/validators.py @@ -3,7 +3,7 @@ from django.core.exceptions import ValidationError from django.core.validators import URLValidator -from django.utils.encoding import force_text +from django.utils.encoding import force_str class URIValidator(URLValidator): @@ -28,7 +28,7 @@ def __init__(self, allowed_schemes, allow_fragments=False): def __call__(self, value): super().__call__(value) - value = force_text(value) + value = force_str(value) scheme, netloc, path, query, fragment = urlsplit(value) if fragment and not self.allow_fragments: raise ValidationError("Redirect URIs must not contain fragments") diff --git a/oauth2_provider/views/base.py b/oauth2_provider/views/base.py index 02c32c6aa..8a3a59c25 100644 --- a/oauth2_provider/views/base.py +++ b/oauth2_provider/views/base.py @@ -4,13 +4,13 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.http import HttpResponse, JsonResponse +from django.shortcuts import render +from django.urls import reverse from django.utils import timezone from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt from django.views.decorators.debug import sensitive_post_parameters from django.views.generic import FormView, View -from django.shortcuts import render -from django.urls import reverse from ..exceptions import OAuthToolkitError from ..forms import AllowForm @@ -21,6 +21,7 @@ from ..signals import app_authorized from .mixins import OAuthLibMixin + log = logging.getLogger("oauth2_provider") @@ -61,7 +62,9 @@ def redirect(self, redirect_to, application): allowed_schemes = application.get_allowed_schemes() return OAuth2ResponseRedirect(redirect_to, allowed_schemes) -RFC3339 = '%Y-%m-%dT%H:%M:%SZ' + +RFC3339 = "%Y-%m-%dT%H:%M:%SZ" + class AuthorizationView(BaseAuthorizationView, FormView): """ @@ -208,23 +211,22 @@ def get(self, request, *args, **kwargs): return self.render_to_response(self.get_context_data(**kwargs)) - def redirect(self, redirect_to, application, - token = None): + def redirect(self, redirect_to, application, token=None): if not redirect_to.startswith("urn:ietf:wg:oauth:2.0:oob"): return super().redirect(redirect_to, application) parsed_redirect = urllib.parse.urlparse(redirect_to) - code = urllib.parse.parse_qs(parsed_redirect.query)['code'][0] + code = urllib.parse.parse_qs(parsed_redirect.query)["code"][0] - if redirect_to.startswith('urn:ietf:wg:oauth:2.0:oob:auto'): + if redirect_to.startswith("urn:ietf:wg:oauth:2.0:oob:auto"): response = { - 'access_token': code, - 'token_uri': redirect_to, - 'client_id': application.client_id, - 'client_secret': application.client_secret, - 'revoke_uri': reverse('oauth2_provider:revoke-token'), + "access_token": code, + "token_uri": redirect_to, + "client_id": application.client_id, + "client_secret": application.client_secret, + "revoke_uri": reverse("oauth2_provider:revoke-token"), } return JsonResponse(response) @@ -234,10 +236,11 @@ def redirect(self, redirect_to, application, request=self.request, template_name="oauth2_provider/authorized-oob.html", context={ - 'code': code, + "code": code, }, ) + @method_decorator(csrf_exempt, name="dispatch") class TokenView(OAuthLibMixin, View): """ diff --git a/tests/models.py b/tests/models.py index cbbc50ba9..7ca0c57c5 100644 --- a/tests/models.py +++ b/tests/models.py @@ -1,10 +1,10 @@ from django.db import models -from oauth2_provider.settings import oauth2_settings from oauth2_provider.models import ( AbstractAccessToken, AbstractApplication, AbstractGrant, AbstractRefreshToken ) +from oauth2_provider.settings import oauth2_settings class BaseTestApplication(AbstractApplication): diff --git a/tests/settings.py b/tests/settings.py index 1b7ba8db6..40eef5ebd 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -9,7 +9,7 @@ } } -AUTH_USER_MODEL = 'auth.User' +AUTH_USER_MODEL = "auth.User" OAUTH2_PROVIDER_APPLICATION_MODEL = "oauth2_provider.Application" OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL = "oauth2_provider.AccessToken" OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL = "oauth2_provider.RefreshToken" diff --git a/tests/test_application_views.py b/tests/test_application_views.py index 74162f087..6130876ce 100644 --- a/tests/test_application_views.py +++ b/tests/test_application_views.py @@ -8,6 +8,7 @@ from .models import SampleApplication + Application = get_application_model() UserModel = get_user_model() diff --git a/tests/test_authorization_code.py b/tests/test_authorization_code.py index 793cca2d9..9a95bc269 100644 --- a/tests/test_authorization_code.py +++ b/tests/test_authorization_code.py @@ -31,6 +31,7 @@ URI_OOB = "urn:ietf:wg:oauth:2.0:oob" URI_OOB_AUTO = "urn:ietf:wg:oauth:2.0:oob:auto" + # mocking a protected resource view class ResourceView(ProtectedResourceView): def get(self, request, *args, **kwargs): @@ -1467,7 +1468,7 @@ def test_oob_as_html(self): response = self.client.post(reverse("oauth2_provider:authorize"), data=authcode_data) self.assertEqual(response.status_code, 200) - self.assertRegex(response['Content-Type'], r'^text/html') + self.assertRegex(response["Content-Type"], r"^text/html") content = response.content.decode("utf-8") @@ -1475,12 +1476,9 @@ def test_oob_as_html(self): # to extract the token, risking summoning zalgo in the process." # -- https://github.com/jazzband/django-oauth-toolkit/issues/235 - matches = re.search(r'.*([^<>]*)', - content) - self.assertIsNotNone(matches, - msg="OOB response contains code inside tag") - self.assertEqual(len(matches.groups()), 1, - msg="OOB response contains multiple tags") + matches = re.search(r".*([^<>]*)", content) + self.assertIsNotNone(matches, msg="OOB response contains code inside tag") + self.assertEqual(len(matches.groups()), 1, msg="OOB response contains multiple tags") authorization_code = matches.groups()[0] token_request_data = { @@ -1516,12 +1514,12 @@ def test_oob_as_json(self): response = self.client.post(reverse("oauth2_provider:authorize"), data=authcode_data) self.assertEqual(response.status_code, 200) - self.assertRegex(response['Content-Type'], '^application/json') + self.assertRegex(response["Content-Type"], "^application/json") parsed_response = json.loads(response.content.decode("utf-8")) - self.assertIn('access_token', parsed_response) - authorization_code = parsed_response['access_token'] + self.assertIn("access_token", parsed_response) + authorization_code = parsed_response["access_token"] token_request_data = { "grant_type": "authorization_code", @@ -1539,6 +1537,7 @@ def test_oob_as_json(self): self.assertEqual(content["scope"], "read write") self.assertEqual(content["expires_in"], oauth2_settings.ACCESS_TOKEN_EXPIRE_SECONDS) + class TestAuthorizationCodeProtectedResource(BaseTest): def test_resource_access_allowed(self): self.client.login(username="test_user", password="123456") diff --git a/tests/test_commands.py b/tests/test_commands.py index 8f1ddc27f..274eccec5 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -7,6 +7,7 @@ from oauth2_provider.models import get_application_model + Application = get_application_model() @@ -16,35 +17,35 @@ def test_command_creates_application(self): output = StringIO() self.assertEqual(Application.objects.count(), 0) call_command( - 'createapplication', - 'confidential', - 'authorization-code', - '--redirect-uris=http://example.com http://example2.com', + "createapplication", + "confidential", + "authorization-code", + "--redirect-uris=http://example.com http://example2.com", stdout=output, ) self.assertEqual(Application.objects.count(), 1) - self.assertIn('New application created successfully', output.getvalue()) + self.assertIn("New application created successfully", output.getvalue()) def test_missing_required_args(self): self.assertEqual(Application.objects.count(), 0) with self.assertRaises(CommandError) as ctx: call_command( - 'createapplication', - '--redirect-uris=http://example.com http://example2.com', + "createapplication", + "--redirect-uris=http://example.com http://example2.com", ) - self.assertIn('client_type', ctx.exception.args[0]) - self.assertIn('authorization_grant_type', ctx.exception.args[0]) + self.assertIn("client_type", ctx.exception.args[0]) + self.assertIn("authorization_grant_type", ctx.exception.args[0]) self.assertEqual(Application.objects.count(), 0) def test_command_creates_application_with_skipped_auth(self): self.assertEqual(Application.objects.count(), 0) call_command( - 'createapplication', - 'confidential', - 'authorization-code', - '--redirect-uris=http://example.com http://example2.com', - '--skip-authorization', + "createapplication", + "confidential", + "authorization-code", + "--redirect-uris=http://example.com http://example2.com", + "--skip-authorization", ) app = Application.objects.get() @@ -52,10 +53,10 @@ def test_command_creates_application_with_skipped_auth(self): def test_application_created_normally_with_no_skipped_auth(self): call_command( - 'createapplication', - 'confidential', - 'authorization-code', - '--redirect-uris=http://example.com http://example2.com', + "createapplication", + "confidential", + "authorization-code", + "--redirect-uris=http://example.com http://example2.com", ) app = Application.objects.get() @@ -63,49 +64,49 @@ def test_application_created_normally_with_no_skipped_auth(self): def test_application_created_with_name(self): call_command( - 'createapplication', - 'confidential', - 'authorization-code', - '--redirect-uris=http://example.com http://example2.com', - '--name=TEST', + "createapplication", + "confidential", + "authorization-code", + "--redirect-uris=http://example.com http://example2.com", + "--name=TEST", ) app = Application.objects.get() - self.assertEqual(app.name, 'TEST') + self.assertEqual(app.name, "TEST") def test_application_created_with_client_secret(self): call_command( - 'createapplication', - 'confidential', - 'authorization-code', - '--redirect-uris=http://example.com http://example2.com', - '--client-secret=SECRET', + "createapplication", + "confidential", + "authorization-code", + "--redirect-uris=http://example.com http://example2.com", + "--client-secret=SECRET", ) app = Application.objects.get() - self.assertEqual(app.client_secret, 'SECRET') + self.assertEqual(app.client_secret, "SECRET") def test_application_created_with_client_id(self): call_command( - 'createapplication', - 'confidential', - 'authorization-code', - '--redirect-uris=http://example.com http://example2.com', - '--client-id=someId', + "createapplication", + "confidential", + "authorization-code", + "--redirect-uris=http://example.com http://example2.com", + "--client-id=someId", ) app = Application.objects.get() - self.assertEqual(app.client_id, 'someId') + self.assertEqual(app.client_id, "someId") def test_application_created_with_user(self): User = get_user_model() user = User.objects.create() call_command( - 'createapplication', - 'confidential', - 'authorization-code', - '--redirect-uris=http://example.com http://example2.com', - '--user=%s' % user.pk, + "createapplication", + "confidential", + "authorization-code", + "--redirect-uris=http://example.com http://example2.com", + "--user=%s" % user.pk, ) app = Application.objects.get() @@ -114,14 +115,14 @@ def test_application_created_with_user(self): def test_validation_failed_message(self): output = StringIO() call_command( - 'createapplication', - 'confidential', - 'authorization-code', - '--redirect-uris=http://example.com http://example2.com', - '--user=783', + "createapplication", + "confidential", + "authorization-code", + "--redirect-uris=http://example.com http://example2.com", + "--user=783", stdout=output, ) - self.assertIn('user', output.getvalue()) - self.assertIn('783', output.getvalue()) - self.assertIn('does not exist', output.getvalue()) + self.assertIn("user", output.getvalue()) + self.assertIn("783", output.getvalue()) + self.assertIn("does not exist", output.getvalue()) diff --git a/tests/test_rest_framework.py b/tests/test_rest_framework.py index 0251d98fe..21a6ccd71 100644 --- a/tests/test_rest_framework.py +++ b/tests/test_rest_framework.py @@ -98,10 +98,12 @@ class TokenHasScopeViewWrongAuth(BrokenOAuth2View): class MethodScopeAltViewWrongAuth(BrokenOAuth2View): permission_classes = [TokenMatchesOASRequirements] + class AuthenticationNone(OAuth2Authentication): def authenticate(self, request): return None + class AuthenticationNoneOAuth2View(MockView): authentication_classes = [AuthenticationNone] diff --git a/tests/test_token_revocation.py b/tests/test_token_revocation.py index d1ab591d2..fdbc07229 100644 --- a/tests/test_token_revocation.py +++ b/tests/test_token_revocation.py @@ -1,5 +1,4 @@ import datetime -from urllib.parse import urlencode from django.contrib.auth import get_user_model from django.test import RequestFactory, TestCase diff --git a/tox.ini b/tox.ini index 9c476eab8..7fce944af 100644 --- a/tox.ini +++ b/tox.ini @@ -46,12 +46,11 @@ deps = sphinx [testenv:py37-flake8] skip_install = True commands = - flake8 --exit-zero {toxinidir} + flake8 {toxinidir} deps = flake8 flake8-isort -# TODO: restore this: -# flake8-quotes + flake8-quotes [coverage:run] source = oauth2_provider @@ -59,7 +58,7 @@ omit = */migrations/* [flake8] max-line-length = 110 -exclude = docs/, oauth2_provider/migrations/, .tox/ +exclude = docs/, oauth2_provider/migrations/, tests/migrations/, .tox/ application-import-names = oauth2_provider inline-quotes = double