def absolute_uri(request, base_url): if not absolute_http_url_re.match(base_url): if base_url.startswith("/"): base_url = base_url[1:] return '%s://%s/%s' % ( request.is_secure() and 'https' or 'http', request.get_host(), base_url) return base_url
def build_absolute_uri(location, is_secure=False): try: host = settings.CANONICAL_HOSTNAME except AttributeError: raise ImproperlyConfigured('You need to specify CANONICAL_HOSTNAME in ' 'your Django settings file') if not absolute_http_url_re.match(location): current_uri = '%s://%s' % ('https' if is_secure else 'http', host) location = urljoin(current_uri, location) return iri_to_uri(location)
def build_absolute_uri(location, is_secure=False): try: host = settings.CANONICAL_HOSTNAME except AttributeError: raise ImproperlyConfigured('You need to specify CANONICAL_HOSTNAME in ' 'your Django settings file') if not absolute_http_url_re.match(location): current_uri = '%s://%s' % ('https' if is_secure else 'http', host) location = urljoin(current_uri, location) return iri_to_uri(location)
def rewrite_location(request, prefix_path, location): prefix_path = prefix_path or '' url = urlparse(location) scheme = request.is_secure() and 'https' or 'http' if not absolute_http_url_re.match(location): # remote server doesn't follow rfc2616 proxy_uri = '%s://%s%s' % (scheme, request.get_host(), prefix_path) return urljoin(proxy_uri, location) elif url.scheme != scheme or url.netloc != request.get_host(): return urlunparse((scheme, request.get_host(), prefix_path + url.path, url.params, url.query, url.fragment)) return location
def _validate(self): """Validate the request.""" if self.client_id is None: raise InvalidRequest('No client_id') try: self.client = Client.objects.get(key=self.client_id) except Client.DoesNotExist: raise InvalidClient("client_id %s doesn't exist" % self.client_id) # Redirect URI if self.redirect_uri is None: if self.client.redirect_uri is None: raise MissingRedirectURI("No redirect_uri" "provided or registered.") elif self.client.redirect_uri is not None: if normalize(self.redirect_uri) != normalize( self.client.redirect_uri): self.redirect_uri = self.client.redirect_uri raise InvalidRequest("Registered redirect_uri doesn't " "match provided redirect_uri.") self.redirect_uri = self.redirect_uri or self.client.redirect_uri # Check response type if self.response_type is None: raise InvalidRequest('response_type is a required parameter.') if self.response_type not in ["code", "token"]: raise InvalidRequest("No such response type %s" % self.response_type) # Response type if self.authorized_response_type & RESPONSE_TYPES[ self.response_type] == 0: raise UnauthorizedClient("Response type %s not allowed." % self.response_type) if not absolute_http_url_re.match(self.redirect_uri): raise InvalidRequest('Absolute URI required for redirect_uri') # Scope if self.authorized_scope is not None and self.scope is None: self.scope = self.authorized_scope if self.scope is not None: self.access_ranges = AccessRange.objects.filter(key__in=self.scope) access_ranges = set( self.access_ranges.values_list('key', flat=True)) difference = access_ranges.symmetric_difference(self.scope) if len(difference) != 0: raise InvalidScope("Following access ranges do not " "exist: %s" % ', '.join(difference)) if self.authorized_scope is not None: new_scope = self.scope - self.authorized_scope if len(new_scope) > 0: raise InvalidScope("Invalid scope: %s" % ','.join(new_scope))
def _validate(self): """Validate the request.""" ClientModel = get_model(*CLIENT_MODEL.split('.', 1)) if self.client_id is None: raise InvalidRequest('No client_id') try: self.client = ClientModel.objects.get(key=self.client_id) except ClientModel.DoesNotExist: raise InvalidClient("client_id %s doesn't exist" % self.client_id) # Redirect URI if self.redirect_uri is None: if self.client.redirect_uri is None: raise MissingRedirectURI("No redirect_uri" "provided or registered.") elif self.client.redirect_uri is not None: if normalize(self.redirect_uri) != normalize(self.client.redirect_uri): self.redirect_uri = self.client.redirect_uri raise InvalidRequest("Registered redirect_uri doesn't " "match provided redirect_uri.") self.redirect_uri = self.redirect_uri or self.client.redirect_uri # Check response type if self.response_type is None: raise InvalidRequest('response_type is a required parameter.') if self.response_type not in ["code", "token"]: raise InvalidRequest("No such response type %s" % self.response_type) # Response type if self.authorized_response_type & RESPONSE_TYPES[self.response_type] == 0: raise UnauthorizedClient("Response type %s not allowed." % self.response_type) if not absolute_http_url_re.match(self.redirect_uri): raise InvalidRequest('Absolute URI required for redirect_uri') # Scope if self.authorized_scope is not None and self.scope is None: self.scope = self.authorized_scope if self.scope is not None: self.access_ranges = AccessRange.objects.filter(key__in=self.scope) access_ranges = set(self.access_ranges.values_list('key', flat=True)) difference = access_ranges.symmetric_difference(self.scope) if len(difference) != 0: raise InvalidScope("Following access ranges do not " "exist: %s" % ', '.join(difference)) if self.authorized_scope is not None: new_scope = self.scope - self.authorized_scope if len(new_scope) > 0: raise InvalidScope("Invalid scope: %s" % ','.join(new_scope))
def _check_redirect_uri(self): """Raise MissingRedirectURI if no redirect_uri is available.""" if self.redirect_uri is None: raise MissingRedirectURI('No redirect_uri to send response.') if not absolute_http_url_re.match(self.redirect_uri): raise MissingRedirectURI('Absolute redirect_uri required.')
def _check_redirect_uri(self): """Raise MissingRedirectURI if no redirect_uri is available.""" if self.redirect_uri is None: raise MissingRedirectURI('No redirect_uri to send response.') if not absolute_http_url_re.match(self.redirect_uri): raise MissingRedirectURI('Absolute redirect_uri required.')
def validate(self, request): """ Check that a Client's authorization request is valid. If the request is invalid or malformed in any way, raises the appropriate exception. Read `the relevant section of the specification <http://tools.ietf.org/html/rfc6749#section-4.1 .>`_ for descriptions of each type of error. :raises: a :py:class:`AuthorizationException` if the request is invalid. """ # From http://tools.ietf.org/html/rfc6749#section-3.1 : # # The authorization server MUST support the use of the HTTP "GET" # method [RFC2616] for the authorization endpoint and MAY support the # use of the "POST" method as well. # if not request.method in ['GET', 'POST']: raise InvalidRequest('must be GET or POST request') if settings.DJOAUTH2_SSL_ONLY and not request.is_secure(): raise InvalidRequest('all requests must use TLS') self.request = request self.user = request.user if not self.user.is_authenticated(): raise UnauthenticatedUser('user must be authenticated') client_id = request.REQUEST.get('client_id') if not client_id: raise InvalidRequest('no "client_id" provided') try: self.client = Client.objects.get(key=client_id) except Client.DoesNotExist: raise InvalidRequest('"client_id" does not exist') # From http://tools.ietf.org/html/rfc6749#section-3.1.2.3 : # # If multiple redirection URIs have been registered, if only part of # the redirection URI has been registered, or if no redirection URI has # been registered, the client MUST include a redirection URI with the # authorization request using the "redirect_uri" request parameter. # self.request_redirect_uri = request.REQUEST.get('redirect_uri') if not (self.client.redirect_uri or self.request_redirect_uri): raise InvalidRequest('no "redirect_uri" provided or registered') # From http://tools.ietf.org/html/rfc6749#section-3.1.2.3 : # # When a redirection URI is included in an authorization request, the # authorization server MUST compare and match the value received # against at least one of the registered redirection URIs (or URI # components) as defined in [RFC3986] Section 6, if any redirection # URIs were registered. If the client registration included the full # redirection URI, the authorization server MUST compare the two URIs # using simple string comparison as defined in [RFC3986] Section 6.2.1. # if (self.client.redirect_uri and self.request_redirect_uri and self.client.redirect_uri != self.request_redirect_uri): raise InvalidRequest('"redirect_uri" does not matched the registered URI') # From http://tools.ietf.org/html/rfc6749#section-3.1.2 : # # The redirection endpoint URI MUST be an absolute URI as defined by # [RFC3986] Section 4.3. # redirect_uri = self.client.redirect_uri or self.request_redirect_uri if not absolute_http_url_re.match(redirect_uri): raise InvalidRequest('"redirect_uri" must be absolute') # From http://tools.ietf.org/html/rfc6749#section-3.1.2 : # # The endpoint URI MUST NOT include a fragment component. # if urlparse(redirect_uri).fragment: raise InvalidRequest('"redirect_uri" must not contain a fragment') # From http://tools.ietf.org/html/rfc6749#section-3.1.2.1 : # # The redirection endpoint SHOULD require the use of TLS as described # in Section 1.6 when the requested response type is "code" or "token", # or when the redirection request will result in the transmission of # sensitive credentials over an open network. This specification does # not mandate the use of TLS because at the time of this writing, # requiring clients to deploy TLS is a significant hurdle for many # client developers. If TLS is not available, the authorization server # SHOULD warn the resource owner about the insecure endpoint prior to # redirection (e.g., display a message during the authorization # request). # if (settings.DJOAUTH2_SSL_ONLY and urlparse(redirect_uri).scheme != 'https'): raise InvalidRequest('"redirect_uri" must use TLS') # Only store the redirect_uri value if it validates successfully. The # 'make_error_redirect' method will use the 'missing_redirect_uri' passed # to the '__init__' method if 'self.redirect_uri' is None. self.redirect_uri = redirect_uri # From http://tools.ietf.org/html/rfc6749#section-3.1.1 : # # The client informs the authorization server of the desired grant type # using the following parameter: # # response_type # REQUIRED. The value MUST be one of "code" for requesting an # authorization code as described by Section 4.1.1, "token" for # requesting an access token (implicit grant) as described by # Section 4.2.1, or a registered extension value as described by # Section 8.4. # # This implementation only supports the "code" "response_type". response_type = request.REQUEST.get('response_type') if response_type != 'code': raise UnsupportedResponseType('"response_type" must be "code"') # As recommended by http://tools.ietf.org/html/rfc6749#section-4.1.1 : # # state # RECOMMENDED. An opaque value used by the client to maintain # state between the request and callback. The authorization # server includes this value when redirecting the user-agent back # to the client. The parameter SHOULD be used for preventing # cross-site request forgery as described in Section 10.12. # # and necessary for the CSRF recommendation mandated by # http://tools.ietf.org/html/rfc6749#section-10.12 : # # The client MUST implement CSRF protection for its redirection URI. # This is typically accomplished by requiring any request sent to the # redirection URI endpoint to include a value that binds the request to # the user-agent's authenticated state (e.g., a hash of the session # cookie used to authenticate the user-agent). The client SHOULD # utilize the "state" request parameter to deliver this value to the # authorization server when making an authorization request. # self.state = request.REQUEST.get('state') if settings.DJOAUTH2_REQUIRE_STATE and not self.state: raise InvalidRequest('"state" must be included') requested_scope_string = request.REQUEST.get('scope', '') if not requested_scope_string: raise InvalidScope('"scope" must be included') requested_scope_names = set(requested_scope_string.split(' ')) self.valid_scope_objects = Scope.objects.filter( name__in=requested_scope_names) valid_scope_names = {scope.name for scope in self.valid_scope_objects} if valid_scope_names < requested_scope_names: raise InvalidScope('The following scopes are invalid: {}'.format( ', '.join('"{}"'.format(name) for name in (requested_scope_names - valid_scope_names))))
def validate(self, request): """ Check that a Client's authorization request is valid. If the request is invalid or malformed in any way, raises the appropriate exception. Read `the relevant section of the specification <http://tools.ietf.org/html/rfc6749#section-4.1 .>`_ for descriptions of each type of error. :raises: a :py:class:`AuthorizationError` if the request is invalid. """ # From http://tools.ietf.org/html/rfc6749#section-3.1 : # # The authorization server MUST support the use of the HTTP "GET" # method [RFC2616] for the authorization endpoint and MAY support the # use of the "POST" method as well. # if not request.method in ['GET', 'POST']: raise InvalidRequest('must be GET or POST request') if settings.DJOAUTH2_SSL_ONLY and not request.is_secure(): raise InvalidRequest('all requests must use TLS') self.request = request self.user = request.user if not self.user.is_authenticated(): raise UnauthenticatedUser('user must be authenticated') client_id = request.REQUEST.get('client_id') if not client_id: raise InvalidRequest('no "client_id" provided') try: self.client = Client.objects.get(key=client_id) except Client.DoesNotExist: raise InvalidRequest('"client_id" does not exist') # From http://tools.ietf.org/html/rfc6749#section-3.1.2.3 : # # If multiple redirection URIs have been registered, if only part of # the redirection URI has been registered, or if no redirection URI has # been registered, the client MUST include a redirection URI with the # authorization request using the "redirect_uri" request parameter. # self.request_redirect_uri = request.REQUEST.get('redirect_uri') if not (self.client.redirect_uri or self.request_redirect_uri): raise InvalidRequest('no "redirect_uri" provided or registered') # From http://tools.ietf.org/html/rfc6749#section-3.1.2.3 : # # When a redirection URI is included in an authorization request, the # authorization server MUST compare and match the value received # against at least one of the registered redirection URIs (or URI # components) as defined in [RFC3986] Section 6, if any redirection # URIs were registered. If the client registration included the full # redirection URI, the authorization server MUST compare the two URIs # using simple string comparison as defined in [RFC3986] Section 6.2.1. # if (self.client.redirect_uri and self.request_redirect_uri and self.client.redirect_uri != self.request_redirect_uri): raise InvalidRequest('"redirect_uri" does not matched the registered URI') # From http://tools.ietf.org/html/rfc6749#section-3.1.2 : # # The redirection endpoint URI MUST be an absolute URI as defined by # [RFC3986] Section 4.3. # redirect_uri = self.client.redirect_uri or self.request_redirect_uri if not absolute_http_url_re.match(redirect_uri): raise InvalidRequest('"redirect_uri" must be absolute') # From http://tools.ietf.org/html/rfc6749#section-3.1.2 : # # The endpoint URI MUST NOT include a fragment component. # if urlparse(redirect_uri).fragment: raise InvalidRequest('"redirect_uri" must not contain a fragment') # From http://tools.ietf.org/html/rfc6749#section-3.1.2.1 : # # The redirection endpoint SHOULD require the use of TLS as described # in Section 1.6 when the requested response type is "code" or "token", # or when the redirection request will result in the transmission of # sensitive credentials over an open network. This specification does # not mandate the use of TLS because at the time of this writing, # requiring clients to deploy TLS is a significant hurdle for many # client developers. If TLS is not available, the authorization server # SHOULD warn the resource owner about the insecure endpoint prior to # redirection (e.g., display a message during the authorization # request). # if (settings.DJOAUTH2_SSL_ONLY and urlparse(redirect_uri).scheme != 'https'): raise InvalidRequest('"redirect_uri" must use TLS') # Only store the redirect_uri value if it validates successfully. The # 'make_error_redirect' method will use the 'missing_redirect_uri' passed # to the '__init__' method if 'self.redirect_uri' is None. self.redirect_uri = redirect_uri # From http://tools.ietf.org/html/rfc6749#section-3.1.1 : # # The client informs the authorization server of the desired grant type # using the following parameter: # # response_type # REQUIRED. The value MUST be one of "code" for requesting an # authorization code as described by Section 4.1.1, "token" for # requesting an access token (implicit grant) as described by # Section 4.2.1, or a registered extension value as described by # Section 8.4. # # This implementation only supports the "code" "response_type". response_type = request.REQUEST.get('response_type') if response_type != 'code': raise UnsupportedResponseType('"response_type" must be "code"') # As recommended by http://tools.ietf.org/html/rfc6749#section-4.1.1 : # # state # RECOMMENDED. An opaque value used by the client to maintain # state between the request and callback. The authorization # server includes this value when redirecting the user-agent back # to the client. The parameter SHOULD be used for preventing # cross-site request forgery as described in Section 10.12. # # and necessary for the CSRF recommendation mandated by # http://tools.ietf.org/html/rfc6749#section-10.12 : # # The client MUST implement CSRF protection for its redirection URI. # This is typically accomplished by requiring any request sent to the # redirection URI endpoint to include a value that binds the request to # the user-agent's authenticated state (e.g., a hash of the session # cookie used to authenticate the user-agent). The client SHOULD # utilize the "state" request parameter to deliver this value to the # authorization server when making an authorization request. # self.state = request.REQUEST.get('state') if settings.DJOAUTH2_REQUIRE_STATE and not self.state: raise InvalidRequest('"state" must be included') requested_scope_string = request.REQUEST.get('scope', '') if not requested_scope_string: raise InvalidScope('"scope" must be included') requested_scope_names = set(requested_scope_string.split(' ')) self.valid_scope_objects = Scope.objects.filter( name__in=requested_scope_names) valid_scope_names = {scope.name for scope in self.valid_scope_objects} if valid_scope_names < requested_scope_names: raise InvalidScope('The following scopes are invalid: {}'.format( ', '.join('"{}"'.format(name) for name in (requested_scope_names - valid_scope_names))))