def check_relationships(Manager, Consumer, RequestToken, AccessToken, orphans=True): r"""The relationship checking function we want to run on various relationship setups""" # Drop all previous tables self.metadata.drop_all(tables=[ Consumer.__table__, RequestToken.__table__, AccessToken.__table__, ]) # The default manager creates the tables and sets up relationships manager = Manager(engine=self.engine) # Check that relationships were established properly self.assertTrue(hasattr(RequestToken, 'consumer_key')) self.assertTrue(hasattr(Consumer, 'request_tokens')) self.assertTrue(hasattr(Consumer, 'access_tokens')) # Also check that orphaned tokens are deleted as a consumer gets # deleted # First create two consumers cons1 = Consumer(key='consumer1', secret='secret1') cons2 = Consumer(key='consumer2', secret='secret2') self.session.add_all((cons1, cons2)) # Then the tokens self.session.add_all(( RequestToken(key='token1', secret='secret1', consumer=cons1), AccessToken(key='token2', secret='secret2', userid=u'some-user', consumer=cons1), AccessToken(key='token3', secret='secret3', userid=u'some-user', consumer=cons2), )) self.session.flush() # We have 2 consumers and 3 tokens now self.assertEquals(len(list(self.session.query(Consumer))), 2) self.assertEquals(len(list(self.session.query(RequestToken))), 1) self.assertEquals(len(list(self.session.query(AccessToken))), 2) # If the cascade=delete is defined and we remove the first consumer # now the two tokens belonging to it have to be autoremoved too. # Else, they should stay self.session.delete(cons1) if orphans: request_tokens = 0 access_tokens = 1 else: request_tokens = 1 access_tokens = 2 self.assertEquals(len(list(self.session.query(RequestToken))), request_tokens) self.assertEquals(len(list(self.session.query(AccessToken))), access_tokens) # Discard all the objects we had here self.session.expunge_all()
def test_get_consumer(self): r"""Check how consumer is fetched from the database""" plugin = self._makeOne() manager = plugin.manager # Ensure we are clean from repoze.who.plugins.oauth import Consumer self.assertEquals(len(list(self.session.query(Consumer))), 0) # Create a consumer in the DB consumer = Consumer(key=u'some-consumer', secret='some-secret') manager.DBSession.add(consumer) manager.DBSession.flush() # Provide the right consumer key and expect if to be found env = dict(environ={}, identity={'oauth_consumer_key': 'some-consumer'}) self.assertTrue(plugin._get_consumer(env)) # Provide a non existing consumer key env['identity']['oauth_consumer_key'] = 'another-consumer' self.assertFalse(plugin._get_consumer(env)) # Provide an empty consumer key env['identity']['oauth_consumer_key'] = '' self.assertFalse(plugin._get_consumer(env)) # Provide no consumer key del env['identity']['oauth_consumer_key'] self.assertFalse(plugin._get_consumer(env)) # Cleanup manager.DBSession.delete(consumer) manager.DBSession.flush()
def test_token_authorization(self): r"""Test how token_authorization behaves in GET and POST requests""" env = self._make_environ() p = token_authorization(engine=self.engine) session = p.manager.DBSession # First try an empty environment self.eval_unmet_predicate(p, env, 'No valid matching OAuth token found') # Then try a non-existing token env['QUERY_STRING'] = urlencode(dict(oauth_token='some-token')) self.eval_unmet_predicate(p, env, 'No valid matching OAuth token found') # There is no token in the environment self.assertFalse(env['repoze.what.oauth'].get('token')) # Now create a consumer and the token and try again consumer = Consumer(key='some-consumer', secret='some-secret') token = RequestToken.create( consumer, session=session, key='some-token', callback=u'http://www.test.com/some/path?x=1&y=%20a') session.add(consumer) session.flush() # This time we are passed through self.eval_met_predicate(p, env) # Environment now contains a token which was found according to the # query string parameters self.assertEquals(env['repoze.what.oauth']['token'], token) # Now construct a POST query and expect to find a callback function to # authorize the request token env = self._make_environ() env['REQUEST_METHOD'] = 'POST' self.eval_met_predicate(p, env) callback_maker = env['repoze.what.oauth']['make_callback'] self.assertTrue(callback_maker) # We must provide a request token key and a userid to authorize a # request callback callback = callback_maker('some-token', u'some-user') self.assertEquals(len(callback['verifier']), 6) self.assertTrue(callback['verifier'] in callback['url']) # If the token callback url was provided as 'oob' (out of band) then the # callback['url'] should also specify oob token.callback = u'oob' callback = callback_maker('some-token', u'some-user') self.assertEquals(callback['url'], 'oob')
def test_token_creation(self): r"""Test token creation at the model level (not manager actually). Use an in-memory database""" from repoze.who.plugins.oauth import (DefaultManager, Consumer, RequestToken, AccessToken) manager = DefaultManager(engine='sqlite:///:memory:') # Create a consumer and a request token cons1 = Consumer(key='consumer1', secret='secret1') self.session.add(cons1) req_token = RequestToken.create(cons1, u'http://someurl.com', session=self.session) # Check various attributes and relations self.assertEquals(req_token.consumer, cons1) self.assertEquals(cons1.request_tokens, [req_token]) # We have exactly one token now self.assertEquals(len(list(self.session.query(RequestToken))), 1) self.assertEquals(len(req_token.key), 40) self.assertEquals(len(req_token.secret), 40) # Now create an access token and also check it acc_token = AccessToken.create(cons1, u'some-user', session=self.session) self.assertNotEquals(acc_token.key, req_token.key) self.assertEquals(cons1.access_tokens, [acc_token]) # Let's try to create two tokens with the same key and test the unique # key requirement token1 = RequestToken.create(cons1, u'http://someurl.com', key='rtkey') token2 = RequestToken.create(cons1, u'http://someurl.com', key='rtkey') # We do not provide the session so the exception happens on flush self.assertRaises(sa.exc.IntegrityError, self.session.flush) # However if we do provide the session, it will be flushed, the error # will be caught and the key will be changed to a random string token1 = RequestToken.create(cons1, u'http://someurl.com', session=self.session, key='rtkey') token2 = RequestToken.create(cons1, u'http://someurl.com', session=self.session, key='rtkey') # The first token has the provided key self.assertEquals(token1.key, 'rtkey') # The second token got a new random key as the provided one would have # conflicted with the token1 key self.assertNotEquals(token2.key, 'rtkey') self.assertEquals(len(token2.key), 40) # Cleanup self.session.delete(cons1) self.assertEquals(len(list(self.session.query(RequestToken))), 0) self.assertEquals(len(list(self.session.query(AccessToken))), 0)
def test_get_consumer_by_key(self): r"""Test how the manager finds consumers by keys""" # Create the manager from repoze.who.plugins.oauth import DefaultManager, Consumer manager = DefaultManager(engine=self.engine) # Consumer exists not self.assertEquals(manager.get_consumer_by_key('abcd'), None) # Create a sample consumer consumer = Consumer(key='abcd', secret='abcdef') self.session.add(consumer) self.session.flush() # Consumer exists self.assertEquals(manager.get_consumer_by_key('abcd').key, consumer.key) # Consumer exists not self.assertEquals(manager.get_consumer_by_key('abdc'), None)
def test_get_access_token(self): r"""Test how access token is fetched from the database""" plugin = self._makeOne() manager = plugin.manager # Ensure we are clean from repoze.who.plugins.oauth import Consumer self.assertEquals(len(list(self.session.query(Consumer))), 0) # Create a consumer in the DB consumer = Consumer(key=u'some-consumer', secret='some-secret') manager.DBSession.add(consumer) # Create a request token and verify it for some-user. The request token # is needed in order to create an access token rtoken = manager.create_request_token(consumer, 'http://test.com') rtoken = manager.set_request_token_user(rtoken.key, u'some-user') # Now create the access token in the db atoken = manager.create_access_token(rtoken) # Provide an access token key - found env = dict(environ={}, consumer=consumer, identity={'oauth_token': atoken.key}) self.assertTrue(plugin._get_access_token(env)) self.assertTrue(env.pop('token')) # Unauthorized # A non-existing token key - not found env = dict(environ={}, consumer=consumer, identity={'oauth_token': atoken.key[:-1]}) self.assertFalse(plugin._get_access_token(env)) self.assertFalse('token' in env) # An empty identity - not found env = dict(environ={}, consumer=consumer, identity={}) self.assertFalse(plugin._get_access_token(env)) self.assertFalse('token' in env) # Cleanup manager.DBSession.delete(consumer) manager.DBSession.flush()
def test_access_token_app(self): r"""Test the application that creates the request token""" plugin = self._makeOne() # Ensure we are clean from repoze.who.plugins.oauth import Consumer, AccessToken self.assertEquals(len(list(self.session.query(Consumer))), 0) # Create a consumer in the DB consumer = Consumer(key=u'some-consumer', secret='some-secret') manager = plugin.manager manager.DBSession.add(consumer) manager.DBSession.flush() # Create a request token and verify it for some-user. The request token # is needed in order to create an access token rtoken = manager.create_request_token(consumer, 'oob') rtoken = manager.set_request_token_user(rtoken.key, u'some-user') # The function needs nothing but the request token. And it does nothing # but assign the access token creation app to the environ env = dict(environ={}, token=rtoken, identity={}) self.assertTrue(plugin._access_token_app(env)) # Here it is app = env['environ']['repoze.who.application'] # Run the app and get the access token attributes enc_token = ''.join(app(env['environ'], lambda *args: None)) # Decode access token dec_token = parse_qs(enc_token) # By now the access token should have been created in the DB token = self.session.query(AccessToken).filter_by( key=dec_token['oauth_token'][0]).first() # Yes, found it self.assertTrue(token) # And the token secret matches self.assertEquals(dec_token['oauth_token_secret'][0], token.secret) # Cleanup manager.DBSession.delete(consumer) manager.DBSession.flush()
def test_request_token_app(self): r"""Test the application that creates the request token""" # Ensure we are clean from repoze.who.plugins.oauth import Consumer, RequestToken self.assertEquals(len(list(self.session.query(Consumer))), 0) # Create a consumer in the DB consumer = Consumer(key=u'some-consumer', secret='some-secret') self.session.add(consumer) plugin = self._makeOne() env = dict(environ={}, consumer=consumer, identity={'oauth_callback': 'oob'}) # The function does not do anything besides assigning the request token # creation application to the env self.assertTrue(plugin._request_token_app(env)) # Here it is - the request token creation application app = env['environ']['repoze.who.application'] # Encoded token enc_token = ''.join(app(env['environ'], lambda *args: None)) # Decode the token attributes dec_token = parse_qs(enc_token) # The token should have been created by now - look for it in the DB token = self.session.query(RequestToken).filter_by( key=dec_token['oauth_token'][0]).first() # Found it self.assertTrue(token) # The token secret matches self.assertEquals(dec_token['oauth_token_secret'][0], token.secret) # And we support the updated OAuth specification which requires callback # confirmation self.assertEquals(dec_token['oauth_callback_confirmed'][0], 'true') # Cleanup self.session.delete(consumer) self.session.flush()
def test_3_legged_flow(self): r"""Test a 2-legged flow end-to-end""" # The OAuth spec allows the access token to be the same as request # token. Let's try this here plugin = self._makeOne(access_token_path='/oauth/request_token') std_env_params = { 'wsgi.url_scheme': 'http', 'SERVER_NAME': 'www.example.com', 'SERVER_PORT': '80', 'REQUEST_METHOD': 'POST', 'QUERY_STRING': '', 'wsgi.input': '', } # Create one consumer in our DB from repoze.who.plugins.oauth.model import (Consumer, RequestToken, AccessToken) self.session.add(Consumer(key='cons1', secret='secret1')) self.session.flush() # Construct a nice request token request and try to pass the # authenticator check. We do not have any tokens yet consumer = oauth2.Consumer('cons1', 'secret1') req = oauth2.Request.from_consumer_and_token( consumer=consumer, token=None, http_method='POST', http_url='http://www.example.com/oauth/request_token', parameters=dict(oauth_callback='http://test.com/?x=2')) req.sign_request(signature_method=oauth2.SignatureMethod_HMAC_SHA1(), consumer=consumer, token=None) # Pass the oauth parameters in the Authorization header env_params = { 'HTTP_AUTHORIZATION': req.to_header()['Authorization'], 'PATH_INFO': '/oauth/request_token', } env_params.update(std_env_params) environ = self._makeEnviron(env_params) identity = plugin.identify(environ) userid = plugin.authenticate(environ, identity) # While userid now contains the key of the consumer we don't care much # about it as the downstream application will never execute. What is # more important, `authenticate` replaced the downstream app with a # custom one. self.assertEquals(userid, 'consumer:%s' % consumer.key) app = environ['repoze.who.application'] def assertUrlEncoded(code, headers, *args): self.assertEquals( dict(headers)['Content-Type'], 'application/x-www-form-urlencoded') # The custom app will return a new request token for this consumer enc_token = ''.join(app(environ, assertUrlEncoded)) # Decode the token dec_token = parse_qs(enc_token) # And create an oauth equivalent rtoken = oauth2.Token(key=dec_token['oauth_token'][0], secret=dec_token['oauth_token_secret'][0]) # Check token attributes # Autogenerated key and secret should be 40 chars long self.assertEquals(len(rtoken.key), 40) self.assertEquals(len(rtoken.secret), 40) # The plugin supports the updated OAuth specification self.assertEquals(dec_token['oauth_callback_confirmed'][0], 'true') # Such a token really exists dbtoken = plugin.manager.get_request_token(key=rtoken.key) self.assertEquals(dbtoken.secret, rtoken.secret) # And it really belongs to our consumer self.assertEquals(dbtoken.consumer.key, consumer.key) # And the callback url was set correctly self.assertEquals(dbtoken.callback, u'http://test.com/?x=2') # Now that we have the request token we should ask the user to authorize # it env_params = {} env_params.update(std_env_params) env_params.update({ 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/oauth/authorize', 'QUERY_STRING': urlencode(dict(oauth_token=rtoken.key)) }) environ = self._makeEnviron(env_params) # The token_authorization predicate is supposed to protect the token # authorization method from repoze.what.plugins.oauth import token_authorization authorizer = token_authorization(engine=self.engine) authorizer.check_authorization(environ) # environ now stores the same token taken from the DB. And we can use # the information associated with that token self.assertEquals(environ['repoze.what.oauth']['token'].key, rtoken.key) self.assertEquals(environ['repoze.what.oauth']['token'].consumer.key, consumer.key) # Suppose a user confirms that the consumer is legitimate and gives # permission to the user resources. Usually it will happen through a # form POSTed to 'authorize'. The predicate will intercept that and add # a method to environ which will mark the request token as validated and # create a verification key environ['REQUEST_METHOD'] = 'POST' authorizer.check_authorization(environ) # The request token validator method got added to the environ callback_maker = environ['repoze.what.oauth']['make_callback'] # Call it providing the request token key and a userid - a callback dict # is returned callback = callback_maker(rtoken.key, u'some-user') # The autogenerated verifier should be 6 chars long self.assertEquals(len(callback['verifier']), 6) # And normally it is included in the callback url (except for # out-of-band callbacks) self.assertTrue(callback['verifier'] in callback['url']) # The request token is now attached to the userid self.assertEquals(environ['repoze.what.oauth']['token'].userid, u'some-user') # Now that we have the request token verified we can convert it to an # access token # Set the token verifier - a wrong one first rtoken.set_verifier('-wrong-') # Create a new request using the new request token and verifier req = oauth2.Request.from_consumer_and_token( consumer=consumer, token=rtoken, http_method='POST', http_url='http://www.example.com/oauth/request_token') req.sign_request(signature_method=oauth2.SignatureMethod_HMAC_SHA1(), consumer=consumer, token=rtoken) env_params = { 'HTTP_AUTHORIZATION': req.to_header()['Authorization'], # This url handles both request and access tokens 'PATH_INFO': '/oauth/request_token', } env_params.update(std_env_params) environ = self._makeEnviron(env_params) # Force manager to reload all the objects as they might be out of date plugin.manager.DBSession.expire_all() # As we are providing a request token and a verifier we should get an # access token in exchange. identity = plugin.identify(environ) self.assertEquals(plugin.authenticate(environ, identity), None) # ... and we failed with the wrong verification code # Check that the plugin returned an Unauthorized response def start_401_response(code, *args): self.assertTrue(code.startswith('401')) environ['repoze.who.application'](environ, start_401_response) # Now create a request with the correct verifier rtoken.set_verifier(callback['verifier']) # Create a new request using the new request token and verifier req = oauth2.Request.from_consumer_and_token( consumer=consumer, token=rtoken, http_method='POST', http_url='http://www.example.com/oauth/request_token') req.sign_request(signature_method=oauth2.SignatureMethod_HMAC_SHA1(), consumer=consumer, token=rtoken) env_params = { 'HTTP_AUTHORIZATION': req.to_header()['Authorization'], 'PATH_INFO': '/oauth/request_token', } env_params.update(std_env_params) environ = self._makeEnviron(env_params) # As we are providing a request token and a verifier we should get an # access token in exchange identity = plugin.identify(environ) userid = plugin.authenticate(environ, identity) # The repoze.who.application now contains an app that will remove the # request token, create a new access token and return it as a parameter. # Let's call it and see. app = environ['repoze.who.application'] enc_token = ''.join(app(environ, assertUrlEncoded)) # Decode the access token dec_token = parse_qs(enc_token) # Create a local oauth equivalent of the access token atoken = oauth2.Token(key=dec_token['oauth_token'][0], secret=dec_token['oauth_token_secret'][0]) # If we repeat the request we get a 401 again identity = plugin.identify(environ) self.assertEquals(plugin.authenticate(environ, identity), None) environ['repoze.who.application'](environ, start_401_response) # So now we have a valid access token. Let's try an authorized request req = oauth2.Request.from_consumer_and_token( consumer=consumer, token=atoken, http_method='GET', http_url='http://www.example.com/app') req.sign_request(signature_method=oauth2.SignatureMethod_HMAC_SHA1(), consumer=consumer, token=atoken) env_params = { 'HTTP_AUTHORIZATION': req.to_header()['Authorization'], 'PATH_INFO': '/app', } env_params.update(std_env_params) env_params['REQUEST_METHOD'] = 'GET' environ = self._makeEnviron(env_params) identity = plugin.identify(environ) userid = plugin.authenticate(environ, identity) # We got a *user* id self.assertEquals(userid, 'some-user') # Identity also contains the consumer and consumer key self.assertEquals(identity['repoze.who.consumerkey'], consumer.key) # And we're good to go with the downstream app self.assertEquals(environ.get('repoze.who.application'), None) # Cleanup consumers self.session.execute(Consumer.__table__.delete())
def test_2_legged_flow(self): r"""Test a 2-legged flow end-to-end""" plugin = self._makeOne() # Create some standard environment params we are going to reuse std_env_params = { 'wsgi.url_scheme': 'http', 'SERVER_NAME': 'www.example.com', 'SERVER_PORT': '80', 'PATH_INFO': '/get_some_resource', 'REQUEST_METHOD': 'POST', 'QUERY_STRING': '', 'wsgi.input': '', } # Create one consumer in our DB from repoze.who.plugins.oauth.model import Consumer self.session.add(Consumer(key='cons1', secret='secret1')) self.session.flush() # Construct a nice request and pass the authenticator check consumer = oauth2.Consumer('cons1', 'secret1') req = oauth2.Request.from_consumer_and_token( consumer=consumer, token=None, http_method='POST', http_url='http://www.example.com/get_some_resource') req.sign_request(signature_method=oauth2.SignatureMethod_HMAC_SHA1(), consumer=consumer, token=None) # Put the oauth params in the Authorization header env_params = {'HTTP_AUTHORIZATION': req.to_header()['Authorization']} env_params.update(std_env_params) environ = self._makeEnviron(env_params) # Extract the oauth params identity = plugin.identify(environ) # Authenticate userid = plugin.authenticate(environ, identity) # The repoze.who.userid contains the key of the consumer, so does # repoze.who.consumerkey self.assertEquals(userid, 'consumer:%s' % consumer.key) self.assertEquals(identity['repoze.who.consumerkey'], consumer.key) # Now tweak some parameters and see how authenticator rejects the # consumer # One extra non-oauth parameter env_params = {'HTTP_AUTHORIZATION': req.to_header()['Authorization']} env_params.update(std_env_params) environ = self._makeEnviron(env_params) identity = plugin.identify(environ) identity['non_oauth'] = True # The plugin assumes he got identity from some other identifier and # ignores it self.assertEquals(plugin.authenticate(environ, identity), None) # Bad timestamp good_tstamp = req['oauth_timestamp'] req['oauth_timestamp'] += '123' env_params = {'HTTP_AUTHORIZATION': req.to_header()['Authorization']} env_params.update(std_env_params) environ = self._makeEnviron(env_params) identity = plugin.identify(environ) self.assertEquals(plugin.authenticate(environ, identity), None) # Restore the good timestamp req['oauth_timestamp'] = good_tstamp # Bad signature good_signature = req['oauth_signature'] req['oauth_signature'] = 'AAAAAA' + good_signature[6:] env_params = {'HTTP_AUTHORIZATION': req.to_header()['Authorization']} env_params.update(std_env_params) environ = self._makeEnviron(env_params) identity = plugin.identify(environ) self.assertEquals(plugin.authenticate(environ, identity), None) # Restore the good signature req['oauth_signature'] = good_signature # Bad consumer key - consumer not found good_consumer_key = req['oauth_consumer_key'] req['oauth_consumer_key'] = good_consumer_key[:-2] env_params = {'HTTP_AUTHORIZATION': req.to_header()['Authorization']} env_params.update(std_env_params) environ = self._makeEnviron(env_params) identity = plugin.identify(environ) self.assertEquals(plugin.authenticate(environ, identity), None) # Restore the good consumer key req['oauth_consumer_key'] = good_consumer_key # Now test a GET request req = oauth2.Request.from_consumer_and_token( consumer=consumer, token=None, http_method='GET', http_url='http://www.example.com/get_some_resource') req.sign_request(signature_method=oauth2.SignatureMethod_HMAC_SHA1(), consumer=consumer, token=None) env_params = {'HTTP_AUTHORIZATION': req.to_header()['Authorization']} env_params.update(std_env_params) env_params['REQUEST_METHOD'] = 'GET' environ = self._makeEnviron(env_params) identity = plugin.identify(environ) userid = plugin.authenticate(environ, identity) self.assertEquals(identity['consumer'].key, consumer.key) # Cleanup consumers self.session.execute(Consumer.__table__.delete())
def test_get_request_token(self): r"""Test how request token is fetched from the database""" plugin = self._makeOne() manager = plugin.manager # Ensure we are clean from repoze.who.plugins.oauth import Consumer self.assertEquals(len(list(self.session.query(Consumer))), 0) # Create a consumer in the DB consumer = Consumer(key=u'some-consumer', secret='some-secret') manager.DBSession.add(consumer) # Create a token and verify it for some-user rtoken = manager.create_request_token(consumer, 'http://test.com') rtoken = manager.set_request_token_user(rtoken.key, u'some-user') # If we supply a non-existing key to set_request_token_user nothing # happens and we get nothing back self.assertEquals( manager.set_request_token_user(rtoken.key[:-2], u'some-user'), None) # Token key and verification code provided - token found env = dict(environ={}, identity={ 'oauth_token': rtoken.key, 'oauth_verifier': rtoken.verifier, }) self.assertTrue(plugin._get_request_token(env)) self.assertTrue(env.pop('token')) # A wrong verifier - not found env = dict(environ={}, identity={ 'oauth_token': rtoken.key, 'oauth_verifier': rtoken.verifier[:-1], }) self.assertFalse(plugin._get_request_token(env)) self.assertFalse('token' in env) # No verifier - not found env = dict(environ={}, identity={ 'oauth_token': rtoken.key, }) self.assertFalse(plugin._get_request_token(env)) self.assertFalse('token' in env) # No token key - not found env = dict(environ={}, identity={ 'oauth_verifier': rtoken.verifier, }) self.assertFalse(plugin._get_request_token(env)) self.assertFalse('token' in env) # Empty dict - not found of course env = dict(environ={}, identity={}) self.assertFalse(plugin._get_request_token(env)) self.assertFalse('token' in env) # Cleanup manager.DBSession.delete(consumer) manager.DBSession.flush()