Esempio n. 1
0
 def testClientAttributeTypes(self):
     """ Ensure that all attributes of the client are of the expected type. """
     client = PasswordClient('clientId', ['https://valid.nonexistent'],
                             ['password'], 'secret')
     self.assertTrue(isAnyStr(client.id),
                     msg='The client id must be a string.')
     self.assertIsInstance(client.secret,
                           str,
                           message='The client secret must be a string.')
     self.assertIsInstance(client.redirectUris,
                           list,
                           message='The redirect uris must be a list.')
     for uri in client.redirectUris:
         self.assertIsInstance(uri,
                               str,
                               message='All redirect uris must be strings.')
     self.assertIsInstance(
         client.authorizedGrantTypes,
         list,
         message='The authorized grant types must be a list.')
     for grantType in client.authorizedGrantTypes:
         self.assertIsInstance(grantType,
                               str,
                               message='All grant types must be strings.')
     client = PasswordClient(u'clientId', ['https://valid.nonexistent'],
                             ['password'], 'secret')
     self.assertTrue(isAnyStr(client.id),
                     msg='The client id must be a string.')
Esempio n. 2
0
 def testCustomResponseTypeNotAllowed(self):
     """ Test that a request with a custom response type is rejected if it is not enabled. """
     responseType = 'myCustomResponseType'
     state = b'state\xFF\xFF'
     client = PasswordClient('customResponseTypeClientNotAllowed',
                             ['https://redirect.noexistent'],
                             [responseType], 'clientSecret')
     redirectUri = client.redirectUris[0]
     parameters = {
         'response_type': responseType,
         'client_id': client.id,
         'redirect_uri': redirectUri,
         'scope': 'All',
         'state': state
     }
     request = self.createAuthRequest(arguments=parameters)
     self._CLIENT_STORAGE.addClient(client)
     result = self._AUTH_RESOURCE.render_GET(request)
     self.assertFailedRequest(
         request,
         result,
         UnsupportedResponseTypeError(responseType, state),
         redirectUri=redirectUri,
         msg='Expected the authorization token resource to reject a '
         'request with a custom response type that is not allowed.')
Esempio n. 3
0
 def testGrantAccessCustomResponseType(self):
     """ Test that grantAccess rejects a call for a request with a custom response type. """
     responseType = 'myCustomResponseType'
     state = b'state\xFF\xFF'
     client = PasswordClient('customResponseTypeClientGrantAccess',
                             ['https://redirect.noexistent'],
                             [responseType], 'clientSecret')
     dataKey = 'customResponseTypeDataKey'
     self._PERSISTENT_STORAGE.put(
         dataKey, {
             'response_type': responseType,
             'client_id': client.id,
             'redirect_uri': client.redirectUris[0],
             'scope': 'All',
             'state': state
         })
     request = MockRequest('GET', 'some/path')
     self._CLIENT_STORAGE.addClient(client)
     self.assertRaises(ValueError, self._AUTH_RESOURCE.grantAccess, request,
                       dataKey)
     try:
         self.assertEqual(
             self._AUTH_RESOURCE.requestDataLifetime,
             self._PERSISTENT_STORAGE.getExpireTime(dataKey),
             msg='Expected the data to be stored with the expected lifetime.'
         )
         self._PERSISTENT_STORAGE.pop(dataKey)
     except KeyError:
         self.fail(
             'Expected the data to still be in the persistent storage.')
Esempio n. 4
0
 def testCustomResponseTypeUnauthorizedClient(self):
     """
     Test that a request with a custom response type is rejected
     if the client is not authorized to use that response type.
     """
     responseType = 'myCustomResponseType'
     state = b'state\xFF\xFF'
     client = PasswordClient('customResponseTypeClientUnauthorized',
                             ['https://redirect.noexistent'], [],
                             'clientSecret')
     redirectUri = client.redirectUris[0]
     parameters = {
         'response_type': responseType,
         'client_id': client.id,
         'redirect_uri': redirectUri,
         'scope': 'All',
         'state': state
     }
     request = self.createAuthRequest(arguments=parameters)
     self._CLIENT_STORAGE.addClient(client)
     authResource = self.TestOAuth2Resource(
         self._TOKEN_FACTORY,
         self._PERSISTENT_STORAGE,
         self._CLIENT_STORAGE,
         authTokenStorage=self._TOKEN_STORAGE,
         grantTypes=[responseType])
     result = authResource.render_GET(request)
     self.assertFailedRequest(
         request,
         result,
         UnauthorizedClientError(responseType, state),
         redirectUri=redirectUri,
         msg=
         'Expected the authorization token resource to reject a request with a '
         'custom response type that the client is not allowed to use.')
Esempio n. 5
0
 def testCustomResponseType(self):
     """ Test that a request with a custom response type is accepted. """
     responseType = 'myCustomResponseType'
     state = b'state\xFF\xFF'
     client = PasswordClient('customResponseTypeClient',
                             ['https://redirect.noexistent'],
                             [responseType], 'clientSecret')
     parameters = {
         'response_type': responseType,
         'client_id': client.id,
         'redirect_uri': client.redirectUris[0],
         'scope': 'All',
         'state': state
     }
     request = self.createAuthRequest(arguments=parameters)
     self._CLIENT_STORAGE.addClient(client)
     authResource = self.TestOAuth2Resource(
         self._TOKEN_FACTORY,
         self._PERSISTENT_STORAGE,
         self._CLIENT_STORAGE,
         authTokenStorage=self._TOKEN_STORAGE,
         grantTypes=[responseType])
     result = authResource.render_GET(request)
     self.assertValidAuthRequest(
         request,
         result,
         parameters,
         msg='Expected the authorization token resource to accept '
         'a valid request with a custom response type.')
Esempio n. 6
0
def getTestClient():
    """
    :return: A client to use for this example.
    """
    return PasswordClient(clientId='test',
                          redirectUris=['https://clientServer.com/return'],
                          secret='test_secret',
                          authorizedGrantTypes=[
                              GrantTypes.REFRESH_TOKEN,
                              GrantTypes.AUTHORIZATION_CODE
                          ])
Esempio n. 7
0
def getTestClient():
    """
    :return: A client to use for this example.
    """
    return PasswordClient(clientId='test',
                          redirectUris=['https://clientServer.com/return'],
                          secret='test_secret',
                          authorizedGrantTypes=[
                              GrantTypes.RefreshToken,
                              GrantTypes.AuthorizationCode
                          ])
Esempio n. 8
0
def getTestPasswordClient(clientId=None, authorizedGrantTypes=None):
    """
    :param clientId: The client id or None for a random client id.
    :param authorizedGrantTypes: The grant types the clients will be authorized to use,
                                 None for all.
    :return: A dummy password client that can be used in the tests.
    """
    if clientId is None:
        clientId = str(uuid4())
    if authorizedGrantTypes is None:
        # noinspection PyTypeChecker
        authorizedGrantTypes = list(GrantTypes)
    return PasswordClient(
        clientId, ['https://return.nonexistent'], authorizedGrantTypes, secret='ClientSecret')
Esempio n. 9
0
 def testAddClient(self):
     """ Test if a client can be added to the client storage. """
     client = PublicClient(
         'newPublicClientId',
         ['https://return.nonexistent', 'https://return2.nonexistent'],
         [GrantTypes.REFRESH_TOKEN])
     self._CLIENT_STORAGE.addClient(client)
     self.assertListEqual(
         self._CLIENT_STORAGE.getClient(client.id).authorizedGrantTypes,
         client.authorizedGrantTypes,
         msg=
         'Expected the client storage to contain a client after adding him.'
     )
     client = PasswordClient(
         'newPasswordClientId',
         ['https://return.nonexistent', 'https://return2.nonexistent'],
         ['client_credentials'], 'newClientSecret')
     self._CLIENT_STORAGE.addClient(client)
     self.assertEqual(
         client.secret,
         self._CLIENT_STORAGE.getClient(client.id).secret,
         msg=
         'Expected the client storage to contain a client after adding him.'
     )
Esempio n. 10
0
 def testInsecureRedirectUriClient(self):
     """ Test that a request with a non https redirect uri is accepted. """
     state = b'state\xFF\xFF'
     client = PasswordClient('customResponseTypeClientUnauthorized',
                             ['custom://callback'],
                             [GrantTypes.AUTHORIZATION_CODE],
                             'clientSecret')
     redirectUri = client.redirectUris[0]
     parameters = {
         'response_type': 'code',
         'client_id': client.id,
         'redirect_uri': redirectUri,
         'scope': 'All',
         'state': state
     }
     request = self.createAuthRequest(arguments=parameters)
     self._CLIENT_STORAGE.addClient(client)
     result = self._AUTH_RESOURCE.render_GET(request)
     self.assertValidAuthRequest(
         request,
         result,
         parameters,
         msg='Expected the authorization token resource to accept '
         'a valid request with a non https redirect uri.')
Esempio n. 11
0
    class AuthResourceTest(TwistedTestCase):
        """ Abstract base class for test targeting the OAuth2 resource. """
        # noinspection HttpUrlsUsage
        _VALID_CLIENT = PasswordClient('authResourceClientId', [
            'https://return.nonexistent?param=retain',
            'http://return.nonexistent/notSecure?param=retain'
        ],
                                       list(GrantTypes),
                                       secret='ClientSecret')
        _RESPONSE_GRANT_TYPE_MAPPING = {
            'code': GrantTypes.AUTHORIZATION_CODE.value,
            'token': GrantTypes.IMPLICIT.value
        }

        class TestOAuth2Resource(OAuth2):
            """ A test OAuth2 resource that returns the parameters given to onAuthenticate. """
            raiseErrorInOnAuthenticate = False
            UNKNOWN_SCOPE = 'unknown'
            UNKNOWN_SCOPE_RETURN = 'unknown_return'
            UNKNOWN_SCOPE_RAISING_OAUTH2_ERROR = 'unknown_raise_oauth2_error'
            TEMPORARY_UNAVAILABLE_SCOPE = 'temporary_unavailable'
            ERROR_MESSAGE = 'Expected the auth resource to catch this error'

            def onAuthenticate(self, request, client, responseType, scope,
                               redirectUri, state, dataKey):
                if self.raiseErrorInOnAuthenticate:
                    self.raiseErrorInOnAuthenticate = False
                    raise RuntimeError(self.ERROR_MESSAGE)
                if self.UNKNOWN_SCOPE in scope:
                    raise InvalidScopeError(scope, state=state)
                if self.UNKNOWN_SCOPE_RETURN in scope:
                    return InvalidScopeError(scope, state=state)
                if self.UNKNOWN_SCOPE_RAISING_OAUTH2_ERROR in scope:
                    raise InvalidTokenError(self.ERROR_MESSAGE)
                if self.TEMPORARY_UNAVAILABLE_SCOPE in scope:
                    raise TemporarilyUnavailableError(state=state)
                return request, client, responseType, scope, redirectUri, state, dataKey

        @classmethod
        def setUpClass(cls):
            super(Abstract.AuthResourceTest, cls).setUpClass()
            cls._TOKEN_FACTORY = TestTokenFactory()
            cls._TOKEN_STORAGE = DictTokenStorage()
            cls._PERSISTENT_STORAGE = TestPersistentStorage()
            cls._CLIENT_STORAGE = TestClientStorage()
            cls._CLIENT_STORAGE.addClient(cls._VALID_CLIENT)
            cls._AUTH_RESOURCE = cls.TestOAuth2Resource(
                cls._TOKEN_FACTORY,
                cls._PERSISTENT_STORAGE,
                cls._CLIENT_STORAGE,
                authTokenStorage=cls._TOKEN_STORAGE)

        def setUp(self):
            super(Abstract.AuthResourceTest, self).setUp()
            self._TOKEN_FACTORY.reset(self)

        @staticmethod
        def createAuthRequest(**kwargs):
            """
            :param kwargs: Arguments to the request.
            :return: A GET request to the OAuth2 resource with the given arguments.
            """
            return MockRequest('GET', 'oauth2', **kwargs)

        @staticmethod
        def getParameterFromRedirectUrl(url, parameterInFragment):
            """
            :param url: The url that the resource redirected to.
            :param parameterInFragment: Whether the parameter should be in
                                        the fragment or in the query.
            :return: The parameter transmitted via the redirect url.
            """
            if not isinstance(url, str):
                url = url.decode('utf-8')
            parsedUrl = urlparse(url)
            parameter = parse_qs(
                parsedUrl.fragment if parameterInFragment else parsedUrl.query)
            for key, value in parameter.items():
                if len(value) == 1:
                    parameter[key] = value[0]
            return parameter

        def assertRedirectsTo(self, request, redirectUri, msg):
            """
            Assert that the request redirects to the given uri and retains the query parameters.

            :param request: The request that should redirect.
            :param redirectUri: The uri where the request should redirect to.
            :param msg: The assertion message.
            :return: The actual url the request is redirecting to.
            """
            self.assertEqual(
                302,
                request.responseCode,
                msg=msg +
                ': Expected the auth resource to redirect the resource owner.')
            redirectUrl = request.getResponseHeader(b'location')
            self.assertIsNotNone(
                redirectUrl,
                msg=msg +
                ': Expected the auth resource to redirect the resource owner.')
            parsedUrl = urlparse(redirectUrl)
            parsedUri = urlparse(redirectUri.encode('utf-8'))
            self.assertTrue(
                parsedUrl.scheme == parsedUri.scheme
                and parsedUrl.netloc == parsedUri.netloc
                and parsedUrl.path == parsedUri.path
                and parsedUrl.params == parsedUri.params,
                msg='{msg}: The auth token endpoint did not redirect '
                'the resource owner to the expected url: '
                '{expected} <> {actual}'.format(msg=msg,
                                                expected=redirectUri,
                                                actual=redirectUrl))
            self.assertIn(
                parsedUri.query,
                parsedUrl.query,
                msg=msg +
                ': Expected the redirect uri to contain the query parameters '
                'of the original redirect uri of the client.')
            return redirectUrl

        def assertFailedRequest(self,
                                request,
                                result,
                                expectedError,
                                msg=None,
                                redirectUri=None,
                                parameterInFragment=False):
            """
            Assert that the request did not succeed and that
            the auth resource returned an appropriate error response.

            :param request: The request.
            :param result: The return value of the render_POST function of the token resource.
            :param expectedError: The expected error.
            :param msg: The assertion error message.
            :param redirectUri: The redirect uri of the client.
            :param parameterInFragment: If the error parameters are
                                        in the fragment of the redirect uri.
            """
            if result == NOT_DONE_YET:
                result = request.getResponse()
            if msg.endswith('.'):
                msg = msg[:-1]
            self.assertFalse(
                isinstance(result, tuple),
                msg=msg +
                ': Expected the auth resource not to call onAuthenticate.')
            if redirectUri is not None:
                redirectUrl = self.assertRedirectsTo(request, redirectUri, msg)
                errorResult = self.getParameterFromRedirectUrl(
                    redirectUrl, parameterInFragment)
            else:
                self.assertEqual(
                    'application/json;charset=UTF-8',
                    request.getResponseHeader('Content-Type'),
                    msg=
                    'Expected the auth resource to return an error in the json format.'
                )
                self.assertEqual(
                    'no-store',
                    request.getResponseHeader('Cache-Control'),
                    msg=
                    'Expected the auth resource to set Cache-Control to "no-store".'
                )
                self.assertEqual(
                    'no-cache',
                    request.getResponseHeader('Pragma'),
                    msg=
                    'Expected the auth resource to set Pragma to "no-cache".')
                self.assertEqual(
                    expectedError.code,
                    request.responseCode,
                    msg='Expected the auth resource to return a response '
                    'with the HTTP code {code}.'.format(
                        code=expectedError.code))
                errorResult = json.loads(result.decode('utf-8'))
            self.assertIn('error',
                          errorResult,
                          msg=msg + ': Missing error parameter in response.')
            self.assertEqual(
                expectedError.name,
                errorResult['error'],
                msg=msg +
                ': Result contained a different error than expected.')
            self.assertIn('error_description',
                          errorResult,
                          msg=msg +
                          ': Missing error_description parameter in response.')
            if not isinstance(expectedError.description, (bytes, str)):
                self.assertEqual(
                    expectedError.description.encode('utf-8'),
                    errorResult['error_description'],
                    msg=msg +
                    ': Result contained a different error description than expected.'
                )
            else:
                self.assertEqual(
                    expectedError.description,
                    errorResult['error_description'],
                    msg=msg +
                    ': Result contained a different error description than expected.'
                )
            if expectedError.errorUri is not None:
                self.assertIn('error_uri',
                              errorResult,
                              msg=msg +
                              ': Missing error_uri parameter in response.')
                self.assertEqual(expectedError.errorUri,
                                 errorResult['error_uri'],
                                 msg=msg +
                                 ': Result contained an unexpected error_uri.')
            if hasattr(expectedError, 'state') and getattr(
                    expectedError, 'state') is not None:
                self.assertIn('state',
                              errorResult,
                              msg=msg +
                              ': Missing state parameter in response.')
                self.assertEqual(
                    expectedError.state if isinstance(expectedError.state, str)
                    else expectedError.state.decode('utf-8', errors='replace'),
                    errorResult['state'],
                    msg=msg + ': Result contained an unexpected state.')

        def assertValidAuthRequest(self,
                                   request,
                                   result,
                                   parameters,
                                   msg,
                                   expectedDataLifetime=None):
            """
            Assert that a GET request is processed correctly and the expected data has been stored.

            :param request: The GET request.
            :param result: The result of render_GET and onAuthenticate.
            :param parameters: The parameters of the request.
            :param msg: The assertion error message.
            :param expectedDataLifetime: The expected lifetime of the stored data.
            """
            if msg.endswith('.'):
                msg = msg[:-1]
            self.assertFalse(
                request.finished,
                msg=msg +
                ': Expected the auth resource not to close the request.')
            self.assertIsInstance(
                result,
                tuple,
                message=msg +
                ': Expected the auth resource to call onAuthenticate.')
            requestParam, client, responseType, scope, redirectUri, state, dataKey = result
            self.assertIs(request,
                          requestParam,
                          msg=msg +
                          ': Expected the auth resource to pass the request '
                          'to onAuthenticate as the first parameter.')
            self.assertEqual(
                parameters['client_id'],
                client.id,
                msg=msg + ': Expected the auth resource to pass the received '
                'client to onAuthenticate as the second parameter.')
            parameters[
                'response_type'] = self._RESPONSE_GRANT_TYPE_MAPPING.get(
                    parameters['response_type'], parameters['response_type'])
            self.assertEqual(
                parameters['response_type'],
                responseType,
                msg=msg + ': Expected the auth resource to pass the response '
                'type to onAuthenticate as the third parameter.')
            parameters['scope'] = parameters['scope'].split(' ')
            self.assertListEqual(
                scope,
                parameters['scope'],
                msg=msg + ': Expected the auth resource to pass the scope '
                'to onAuthenticate as the fourth parameter.')
            expectedRedirectUri = parameters['redirect_uri'] \
                if parameters['redirect_uri'] is not None else self._VALID_CLIENT.redirectUris[0]
            self.assertEqual(
                expectedRedirectUri,
                redirectUri,
                msg=msg + ': Expected the auth resource to pass the redirect '
                'uri to onAuthenticate as the fifth parameter.')
            expectedState = parameters.get('state', None)
            self.assertEqual(expectedState,
                             state,
                             msg=msg +
                             ': Expected the auth resource to pass the state '
                             'to onAuthenticate as the sixth parameter.')
            if expectedDataLifetime is None:
                expectedDataLifetime = self._AUTH_RESOURCE.requestDataLifetime
            try:
                self.assertEqual(
                    expectedDataLifetime,
                    self._PERSISTENT_STORAGE.getExpireTime(dataKey),
                    msg=msg + ': Expected the auth resource to store '
                    'the request data with the given lifetime.')
                data = self._PERSISTENT_STORAGE.pop(dataKey)
            except KeyError:
                self.fail(msg=msg +
                          ': Expected the auth resource to pass a valid '
                          'data key to onAuthenticate as the sixth parameter.')
            for key, value in parameters.items():
                self.assertIn(
                    key,
                    data,
                    msg=msg +
                    ': Expected the data stored by auth token resource '
                    'to contain the {name} parameter.'.format(name=key))
                self.assertEqual(
                    value,
                    data[key],
                    msg=msg +
                    ': Expected the auth token resource to store the value '
                    'of the {name} parameter.'.format(name=key))