2015-06-22 21:42:42 +00:00
|
|
|
from datetime import timedelta
|
2015-06-08 19:36:49 +00:00
|
|
|
import logging
|
2015-07-10 10:22:25 +00:00
|
|
|
try:
|
|
|
|
from urllib import urlencode
|
|
|
|
from urlparse import urlsplit, parse_qs, urlunsplit
|
|
|
|
except ImportError:
|
|
|
|
from urllib.parse import urlsplit, parse_qs, urlunsplit, urlencode
|
2015-06-22 21:42:42 +00:00
|
|
|
|
2015-07-14 15:44:25 +00:00
|
|
|
from django.utils import timezone
|
|
|
|
|
2015-02-18 18:07:22 +00:00
|
|
|
from oidc_provider.lib.errors import *
|
|
|
|
from oidc_provider.lib.utils.params import *
|
|
|
|
from oidc_provider.lib.utils.token import *
|
|
|
|
from oidc_provider.models import *
|
2015-07-21 13:59:23 +00:00
|
|
|
from oidc_provider import settings
|
2015-01-08 20:55:24 +00:00
|
|
|
|
2015-06-19 20:46:00 +00:00
|
|
|
|
2015-06-08 19:36:49 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
2015-01-08 20:55:24 +00:00
|
|
|
|
|
|
|
class AuthorizeEndpoint(object):
|
|
|
|
|
|
|
|
def __init__(self, request):
|
|
|
|
self.request = request
|
2015-01-09 17:59:23 +00:00
|
|
|
self.params = Params()
|
2015-01-08 20:55:24 +00:00
|
|
|
|
|
|
|
self._extract_params()
|
|
|
|
|
|
|
|
# Determine which flow to use.
|
|
|
|
if self.params.response_type in ['code']:
|
|
|
|
self.grant_type = 'authorization_code'
|
2016-02-16 20:33:12 +00:00
|
|
|
elif self.params.response_type in ['id_token', 'id_token token', 'token']:
|
2015-01-08 20:55:24 +00:00
|
|
|
self.grant_type = 'implicit'
|
|
|
|
else:
|
|
|
|
self.grant_type = None
|
|
|
|
|
2016-02-16 20:33:12 +00:00
|
|
|
# Determine if it's an OpenID Authentication request (or OAuth2).
|
|
|
|
self.is_authentication = 'openid' in self.params.scope
|
|
|
|
|
2015-01-08 20:55:24 +00:00
|
|
|
def _extract_params(self):
|
2015-01-28 18:19:36 +00:00
|
|
|
"""
|
2015-01-08 20:55:24 +00:00
|
|
|
Get all the params used by the Authorization Code Flow
|
|
|
|
(and also for the Implicit).
|
|
|
|
|
|
|
|
See: http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
|
2015-01-28 18:19:36 +00:00
|
|
|
"""
|
2015-07-28 18:55:30 +00:00
|
|
|
# Because in this endpoint we handle both GET
|
|
|
|
# and POST request.
|
|
|
|
query_dict = (self.request.POST if self.request.method == 'POST'
|
|
|
|
else self.request.GET)
|
|
|
|
|
|
|
|
self.params.client_id = query_dict.get('client_id', '')
|
|
|
|
self.params.redirect_uri = query_dict.get('redirect_uri', '')
|
|
|
|
self.params.response_type = query_dict.get('response_type', '')
|
|
|
|
self.params.scope = query_dict.get('scope', '').split()
|
|
|
|
self.params.state = query_dict.get('state', '')
|
|
|
|
self.params.nonce = query_dict.get('nonce', '')
|
2015-01-08 20:55:24 +00:00
|
|
|
|
2016-04-06 21:03:30 +00:00
|
|
|
# PKCE parameters.
|
|
|
|
self.params.code_challenge = query_dict.get('code_challenge')
|
|
|
|
self.params.code_challenge_method = query_dict.get('code_challenge_method')
|
|
|
|
|
2015-01-08 20:55:24 +00:00
|
|
|
def validate_params(self):
|
2016-02-16 20:33:12 +00:00
|
|
|
try:
|
|
|
|
self.client = Client.objects.get(client_id=self.params.client_id)
|
|
|
|
except Client.DoesNotExist:
|
2016-03-17 18:31:41 +00:00
|
|
|
logger.debug('[Authorize] Invalid client identifier: %s', self.params.client_id)
|
2016-02-16 20:33:12 +00:00
|
|
|
raise ClientIdError()
|
|
|
|
|
|
|
|
if self.is_authentication and not self.params.redirect_uri:
|
2016-03-17 18:31:41 +00:00
|
|
|
logger.debug('[Authorize] Missing redirect uri.')
|
2015-01-08 20:55:24 +00:00
|
|
|
raise RedirectUriError()
|
|
|
|
|
2016-02-16 20:33:12 +00:00
|
|
|
if not self.grant_type:
|
2016-03-17 18:31:41 +00:00
|
|
|
logger.debug('[Authorize] Invalid response type: %s', self.params.response_type)
|
2016-02-16 20:33:12 +00:00
|
|
|
raise AuthorizeError(self.params.redirect_uri, 'unsupported_response_type',
|
2016-01-19 19:05:34 +00:00
|
|
|
self.grant_type)
|
|
|
|
|
2016-02-16 20:33:12 +00:00
|
|
|
if self.is_authentication and self.grant_type == 'implicit' and not self.params.nonce:
|
2016-01-19 19:05:34 +00:00
|
|
|
raise AuthorizeError(self.params.redirect_uri, 'invalid_request',
|
2015-01-12 22:13:48 +00:00
|
|
|
self.grant_type)
|
2015-01-08 20:55:24 +00:00
|
|
|
|
2016-03-08 13:21:55 +00:00
|
|
|
if self.is_authentication and self.params.response_type != self.client.response_type:
|
|
|
|
raise AuthorizeError(self.params.redirect_uri, 'invalid_request',
|
|
|
|
self.grant_type)
|
|
|
|
|
2016-01-19 19:05:34 +00:00
|
|
|
clean_redirect_uri = urlsplit(self.params.redirect_uri)
|
|
|
|
clean_redirect_uri = urlunsplit(clean_redirect_uri._replace(query=''))
|
|
|
|
if not (clean_redirect_uri in self.client.redirect_uris):
|
2016-03-17 18:31:41 +00:00
|
|
|
logger.debug('[Authorize] Invalid redirect uri: %s', self.params.redirect_uri)
|
2016-01-19 19:05:34 +00:00
|
|
|
raise RedirectUriError()
|
2016-04-06 21:03:30 +00:00
|
|
|
|
|
|
|
# PKCE validation of the transformation method.
|
|
|
|
if self.params.code_challenge and self.params.code_challenge_method:
|
|
|
|
if not (self.params.code_challenge_method in ['plain', 'S256']):
|
|
|
|
raise AuthorizeError(self.params.redirect_uri, 'invalid_request', self.grant_type)
|
2016-01-19 19:05:34 +00:00
|
|
|
|
2015-06-15 19:04:44 +00:00
|
|
|
def create_response_uri(self):
|
2015-07-10 10:22:25 +00:00
|
|
|
uri = urlsplit(self.params.redirect_uri)
|
|
|
|
query_params = parse_qs(uri.query)
|
|
|
|
query_fragment = parse_qs(uri.fragment)
|
|
|
|
|
2015-01-08 20:55:24 +00:00
|
|
|
try:
|
2015-01-28 18:19:36 +00:00
|
|
|
if self.grant_type == 'authorization_code':
|
2015-03-12 15:40:36 +00:00
|
|
|
code = create_code(
|
|
|
|
user=self.request.user,
|
|
|
|
client=self.client,
|
2015-07-15 19:23:36 +00:00
|
|
|
scope=self.params.scope,
|
2016-02-16 20:33:12 +00:00
|
|
|
nonce=self.params.nonce,
|
2016-04-06 21:03:30 +00:00
|
|
|
is_authentication=self.is_authentication,
|
|
|
|
code_challenge=self.params.code_challenge,
|
|
|
|
code_challenge_method=self.params.code_challenge_method)
|
2015-03-12 15:40:36 +00:00
|
|
|
|
2015-01-08 20:55:24 +00:00
|
|
|
code.save()
|
|
|
|
|
2015-07-10 10:22:25 +00:00
|
|
|
query_params['code'] = code.code
|
|
|
|
query_params['state'] = self.params.state if self.params.state else ''
|
2015-01-09 17:59:23 +00:00
|
|
|
|
2015-06-15 20:34:36 +00:00
|
|
|
elif self.grant_type == 'implicit':
|
2016-02-16 20:33:12 +00:00
|
|
|
# We don't need id_token if it's an OAuth2 request.
|
|
|
|
if self.is_authentication:
|
|
|
|
id_token_dic = create_id_token(
|
|
|
|
user=self.request.user,
|
|
|
|
aud=self.client.client_id,
|
|
|
|
nonce=self.params.nonce)
|
|
|
|
query_fragment['id_token'] = encode_id_token(id_token_dic)
|
|
|
|
else:
|
|
|
|
id_token_dic = {}
|
2015-01-08 20:55:24 +00:00
|
|
|
|
|
|
|
token = create_token(
|
|
|
|
user=self.request.user,
|
|
|
|
client=self.client,
|
|
|
|
id_token_dic=id_token_dic,
|
|
|
|
scope=self.params.scope)
|
|
|
|
|
|
|
|
# Store the token.
|
|
|
|
token.save()
|
|
|
|
|
2015-07-10 10:22:25 +00:00
|
|
|
query_fragment['token_type'] = 'bearer'
|
2016-02-16 20:33:12 +00:00
|
|
|
# TODO: Create setting 'OIDC_TOKEN_EXPIRE'.
|
2015-07-10 10:22:25 +00:00
|
|
|
query_fragment['expires_in'] = 60 * 10
|
2015-02-02 20:39:01 +00:00
|
|
|
|
2016-02-16 20:33:12 +00:00
|
|
|
# Check if response_type is an OpenID request with value 'id_token token'
|
|
|
|
# or it's an OAuth2 Implicit Flow request.
|
|
|
|
if self.params.response_type in ['id_token token', 'token']:
|
2015-07-10 10:22:25 +00:00
|
|
|
query_fragment['access_token'] = token.access_token
|
|
|
|
|
|
|
|
query_fragment['state'] = self.params.state if self.params.state else ''
|
|
|
|
|
2015-06-19 20:46:00 +00:00
|
|
|
except Exception as error:
|
2016-03-17 18:31:41 +00:00
|
|
|
logger.debug('[Authorize] Error when trying to create response uri: %s', error)
|
2015-01-12 22:13:48 +00:00
|
|
|
raise AuthorizeError(
|
|
|
|
self.params.redirect_uri,
|
|
|
|
'server_error',
|
|
|
|
self.grant_type)
|
2015-01-08 20:55:24 +00:00
|
|
|
|
2015-07-10 10:22:25 +00:00
|
|
|
uri = uri._replace(query=urlencode(query_params, doseq=True))
|
|
|
|
uri = uri._replace(fragment=urlencode(query_fragment, doseq=True))
|
2015-01-08 20:55:24 +00:00
|
|
|
|
2015-07-10 10:22:25 +00:00
|
|
|
return urlunsplit(uri)
|
2015-06-22 21:42:42 +00:00
|
|
|
|
|
|
|
def set_client_user_consent(self):
|
|
|
|
"""
|
|
|
|
Save the user consent given to a specific client.
|
|
|
|
|
|
|
|
Return None.
|
|
|
|
"""
|
|
|
|
expires_at = timezone.now() + timedelta(
|
2015-06-24 15:40:00 +00:00
|
|
|
days=settings.get('OIDC_SKIP_CONSENT_EXPIRE'))
|
2015-06-22 21:42:42 +00:00
|
|
|
|
|
|
|
uc, created = UserConsent.objects.get_or_create(
|
|
|
|
user=self.request.user,
|
|
|
|
client=self.client,
|
|
|
|
defaults={'expires_at': expires_at})
|
|
|
|
uc.scope = self.params.scope
|
|
|
|
|
|
|
|
# Rewrite expires_at if object already exists.
|
|
|
|
if not created:
|
|
|
|
uc.expires_at = expires_at
|
|
|
|
|
|
|
|
uc.save()
|
|
|
|
|
|
|
|
def client_has_user_consent(self):
|
|
|
|
"""
|
|
|
|
Check if already exists user consent for some client.
|
|
|
|
|
|
|
|
Return bool.
|
|
|
|
"""
|
|
|
|
value = False
|
|
|
|
try:
|
|
|
|
uc = UserConsent.objects.get(user=self.request.user,
|
|
|
|
client=self.client)
|
|
|
|
if (set(self.params.scope).issubset(uc.scope)) and \
|
|
|
|
not (uc.has_expired()):
|
|
|
|
value = True
|
|
|
|
except UserConsent.DoesNotExist:
|
|
|
|
pass
|
|
|
|
|
|
|
|
return value
|