Пример #1
0
def create_id_token(user, aud, nonce):
    """
    Receives a user object and aud (audience).
    Then creates the id_token dictionary.
    See: http://openid.net/specs/openid-connect-core-1_0.html#IDToken

    Return a dic.
    """
    sub = settings.get('OIDC_IDTOKEN_SUB_GENERATOR')(user=user)

    expires_in = settings.get('OIDC_IDTOKEN_EXPIRE')

    # Convert datetimes into timestamps.
    now = timezone.now()
    iat_time = int(time.mktime(now.timetuple()))
    exp_time = int(time.mktime((now + timedelta(seconds=expires_in)).timetuple()))
    user_auth_time = user.last_login or user.date_joined
    auth_time = int(time.mktime(user_auth_time.timetuple()))

    dic = {
        'iss': get_issuer(),
        'sub': sub,
        'aud': str(aud),
        'exp': exp_time,
        'iat': iat_time,
        'auth_time': auth_time,
    }

    if nonce:
        dic['nonce'] = str(nonce)

    return dic
Пример #2
0
def userinfo(request, *args, **kwargs):
    """
    Create a diccionary with all the requested claims about the End-User.
    See: http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse

    Return a diccionary.
    """
    token = kwargs['token']

    dic = {
        'sub': token.id_token.get('sub'),
    }

    standard_claims = StandardScopeClaims(token.user, token.scope)
    dic.update(standard_claims.create_response_dic())

    if settings.get('OIDC_EXTRA_SCOPE_CLAIMS'):
        extra_claims = settings.get('OIDC_EXTRA_SCOPE_CLAIMS', import_str=True)(token.user, token.scope)
        dic.update(extra_claims.create_response_dic())

    response = JsonResponse(dic, status=200)
    response['Cache-Control'] = 'no-store'
    response['Pragma'] = 'no-cache'

    return response
Пример #3
0
    def create_response_dic(self):

        sub = settings.get('OIDC_IDTOKEN_SUB_GENERATOR')(
            user=self.code.user)

        id_token_dic = create_id_token(
            iss=get_issuer(),
            sub=sub,
            aud=self.client.client_id,
            auth_time=self.code.user.last_login)

        token = create_token(
            user=self.code.user,
            client=self.code.client,
            id_token_dic=id_token_dic,
            scope=self.code.scope)

        # Store the token.
        token.save()

        # We don't need to store the code anymore.
        self.code.delete()

        id_token = encode_id_token(id_token_dic, self.client.client_secret)

        dic = {
            'access_token': token.access_token,
            'token_type': 'bearer',
            'expires_in': settings.get('OIDC_TOKEN_EXPIRE'),
            'id_token': id_token,
        }

        return dic
Пример #4
0
def userinfo(request, *args, **kwargs):
    """
    Create a dictionary with all the requested claims about the End-User.
    See: http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse

    Return a dictionary.
    """

    def set_headers(response):
        response['Cache-Control'] = 'no-store'
        response['Pragma'] = 'no-cache'
        cors_allow_any(request, response)
        return response

    if request.method == 'OPTIONS':
        return set_headers(HttpResponse())

    token = kwargs['token']

    dic = {
        'sub': token.id_token.get('sub'),
    }

    standard_claims = StandardScopeClaims(token)
    dic.update(standard_claims.create_response_dic())

    if settings.get('OIDC_EXTRA_SCOPE_CLAIMS'):
        extra_claims = settings.get('OIDC_EXTRA_SCOPE_CLAIMS', import_str=True)(token)
        dic.update(extra_claims.create_response_dic())

    success_response = JsonResponse(dic, status=200)
    set_headers(success_response)

    return success_response
    def test_prompt_login_parameter(self, logout_function):
        """
        Specifies whether the Authorization Server prompts the End-User for
        reauthentication and consent.
        See: http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
        """
        data = {
            'client_id': self.client.client_id,
            'response_type': next(self.client.response_type_values()),
            'redirect_uri': self.client.default_redirect_uri,
            'scope': 'openid email',
            'state': self.state,
            'prompt': 'login'
        }

        response = self._auth_request('get', data)
        self.assertIn(settings.get('OIDC_LOGIN_URL'), response['Location'])
        self.assertNotIn(
            quote('prompt=login'),
            response['Location'],
            "Found prompt=login, this leads to infinite login loop. See "
            "https://github.com/juanifioren/django-oidc-provider/issues/197."
        )

        response = self._auth_request('get', data, is_user_authenticated=True)
        self.assertIn(settings.get('OIDC_LOGIN_URL'), response['Location'])
        self.assertTrue(logout_function.called_once())
        self.assertNotIn(
            quote('prompt=login'),
            response['Location'],
            "Found prompt=login, this leads to infinite login loop. See "
            "https://github.com/juanifioren/django-oidc-provider/issues/197."
        )
Пример #6
0
    def validate_params(self):
        # Make sure appropriate parameters are there
        # See: https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata
        # We want client name (optional), and redirect URIs (required).  
        # "Response type" will default to code (Authorization code flow)
        
        # Is this endpoint enabled in the configuration?
        if not settings.get('REGISTRATION_ENDPOINT_ENABLED'):
            raise RegisterError('invalid_request')
            
        # If authorization is required, has user provided valid access token?
        if settings.get('REGISTRATION_ENDPOINT_REQ_TOKEN'):
            if self.params.access_token is None:
                raise RegisterError('invalid_request')
            # Check whether token is valid
            try: 
                self.token = Token.objects.get(access_token=self.params.access_token)
            
                if self.token.has_expired():
                    logger.error('[Register] Token has expired: %s', self.params.access_token)
                    raise RegisterError('invalid_token')

                if not ('openid' in self.token.scope):
                    logger.error('[Register] Missing openid scope.')
                    raise RegisterError('insufficient_scope')
            
            except Token.DoesNotExist:
                #logger.error('[UserInfo] Token does not exist: %s', self.params.access_token)
                raise RegisterError('invalid_token')
         
        # Has the user provided redirect URIs in JSON?
        if self.params.redirecturis is None:
            raise RegisterError('invalid_request') 
Пример #7
0
    def get(self, request, *args, **kwargs):

        authorize = AuthorizeEndpoint(request)

        try:
            authorize.validate_params()

            if request.user.is_authenticated():
                # Check if there's a hook setted.
                hook_resp = settings.get('OIDC_AFTER_USERLOGIN_HOOK', import_str=True)(
                    request=request, user=request.user,
                    client=authorize.client)
                if hook_resp:
                    return hook_resp

                if settings.get('OIDC_SKIP_CONSENT_ALWAYS') and not (authorize.client.client_type == 'public'):
                    return redirect(authorize.create_response_uri())

                if settings.get('OIDC_SKIP_CONSENT_ENABLE'):
                    # Check if user previously give consent.
                    if authorize.client_has_user_consent() and not (authorize.client.client_type == 'public'):
                        return redirect(authorize.create_response_uri())

                # Generate hidden inputs for the form.
                context = {
                    'params': authorize.params,
                }
                hidden_inputs = render_to_string(
                    'oidc_provider/hidden_inputs.html', context)

                # Remove `openid` from scope list
                # since we don't need to print it.
                if 'openid' in authorize.params.scope:
                    authorize.params.scope.remove('openid')

                context = {
                    'client': authorize.client,
                    'hidden_inputs': hidden_inputs,
                    'params': authorize.params,
                }

                return render(request, 'oidc_provider/authorize.html', context)
            else:
                path = request.get_full_path()
                return redirect_to_login(path)

        except (ClientIdError, RedirectUriError) as error:
            context = {
                'error': error.error,
                'description': error.description,
            }

            return render(request, 'oidc_provider/error.html', context)

        except (AuthorizeError) as error:
            uri = error.create_uri(
                authorize.params.redirect_uri,
                authorize.params.state)

            return redirect(uri)
Пример #8
0
    def get_scopes_information(self):
        """
        Return a list with the description of all the scopes requested.
        """
        scopes = StandardScopeClaims.get_scopes_info(self.params.scope)
        if settings.get('OIDC_EXTRA_SCOPE_CLAIMS'):
            scopes_extra = settings.get('OIDC_EXTRA_SCOPE_CLAIMS', import_str=True).get_scopes_info(self.params.scope)
            for index_extra, scope_extra in enumerate(scopes_extra):
                for index, scope in enumerate(scopes[:]):
                    if scope_extra['scope'] == scope['scope']:
                        del scopes[index]
        else:
            scopes_extra = []

        return scopes + scopes_extra
Пример #9
0
def create_id_token(token, user, aud, nonce='', at_hash='', request=None, scope=None):
    """
    Creates the id_token dictionary.
    See: http://openid.net/specs/openid-connect-core-1_0.html#IDToken
    Return a dic.
    """
    if scope is None:
        scope = []
    sub = settings.get('OIDC_IDTOKEN_SUB_GENERATOR', import_str=True)(user=user)

    expires_in = settings.get('OIDC_IDTOKEN_EXPIRE')

    # Convert datetimes into timestamps.
    now = int(time.time())
    iat_time = now
    exp_time = int(now + expires_in)
    user_auth_time = user.last_login or user.date_joined
    auth_time = int(dateformat.format(user_auth_time, 'U'))

    dic = {
        'iss': get_issuer(request=request),
        'sub': sub,
        'aud': str(aud),
        'exp': exp_time,
        'iat': iat_time,
        'auth_time': auth_time,
    }

    if nonce:
        dic['nonce'] = str(nonce)

    if at_hash:
        dic['at_hash'] = at_hash

    # Inlude (or not) user standard claims in the id_token.
    if settings.get('OIDC_IDTOKEN_INCLUDE_CLAIMS'):
        if settings.get('OIDC_EXTRA_SCOPE_CLAIMS'):
            custom_claims = settings.get('OIDC_EXTRA_SCOPE_CLAIMS', import_str=True)(token)
            claims = custom_claims.create_response_dic()
        else:
            claims = StandardScopeClaims(token).create_response_dic()
        dic.update(claims)

    dic = run_processing_hook(
        dic, 'OIDC_IDTOKEN_PROCESSING_HOOK',
        user=user, token=token, request=request)

    return dic
Пример #10
0
    def create_code_response_dic(self):
        token = create_token(
            user=self.code.user,
            client=self.code.client,
            scope=self.code.scope)

        if self.code.is_authentication:
            id_token_dic = create_id_token(
                user=self.code.user,
                aud=self.client.client_id,
                nonce=self.code.nonce,
                at_hash=token.at_hash,
                request=self.request,
                scope=self.params['scope'],
            )
        else:
            id_token_dic = {}
        token.id_token = id_token_dic

        # Store the token.
        token.save()

        # We don't need to store the code anymore.
        self.code.delete()

        dic = {
            'access_token': token.access_token,
            'refresh_token': token.refresh_token,
            'token_type': 'bearer',
            'expires_in': settings.get('OIDC_TOKEN_EXPIRE'),
            'id_token': encode_id_token(id_token_dic, token.client),
        }

        return dic
Пример #11
0
    def set_client_user_consent(self):
        """
        Save the user consent given to a specific client.

        Return None.
        """
        date_given = timezone.now()
        expires_at = date_given + timedelta(
            days=settings.get('OIDC_SKIP_CONSENT_EXPIRE'))

        uc, created = UserConsent.objects.get_or_create(
            user=self.request.user,
            client=self.client,
            defaults={
                'expires_at': expires_at,
                'date_given': date_given,
            }
        )
        uc.scope = self.params.scope

        # Rewrite expires_at and date_given if object already exists.
        if not created:
            uc.expires_at = expires_at
            uc.date_given = date_given

        uc.save()
    def create_refresh_response_dic(self):
        id_token_dic = create_id_token(
            user=self.token.user,
            aud=self.client.client_id,
            nonce=None,
        )

        token = create_token(
            user=self.token.user,
            client=self.token.client,
            id_token_dic=id_token_dic,
            scope=self.token.scope)

        # Store the token.
        token.save()

        # Forget the old token.
        self.token.delete()

        dic = {
            'access_token': token.access_token,
            'refresh_token': token.refresh_token,
            'token_type': 'bearer',
            'expires_in': settings.get('OIDC_TOKEN_EXPIRE'),
            'id_token': encode_id_token(id_token_dic),
        }

        return dic
Пример #13
0
    def create_response_dic(self):
        id_token_dic = create_id_token(
            user=self.code.user,
            aud=self.client.client_id)

        token = create_token(
            user=self.code.user,
            client=self.code.client,
            id_token_dic=id_token_dic,
            scope=self.code.scope)

        # Store the token.
        token.save()

        # We don't need to store the code anymore.
        self.code.delete()

        id_token = encode_id_token(id_token_dic, self.client.client_secret)

        dic = {
            'access_token': token.access_token,
            'token_type': 'bearer',
            'expires_in': settings.get('OIDC_TOKEN_EXPIRE'),
            'id_token': id_token,
        }
        logger.debug('Response dictionary --> : %s', dic)
        return dic
    def test_user_not_logged(self):
        """
        The Authorization Server attempts to Authenticate the End-User by
        redirecting to the login view.

        See: http://openid.net/specs/openid-connect-core-1_0.html#Authenticates
        """
        query_str = urlencode({
            'client_id': self.client.client_id,
            'response_type': 'code',
            'redirect_uri': self.client.default_redirect_uri,
            'scope': 'openid email',
            'state': self.state,
        }).replace('+', '%20')

        url = reverse('oidc_provider:authorize') + '?' + query_str

        request = self.factory.get(url)
        request.user = AnonymousUser()

        response = AuthorizeView.as_view()(request)

        # Check if user was redirected to the login view.
        login_url_exists = settings.get('LOGIN_URL') in response['Location']
        self.assertEqual(login_url_exists, True)

        # Check if the login will redirect to a valid url.
        try:
            next_value = response['Location'].split(REDIRECT_FIELD_NAME + '=')[1]
            next_url = unquote(next_value)
            is_next_ok = next_url == url
        except:
            is_next_ok = False
        self.assertEqual(is_next_ok, True)
Пример #15
0
    def create_access_token_response_dic(self):
        # See https://tools.ietf.org/html/rfc6749#section-4.3

        token = create_token(
            self.user,
            self.client,
            self.params['scope'].split(' '))

        id_token_dic = create_id_token(
            token=token,
            user=self.user,
            aud=self.client.client_id,
            nonce='self.code.nonce',
            at_hash=token.at_hash,
            request=self.request,
            scope=token.scope,
        )

        token.id_token = id_token_dic
        token.save()

        return {
            'access_token': token.access_token,
            'refresh_token': token.refresh_token,
            'expires_in': settings.get('OIDC_TOKEN_EXPIRE'),
            'token_type': 'bearer',
            'id_token': encode_id_token(id_token_dic, token.client),
        }
Пример #16
0
    def create_refresh_response_dic(self):
        token = create_token(
            user=self.token.user,
            client=self.token.client,
            scope=self.token.scope)

        # If the Token has an id_token it's an Authentication request.
        if self.token.id_token:
            id_token_dic = create_id_token(
                user=self.token.user,
                aud=self.client.client_id,
                nonce=None,
                at_hash=token.at_hash,
                request=self.request,
                scope=self.params['scope'],
            )
        else:
            id_token_dic = {}
        token.id_token = id_token_dic

        # Store the token.
        token.save()

        # Forget the old token.
        self.token.delete()

        dic = {
            'access_token': token.access_token,
            'refresh_token': token.refresh_token,
            'token_type': 'bearer',
            'expires_in': settings.get('OIDC_TOKEN_EXPIRE'),
            'id_token': encode_id_token(id_token_dic, self.token.client),
        }

        return dic
Пример #17
0
    def get(self, request, *args, **kwargs):
        dic = dict()

        site_url = get_site_url(request=request)
        dic['issuer'] = get_issuer(site_url=site_url, request=request)

        dic['authorization_endpoint'] = site_url + reverse('oidc_provider:authorize')
        dic['token_endpoint'] = site_url + reverse('oidc_provider:token')
        dic['userinfo_endpoint'] = site_url + reverse('oidc_provider:userinfo')
        dic['end_session_endpoint'] = site_url + reverse('oidc_provider:end-session')

        types_supported = [x[0] for x in RESPONSE_TYPE_CHOICES]
        dic['response_types_supported'] = types_supported

        dic['jwks_uri'] = site_url + reverse('oidc_provider:jwks')

        dic['id_token_signing_alg_values_supported'] = ['HS256', 'RS256']

        # See: http://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes
        dic['subject_types_supported'] = ['public']

        dic['token_endpoint_auth_methods_supported'] = ['client_secret_post',
                                                        'client_secret_basic']

        if settings.get('OIDC_SESSION_MANAGEMENT_ENABLE'):
            dic['check_session_iframe'] = site_url + reverse('oidc_provider:check-session-iframe')

        response = JsonResponse(dic)
        response['Access-Control-Allow-Origin'] = '*'

        return response
Пример #18
0
    def create_response_dic(self):
        id_token_dic = create_id_token(
            user=self.code.user,
            aud=self.client.client_id,
            nonce=self.code.nonce,
        )

        token = create_token(
            user=self.code.user,
            client=self.code.client,
            id_token_dic=id_token_dic,
            scope=self.code.scope)

        # Store the token.
        token.save()

        # We don't need to store the code anymore.
        self.code.delete()

        dic = {
            'access_token': token.access_token,
            'token_type': 'bearer',
            'expires_in': settings.get('OIDC_TOKEN_EXPIRE'),
            'id_token': encode_id_token(id_token_dic),
        }

        return dic
Пример #19
0
def get_browser_state_or_default(request):
    """
    Determine value to use as session state.
    """
    key = (request.session.session_key or
           settings.get('OIDC_UNAUTHENTICATED_SESSION_MANAGEMENT_KEY'))
    return sha224(key.encode('utf-8')).hexdigest()
Пример #20
0
    def get(self, request, *args, **kwargs):
        dic = dict()

        dic['issuer'] = get_issuer()

        SITE_URL = settings.get('SITE_URL')

        dic['authorization_endpoint'] = SITE_URL + reverse('oidc_provider:authorize')
        dic['token_endpoint'] = SITE_URL + reverse('oidc_provider:token')
        dic['userinfo_endpoint'] = SITE_URL + reverse('oidc_provider:userinfo')
        dic['end_session_endpoint'] = SITE_URL + reverse('oidc_provider:logout')

        types_supported = [x[0] for x in RESPONSE_TYPE_CHOICES]
        dic['response_types_supported'] = types_supported

        dic['jwks_uri'] = SITE_URL + reverse('oidc_provider:jwks')

        dic['id_token_signing_alg_values_supported'] = ['HS256', 'RS256']

        # See: http://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes
        dic['subject_types_supported'] = ['public']

        dic['token_endpoint_auth_methods_supported'] = [ 'client_secret_post',
                                                         'client_secret_basic' ]

        return JsonResponse(dic)
Пример #21
0
def create_id_token(iss, sub, aud, auth_time):
    """
    Receives a user object, iss (issuer) and aud (audience).
    Then creates the id_token dic.
    See: http://openid.net/specs/openid-connect-core-1_0.html#IDToken

    Return a dic.
    """
    expires_in = settings.get('OIDC_IDTOKEN_EXPIRE')

    now = timezone.now()

    # Convert datetimes into timestamps.
    iat_time = time.mktime(now.timetuple())
    exp_time = time.mktime((now + timedelta(seconds=expires_in)).timetuple())
    user_auth_time = time.mktime(auth_time.timetuple())

    dic = {
        'iss': iss,
        'sub': sub,
        'aud': aud,
        'exp': exp_time,
        'iat': iat_time,
        'auth_time': user_auth_time,
    }

    return dic
Пример #22
0
def create_id_token(user, aud, nonce='', at_hash='', request=None, scope=[]):
    """
    Creates the id_token dictionary.
    See: http://openid.net/specs/openid-connect-core-1_0.html#IDToken
    Return a dic.
    """
    sub = settings.get('OIDC_IDTOKEN_SUB_GENERATOR', import_str=True)(user=user)

    expires_in = settings.get('OIDC_IDTOKEN_EXPIRE')

    # Convert datetimes into timestamps.
    now = timezone.now()
    iat_time = int(time.mktime(now.timetuple()))
    exp_time = int(time.mktime((now + timedelta(seconds=expires_in)).timetuple()))
    user_auth_time = user.last_login or user.date_joined
    auth_time = int(time.mktime(user_auth_time.timetuple()))

    dic = {
        'iss': get_issuer(request=request),
        'sub': sub,
        'aud': str(aud),
        'exp': exp_time,
        'iat': iat_time,
        'auth_time': auth_time,
    }

    if nonce:
        dic['nonce'] = str(nonce)

    if at_hash:
        dic['at_hash'] = at_hash

    if ('email' in scope) and getattr(user, 'email', None):
        dic['email'] = user.email

    processing_hook = settings.get('OIDC_IDTOKEN_PROCESSING_HOOK')

    if isinstance(processing_hook, (list, tuple)):
        for hook in processing_hook:
            dic = settings.import_from_str(hook)(dic, user=user)
    else:
        dic = settings.import_from_str(processing_hook)(dic, user=user)

    return dic
Пример #23
0
def run_processing_hook(subject, hook_settings_name, **kwargs):
    processing_hooks = settings.get(hook_settings_name)
    if not isinstance(processing_hooks, (list, tuple)):
        processing_hooks = [processing_hooks]

    for hook_string in processing_hooks:
        hook = settings.import_from_str(hook_string)
        subject = hook(subject, **kwargs)

    return subject
Пример #24
0
def get_issuer():
    """
    Construct the issuer full url. Basically is the site url with some path
    appended.
    """
    site_url = settings.get('SITE_URL')
    path = reverse('oidc_provider:provider_info') \
        .split('/.well-known/openid-configuration/')[0]
    issuer = site_url + path

    return issuer
Пример #25
0
def get_rsa_key():
    """
    Load the rsa key previously created with `creatersakey` command.
    """
    file_path = settings.get('OIDC_RSA_KEY_FOLDER') + '/OIDC_RSA_KEY.pem'
    try:
        with open(file_path, 'r') as f:
            key = f.read()
    except IOError:
        raise IOError('We could not find your key file on: ' + file_path)

    return key
    def test_prompt_consent_parameter(self, render_patched):
        """
        Specifies whether the Authorization Server prompts the End-User for
        reauthentication and consent.
        See: http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
        """
        data = {
            'client_id': self.client.client_id,
            'response_type': next(self.client.response_type_values()),
            'redirect_uri': self.client.default_redirect_uri,
            'scope': 'openid email',
            'state': self.state,
            'prompt': 'consent'
        }

        response = self._auth_request('get', data)
        self.assertIn(settings.get('OIDC_LOGIN_URL'), response['Location'])

        response = self._auth_request('get', data, is_user_authenticated=True)
        render_patched.assert_called_once()
        self.assertTrue(
            render_patched.call_args[0][1], settings.get('OIDC_TEMPLATES')['authorize'])
Пример #27
0
    def dispatch(self, request, *args, **kwargs):
        id_token_hint = request.GET.get('id_token_hint', '')
        post_logout_redirect_uri = request.GET.get('post_logout_redirect_uri', '')
        state = request.GET.get('state', '')
        client = None

        next_page = settings.get('OIDC_LOGIN_URL')
        after_end_session_hook = settings.get('OIDC_AFTER_END_SESSION_HOOK', import_str=True)

        if id_token_hint:
            client_id = client_id_from_id_token(id_token_hint)
            try:
                client = Client.objects.get(client_id=client_id)
                if post_logout_redirect_uri in client.post_logout_redirect_uris:
                    if state:
                        uri = urlsplit(post_logout_redirect_uri)
                        query_params = parse_qs(uri.query)
                        query_params['state'] = state
                        uri = uri._replace(query=urlencode(query_params, doseq=True))
                        next_page = urlunsplit(uri)
                    else:
                        next_page = post_logout_redirect_uri
            except Client.DoesNotExist:
                pass

        after_end_session_hook(
            request=request,
            id_token=id_token_hint,
            post_logout_redirect_uri=post_logout_redirect_uri,
            state=state,
            client=client,
            next_page=next_page
        )

        self.next_page = next_page
        return super(EndSessionView, self).dispatch(request, *args, **kwargs)
    def test_redirects(self):
        query_params = {
            'post_logout_redirect_uri': self.LOGOUT_URL,
        }
        response = self.client.get(self.url, query_params)
        # With no id_token the OP MUST NOT redirect to the requested redirect_uri.
        self.assertRedirects(response, settings.get('LOGIN_URL'), fetch_redirect_response=False)

        id_token_dic = create_id_token(user=self.user, aud=self.oidc_client.client_id)
        id_token = encode_id_token(id_token_dic, self.oidc_client)

        query_params['id_token_hint'] = id_token

        response = self.client.get(self.url, query_params)
        self.assertRedirects(response, self.LOGOUT_URL, fetch_redirect_response=False)
Пример #29
0
def create_code(user, client, scope):
    """
    Create and populate a Code object.

    Return a Code object.
    """
    code = Code()
    code.user = user
    code.client = client
    code.code = uuid.uuid4().hex
    code.expires_at = timezone.now() + timedelta(
        seconds=settings.get('OIDC_CODE_EXPIRE'))
    code.scope = scope

    return code
Пример #30
0
    def create_client_credentials_response_dic(self):
        # See https://tools.ietf.org/html/rfc6749#section-4.4.3

        token = create_token(
            user=None,
            client=self.client,
            scope=self.client.scope)

        token.save()

        return {
            'access_token': token.access_token,
            'expires_in': settings.get('OIDC_TOKEN_EXPIRE'),
            'token_type': 'bearer',
            'scope': self.client._scope,
        }
Пример #31
0
 def set_headers(response):
     response['Cache-Control'] = 'no-store'
     response['Pragma'] = 'no-cache'
     if settings.get('OIDC_CORS_MANAGEMENT_ENABLE'):
         cors_allow_any(request, response)
     return response
Пример #32
0
    def get(self, request, *args, **kwargs):

        authorize = AuthorizeEndpoint(request)

        try:
            authorize.validate_params()

            if request.user.is_authenticated():
                # Check if there's a hook setted.
                hook_resp = settings.get('OIDC_AFTER_USERLOGIN_HOOK',
                                         import_str=True)(
                                             request=request,
                                             user=request.user,
                                             client=authorize.client)
                if hook_resp:
                    return hook_resp

                if settings.get('OIDC_SKIP_CONSENT_ALWAYS') and not (authorize.client.client_type == 'public') \
                and not (authorize.params['prompt'] == 'consent'):
                    return redirect(authorize.create_response_uri())

                if settings.get('OIDC_SKIP_CONSENT_ENABLE'):
                    # Check if user previously give consent.
                    if authorize.client_has_user_consent() and not (authorize.client.client_type == 'public') \
                    and not (authorize.params['prompt'] == 'consent'):
                        return redirect(authorize.create_response_uri())

                if authorize.params['prompt'] == 'none':
                    raise AuthorizeError(authorize.params['redirect_uri'],
                                         'interaction_required',
                                         authorize.grant_type)

                if authorize.params['prompt'] == 'login':
                    return redirect_to_login(request.get_full_path())

                if authorize.params['prompt'] == 'select_account':
                    # TODO: see how we can support multiple accounts for the end-user.
                    raise AuthorizeError(authorize.params['redirect_uri'],
                                         'account_selection_required',
                                         authorize.grant_type)

                # Generate hidden inputs for the form.
                context = {
                    'params': authorize.params,
                }
                hidden_inputs = render_to_string(
                    'oidc_provider/hidden_inputs.html', context)

                # Remove `openid` from scope list
                # since we don't need to print it.
                if 'openid' in authorize.params['scope']:
                    authorize.params['scope'].remove('openid')

                context = {
                    'client': authorize.client,
                    'hidden_inputs': hidden_inputs,
                    'params': authorize.params,
                    'scopes': authorize.get_scopes_information(),
                }

                return render(request, 'oidc_provider/authorize.html', context)
            else:
                if authorize.params['prompt'] == 'none':
                    raise AuthorizeError(authorize.params['redirect_uri'],
                                         'login_required',
                                         authorize.grant_type)

                return redirect_to_login(request.get_full_path())

        except (ClientIdError, RedirectUriError) as error:
            context = {
                'error': error.error,
                'description': error.description,
            }

            return render(request, 'oidc_provider/error.html', context)

        except (AuthorizeError) as error:
            uri = error.create_uri(authorize.params['redirect_uri'],
                                   authorize.params['state'])

            return redirect(uri)
Пример #33
0
    def validate_params(self):
        try:
            self.client = Client.objects.get(
                client_id=self.params['client_id'])
        except Client.DoesNotExist:
            logger.debug('[Token] Client does not exist: %s',
                         self.params['client_id'])
            raise TokenError('invalid_client')

        if self.client.client_type == 'confidential':
            if not (self.client.client_secret == self.params['client_secret']):
                logger.debug(
                    '[Token] Invalid client secret: client %s do not have secret %s',
                    self.client.client_id, self.client.client_secret)
                raise TokenError('invalid_client')

        if self.params['grant_type'] == 'authorization_code':
            if not redirect_uri_valid(self.params['redirect_uri'],
                                      self.client.redirect_uris):
                logger.debug('[Token] Invalid redirect uri: %s',
                             self.params['redirect_uri'])
                raise TokenError('invalid_client')

            try:
                self.code = Code.objects.get(code=self.params['code'])
            except Code.DoesNotExist:
                logger.debug('[Token] Code does not exist: %s',
                             self.params['code'])
                raise TokenError('invalid_grant')

            if not (self.code.client == self.client) \
               or self.code.has_expired():
                logger.debug(
                    '[Token] Invalid code: invalid client or code has expired')
                raise TokenError('invalid_grant')

            # Validate PKCE parameters.
            if self.params['code_verifier']:
                if self.code.code_challenge_method == 'S256':
                    new_code_challenge = urlsafe_b64encode(
                        hashlib.sha256(self.params['code_verifier'].encode(
                            'ascii')).digest()).decode('utf-8').replace(
                                '=', '')
                else:
                    new_code_challenge = self.params['code_verifier']

                # TODO: We should explain the error.
                if not (new_code_challenge == self.code.code_challenge):
                    raise TokenError('invalid_grant')

        elif self.params['grant_type'] == 'password':
            if not settings.get('OIDC_GRANT_TYPE_PASSWORD_ENABLE'):
                raise TokenError('unsupported_grant_type')

            auth_args = (self.request, )
            try:
                inspect.getcallargs(authenticate, *auth_args)
            except TypeError:
                auth_args = ()

            user = authenticate(*auth_args,
                                username=self.params['username'],
                                password=self.params['password'])

            if not user:
                raise UserAuthError()

            self.user = user

        elif self.params['grant_type'] == 'refresh_token':
            if not self.params['refresh_token']:
                logger.debug('[Token] Missing refresh token')
                raise TokenError('invalid_grant')

            try:
                self.token = Token.objects.get(
                    refresh_token=self.params['refresh_token'],
                    client=self.client)

            except Token.DoesNotExist:
                logger.debug('[Token] Refresh token does not exist: %s',
                             self.params['refresh_token'])
                raise TokenError('invalid_grant')
        elif self.params['grant_type'] == 'client_credentials':
            if not self.client._scope:
                logger.debug(
                    '[Token] Client using client credentials with empty scope')
                raise TokenError('invalid_scope')
        else:
            logger.debug('[Token] Invalid grant type: %s',
                         self.params['grant_type'])
            raise TokenError('unsupported_grant_type')
Пример #34
0
from oidc_provider import (
    settings,
    views,
)

app_name = 'oidc_provider'
urlpatterns = [
    url(r'^authorize/?$', views.AuthorizeView.as_view(), name='authorize'),
    url(r'^token/?$', csrf_exempt(views.TokenView.as_view()), name='token'),
    url(r'^refresh/?$',
        csrf_exempt(views.TokenRefreshClientView.as_view()),
        name='token-refresh'),
    url(r'^userinfo/?$', csrf_exempt(views.userinfo), name='userinfo'),
    url(r'^end-session/?$', views.EndSessionView.as_view(),
        name='end-session'),
    url(r'^\.well-known/openid-configuration/?$',
        views.ProviderInfoView.as_view(),
        name='provider-info'),
    url(r'^introspect/?$',
        views.TokenIntrospectionView.as_view(),
        name='token-introspection'),
    url(r'^jwks/?$', views.JwksView.as_view(), name='jwks'),
]

if settings.get('OIDC_SESSION_MANAGEMENT_ENABLE'):
    urlpatterns += [
        url(r'^check-session-iframe/?$',
            views.CheckSessionIframeView.as_view(),
            name='check-session-iframe'),
    ]
Пример #35
0
    get_site_url,
    get_issuer,
)
from oidc_provider.lib.utils.oauth2 import protected_resource_view
from oidc_provider.lib.utils.token import client_id_from_id_token
from oidc_provider.models import (
    Client,
    RESPONSE_TYPE_CHOICES,
    RSAKey,
)
from oidc_provider import settings
from oidc_provider import signals

logger = logging.getLogger(__name__)

OIDC_TEMPLATES = settings.get('OIDC_TEMPLATES')


class AuthorizeView(View):
    def get(self, request, *args, **kwargs):

        authorize = AuthorizeEndpoint(request)

        try:
            authorize.validate_params()

            if request.user.is_authenticated():
                # Check if there's a hook setted.
                hook_resp = settings.get('OIDC_AFTER_USERLOGIN_HOOK',
                                         import_str=True)(
                                             request=request,
Пример #36
0
 def __init__(self, user, scopes):
     self.user = user
     self.userinfo = settings.get('OIDC_USERINFO',
                                  import_str=True).get_by_user(self.user)
     self.scopes = scopes
Пример #37
0
    def create_response_uri(self):
        uri = urlsplit(self.params['redirect_uri'])
        query_params = parse_qs(uri.query)
        query_fragment = {}

        try:
            if self.grant_type in ['authorization_code', 'hybrid']:
                try:
                    session = self.request.session
                except AttributeError:
                    session = {}

                code = create_code(
                    user=self.request.user,
                    client=self.client,
                    scope=self.params['scope'],
                    nonce=self.params['nonce'],
                    is_authentication=self.is_authentication,
                    code_challenge=self.params['code_challenge'],
                    code_challenge_method=self.params['code_challenge_method'],
                    acr=session['acr'] if 'acr' in session else '',
                    amr=session['amr'] if 'amr' in session else '',
                    ae=session['ae'] if 'ae' in session else None,
                    rid=session['rid'] if 'rid' in session else None)
                code.save()

            if self.grant_type == 'authorization_code':
                query_params['code'] = code.code
                query_params['state'] = self.params['state'] if self.params['state'] else ''
            elif self.grant_type in ['implicit', 'hybrid']:
                token = create_token(
                    user=self.request.user,
                    client=self.client,
                    scope=self.params['scope'])

                # Check if response_type must include access_token in the response.
                if (self.params['response_type'] in
                   ['id_token token', 'token', 'code token', 'code id_token token']):
                    query_fragment['access_token'] = token.access_token

                # We don't need id_token if it's an OAuth2 request.
                if self.is_authentication:
                    kwargs = {
                        'token': token,
                        'user': self.request.user,
                        'aud': self.client.client_id,
                        'nonce': self.params['nonce'],
                        'request': self.request,
                        'scope': self.params['scope'],
                    }
                    # Include at_hash when access_token is being returned.
                    if 'access_token' in query_fragment:
                        kwargs['at_hash'] = token.at_hash
                    id_token_dic = create_id_token(**kwargs)

                    # Check if response_type must include id_token in the response.
                    if self.params['response_type'] in [
                            'id_token', 'id_token token', 'code id_token', 'code id_token token']:
                        query_fragment['id_token'] = encode_id_token(id_token_dic, self.client)
                else:
                    id_token_dic = {}

                # Store the token.
                token.id_token = id_token_dic
                token.save()

                # Code parameter must be present if it's Hybrid Flow.
                if self.grant_type == 'hybrid':
                    query_fragment['code'] = code.code

                query_fragment['token_type'] = 'bearer'

                query_fragment['expires_in'] = settings.get('OIDC_TOKEN_EXPIRE')

                query_fragment['state'] = self.params['state'] if self.params['state'] else ''

            if settings.get('OIDC_SESSION_MANAGEMENT_ENABLE'):
                # Generate client origin URI from the redirect_uri param.
                redirect_uri_parsed = urlsplit(self.params['redirect_uri'])
                client_origin = '{0}://{1}'.format(
                    redirect_uri_parsed.scheme, redirect_uri_parsed.netloc)

                # Create random salt.
                salt = md5(uuid4().hex.encode()).hexdigest()

                # The generation of suitable Session State values is based
                # on a salted cryptographic hash of Client ID, origin URL,
                # and OP browser state.
                session_state = '{client_id} {origin} {browser_state} {salt}'.format(
                    client_id=self.client.client_id,
                    origin=client_origin,
                    browser_state=get_browser_state_or_default(self.request),
                    salt=salt)
                session_state = sha256(session_state.encode('utf-8')).hexdigest()
                session_state += '.' + salt
                if self.grant_type == 'authorization_code':
                    query_params['session_state'] = session_state
                elif self.grant_type in ['implicit', 'hybrid']:
                    query_fragment['session_state'] = session_state

        except Exception as error:
            logger.exception('[Authorize] Error when trying to create response uri: %s', error)
            raise AuthorizeError(self.params['redirect_uri'], 'server_error', self.grant_type)

        uri = uri._replace(
            query=urlencode(query_params, doseq=True),
            fragment=uri.fragment + urlencode(query_fragment, doseq=True))

        return urlunsplit(uri)
Пример #38
0
def get_browser_state_or_default(request):
    """
    Determine value to use as session state.
    """
    key = request.session.session_key or settings.get('OIDC_UNAUTHENTICATED_SESSION_MANAGEMENT_KEY')
    return sha224(key.encode('utf-8')).hexdigest()
Пример #39
0
from oidc_provider.lib.utils.authorize import strip_prompt_login
from oidc_provider.lib.utils.common import (
    redirect,
    get_site_url,
    get_issuer,
    cors_allow_any,
)
from oidc_provider.lib.utils.oauth2 import protected_resource_view
from oidc_provider.lib.utils.token import client_id_from_id_token
from oidc_provider.models import (Client, RSAKey, ResponseType)
from oidc_provider import settings
from oidc_provider import signals

logger = logging.getLogger(__name__)

OIDC_TEMPLATES = settings.get('OIDC_TEMPLATES')
AUTHORIZE_CONTEXT_TRANSFORMER = settings.get('OIDC_CONTEXT_TRANSFORMERS',
                                             import_str=True,
                                             key='authorize')
OIDC_PRIOR_TO_REDIRECT_HOOK = settings.get('OIDC_PRIOR_TO_REDIRECT_HOOK',
                                           import_str=True)
OIDC_DECLINED_USERCONSENT_HOOK = settings.get('OIDC_DECLINED_USERCONSENT_HOOK',
                                              import_str=True)


def call_oidc_prior_to_redirect_hook(request, client):
    if OIDC_PRIOR_TO_REDIRECT_HOOK:
        OIDC_PRIOR_TO_REDIRECT_HOOK(request=request,
                                    user=request.user,
                                    client=client)
Пример #40
0
    def get(self, request, *args, **kwargs):
        authorize = self.authorize_endpoint_class(request)

        try:
            authorize.validate_params()

            if get_attr_or_callable(request.user, 'is_authenticated'):
                # Check if there's a hook setted.
                hook_resp = settings.get('OIDC_AFTER_USERLOGIN_HOOK',
                                         import_str=True)(
                                             request=request,
                                             user=request.user,
                                             client=authorize.client)
                if hook_resp:
                    return hook_resp

                check_login_required(request, authorize)

                if 'select_account' in authorize.params['prompt']:
                    # TODO: see how we can support multiple accounts for the end-user.
                    if 'none' in authorize.params['prompt']:
                        raise AuthorizeError(authorize.params['redirect_uri'],
                                             'account_selection_required',
                                             authorize.grant_type)
                    else:
                        django_user_logout(request)
                        return redirect_to_login(
                            request.get_full_path(),
                            settings.get('OIDC_LOGIN_URL'))

                if {'none', 'consent'}.issubset(authorize.params['prompt']):
                    raise AuthorizeError(authorize.params['redirect_uri'],
                                         'consent_required',
                                         authorize.grant_type)

                implicit_flow_resp_types = {'id_token', 'id_token token'}
                allow_skipping_consent = (
                    authorize.client.client_type != 'public'
                    or authorize.params['response_type']
                    in implicit_flow_resp_types)

                if not authorize.client.require_consent and (
                        allow_skipping_consent
                        and 'consent' not in authorize.params['prompt']):
                    call_oidc_prior_to_redirect_hook(request, authorize.client)
                    return redirect(authorize.create_response_uri())

                if authorize.client.reuse_consent:
                    # Check if user previously give consent.
                    if authorize.client_has_user_consent() and (
                            allow_skipping_consent
                            and 'consent' not in authorize.params['prompt']):
                        call_oidc_prior_to_redirect_hook(
                            request, authorize.client)
                        return redirect(authorize.create_response_uri())

                if 'none' in authorize.params['prompt']:
                    raise AuthorizeError(authorize.params['redirect_uri'],
                                         'consent_required',
                                         authorize.grant_type)

                # Generate hidden inputs for the form.
                context = {
                    'params': authorize.params,
                }
                hidden_inputs = render_to_string(
                    'oidc_provider/hidden_inputs.html', context)

                # Remove `openid` from scope list
                # since we don't need to print it.
                if 'openid' in authorize.params['scope']:
                    authorize.params['scope'].remove('openid')

                context = {
                    'client': authorize.client,
                    'hidden_inputs': hidden_inputs,
                    'params': authorize.params,
                    'scopes': authorize.get_scopes_information(),
                }

                # Extra OIDC context, if required, from a callable.
                if AUTHORIZE_CONTEXT_TRANSFORMER:
                    context = AUTHORIZE_CONTEXT_TRANSFORMER(request, context)

                return render(request, OIDC_TEMPLATES['authorize'], context)
            else:
                if 'none' in authorize.params['prompt']:
                    raise AuthorizeError(authorize.params['redirect_uri'],
                                         'login_required',
                                         authorize.grant_type)
                if 'login' in authorize.params['prompt']:
                    next_page = strip_prompt_login(request.get_full_path())
                    return redirect_to_login(next_page,
                                             settings.get('OIDC_LOGIN_URL'))

                return redirect_to_login(request.get_full_path(),
                                         settings.get('OIDC_LOGIN_URL'))

        except (ClientIdError, RedirectUriError) as error:
            context = {
                'error': error.error,
                'description': error.description,
            }

            return render(request, OIDC_TEMPLATES['error'], context)

        except AuthorizeError as error:
            uri = error.create_uri(authorize.params['redirect_uri'],
                                   authorize.params['state'])

            return redirect(uri)
        except JustRedirect as error:
            return error.redirection
 def process_response(self, request, response):
     if settings.get('OIDC_SESSION_MANAGEMENT_ENABLE'):
         response.set_cookie('op_browser_state',
                             get_browser_state_or_default(request))
     return response
Пример #42
0
class EndSessionTestCase(TestCase):
    """
    See: http://openid.net/specs/openid-connect-session-1_0.html#RPLogout
    """
    def setUp(self):
        call_command('creatersakey')
        self.user = create_fake_user()

        self.oidc_client = create_fake_client('id_token')
        self.LOGOUT_URL = 'http://example.com/logged-out/'
        self.oidc_client.post_logout_redirect_uris = [self.LOGOUT_URL]
        self.oidc_client.save()

        self.url = reverse('oidc_provider:end-session')

    @override_settings(OIDC_LOGOUT_URL='/post-logout/')
    def test_redirects_when_aud_is_str(self):
        query_params = {
            'post_logout_redirect_uri': self.LOGOUT_URL,
        }
        response = self.client.get(self.url, query_params)
        # With no id_token the OP MUST NOT redirect to the requested
        # redirect_uri.
        self.assertRedirects(response,
                             '/post-logout/',
                             fetch_redirect_response=False)

        token = create_token(self.user, self.oidc_client, [])
        id_token_dic = create_id_token(token=token,
                                       user=self.user,
                                       aud=self.oidc_client.client_id)
        id_token = encode_id_token(id_token_dic, self.oidc_client)

        query_params['id_token_hint'] = id_token

        response = self.client.get(self.url, query_params)
        self.assertRedirects(response,
                             self.LOGOUT_URL,
                             fetch_redirect_response=False)

    def test_redirects_when_aud_is_list(self):
        """Check with 'aud' containing a list of str."""
        query_params = {
            'post_logout_redirect_uri': self.LOGOUT_URL,
        }
        token = create_token(self.user, self.oidc_client, [])
        id_token_dic = create_id_token(token=token,
                                       user=self.user,
                                       aud=self.oidc_client.client_id)
        id_token_dic['aud'] = [id_token_dic['aud']]
        id_token = encode_id_token(id_token_dic, self.oidc_client)
        query_params['id_token_hint'] = id_token
        response = self.client.get(self.url, query_params)
        self.assertRedirects(response,
                             self.LOGOUT_URL,
                             fetch_redirect_response=False)

    @mock.patch(settings.get('OIDC_AFTER_END_SESSION_HOOK'))
    def test_call_post_end_session_hook(self, hook_function):
        self.client.get(self.url)
        self.assertTrue(hook_function.called,
                        'OIDC_AFTER_END_SESSION_HOOK should be called')
        self.assertTrue(hook_function.call_count == 1,
                        'OIDC_AFTER_END_SESSION_HOOK should be called once')