def setUp(self): jane = User.objects.create_user('jane', '*****@*****.**', 'toto') self.resource = Resource(name='photos', url='/oauth/photo/') self.resource.save() self.CONSUMER_KEY = 'dpf43f3p2l4k3l03' self.CONSUMER_SECRET = 'kd94hf93k423kf44' self.consumer = Consumer(key=self.CONSUMER_KEY, secret=self.CONSUMER_SECRET, name='printer.example.com', user=jane) self.consumer.save()
def create_consumer(): ConsumerInfo.objects.filter(consumer__key=KEY).delete() Consumer.objects.filter(key=KEY).delete() c = Consumer(name='Example Consumer', description='Consumer to do some demos with', status=ACCEPTED, user=User.objects.get(username='******'), xauth_allowed=False, key=KEY, secret=SECRET) #c.generate_random_codes() c.save() i = ConsumerInfo(consumer=c) i.admin_contact = '*****@*****.**' i.permissions = ['courses', 'grades'] i.save() return c
def setup_func(): #from http://code.google.com/p/nose-gae/issues/detail?id=13 os.environ['SERVER_NAME'] = 'localhost' os.environ['SERVER_PORT'] = '8080' os.environ['AUTH_DOMAIN'] = 'example.org' os.environ['USER_EMAIL'] = '' os.environ['USER_ID'] = '' resource = Resource(name='default', url='/oauth/photo/') resource.put() consumer = Consumer(key_=CONSUMER_KEY, secret=CONSUMER_SECRET, name='printer.example.com') consumer.put()
def setUp(self): self.client = Client() self.username = '******' self.email = '*****@*****.**' self.password = '******' self.user = User.objects.create_user(self.username, self.email, self.password) # OAuth requirements self.resource = Resource(name='data', url='/') self.resource.save() self.CONSUMER_KEY = 'dpf43f3p2l4k3l03' self.CONSUMER_SECRET = 'kd94hf93k423kf44' self.consumer = Consumer(key=self.CONSUMER_KEY, secret=self.CONSUMER_SECRET, name='api.example.com', user=self.user) self.consumer.save()
def setUp(self): username = self.username = '******' password = self.password = '******' email = self.email = '*****@*****.**' jane = self.jane = User.objects.create_user(username, email, password) resource = self.resource = Resource(name='photos', url='/api/v1/user/jane/') resource.save() CONSUMER_KEY = self.CONSUMER_KEY = 'dpf43f3p2l4k3l03' CONSUMER_SECRET = self.CONSUMER_SECRET = 'kd94hf93k423kf44' consumer = self.consumer = Consumer(key=CONSUMER_KEY, secret=CONSUMER_SECRET, name='printer.example.com', user=jane) consumer.save() self.callback_token = self.callback = 'http://printer.example.com/request_token_ready' self.callback_confirmed = True self.request_token_parameters = { 'oauth_consumer_key': self.CONSUMER_KEY, 'oauth_signature_method': 'PLAINTEXT', 'oauth_signature': '%s&' % self.CONSUMER_SECRET, 'oauth_timestamp': str(int(time.time())), 'oauth_nonce': 'requestnonce', 'oauth_version': '1.0', 'oauth_callback': self.callback, 'scope': 'photos', # custom argument to specify Protected Resource } self.c = Client()
def setUp(self): self.faketime = 525942870 self.client = Client() # create a Consumer (and associated stuff) try: u = User.objects.get(username='******') except User.DoesNotExist: u = User(username='******') u.save() try: c = Consumer.objects.get(name='Test Consumer') except Consumer.DoesNotExist: c = Consumer(name='Test Consumer') c.description = 'Consumer to do some tests with' c.status = ACCEPTED c.user = u c.xauth_allowed = False c.generate_random_codes() c.save() self.consumer = c i = ConsumerInfo(consumer=c) i.admin_contact = '*****@*****.**' i.permissions = ['courses'] i.timestamp = self.faketime - 10 # make sure the ConsumerInfo was there "before" the Token was created i.save() self.consumerinfo = i # create an access token so we can jump in to requests try: t = Token.objects.get(token_type=Token.ACCESS, consumer=c, user=u) except Token.DoesNotExist: t = Token(token_type=Token.ACCESS, consumer=c, user=u, timestamp=self.faketime) t.is_approved = True t.generate_random_codes() t.verifier = VERIFIER t.save() self.token = t
def setUp(self): jane = User.objects.create_user("jane", "*****@*****.**", "toto") self.resource = Resource(name="photos", url="/oauth/photo/") self.resource.save() self.CONSUMER_KEY = "dpf43f3p2l4k3l03" self.CONSUMER_SECRET = "kd94hf93k423kf44" self.consumer = Consumer( key=self.CONSUMER_KEY, secret=self.CONSUMER_SECRET, name="printer.example.com", user=jane ) self.consumer.save()
def create_consumer(name, description, owner_userid, admin_contact, permissions): """ Create a new Consumer with all of the info we need recorded. Arguments: (name, description, owner_userid, admin_contact, permissions) Could be rolled into a form+view in /sysadmin/, but how many could there possibly be? """ assert set(permissions) <= set(PERMISSION_OPTIONS.keys()), 'Permissions must be chosen from PERMISSION_CHOICES.' User = get_user_model() c = Consumer(name=name, description=description, status=ACCEPTED, user=User.objects.get(username=owner_userid), xauth_allowed=False) c.generate_random_codes() c.save() i = ConsumerInfo(consumer=c) i.admin_contact = admin_contact i.permissions = list(permissions) i.save() print("Consumer key:", c.key) print("Consumer secret:", c.secret)
def create_consumer(name, description, owner_userid, admin_contact, permissions): """ Create a new Consumer with all of the info we need recorded. Arguments: (name, description, owner_userid, admin_contact, permissions) Could be rolled into a form+view in /sysadmin/, but how many could there possibly be? """ assert set(permissions) <= set(PERMISSION_OPTIONS.keys( )), 'Permissions must be chosen from PERMISSION_CHOICES.' User = get_user_model() c = Consumer(name=name, description=description, status=ACCEPTED, user=User.objects.get(username=owner_userid), xauth_allowed=False) c.generate_random_codes() c.save() i = ConsumerInfo(consumer=c) i.admin_contact = admin_contact i.permissions = list(permissions) i.save() print("Consumer key:", c.key) print("Consumer secret:", c.secret)
def setUp(self): self.client = Client() self.username = "******" self.email = "*****@*****.**" self.password = "******" self.user = User.objects.create_user(self.username, self.email, self.password) # OAuth requirements self.resource = Resource(name="data", url="/") self.resource.save() self.CONSUMER_KEY = "dpf43f3p2l4k3l03" self.CONSUMER_SECRET = "kd94hf93k423kf44" self.consumer = Consumer( key=self.CONSUMER_KEY, secret=self.CONSUMER_SECRET, name="api.example.com", user=self.user ) self.consumer.save()
def setUp(self): self.username = '******' self.password = '******' self.email = '*****@*****.**' self.jane = User.objects.create_user(self.username, self.email, self.password) self.scope = Scope.objects.create(name='photos', url='/oauth/photo/') self.CONSUMER_KEY = 'dpf43f3p2l4k3l03' self.CONSUMER_SECRET = 'kd94hf93k423kf44' consumer = self.consumer = Consumer(key=self.CONSUMER_KEY, secret=self.CONSUMER_SECRET, name='printer.example.com', user=self.jane) consumer.save() self.callback_token = self.callback = 'http://printer.example.com/request_token_ready' self.callback_confirmed = True self.c = Client()
from oauth_provider.models import Resource,Consumer from oauth_provider.consts import ACCEPTED default_consumer = Consumer(name="Tomboy default consumer", description="Tomboy default consumer", key_ = 'anyone', secret = 'anyone', status = ACCEPTED ) default_consumer.put() default_resource = Resource(name="default") default_resource.put()
def test_oauth1_0(): """ Test to veriy OAuth 1.0 flow """ """ After Jane informs printer.example.com that she would like to print her vacation photo stored at photos.example.net, the printer website tries to access the photo and receives HTTP 401 Unauthorized indicating it is private. The Service Provider includes the following header with the response:: """ #TODO - not able to handle /request_token/ - returns 404 #TODO - add test for reuse of nonce for the request token stage? #TODO - add test for oauth params in Authorization, GET and POST response = app.get('/request_token',status=401) assert response.status == '401 Invalid request parameters.' assert response.headers['WWW-Authenticate'] == 'OAuth realm="http://events.example.net/"' assert response.body == 'Invalid request parameters.' """ The Consumer sends the following HTTP POST request to the Service Provider:: """ import time parameters = { 'oauth_consumer_key': CONSUMER_KEY, 'oauth_signature_method': 'PLAINTEXT', 'oauth_signature': '%s&' % CONSUMER_SECRET, 'oauth_timestamp': str(int(time.time())), 'oauth_nonce': 'requestnonce', 'oauth_version': '1.0', #'scope': 'default', # custom argument to specify Protected Resource } response = app.post('/request_token',parameters) """ The Service Provider checks the signature and replies with an unauthorized Request Token in the body of the HTTP response:: """ assert response.status == '200 OK' tokens = Token.all().fetch(1000) #double checking the sanity of the test - there should only be one token in #the store at this stage assert len(tokens) == 1 token = tokens[0] assert 'oauth_token_secret=%s&oauth_token=%s'%(token.secret,token.key_) == response.body #Ensure that the token returned is unauthorized request token assert token.is_approved == False assert token.token_type == Token.REQUEST """ If you try to access a resource with a wrong scope, it will return an error:: """ #the scope related tests are not here as they the scope #function has not been implemented yet #>> parameters['scope'] = 'videos' #>>> response = c.get("/oauth/request_token/", parameters) #>>> response.status_code #401 #>>> response.content #'Resource videos does not exist.' """ Requesting User Authorization ----------------------------- The Consumer redirects Jane's browser to the Service Provider User Authorization URL to obtain Jane's approval for accessing her private photos. The Service Provider asks Jane to sign-in using her username and password:: """ parameters = { 'oauth_token' : token.key_, 'oauth_callback' : 'http://test.com/request_token_ready' } response = app.post('/authorize', parameters) assert response.status == '302 Moved Temporarily' assert response.location ==\ 'http://localhost/_ah/login?continue=http%%3A//localhost/authorize%%3Foauth_token%%3D%s%%26oauth_callback%%3Dhttp%%3A//test.com/request_token_ready'%(token.key_) #the token details should not be altered by this call tokens = Token.all().fetch(1000) assert len(tokens) == 1 token = tokens[0] assert token.is_approved == False assert token.token_type == Token.REQUEST #In reality the user would of been redirected to the Google login page and #then redirected to the authorization url. We simulate the user being logged #in and redirect manually to the authorization page #simulate logged in os.environ['USER_EMAIL'] = '*****@*****.**' response = app.post('/authorize', parameters) #verify that the request token is now approved tokens = Token.all().fetch(1000) assert len(tokens) == 1 token = tokens[0] assert token.is_approved == True assert token.token_type == Token.REQUEST #verify the response - now that we are approved we should get assert response.status == '302 Moved Temporarily' assert response.location == 'http://test.com/request_token_ready?oauth_token=%s'%(token.key_) #TODO add test for case where the user rejects the approval, #now the flow assumes that it will always be approved upon logging in """ Obtaining an Access Token ------------------------- Now that the Consumer knows Jane approved the Request Token, it asks the Service Provider to exchange it for an Access Token:: """ parameters = { 'oauth_consumer_key': CONSUMER_KEY, 'oauth_token': token.key_, 'oauth_signature_method': 'PLAINTEXT', 'oauth_signature': '%s&%s' % (CONSUMER_SECRET, token.secret), 'oauth_timestamp': str(int(time.time())), 'oauth_nonce': 'accessnonce', 'oauth_version': '1.0', } response = app.post("/access_token", parameters) """ The Service Provider checks the signature and replies with an Access Token in the body of the HTTP response:: """ response.status == '200 OK' #verify the access token now access_tokens = Token.all()\ .filter('token_type =',Token.ACCESS).fetch(1000) #double checking the sanity of the test - there should only be one token in #the store at this stage assert len(access_tokens) == 1 access_token = access_tokens[0] assert 'oauth_token_secret=%s&oauth_token=%s'%(access_token.secret,access_token.key_) == response.body assert str(access_token.user) == '*****@*****.**' """ The Consumer will not be able to request another Access Token with the same Nonce:: """ nonces = Nonce.all().fetch(1000) assert nonces[0].key_ == "accessnonce" response = app.post("/access_token", parameters,status=401) assert response.status == '401 Nonce already used: accessnonce' assert response.body == 'Nonce already used: accessnonce' """ The Consumer will not be able to request an Access Token if the token is not approved:: """ token.is_approved = False token.put() parameters['oauth_nonce'] = 'anotheraccessnonce' response = app.post("/access_token", parameters,status=401) response.status == "401 Consumer key or token key does not match. Make sure your request token is approved. Check your verifier too if you use OAuth 1.0a." response.body == 'Consumer key or token key does not match. Make sure your request token is approved. Check your verifier too if you use OAuth 1.0a.' """ Accessing Protected Resources ----------------------------- The Consumer is now ready to request the private photo. Since the photo URL is not secure (HTTP), it must use HMAC-SHA1. Generating Signature Base String ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To generate the signature, it first needs to generate the Signature Base String. The request contains the following parameters (oauth_signature excluded) which are ordered and concatenated into a normalized string:: """ parameters = { 'oauth_consumer_key': CONSUMER_KEY, 'oauth_token': access_token.key_, 'oauth_signature_method': 'HMAC-SHA1', 'oauth_timestamp': str(int(time.time())), 'oauth_nonce': 'accessresourcenonce', 'oauth_version': '1.0', } """ Calculating Signature Value ~~~~~~~~~~~~~~~~~~~~~~~~~~~ HMAC-SHA1 produces the following digest value as a base64-encoded string (using the Signature Base String as text and kd94hf93k423kf44&pfkkdhi9sl3r4s00 as key):: """ consumer = Consumer.all().fetch(1000)[0] #we dont use the OAuthRequest.from_token_and_callback function as how the #original method does as it does a token.key instead of token.key_ oauth_request = OAuthRequest('POST','http://localhost/protected',parameters) signature_method = OAuthSignatureMethod_HMAC_SHA1() signature = signature_method.build_signature(oauth_request, consumer, access_token) """ Requesting Protected Resource ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ All together, the Consumer request for the photo is:: """ parameters['oauth_signature'] = signature response = app.post("/protected", parameters) assert response.status == '200 OK' assert response.body == 'Protected Resource access!' """ Otherwise, an explicit error will be raised:: """ parameters['oauth_signature'] = 'wrongsignature' parameters['oauth_nonce'] = 'anotheraccessresourcenonce' response = app.post("/protected", parameters,status=401) assert '401 Invalid signature. Expected signature base string: POST' in response.status assert 'Invalid signature. Expected signature base string: POST' in response.body response = app.post("/protected",status=401) #TODO - why are we getting "Invalid OAuth parameters instead of Invalid request parameters" assert response.status == '401 Invalid OAuth parameters' assert response.headers['WWW-Authenticate'] == 'OAuth realm="http://events.example.net/"' assert response.body == 'Invalid OAuth parameters' """ Revoking Access --------------- If Jane deletes the Access Token of printer.example.com, the Consumer will not be able to access the Protected Resource anymore:: """ access_token_key = access_token.key_ access_token.delete() parameters['oauth_nonce'] = 'yetanotheraccessresourcenonce' oauth_request = OAuthRequest('POST','http://localhost/protected',parameters) signature_method = OAuthSignatureMethod_HMAC_SHA1() signature = signature_method.build_signature(oauth_request, consumer, access_token) parameters['oauth_signature'] = signature response = app.post("/protected", parameters,status=401) assert response.status == '401 Invalid access token: %s'%(access_token_key) assert response.body == 'Invalid access token: %s'%(access_token_key) """
class OAuthTests(TestCase): """ OAuth authentication: * the user would like to access his API data from a third-party website * the third-party website proposes a link to get that API data * the user is redirected to the API and must log in if not authenticated * the API displays a webpage to confirm that the user trusts the third-party website * if confirmed, the user is redirected to the third-party website through the callback view * the third-party website is able to retrieve data from the API """ urls = 'djangorestframework.tests.oauthentication' def setUp(self): self.client = Client() self.username = '******' self.email = '*****@*****.**' self.password = '******' self.user = User.objects.create_user(self.username, self.email, self.password) # OAuth requirements self.resource = Resource(name='data', url='/') self.resource.save() self.CONSUMER_KEY = 'dpf43f3p2l4k3l03' self.CONSUMER_SECRET = 'kd94hf93k423kf44' self.consumer = Consumer(key=self.CONSUMER_KEY, secret=self.CONSUMER_SECRET, name='api.example.com', user=self.user) self.consumer.save() def test_oauth_invalid_and_anonymous_access(self): """ Verify that the resource is protected and the OAuth authorization view require the user to be logged in. """ response = self.client.get('/') self.assertEqual(response.content, 'Invalid request parameters.') self.assertEqual(response.status_code, 401) response = self.client.get('/oauth/authorize/', follow=True) self.assertRedirects(response, '/accounts/login/?next=/oauth/authorize/') def test_oauth_authorize_access(self): """ Verify that once logged in, the user can access the authorization page but can't display the page because the request token is not specified. """ self.client.login(username=self.username, password=self.password) response = self.client.get('/oauth/authorize/', follow=True) self.assertEqual(response.content, 'No request token specified.') def _create_request_token_parameters(self): """ A shortcut to create request's token parameters. """ return { 'oauth_consumer_key': self.CONSUMER_KEY, 'oauth_signature_method': 'PLAINTEXT', 'oauth_signature': '%s&' % self.CONSUMER_SECRET, 'oauth_timestamp': str(int(time.time())), 'oauth_nonce': 'requestnonce', 'oauth_version': '1.0', 'oauth_callback': 'http://api.example.com/request_token_ready', 'scope': 'data', } def test_oauth_request_token_retrieval(self): """ Verify that the request token can be retrieved by the server. """ response = self.client.get("/oauth/request_token/", self._create_request_token_parameters()) self.assertEqual(response.status_code, 200) token = list(Token.objects.all())[-1] self.failIf(token.key not in response.content) self.failIf(token.secret not in response.content) def test_oauth_user_request_authorization(self): """ Verify that the user can access the authorization page once logged in and the request token has been retrieved. """ # Setup response = self.client.get("/oauth/request_token/", self._create_request_token_parameters()) token = list(Token.objects.all())[-1] # Starting the test here self.client.login(username=self.username, password=self.password) parameters = {'oauth_token': token.key,} response = self.client.get("/oauth/authorize/", parameters) self.assertEqual(response.status_code, 200) self.failIf(not response.content.startswith('Fake authorize view for api.example.com with params: oauth_token=')) self.assertEqual(token.is_approved, 0) parameters['authorize_access'] = 1 # fake authorization by the user response = self.client.post("/oauth/authorize/", parameters) self.assertEqual(response.status_code, 302) self.failIf(not response['Location'].startswith('http://api.example.com/request_token_ready?oauth_verifier=')) token = Token.objects.get(key=token.key) self.failIf(token.key not in response['Location']) self.assertEqual(token.is_approved, 1) def _create_access_token_parameters(self, token): """ A shortcut to create access' token parameters. """ return { 'oauth_consumer_key': self.CONSUMER_KEY, 'oauth_token': token.key, 'oauth_signature_method': 'PLAINTEXT', 'oauth_signature': '%s&%s' % (self.CONSUMER_SECRET, token.secret), 'oauth_timestamp': str(int(time.time())), 'oauth_nonce': 'accessnonce', 'oauth_version': '1.0', 'oauth_verifier': token.verifier, 'scope': 'data', } def test_oauth_access_token_retrieval(self): """ Verify that the request token can be retrieved by the server. """ # Setup response = self.client.get("/oauth/request_token/", self._create_request_token_parameters()) token = list(Token.objects.all())[-1] self.client.login(username=self.username, password=self.password) parameters = {'oauth_token': token.key,} response = self.client.get("/oauth/authorize/", parameters) parameters['authorize_access'] = 1 # fake authorization by the user response = self.client.post("/oauth/authorize/", parameters) token = Token.objects.get(key=token.key) # Starting the test here response = self.client.get("/oauth/access_token/", self._create_access_token_parameters(token)) self.assertEqual(response.status_code, 200) self.failIf(not response.content.startswith('oauth_token_secret=')) access_token = list(Token.objects.filter(token_type=Token.ACCESS))[-1] self.failIf(access_token.key not in response.content) self.failIf(access_token.secret not in response.content) self.assertEqual(access_token.user.username, 'john') def _create_access_parameters(self, access_token): """ A shortcut to create access' parameters. """ parameters = { 'oauth_consumer_key': self.CONSUMER_KEY, 'oauth_token': access_token.key, 'oauth_signature_method': 'HMAC-SHA1', 'oauth_timestamp': str(int(time.time())), 'oauth_nonce': 'accessresourcenonce', 'oauth_version': '1.0', } oauth_request = oauth.Request.from_token_and_callback(access_token, http_url='http://testserver/', parameters=parameters) signature_method = oauth.SignatureMethod_HMAC_SHA1() signature = signature_method.sign(oauth_request, self.consumer, access_token) parameters['oauth_signature'] = signature return parameters def test_oauth_protected_resource_access(self): """ Verify that the request token can be retrieved by the server. """ # Setup response = self.client.get("/oauth/request_token/", self._create_request_token_parameters()) token = list(Token.objects.all())[-1] self.client.login(username=self.username, password=self.password) parameters = {'oauth_token': token.key,} response = self.client.get("/oauth/authorize/", parameters) parameters['authorize_access'] = 1 # fake authorization by the user response = self.client.post("/oauth/authorize/", parameters) token = Token.objects.get(key=token.key) response = self.client.get("/oauth/access_token/", self._create_access_token_parameters(token)) access_token = list(Token.objects.filter(token_type=Token.ACCESS))[-1] # Starting the test here response = self.client.get("/", self._create_access_token_parameters(access_token)) self.assertEqual(response.status_code, 200) self.assertEqual(response.content, '{"resource": "Protected!"}')
class Test1(TestCase): def setUp(self): jane = User.objects.create_user('jane', '*****@*****.**', 'toto') self.resource = Resource(name='photos', url='/oauth/photo/') self.resource.save() self.CONSUMER_KEY = 'dpf43f3p2l4k3l03' self.CONSUMER_SECRET = 'kd94hf93k423kf44' self.consumer = Consumer(key=self.CONSUMER_KEY, secret=self.CONSUMER_SECRET, name='printer.example.com', user=jane) self.consumer.save() def test_1(self): """Obtaining a Request Token""" response = self.client.get("/oauth/request_token/") self.assertEqual(response.status_code, httplib.UNAUTHORIZED) # depends on REALM_KEY_NAME Django setting self.assertEqual(response._headers['www-authenticate'], ('WWW-Authenticate', 'OAuth realm=""')) self.assertEqual(response.content, 'Invalid request parameters.') # The Consumer sends the following HTTP POST request to the Service Provider parameters = { 'oauth_consumer_key': self.CONSUMER_KEY, 'oauth_signature_method': 'PLAINTEXT', 'oauth_signature': '%s&' % self.CONSUMER_SECRET, 'oauth_timestamp': str(int(time.time())), 'oauth_nonce': 'requestnonce', 'oauth_version': '1.0', 'oauth_callback': 'http://printer.example.com/request_token_ready', 'scope': 'photos', # custom argument to specify Protected Resource } response = self.client.get("/oauth/request_token/", parameters) # The Service Provider checks the signature and replies with an unauthorized # Request Token in the body of the HTTP response:: self.assertEqual(response.status_code, httplib.OK) self.assertTrue("oauth_token_secret=" in response.content) self.assertTrue("oauth_token=" in response.content) self.assertTrue("oauth_callback_confirmed=true" in response.content) token = list(Token.objects.all())[-1] self.assertTrue(token.key in response.content) self.assertTrue(token.secret in response.content) self.assertEqual(token.callback, u'http://printer.example.com/request_token_ready') self.assertTrue(token.callback_confirmed) # If you try to access a resource with a wrong scope, it will return an error:: # This is not supported. # parameters['scope'] = 'videos' # parameters['oauth_nonce'] = 'requestnoncevideos' # response = self.client.get("/oauth/request_token/", parameters) # print "-------" # print response # print "-------" # self.assertEqual(response.status_code, httplib.UNAUTHORIZED) # self.assertEqual(response.content, 'Resource videos does not exist.') parameters['scope'] = 'photos' # restore # If you try to put a wrong callback, it will return an error:: parameters['oauth_callback'] = 'wrongcallback' parameters['oauth_nonce'] = 'requestnoncewrongcallback' response = self.client.get("/oauth/request_token/", parameters) self.assertEqual(response.status_code, httplib.UNAUTHORIZED) self.assertEqual(response.content, 'Invalid callback URL.') # If you do not provide any callback (i.e. oob), the Service Provider SHOULD # display the value of the verification code:: parameters['oauth_callback'] = 'oob' parameters['oauth_nonce'] = 'requestnonceoob' response = self.client.get("/oauth/request_token/", parameters) self.assertEqual(response.status_code, httplib.OK) self.assertTrue("oauth_token_secret=" in response.content) self.assertTrue("oauth_token=" in response.content) self.assertTrue("oauth_callback_confirmed=true" in response.content) oobtoken = list(Token.objects.all())[-1] self.assertTrue(oobtoken.key in response.content) self.assertTrue(oobtoken.secret in response.content) self.assertEqual(oobtoken.callback, None) self.assertFalse(oobtoken.callback_confirmed) # ======== Requesting User Authorization ============ # The Consumer redirects Jane's browser to the Service Provider User # Authorization URL to obtain Jane's approval for accessing her private photos. # The Service Provider asks Jane to sign-in using her username and password:: parameters = { 'oauth_token': token.key, } response = self.client.get("/oauth/authorize/", parameters) self.assertEqual(response.status_code, httplib.FOUND) self.assertTrue(True) print response['Location'] self.assertRedirects(response, '/accounts/login/?next=/oauth/authorize/') self.assertTrue(token.key in response['Location']) # If successful, asks her if she approves granting printer.example.com access to # her private photos. If Jane approves the request, the Service Provider # redirects her back to the Consumer's callback URL:: self.assertTrue(self.client.login(username='******', password='******')) self.assertEqual(token.is_approved, 0) response = self.client.get("/oauth/authorize/", parameters) self.assertEqual(response.status_code, httplib.OK) self.assertEqual( response.content, 'Fake authorize view for printer.example.com with params: oauth_token=...' ) # fake authorization by the user parameters['authorize_access'] = 1 response = self.client.post("/oauth/authorize/", parameters) self.assertEqual(response.status_code, httplib.FOUND) self.assertRedirects(response, "/request_token_ready") token = Token.objects.get(key=self.token.key) self.assertTrue(token.key in response['Location']) self.assertEqual(token.is_approved, 1) # without session parameter (previous POST removed it) response = self.client.post("/oauth/authorize/", parameters) self.assertEqual(response.status_code, httplib.UNAUTHORIZED) self.assertEqual(response.content, 'Action not allowed.') # fake access not granted by the user (set session parameter again) response = self.client.get("/oauth/authorize/", parameters) parameters['authorize_access'] = 0 response = self.client.post("/oauth/authorize/", parameters) self.assertEqual(response.status_code, httplib.FOUND) self.assertRedirects(response, "/request_token_ready") self.client.logout() # With OAuth 1.0a, the callback argument can be set to "oob" (out-of-band), # you can specify your own default callback view with the # ``OAUTH_PROVIDER_CALLBACK_VIEW`` setting:: # from oauth_provider.consts import OUT_OF_BAND token.callback = OUT_OF_BAND token.save() parameters = { 'oauth_token': token.key, } self.assertTrue(self.client.login(username='******', password='******')) response = self.client.get("/oauth/authorize/", parameters) parameters['authorize_access'] = 0 response = self.client.post("/oauth/authorize/", parameters) self.assertEqual(response.status_code, httplib.OK) self.assertEqual(response.content, 'Fake callback view.') self.client.logout() # ========== Obtaining an Access Token =========== # Now that the Consumer knows Jane approved the Request Token, it asks the # Service Provider to exchange it for an Access Token:: parameters = { 'oauth_consumer_key': self.CONSUMER_KEY, 'oauth_token': token.key, 'oauth_signature_method': 'PLAINTEXT', 'oauth_signature': '%s&%s' % (self.CONSUMER_SECRET, token.secret), 'oauth_timestamp': str(int(time.time())), 'oauth_nonce': 'accessnonce', 'oauth_version': '1.0', 'oauth_verifier': self.token.verifier, 'scope': 'photos', } response = self.client.get("/oauth/access_token/", parameters) # You can use HTTP Authorization header, if you provide both, header will be # checked before parameters. It depends on your needs. # The Service Provider checks the signature and replies with an Access Token in # the body of the HTTP response:: self.assertEqual(response.status_code, httplib.OK) self.assertTrue('oauth_token_secret=' in response.content) self.assertTrue('oauth_token=' in response.content) access_token = list(Token.objects.filter(token_type=Token.ACCESS))[-1] self.assertTrue(access_token.key in response.content) self.assertTrue(access_token.secret in response.content) self.assertEqual(access_token.user.name, u'jane') # The Consumer will not be able to request another Access Token with the same # parameters because the Request Token has been deleted once Access Token is # created:: response = self.client.get("/oauth/access_token/", parameters) self.assertEqual(response.status_code, httplib.BAD_REQUEST) self.assertEqual(response.content, 'Invalid request token.') # The Consumer will not be able to request another Access Token with a missing # or invalid verifier:: new_request_token = Token.objects.create_token( token_type=Token.REQUEST, timestamp=str(int(time.time())), consumer=Consumer.objects.get(key=self.CONSUMER_KEY), user=User.objects.get(username="******"), resource=Resource.objects.get(name='photos')) new_request_token.is_approved = True new_request_token.save() parameters['oauth_token'] = new_request_token.key parameters['oauth_signature'] = '%s&%s' % (self.CONSUMER_SECRET, new_request_token.secret) parameters['oauth_verifier'] = 'invalidverifier' response = self.client.get("/oauth/access_token/", parameters) self.assertEqual(response.status_code, httplib.BAD_REQUEST) self.assertEqual(response.content, 'Invalid OAuth verifier.') parameters['oauth_verifier'] = new_request_token.verifier # restore # The Consumer will not be able to request an Access Token if the token is not # approved:: new_request_token.is_approved = False new_request_token.save() parameters['oauth_nonce'] = 'anotheraccessnonce' response = self.client.get("/oauth/access_token/", parameters) self.assertEqual(response.status_code, httplib.BAD_REQUEST) self.assertEqual(response.content, 'Request Token not approved by the user.') # Accessing Protected Resources # The Consumer is now ready to request the private photo. Since the photo URL is # not secure (HTTP), it must use HMAC-SHA1. # Generating Signature Base String # To generate the signature, it first needs to generate the Signature Base # String. The request contains the following parameters (oauth_signature # excluded) which are ordered and concatenated into a normalized string:: parameters = { 'oauth_consumer_key': self.CONSUMER_KEY, 'oauth_token': access_token.key, 'oauth_signature_method': 'HMAC-SHA1', 'oauth_timestamp': str(int(time.time())), 'oauth_nonce': 'accessresourcenonce', 'oauth_version': '1.0', } # Calculating Signature Value # HMAC-SHA1 produces the following digest value as a base64-encoded string # (using the Signature Base String as text and kd94hf93k423kf44&pfkkdhi9sl3r4s00 # as key):: import oauth2 as oauth oauth_request = oauth.Request.from_token_and_callback( access_token, http_url='http://testserver/oauth/photo/', parameters=parameters) signature_method = oauth.SignatureMethod_HMAC_SHA1() signature = signature_method.sign(oauth_request, self.consumer, access_token) # Requesting Protected Resource # All together, the Consumer request for the photo is:: parameters['oauth_signature'] = signature response = self.client.get("/oauth/photo/", parameters) self.assertEqual(response.status_code, httplib.OK) self.assertEqual(response.content, 'Protected Resource access!') # Otherwise, an explicit error will be raised:: parameters['oauth_signature'] = 'wrongsignature' parameters['oauth_nonce'] = 'anotheraccessresourcenonce' response = self.client.get("/oauth/photo/", parameters) self.assertEqual(response.status_code, httplib.UNAUTHORIZED) self.assertTrue( 'Invalid signature. Expected signature base string: GET' in response.content) response = self.client.get("/oauth/photo/") self.assertEqual(response.status_code, httplib.UNAUTHORIZED) self.assertEqual(response.content, 'Invalid request parameters.') # Revoking Access # If Jane deletes the Access Token of printer.example.com, the Consumer will not # be able to access the Protected Resource anymore:: access_token.delete() # Note that an "Invalid signature" error will be raised here if the # token is not revoked by Jane because we reuse a previously used one. parameters['oauth_signature'] = signature parameters['oauth_nonce'] = 'yetanotheraccessresourcenonce' response = self.client.get("/oauth/photo/", parameters) self.assertEqual(response.status_code, httplib.UNAUTHORIZED) self.assertTrue('Invalid access token: ...', response.content)
class Test1(TestCase): def setUp(self): jane = User.objects.create_user("jane", "*****@*****.**", "toto") self.resource = Resource(name="photos", url="/oauth/photo/") self.resource.save() self.CONSUMER_KEY = "dpf43f3p2l4k3l03" self.CONSUMER_SECRET = "kd94hf93k423kf44" self.consumer = Consumer( key=self.CONSUMER_KEY, secret=self.CONSUMER_SECRET, name="printer.example.com", user=jane ) self.consumer.save() def test_1(self): """Obtaining a Request Token""" response = self.client.get("/oauth/request_token/") self.assertEqual(response.status_code, httplib.UNAUTHORIZED) # depends on REALM_KEY_NAME Django setting self.assertEqual(response._headers["www-authenticate"], ("WWW-Authenticate", 'OAuth realm=""')) self.assertEqual(response.content, "Invalid request parameters.") # The Consumer sends the following HTTP POST request to the Service Provider parameters = { "oauth_consumer_key": self.CONSUMER_KEY, "oauth_signature_method": "PLAINTEXT", "oauth_signature": "%s&" % self.CONSUMER_SECRET, "oauth_timestamp": str(int(time.time())), "oauth_nonce": "requestnonce", "oauth_version": "1.0", "oauth_callback": "http://printer.example.com/request_token_ready", "scope": "photos", # custom argument to specify Protected Resource } response = self.client.get("/oauth/request_token/", parameters) # The Service Provider checks the signature and replies with an unauthorized # Request Token in the body of the HTTP response:: self.assertEqual(response.status_code, httplib.OK) self.assertTrue("oauth_token_secret=" in response.content) self.assertTrue("oauth_token=" in response.content) self.assertTrue("oauth_callback_confirmed=true" in response.content) token = list(Token.objects.all())[-1] self.assertTrue(token.key in response.content) self.assertTrue(token.secret in response.content) self.assertEqual(token.callback, u"http://printer.example.com/request_token_ready") self.assertTrue(token.callback_confirmed) # If you try to access a resource with a wrong scope, it will return an error:: # This is not supported. # parameters['scope'] = 'videos' # parameters['oauth_nonce'] = 'requestnoncevideos' # response = self.client.get("/oauth/request_token/", parameters) # print "-------" # print response # print "-------" # self.assertEqual(response.status_code, httplib.UNAUTHORIZED) # self.assertEqual(response.content, 'Resource videos does not exist.') parameters["scope"] = "photos" # restore # If you try to put a wrong callback, it will return an error:: parameters["oauth_callback"] = "wrongcallback" parameters["oauth_nonce"] = "requestnoncewrongcallback" response = self.client.get("/oauth/request_token/", parameters) self.assertEqual(response.status_code, httplib.UNAUTHORIZED) self.assertEqual(response.content, "Invalid callback URL.") # If you do not provide any callback (i.e. oob), the Service Provider SHOULD # display the value of the verification code:: parameters["oauth_callback"] = "oob" parameters["oauth_nonce"] = "requestnonceoob" response = self.client.get("/oauth/request_token/", parameters) self.assertEqual(response.status_code, httplib.OK) self.assertTrue("oauth_token_secret=" in response.content) self.assertTrue("oauth_token=" in response.content) self.assertTrue("oauth_callback_confirmed=true" in response.content) oobtoken = list(Token.objects.all())[-1] self.assertTrue(oobtoken.key in response.content) self.assertTrue(oobtoken.secret in response.content) self.assertEqual(oobtoken.callback, None) self.assertFalse(oobtoken.callback_confirmed) # ======== Requesting User Authorization ============ # The Consumer redirects Jane's browser to the Service Provider User # Authorization URL to obtain Jane's approval for accessing her private photos. # The Service Provider asks Jane to sign-in using her username and password:: parameters = {"oauth_token": token.key} response = self.client.get("/oauth/authorize/", parameters) self.assertEqual(response.status_code, httplib.FOUND) self.assertTrue(True) print response["Location"] self.assertRedirects(response, "/accounts/login/?next=/oauth/authorize/") self.assertTrue(token.key in response["Location"]) # If successful, asks her if she approves granting printer.example.com access to # her private photos. If Jane approves the request, the Service Provider # redirects her back to the Consumer's callback URL:: self.assertTrue(self.client.login(username="******", password="******")) self.assertEqual(token.is_approved, 0) response = self.client.get("/oauth/authorize/", parameters) self.assertEqual(response.status_code, httplib.OK) self.assertEqual(response.content, "Fake authorize view for printer.example.com with params: oauth_token=...") # fake authorization by the user parameters["authorize_access"] = 1 response = self.client.post("/oauth/authorize/", parameters) self.assertEqual(response.status_code, httplib.FOUND) self.assertRedirects(response, "/request_token_ready") token = Token.objects.get(key=self.token.key) self.assertTrue(token.key in response["Location"]) self.assertEqual(token.is_approved, 1) # without session parameter (previous POST removed it) response = self.client.post("/oauth/authorize/", parameters) self.assertEqual(response.status_code, httplib.UNAUTHORIZED) self.assertEqual(response.content, "Action not allowed.") # fake access not granted by the user (set session parameter again) response = self.client.get("/oauth/authorize/", parameters) parameters["authorize_access"] = 0 response = self.client.post("/oauth/authorize/", parameters) self.assertEqual(response.status_code, httplib.FOUND) self.assertRedirects(response, "/request_token_ready") self.client.logout() # With OAuth 1.0a, the callback argument can be set to "oob" (out-of-band), # you can specify your own default callback view with the # ``OAUTH_PROVIDER_CALLBACK_VIEW`` setting:: # from oauth_provider.consts import OUT_OF_BAND token.callback = OUT_OF_BAND token.save() parameters = {"oauth_token": token.key} self.assertTrue(self.client.login(username="******", password="******")) response = self.client.get("/oauth/authorize/", parameters) parameters["authorize_access"] = 0 response = self.client.post("/oauth/authorize/", parameters) self.assertEqual(response.status_code, httplib.OK) self.assertEqual(response.content, "Fake callback view.") self.client.logout() # ========== Obtaining an Access Token =========== # Now that the Consumer knows Jane approved the Request Token, it asks the # Service Provider to exchange it for an Access Token:: parameters = { "oauth_consumer_key": self.CONSUMER_KEY, "oauth_token": token.key, "oauth_signature_method": "PLAINTEXT", "oauth_signature": "%s&%s" % (self.CONSUMER_SECRET, token.secret), "oauth_timestamp": str(int(time.time())), "oauth_nonce": "accessnonce", "oauth_version": "1.0", "oauth_verifier": self.token.verifier, "scope": "photos", } response = self.client.get("/oauth/access_token/", parameters) # You can use HTTP Authorization header, if you provide both, header will be # checked before parameters. It depends on your needs. # The Service Provider checks the signature and replies with an Access Token in # the body of the HTTP response:: self.assertEqual(response.status_code, httplib.OK) self.assertTrue("oauth_token_secret=" in response.content) self.assertTrue("oauth_token=" in response.content) access_token = list(Token.objects.filter(token_type=Token.ACCESS))[-1] self.assertTrue(access_token.key in response.content) self.assertTrue(access_token.secret in response.content) self.assertEqual(access_token.user.name, u"jane") # The Consumer will not be able to request another Access Token with the same # parameters because the Request Token has been deleted once Access Token is # created:: response = self.client.get("/oauth/access_token/", parameters) self.assertEqual(response.status_code, httplib.BAD_REQUEST) self.assertEqual(response.content, "Invalid request token.") # The Consumer will not be able to request another Access Token with a missing # or invalid verifier:: new_request_token = Token.objects.create_token( token_type=Token.REQUEST, timestamp=str(int(time.time())), consumer=Consumer.objects.get(key=self.CONSUMER_KEY), user=User.objects.get(username="******"), resource=Resource.objects.get(name="photos"), ) new_request_token.is_approved = True new_request_token.save() parameters["oauth_token"] = new_request_token.key parameters["oauth_signature"] = "%s&%s" % (self.CONSUMER_SECRET, new_request_token.secret) parameters["oauth_verifier"] = "invalidverifier" response = self.client.get("/oauth/access_token/", parameters) self.assertEqual(response.status_code, httplib.BAD_REQUEST) self.assertEqual(response.content, "Invalid OAuth verifier.") parameters["oauth_verifier"] = new_request_token.verifier # restore # The Consumer will not be able to request an Access Token if the token is not # approved:: new_request_token.is_approved = False new_request_token.save() parameters["oauth_nonce"] = "anotheraccessnonce" response = self.client.get("/oauth/access_token/", parameters) self.assertEqual(response.status_code, httplib.BAD_REQUEST) self.assertEqual(response.content, "Request Token not approved by the user.") # Accessing Protected Resources # The Consumer is now ready to request the private photo. Since the photo URL is # not secure (HTTP), it must use HMAC-SHA1. # Generating Signature Base String # To generate the signature, it first needs to generate the Signature Base # String. The request contains the following parameters (oauth_signature # excluded) which are ordered and concatenated into a normalized string:: parameters = { "oauth_consumer_key": self.CONSUMER_KEY, "oauth_token": access_token.key, "oauth_signature_method": "HMAC-SHA1", "oauth_timestamp": str(int(time.time())), "oauth_nonce": "accessresourcenonce", "oauth_version": "1.0", } # Calculating Signature Value # HMAC-SHA1 produces the following digest value as a base64-encoded string # (using the Signature Base String as text and kd94hf93k423kf44&pfkkdhi9sl3r4s00 # as key):: import oauth2 as oauth oauth_request = oauth.Request.from_token_and_callback( access_token, http_url="http://testserver/oauth/photo/", parameters=parameters ) signature_method = oauth.SignatureMethod_HMAC_SHA1() signature = signature_method.sign(oauth_request, self.consumer, access_token) # Requesting Protected Resource # All together, the Consumer request for the photo is:: parameters["oauth_signature"] = signature response = self.client.get("/oauth/photo/", parameters) self.assertEqual(response.status_code, httplib.OK) self.assertEqual(response.content, "Protected Resource access!") # Otherwise, an explicit error will be raised:: parameters["oauth_signature"] = "wrongsignature" parameters["oauth_nonce"] = "anotheraccessresourcenonce" response = self.client.get("/oauth/photo/", parameters) self.assertEqual(response.status_code, httplib.UNAUTHORIZED) self.assertTrue("Invalid signature. Expected signature base string: GET" in response.content) response = self.client.get("/oauth/photo/") self.assertEqual(response.status_code, httplib.UNAUTHORIZED) self.assertEqual(response.content, "Invalid request parameters.") # Revoking Access # If Jane deletes the Access Token of printer.example.com, the Consumer will not # be able to access the Protected Resource anymore:: access_token.delete() # Note that an "Invalid signature" error will be raised here if the # token is not revoked by Jane because we reuse a previously used one. parameters["oauth_signature"] = signature parameters["oauth_nonce"] = "yetanotheraccessresourcenonce" response = self.client.get("/oauth/photo/", parameters) self.assertEqual(response.status_code, httplib.UNAUTHORIZED) self.assertTrue("Invalid access token: ...", response.content)
def test_oauth_workflow(self): request_token_url = 'http://testserver' + reverse( 'api:oauth_request_token') authorize_token_url = 'http://testserver' + reverse( 'api:oauth_user_authorization') # create consumer for tests c = Client() c.login_user('ggbaker') c.logout() consumer = Consumer(name='Test Consumer', description='Consumer to do some tests with', status=ACCEPTED, user=User.objects.get(username='******'), xauth_allowed=False) consumer.generate_random_codes() consumer.save() ci = ConsumerInfo(consumer=consumer) ci.admin_contact = '*****@*****.**' ci.permissions = ['courses', 'grades'] ci.save() # generate request token oauth_request = oauth.Request.from_consumer_and_token( consumer, http_url=request_token_url, parameters={'oauth_callback': 'oob'}) oauth_request.sign_request(oauth.SignatureMethod_HMAC_SHA1(), consumer, None) resp = c.get(request_token_url, **oauth_request.to_header()) self.assertEqual(resp.status_code, 200) request_token = dict( urllib.parse.parse_qsl(resp.content.decode('utf8'))) # get auth verifier c.login_user('ggbaker') resp = c.get(authorize_token_url, {'oauth_token': request_token['oauth_token']}) self.assertEqual(resp.status_code, 200) resp = c.post(authorize_token_url, { 'oauth_token': request_token['oauth_token'], 'authorize_access': 'on' }) self.assertEqual(resp.status_code, 200) parser = etree.HTMLParser() root = etree.fromstring(resp.content, parser=parser) verifier_elt = root.xpath('//*[@id="verifier"]')[0] oauth_verifier = verifier_elt.text.strip() c.logout() # get access token token = oauth.Token(request_token['oauth_token'], request_token['oauth_token_secret']) token.set_verifier(oauth_verifier) oauth_request = oauth.Request.from_consumer_and_token( consumer, token, http_url=authorize_token_url) oauth_request.sign_request(oauth.SignatureMethod_HMAC_SHA1(), consumer, token) resp = c.get(authorize_token_url, **oauth_request.to_header())
def test_oauth1_0(): """ Test to veriy OAuth 1.0 flow """ """ After Jane informs printer.example.com that she would like to print her vacation photo stored at photos.example.net, the printer website tries to access the photo and receives HTTP 401 Unauthorized indicating it is private. The Service Provider includes the following header with the response:: """ #TODO - not able to handle /request_token/ - returns 404 #TODO - add test for reuse of nonce for the request token stage? #TODO - add test for oauth params in Authorization, GET and POST response = app.get('/request_token', status=401) assert response.status == '401 Invalid request parameters.' assert response.headers[ 'WWW-Authenticate'] == 'OAuth realm="http://events.example.net/"' assert response.body == 'Invalid request parameters.' """ The Consumer sends the following HTTP POST request to the Service Provider:: """ import time parameters = { 'oauth_consumer_key': CONSUMER_KEY, 'oauth_signature_method': 'PLAINTEXT', 'oauth_signature': '%s&' % CONSUMER_SECRET, 'oauth_timestamp': str(int(time.time())), 'oauth_nonce': 'requestnonce', 'oauth_version': '1.0', #'scope': 'default', # custom argument to specify Protected Resource } response = app.post('/request_token', parameters) """ The Service Provider checks the signature and replies with an unauthorized Request Token in the body of the HTTP response:: """ assert response.status == '200 OK' tokens = Token.all().fetch(1000) #double checking the sanity of the test - there should only be one token in #the store at this stage assert len(tokens) == 1 token = tokens[0] assert 'oauth_token_secret=%s&oauth_token=%s' % ( token.secret, token.key_) == response.body #Ensure that the token returned is unauthorized request token assert token.is_approved == False assert token.token_type == Token.REQUEST """ If you try to access a resource with a wrong scope, it will return an error:: """ #the scope related tests are not here as they the scope #function has not been implemented yet #>> parameters['scope'] = 'videos' #>>> response = c.get("/oauth/request_token/", parameters) #>>> response.status_code #401 #>>> response.content #'Resource videos does not exist.' """ Requesting User Authorization ----------------------------- The Consumer redirects Jane's browser to the Service Provider User Authorization URL to obtain Jane's approval for accessing her private photos. The Service Provider asks Jane to sign-in using her username and password:: """ parameters = { 'oauth_token': token.key_, 'oauth_callback': 'http://test.com/request_token_ready', 'authorize_access': 1, } response = app.post('/authorize', parameters) assert response.status == '302 Moved Temporarily' assert response.location ==\ 'http://localhost/_ah/login?continue=http%%3A//localhost/authorize%%3Foauth_token%%3D%s%%26oauth_callback%%3Dhttp%%3A//test.com/request_token_ready'%(token.key_) #the token details should not be altered by this call tokens = Token.all().fetch(1000) assert len(tokens) == 1 token = tokens[0] assert token.is_approved == False assert token.token_type == Token.REQUEST #In reality the user would of been redirected to the Google login page and #then redirected to a page where they will be given an opportunity to #authorize the application or otherwise. #Here we simulate the user being logged (by setting the #os.environ['USER_EMAIL']) variable and the user giving authorizing by #setting the 'authorize_access' parameter #and redirect manually to the authorization page os.environ['USER_EMAIL'] = '*****@*****.**' response = app.post('/authorize', parameters) #verify that the request token is now approved tokens = Token.all().fetch(1000) assert len(tokens) == 1 token = tokens[0] assert token.is_approved == True assert token.token_type == Token.REQUEST #verify the response - now that we are approved we should get assert response.status == '302 Moved Temporarily' assert response.location == 'http://test.com/request_token_ready?oauth_token=%s' % ( token.key_) #TODO add test for case where the user rejects the approval, #now the flow assumes that it will always be approved upon logging in """ Obtaining an Access Token ------------------------- Now that the Consumer knows Jane approved the Request Token, it asks the Service Provider to exchange it for an Access Token:: """ parameters = { 'oauth_consumer_key': CONSUMER_KEY, 'oauth_token': token.key_, 'oauth_signature_method': 'PLAINTEXT', 'oauth_signature': '%s&%s' % (CONSUMER_SECRET, token.secret), 'oauth_timestamp': str(int(time.time())), 'oauth_nonce': 'accessnonce', 'oauth_version': '1.0', } response = app.post("/access_token", parameters) """ The Service Provider checks the signature and replies with an Access Token in the body of the HTTP response:: """ response.status == '200 OK' #verify the access token now access_tokens = Token.all()\ .filter('token_type =',Token.ACCESS).fetch(1000) #double checking the sanity of the test - there should only be one token in #the store at this stage assert len(access_tokens) == 1 access_token = access_tokens[0] assert 'oauth_token_secret=%s&oauth_token=%s' % ( access_token.secret, access_token.key_) == response.body assert str(access_token.user) == '*****@*****.**' """ The Consumer will not be able to request another Access Token with the same Nonce:: """ nonces = Nonce.all().fetch(1000) assert nonces[0].key_ == "accessnonce" response = app.post("/access_token", parameters, status=401) assert response.status == '401 Nonce already used: accessnonce' assert response.body == 'Nonce already used: accessnonce' """ The Consumer will not be able to request an Access Token if the token is not approved:: """ token.is_approved = False token.put() parameters['oauth_nonce'] = 'anotheraccessnonce' response = app.post("/access_token", parameters, status=401) response.status == "401 Consumer key or token key does not match. Make sure your request token is approved. Check your verifier too if you use OAuth 1.0a." response.body == 'Consumer key or token key does not match. Make sure your request token is approved. Check your verifier too if you use OAuth 1.0a.' """ Accessing Protected Resources ----------------------------- The Consumer is now ready to request the private photo. Since the photo URL is not secure (HTTP), it must use HMAC-SHA1. Generating Signature Base String ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To generate the signature, it first needs to generate the Signature Base String. The request contains the following parameters (oauth_signature excluded) which are ordered and concatenated into a normalized string:: """ parameters = { 'oauth_consumer_key': CONSUMER_KEY, 'oauth_token': access_token.key_, 'oauth_signature_method': 'HMAC-SHA1', 'oauth_timestamp': str(int(time.time())), 'oauth_nonce': 'accessresourcenonce', 'oauth_version': '1.0', } """ Calculating Signature Value ~~~~~~~~~~~~~~~~~~~~~~~~~~~ HMAC-SHA1 produces the following digest value as a base64-encoded string (using the Signature Base String as text and kd94hf93k423kf44&pfkkdhi9sl3r4s00 as key):: """ consumer = Consumer.all().fetch(1000)[0] #we dont use the OAuthRequest.from_token_and_callback function as how the #original method does as it does a token.key instead of token.key_ oauth_request = OAuthRequest('POST', 'http://localhost/protected', parameters) signature_method = OAuthSignatureMethod_HMAC_SHA1() signature = signature_method.build_signature(oauth_request, consumer, access_token) """ Requesting Protected Resource ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ All together, the Consumer request for the photo is:: """ parameters['oauth_signature'] = signature response = app.post("/protected", parameters) assert response.status == '200 OK' assert response.body == 'Protected Resource access!' """ Otherwise, an explicit error will be raised:: """ parameters['oauth_signature'] = 'wrongsignature' parameters['oauth_nonce'] = 'anotheraccessresourcenonce' response = app.post("/protected", parameters, status=401) assert '401 Invalid signature. Expected signature base string: POST' in response.status assert 'Invalid signature. Expected signature base string: POST' in response.body response = app.post("/protected", status=401) #TODO - why are we getting "Invalid OAuth parameters instead of Invalid request parameters" assert response.status == '401 Invalid OAuth parameters' assert response.headers[ 'WWW-Authenticate'] == 'OAuth realm="http://events.example.net/"' assert response.body == 'Invalid OAuth parameters' """ Revoking Access --------------- If Jane deletes the Access Token of printer.example.com, the Consumer will not be able to access the Protected Resource anymore:: """ access_token_key = access_token.key_ access_token.delete() parameters['oauth_nonce'] = 'yetanotheraccessresourcenonce' oauth_request = OAuthRequest('POST', 'http://localhost/protected', parameters) signature_method = OAuthSignatureMethod_HMAC_SHA1() signature = signature_method.build_signature(oauth_request, consumer, access_token) parameters['oauth_signature'] = signature response = app.post("/protected", parameters, status=401) assert response.status == '401 Invalid access token: %s' % ( access_token_key) assert response.body == 'Invalid access token: %s' % (access_token_key) """