def test_grant_authorization_code(self, settings, client, user):

        # Secure connection check
        with settings(DEBUG=False):
            resp = client.get(URL_TOKEN, {})
            assert resp.status_code == 403

        settings.DEBUG = True

        resp = client.post(URL_TOKEN, {'grant_type': 'a'})
        assert resp.status_code == 400
        assert resp.content_json['error'] == 'unsupported_grant_type'

        client_1 = Client(user=user, title='OClient')
        client_1.save()

        redirect_1 = RedirectionEndpoint(client=client_1, uri='http://redirect-test.com')
        redirect_1.save()

        code_1 = AuthorizationCode(user=user, client=client_1, uri=redirect_1.uri)
        code_1.save()

        Scope(identifier='scope1').save()

        # Missing client authentication data.
        resp = client.post(URL_TOKEN, {'grant_type': 'authorization_code', 'scope': 'scope1'})
        assert resp.status_code == 401
        assert resp.content_json['error'] == 'invalid_client'

        # Missing all required params.
        resp = client.post(
            URL_TOKEN, 
            {'grant_type': 'authorization_code', 'scope': 'scope1', 
             'client_id': client_1.identifier, 'client_secret': client_1.password})
        
        assert resp.status_code == 400
        assert resp.content_json['error'] == 'invalid_request'

        # Missing redirect URI.
        resp = client.post(
            URL_TOKEN, 
            {'grant_type': 'authorization_code', 'scope': 'scope1', 'code': 'wrong_code',
             'client_id': client_1.identifier, 'client_secret': client_1.password})
        
        assert resp.status_code == 400
        assert resp.content_json['error'] == 'invalid_request'

        # Missing code.
        resp = client.post(
            URL_TOKEN,
            {'grant_type': 'authorization_code', 'scope': 'scope1',
             'redirect_uri': 'http://wrong-url.com', 'client_id': client_1.identifier,
             'client_secret': client_1.password})

        assert resp.status_code == 400
        assert resp.content_json['error'] == 'invalid_request'

        # Wrong code.
        resp = client.post(
            URL_TOKEN,
            {'grant_type': 'authorization_code', 'scope': 'scope1', 'code': 'invalid',
             'redirect_uri': 'http://localhost:8000/abc/',
             'client_id': client_1.identifier, 'client_secret': client_1.password})

        assert resp.status_code == 400
        assert resp.content_json['error'] == 'invalid_grant'

        # Wrong URI.
        resp = client.post(
            URL_TOKEN,
            {'grant_type': 'authorization_code', 'scope': 'scope1', 'code': code_1.code,
             'redirect_uri': 'http://wrong-url.com/', 'client_id': client_1.identifier,
             'client_secret': client_1.password})

        assert resp.status_code == 400
        assert resp.content_json['error'] == 'invalid_grant'

        # Valid call for a token.
        resp = client.post(
            URL_TOKEN,
            {'grant_type': 'authorization_code', 'scope': 'scope1', 'code': code_1.code,
             'redirect_uri': redirect_1.uri, 'client_id': client_1.identifier, 'client_secret': client_1.password})

        assert resp.status_code == 200
        assert 'access_token' in resp.content_json
        assert 'refresh_token' in resp.content_json
        assert 'token_type' in resp.content_json

        # An additional call for code issues token and code invalidation.
        resp = client.post(
            URL_TOKEN,
            {'grant_type': 'authorization_code', 'scope': 'scope1', 'code': '1234567',
             'redirect_uri': 'http://localhost:8000/abc/',
             'client_id': client_1.identifier, 'client_secret': client_1.password})

        assert resp.status_code == 400
        assert resp.content_json['error'] == 'invalid_grant'
Exemple #2
0
def endpoint_authorize(request):
    """
    SPEC: The authorization endpoint is used to interact with the resource
    owner and obtain an authorization grant.

    SPEC: The authorization server MUST support TLS...
    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.

    """

    # SPEC: Since requests to the authorization endpoint result in user authentication
    # and the transmission of clear-text credentials (in the HTTP response),
    # the authorization server MUST require the use of a transport-layer
    # security mechanism when sending requests to the authorization endpoint.
    if not request.is_secure() and not settings.DEBUG:
        # Insecure connections are only available in debug mode.
        return ep_auth_response_error_page(
            request, _('OAuth requires secure connection to be established.'),
            403)

    if request.POST.get('auth_decision') is None:
        # User has made no decision on auth confirmation yet.

        input_params = filter_input_params(request.REQUEST)

        response_type = input_params.get('response_type')
        client_id = input_params.get('client_id')

        redirect_uri = input_params.get('redirect_uri')
        redirect_uri_final = redirect_uri

        if client_id is None:
            # Fail fast without a DB hit.
            return ep_auth_response_error_page(
                request, _('Client ID must be supplied.'))

        if response_type not in REGISTRY_EP_AUTH_RESPONSE_TYPE:
            return ep_auth_response_error_page(
                request,
                _('Unknown response type requested. Expected: %s.') %
                ', '.join(REGISTRY_EP_AUTH_RESPONSE_TYPE))

        try:
            client = Client.objects.get(identifier=client_id)
        except ObjectDoesNotExist:
            LOGGER.error(
                'Invalid client ID supplied. Value "%s" was sent from IP "%s".'
                % (client_id, get_remote_ip(request)))
            return ep_auth_response_error_page(
                request, _('Invalid client ID is supplied.'))

        # TODO There should be at least one redirection URI associated with a client. URI validity should be checked while such an association is made.
        registered_uris = [
            url[0] for url in client.redirectionendpoint_set.values_list('uri')
        ]

        # Check redirection URI validity.
        if redirect_uri is None:
            # redirect_uri is optional and was not supplied.
            if len(registered_uris) == 1:
                # There is only one URI associated with client, so we use it.
                redirect_uri_final = registered_uris[0]
            else:
                # Several URIs are registered with the client, decision is ambiguous.
                LOGGER.error(
                    'Redirect URI is no supplied client with ID "%s". Request from IP "%s".'
                    % (client.id, get_remote_ip(request)))
                return ep_auth_response_error_page(
                    request,
                    _('Redirect URI should be supplied for given client.'))

        # SPEC: The authorization server SHOULD NOT redirect the user-agent to unregistered or untrusted URIs
        # to prevent the authorization endpoint from being used as an open redirector.
        if redirect_uri_final not in registered_uris:
            LOGGER.error(
                'An attempt to use an untrusted URI "%s" for client with ID "%s". Request from IP "%s".'
                % (redirect_uri_final, client.id, get_remote_ip(request)))
            return ep_auth_response_error_page(
                request,
                _('Redirection URI supplied is not associated with given client.'
                  ))

        # Access token scope requested,
        scopes_to_apply = resolve_scopes_to_apply(input_params.get('scope'),
                                                  client)

        request.session['oauth_client_id'] = client.id
        request.session['oauth_response_type'] = response_type
        request.session['oauth_redirect_uri'] = redirect_uri_final
        request.session['oauth_scopes_ids'] = [s.id for s in scopes_to_apply]
        request.session['oauth_state'] = input_params.get('state')

        dict_data = {
            'client': client,
            'scopes_obj': scopes_to_apply,
            'oauthost_title': _('Authorization Request')
        }
        return render(request, OAUTHOST_TEMPLATE_AUTHORIZE, dict_data)

    # ========================================================================================
    # User has made his choice using auth form.

    redirect_uri = request.session.get('oauth_redirect_uri')
    response_type = request.session.get('oauth_response_type')
    params_as_uri_fragment = (response_type == 'token')

    if request.POST.get('confirmed') is None:
        # User has declined authorization.
        ep_auth_clear_session_data(request)
        return ep_auth_response_error(redirect_uri, params_as_uri_fragment,
                                      'access_denied',
                                      'Authorization is canceled by user')

    # User confirmed authorization using a web-form.
    client = Client.objects.get(pk=request.session.get('oauth_client_id'))
    scopes_to_apply = Scope.objects.filter(
        id__in=request.session.get('oauth_scopes_ids')).all()

    output_params = {}
    auth_obj = None

    # Used for "Authorization code" Grant Type.
    if response_type == 'code':
        # Generating Authorization Code.
        auth_obj = AuthorizationCode(client=client,
                                     user=request.user,
                                     uri=redirect_uri)
        auth_obj.save()
        output_params['code'] = auth_obj.code

    # Used as "Implicit" Grant Type.
    if response_type == 'token':
        expires_in = client.token_lifetime
        expires_at = None
        if expires_in is not None:
            output_params['expires_in'] = expires_in
            expires_at = datetime.fromtimestamp(int(time() + expires_in))
        # Generating Token.
        auth_obj = Token(client=client,
                         user=request.user,
                         expires_at=expires_at)
        auth_obj.save()
        output_params['access_token'] = auth_obj.access_token
        output_params['token_type'] = auth_obj.access_token_type

    if auth_obj is not None:
        # Link scopes to auth object.
        for scope in scopes_to_apply:
            auth_obj.scopes.add(scope)

    state = request.session.get('state')
    if state is not None:
        output_params['state'] = state

    ep_auth_clear_session_data(request)

    # SPEC: Developers should note that some HTTP client implementations do not
    # support the inclusion of a fragment component in the HTTP "Location"
    # response header field.  Such client will require using other methods
    # for redirecting the client than a 3xx redirection response.
    if not client.hash_sign_supported:
        data_dict = {
            'action_uri':
            ep_auth_build_redirect_uri(redirect_uri, output_params,
                                       params_as_uri_fragment)
        }
        return render(request, OAUTHOST_TEMPLATE_AUTHORIZE_PROCEED, data_dict)

    return ep_auth_response(redirect_uri, output_params,
                            params_as_uri_fragment)
Exemple #3
0
    def test_grant_authorization_code(self):

        # Secure connection check
        settings.DEBUG = False
        resp = self.client.get(URL_TOKEN, {})
        self.assertEqual(resp.status_code, 403)
        settings.DEBUG = True

        resp = self.client.post(URL_TOKEN, {'grant_type': 'a'})
        self.assertEqual(resp.status_code, 400)
        self.assertEqual(resp.content_json['error'], 'unsupported_grant_type')

        user_1 = User(username='******')
        user_1.set_password('12345')
        user_1.save()

        client_1 = Client(user=user_1, title='OClient')
        client_1.save()

        redirect_1 = RedirectionEndpoint(client=client_1, uri='http://redirect-test.com')
        redirect_1.save()

        code_1 = AuthorizationCode(user=user_1, client=client_1, uri=redirect_1.uri)
        code_1.save()

        # Missing client authentication data.
        resp = self.client.post(URL_TOKEN, {'grant_type': 'authorization_code'})
        self.assertEqual(resp.status_code, 401)
        self.assertEqual(resp.content_json['error'], 'invalid_client')

        # Missing all required params.
        resp = self.client.post(URL_TOKEN, {'grant_type': 'authorization_code', 'client_id': client_1.identifier,
                                             'client_secret': client_1.password})
        self.assertEqual(resp.status_code, 400)
        self.assertEqual(resp.content_json['error'], 'invalid_request')

        # Missing redirect URI.
        resp = self.client.post(URL_TOKEN, {'grant_type': 'authorization_code', 'code': 'wrong_code',
                                             'client_id': client_1.identifier, 'client_secret': client_1.password})
        self.assertEqual(resp.status_code, 400)
        self.assertEqual(resp.content_json['error'], 'invalid_request')

        # Missing code.
        resp = self.client.post(URL_TOKEN, {'grant_type': 'authorization_code', 'redirect_uri': 'http://wrong-url.com',
                                             'client_id': client_1.identifier, 'client_secret': client_1.password})
        self.assertEqual(resp.status_code, 400)
        self.assertEqual(resp.content_json['error'], 'invalid_request')

        # Wrong code.
        resp = self.client.post(URL_TOKEN, {'grant_type': 'authorization_code', 'code': 'invalid',
                                             'redirect_uri': 'http://localhost:8000/abc/',
                                             'client_id': client_1.identifier, 'client_secret': client_1.password})
        self.assertEqual(resp.status_code, 400)
        self.assertEqual(resp.content_json['error'], 'invalid_grant')

        # Wrong URI.
        resp = self.client.post(URL_TOKEN, {'grant_type': 'authorization_code', 'code': code_1.code,
                                             'redirect_uri': 'http://wrong-url.com/', 'client_id': client_1.identifier,
                                             'client_secret': client_1.password})
        self.assertEqual(resp.status_code, 400)
        self.assertEqual(resp.content_json['error'], 'invalid_grant')

        # Valid call for a token.
        resp = self.client.post(URL_TOKEN, {'grant_type': 'authorization_code', 'code': code_1.code,
                                             'redirect_uri': redirect_1.uri, 'client_id': client_1.identifier,
                                             'client_secret': client_1.password})
        self.assertEqual(resp.status_code, 200)
        self.assertTrue('access_token' in resp.content_json)
        self.assertTrue('refresh_token' in resp.content_json)
        self.assertTrue('token_type' in resp.content_json)

        # An additional call for code issues token and code invalidation.
        resp = self.client.post(URL_TOKEN, {'grant_type': 'authorization_code', 'code': '1234567',
                                             'redirect_uri': 'http://localhost:8000/abc/',
                                             'client_id': client_1.identifier, 'client_secret': client_1.password})
        self.assertEqual(resp.status_code, 400)
        self.assertEqual(resp.content_json['error'], 'invalid_grant')
Exemple #4
0
def endpoint_authorize(request):
    """
    SPEC: The authorization endpoint is used to interact with the resource
    owner and obtain an authorization grant.

    SPEC: The authorization server MUST support TLS...
    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.

    """

    # SPEC: Since requests to the authorization endpoint result in user authentication
    # and the transmission of clear-text credentials (in the HTTP response),
    # the authorization server MUST require the use of a transport-layer
    # security mechanism when sending requests to the authorization endpoint.
    if not request.is_secure() and not settings.DEBUG:
        # Insecure connections are only available in debug mode.
        return ep_auth_response_error_page(request, _('OAuth requires secure connection to be established.'), 403)

    if request.POST.get('auth_decision') is None:
        # User has made no decision on auth confirmation yet.

        input_params = filter_input_params(request.REQUEST)

        response_type = input_params.get('response_type')
        client_id = input_params.get('client_id')

        redirect_uri = input_params.get('redirect_uri')
        redirect_uri_final = redirect_uri

        if client_id is None:
            # Fail fast without a DB hit.
            return ep_auth_response_error_page(request, _('Client ID must be supplied.'))

        if response_type not in REGISTRY_EP_AUTH_RESPONSE_TYPE:
            return ep_auth_response_error_page(request, _('Unknown response type requested. Expected: %s.') % ', '.join(REGISTRY_EP_AUTH_RESPONSE_TYPE))

        try:
            client = Client.objects.get(identifier=client_id)
        except ObjectDoesNotExist:
            LOGGER.error('Invalid client ID supplied. Value "%s" was sent from IP "%s".' % (client_id, get_remote_ip(request)))
            return ep_auth_response_error_page(request, _('Invalid client ID is supplied.'))

        # TODO There should be at least one redirection URI associated with a client. URI validity should be checked while such an association is made.
        registered_uris = [url[0] for url in client.redirectionendpoint_set.values_list('uri')]

        # Check redirection URI validity.
        if redirect_uri is None:
            # redirect_uri is optional and was not supplied.
            if len(registered_uris) == 1:
                # There is only one URI associated with client, so we use it.
                redirect_uri_final = registered_uris[0]
            else:
                # Several URIs are registered with the client, decision is ambiguous.
                LOGGER.error('Redirect URI is no supplied client with ID "%s". Request from IP "%s".' % (client.id, get_remote_ip(request)))
                return ep_auth_response_error_page(request, _('Redirect URI should be supplied for given client.'))

        # SPEC: The authorization server SHOULD NOT redirect the user-agent to unregistered or untrusted URIs
        # to prevent the authorization endpoint from being used as an open redirector.
        if redirect_uri_final not in registered_uris:
            LOGGER.error('An attempt to use an untrusted URI "%s" for client with ID "%s". Request from IP "%s".' % (redirect_uri_final, client.id, get_remote_ip(request)))
            return ep_auth_response_error_page(request, _('Redirection URI supplied is not associated with given client.'))

        # Access token scope requested,
        scopes_to_apply = resolve_scopes_to_apply(input_params.get('scope'), client)

        request.session['oauth_client_id'] = client.id
        request.session['oauth_response_type'] = response_type
        request.session['oauth_redirect_uri'] = redirect_uri_final
        request.session['oauth_scopes_ids'] = [s.id for s in scopes_to_apply]
        request.session['oauth_state'] = input_params.get('state')

        dict_data = {
            'client': client,
            'scopes_obj': scopes_to_apply,
            'oauthost_title': _('Authorization Request')
        }
        return render(request, OAUTHOST_TEMPLATE_AUTHORIZE, dict_data)

    # ========================================================================================
    # User has made his choice using auth form.

    redirect_uri = request.session.get('oauth_redirect_uri')
    response_type = request.session.get('oauth_response_type')
    params_as_uri_fragment = (response_type == 'token')

    if request.POST.get('confirmed') is None:
        # User has declined authorization.
        ep_auth_clear_session_data(request)
        return ep_auth_response_error(redirect_uri, params_as_uri_fragment, 'access_denied', 'Authorization is canceled by user')

    # User confirmed authorization using a web-form.
    client = Client.objects.get(pk=request.session.get('oauth_client_id'))
    scopes_to_apply = Scope.objects.filter(id__in=request.session.get('oauth_scopes_ids')).all()

    output_params = {}
    auth_obj = None

    # Used for "Authorization code" Grant Type.
    if response_type == 'code':
        # Generating Authorization Code.
        auth_obj = AuthorizationCode(client=client, user=request.user, uri=redirect_uri)
        auth_obj.save()
        output_params['code'] = auth_obj.code

    # Used as "Implicit" Grant Type.
    if response_type == 'token':
        expires_in = client.token_lifetime
        expires_at = None
        if expires_in is not None:
            output_params['expires_in'] = expires_in
            expires_at = datetime.fromtimestamp(int(time() + expires_in))
        # Generating Token.
        auth_obj = Token(client=client, user=request.user, expires_at=expires_at)
        auth_obj.save()
        output_params['access_token'] = auth_obj.access_token
        output_params['token_type'] = auth_obj.access_token_type

    if auth_obj is not None:
        # Link scopes to auth object.
        for scope in scopes_to_apply:
            auth_obj.scopes.add(scope)

    state = request.session.get('state')
    if state is not None:
        output_params['state'] = state

    ep_auth_clear_session_data(request)

    # SPEC: Developers should note that some HTTP client implementations do not
    # support the inclusion of a fragment component in the HTTP "Location"
    # response header field.  Such client will require using other methods
    # for redirecting the client than a 3xx redirection response.
    if not client.hash_sign_supported:
        data_dict = {'action_uri': ep_auth_build_redirect_uri(redirect_uri, output_params, params_as_uri_fragment)}
        return render(request, OAUTHOST_TEMPLATE_AUTHORIZE_PROCEED, data_dict)

    return ep_auth_response(redirect_uri, output_params, params_as_uri_fragment)