|
| 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