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(): 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(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 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)
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)
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())
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)