Skip to content

Commit 24611cb

Browse files
committed
Allow wildcard for redirect_urls in OAuth2 apps
1 parent 782431d commit 24611cb

File tree

4 files changed

+101
-0
lines changed

4 files changed

+101
-0
lines changed

ansible_wisdom/main/settings/base.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"""
1212

1313
import os
14+
import sys
1415
from pathlib import Path
1516

1617
# Build paths inside the project like this: BASE_DIR / 'subdir'.
@@ -170,6 +171,15 @@
170171
'REFRESH_TOKEN_EXPIRE_SECONDS': 1_209_600, # = 2 weeks
171172
}
172173

174+
#
175+
# We need to run 'manage.py migrate' before adding our own OAuth2 application model.
176+
# See https://django-oauth-toolkit.readthedocs.io/en/latest/advanced_topics.html
177+
# #extending-the-application-model
178+
#
179+
if sys.argv[-1] != 'migrate':
180+
INSTALLED_APPS.append('wildcard_oauth2')
181+
OAUTH2_PROVIDER_APPLICATION_MODEL = 'wildcard_oauth2.Application'
182+
173183
# OAUTH: todo
174184
# - remove ansible_wisdom/users/auth.py module
175185
# - remove ansible_wisdom/users/views.py module
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
"""
2+
The module offers a custom OAuth2 Application that allows wildcard URLs.
3+
(From: https://github.com/open-craft/oauth2-wildcard-application)
4+
"""
5+
6+
7+
default_app_config = (
8+
'wildcard_oauth2.apps.WildcardOauth2ApplicationConfig' # pylint: disable=invalid-name
9+
)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"""
2+
Django App Configuration
3+
"""
4+
5+
from django.apps import AppConfig
6+
7+
8+
class WildcardOauth2ApplicationConfig(AppConfig):
9+
"""
10+
Configures wildcard_oauth2 as a Django app plugin
11+
"""
12+
13+
name = 'wildcard_oauth2'
14+
verbose_name = "Wildcard OAuth2 Application"
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
"""
2+
A custom OAuth2 application that allows wildcard for redirect_uris
3+
4+
https://github.com/jazzband/django-oauth-toolkit/issues/443#issuecomment-420255286
5+
"""
6+
import re
7+
from urllib.parse import parse_qsl, urlparse
8+
9+
from django.core.exceptions import ValidationError
10+
from oauth2_provider.models import AbstractApplication
11+
from oauth2_provider.settings import oauth2_settings
12+
13+
14+
def validate_uris(value):
15+
"""Ensure that `value` contains valid blank-separated URIs."""
16+
urls = value.split()
17+
for url in urls:
18+
obj = urlparse(url)
19+
if obj.fragment:
20+
raise ValidationError('Redirect URIs must not contain fragments')
21+
if obj.scheme.lower() not in oauth2_settings.ALLOWED_REDIRECT_URI_SCHEMES:
22+
raise ValidationError('Redirect URI scheme is not allowed.')
23+
if not obj.netloc:
24+
raise ValidationError('Redirect URI must contain a domain.')
25+
26+
27+
class Application(AbstractApplication):
28+
"""Subclass of application to allow for regular expressions for the redirect uri."""
29+
30+
@staticmethod
31+
def _uri_is_allowed(allowed_uri, uri):
32+
"""Check that the URI conforms to these rules."""
33+
schemes_match = allowed_uri.scheme == uri.scheme
34+
netloc_matches_pattern = re.fullmatch(allowed_uri.netloc, uri.netloc)
35+
paths_match = allowed_uri.path == uri.path
36+
37+
return all([schemes_match, netloc_matches_pattern, paths_match])
38+
39+
def __init__(self, *args, **kwargs):
40+
"""Relax the validator to allow for uris with regular expressions."""
41+
self._meta.get_field('redirect_uris').validators = [
42+
validate_uris,
43+
]
44+
super().__init__(*args, **kwargs)
45+
46+
def redirect_uri_allowed(self, uri):
47+
"""
48+
Check if given url is one of the items in :attr:`redirect_uris` string.
49+
A Redirect uri domain may be a regular expression e.g. `^(.*).example.com$` will
50+
match all subdomains of example.com.
51+
A Redirect uri may be `https://(.*).example.com/some/path/?q=x`
52+
:param uri: Url to check
53+
"""
54+
for allowed_uri in self.redirect_uris.split():
55+
parsed_allowed_uri = urlparse(allowed_uri)
56+
parsed_uri = urlparse(uri)
57+
58+
if self._uri_is_allowed(parsed_allowed_uri, parsed_uri):
59+
aqs_set = set(parse_qsl(parsed_allowed_uri.query))
60+
uqs_set = set(parse_qsl(parsed_uri.query))
61+
62+
if aqs_set.issubset(uqs_set):
63+
return True
64+
65+
return False
66+
67+
class Meta:
68+
db_table = 'oauth2_provider_application'

0 commit comments

Comments
 (0)