Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 23 additions & 22 deletions oauth2_provider/management/commands/createapplication.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from oauth2_provider.models import get_application_model


Application = get_application_model()


Expand All @@ -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):
Expand All @@ -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})

Expand All @@ -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")
)
21 changes: 11 additions & 10 deletions oauth2_provider/models.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -15,6 +15,7 @@
from .settings import oauth2_settings
from .validators import RedirectURIValidator, WildcardSet


logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -459,23 +460,23 @@ 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,
expires__lt=now
)
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()
2 changes: 1 addition & 1 deletion oauth2_provider/oauth2_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -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":
Expand Down
11 changes: 6 additions & 5 deletions oauth2_provider/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
4 changes: 2 additions & 2 deletions oauth2_provider/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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")
Expand Down
29 changes: 16 additions & 13 deletions oauth2_provider/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -21,6 +21,7 @@
from ..signals import app_authorized
from .mixins import OAuthLibMixin


log = logging.getLogger("oauth2_provider")


Expand Down Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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)
Expand All @@ -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):
"""
Expand Down
2 changes: 1 addition & 1 deletion tests/models.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down
2 changes: 1 addition & 1 deletion tests/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions tests/test_application_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from .models import SampleApplication


Application = get_application_model()
UserModel = get_user_model()

Expand Down
19 changes: 9 additions & 10 deletions tests/test_authorization_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -1467,20 +1468,17 @@ 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")

# "A lot of applications, for legacy reasons, use this and regex
# to extract the token, risking summoning zalgo in the process."
# -- https://github.com/jazzband/django-oauth-toolkit/issues/235

matches = re.search(r'.*<code>([^<>]*)</code>',
content)
self.assertIsNotNone(matches,
msg="OOB response contains code inside <code> tag")
self.assertEqual(len(matches.groups()), 1,
msg="OOB response contains multiple <code> tags")
matches = re.search(r".*<code>([^<>]*)</code>", content)
self.assertIsNotNone(matches, msg="OOB response contains code inside <code> tag")
self.assertEqual(len(matches.groups()), 1, msg="OOB response contains multiple <code> tags")
authorization_code = matches.groups()[0]

token_request_data = {
Expand Down Expand Up @@ -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",
Expand All @@ -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")
Expand Down
Loading