jazzband / django-oauth-toolkit

OAuth2 goodies for the Djangonauts!
https://django-oauth-toolkit.readthedocs.io
Other
3.16k stars 794 forks source link

Allow wildcards in Application's redirect-uri field #443

Closed tarzzz closed 7 years ago

tarzzz commented 7 years ago

I am not sure if this is already supported (since I could not find it in docs).

Application's Redirect URIs should accept wildcard URIs (for sub-domains), and OAuth2 Validator should be able to validate the redirect_uri received in request against the redirect_uris entry in Application.

We should be able to add:

 Application.redirect_uris = '*.my-domain.com'

and this should be able to accept 'A.my-domain.com' in request's redirect_uri parameter.

I would be happy to make PR for this, if necessary.

jleclanche commented 7 years ago

Big -1 on wildcard support. However, the redirect uri validation should be exposed to the library users so that it can be overridden. I don't know if that's the case yet.

tarzzz commented 7 years ago

the redirect uri validation should be exposed to the library users so that it can be overridden

This should also serve our purpose. Ideas on how we can implement it would be great..

jleclanche commented 7 years ago

@tarzzz i don't know off the top of my head. If you have a suggestion I'm willing to review it.

tarzzz commented 7 years ago

We have figured out a work-around for this for our application. I am going to close this issue, but can be opened if someone needs it..

Thanks @jleclanche anyways.. 😃

jleclanche commented 7 years ago

@tarzzz can you share the workaround, for the poor lad who's gonna end up in this issue off google a year from now? :)

tarzzz commented 7 years ago

@jleclanche: There wasn't a work around (pardon my language in my earlier comment). We simply decided to use another approach, which did not require wildcards/subdomains at all.

jleclanche commented 7 years ago

Alright, excellent. I don't think it's a particularly good idea to use wildcard redirect uris.

samupl commented 7 years ago

Actually, I've found an use-case where it might get useful. I'm trying to integrate OAuth2 based SSO login to a Plone application (don't even ask how I got into it:<).

Plone allows you to have multiple "Sites". They are located in different paths, for example:

Etc. Now for each site, I have to add another redirect URI. It would be helpful if I could just do:

http://example.com/*/acl_users/oauth2

Or even better, with some kind of a regular expression:

http://example.com/(A-Za-z0-9+)/acl_users/oauth2

uroybd commented 6 years ago

I agree with @samupl and I'm also trying to do something like this.

cleder commented 6 years ago

You can subclass AbstractApplication to achieve this, e.g.:

# Standard Library
import re
from urllib.parse import parse_qsl
from urllib.parse import urlparse

# Django
from django.core.exceptions import ValidationError

# 3rd-party
from oauth2_provider.models import AbstractApplication
from oauth2_provider.settings import oauth2_settings

def validate_uris(value):
    """Ensure that `value` contains valid blank-separated URIs."""
    urls = value.split()
    for url in urls:
        obj = urlparse(url)
        if obj.fragment:
            raise ValidationError('Redirect URIs must not contain fragments')
        if obj.scheme.lower() not in oauth2_settings.ALLOWED_REDIRECT_URI_SCHEMES:
            raise ValidationError('Redirect URI scheme is not allowed.')
        if not obj.netloc:
            raise ValidationError('Redirect URI must contain a domain.')

class Application(AbstractApplication):
    """Subclass of application to allow for regular expressions for the redirect uri."""

    @staticmethod
    def _uri_is_allowed(allowed_uri, uri):
        """Check that the URI conforms to these rules."""
        schemes_match = allowed_uri.scheme == uri.scheme
        netloc_matches_pattern = re.fullmatch(allowed_uri.netloc, uri.netloc)
        paths_match = allowed_uri.path == uri.path

        return all([schemes_match, netloc_matches_pattern, paths_match])

    def __init__(self, *args, **kwargs):
        """Relax the validator to allow for uris with regular expressions."""
        self._meta.get_field('redirect_uris').validators = [validate_uris, ]
        super(). __init__(*args, **kwargs)

    def redirect_uri_allowed(self, uri):
        """
        Check if given url is one of the items in :attr:`redirect_uris` string.
        A Redirect uri domain may be a regular expression e.g. `^(.*).example.com$` will
        match all subdomains of example.com.
        A Redirect uri may be `https://(.*).example.com/some/path/?q=x`
        :param uri: Url to check
        """
        for allowed_uri in self.redirect_uris.split():
            parsed_allowed_uri = urlparse(allowed_uri)
            parsed_uri = urlparse(uri)

            if self._uri_is_allowed(parsed_allowed_uri, parsed_uri):
                aqs_set = set(parse_qsl(parsed_allowed_uri.query))
                uqs_set = set(parse_qsl(parsed_uri.query))

                if aqs_set.issubset(uqs_set):
                    return True

        return False
aviars commented 6 years ago

Supporting to RFC-8252 "OAuth2 for Native Apps" using PKCE would be a major upgrade to Django OAuth Toolkit. I think @whytheplatypus and @ekivemark may have also explored solutions/workarounds.

pheki commented 7 months ago

The example on https://github.com/jazzband/django-oauth-toolkit/issues/443#issuecomment-420255286 doesn't work (for domains) since validation has been moved to clean() on https://github.com/jazzband/django-oauth-toolkit/commit/96538876d0d7ea0319ba5286f9bde842a906e1c5, and clean() is not ideal to override as it also validates other fields