def access_user_id_and_email(self, oauth_map): token = OAuthToken(oauth_map.google_access_token, oauth_map.google_access_token_secret) oauth_request = OAuthRequest.from_consumer_and_token( GoogleOAuthClient.Consumer, token=token, http_url="%sapi/auth/current_google_oauth_user_id_and_email" % request.host_url ) oauth_request.sign_request(OAuthSignatureMethod_HMAC_SHA1(), GoogleOAuthClient.Consumer, token) response = get_response(oauth_request.to_url()) try: obj = json.loads(response) if len(obj) == 2: # (user_id, email) return tuple(obj) except ValueError: logging.error("Error decoding json from " "current_google_oauth_user_id_and_email; " "json was %r", response) pass return (None, None)
def fetch_request_token(self, oauth_map): oauth_request = OAuthRequest.from_consumer_and_token( GoogleOAuthClient.Consumer, http_url = "%s/_ah/OAuthGetRequestToken" % App.realm, callback = "%sapi/auth/google_token_callback?oauth_map_id=%s" % (request.host_url, oauth_map.key().id()) ) oauth_request.sign_request(OAuthSignatureMethod_HMAC_SHA1(), GoogleOAuthClient.Consumer, None) response = get_response(oauth_request.to_url()) return OAuthToken.from_string(response)
def fetch_access_token(self, oauth_map): token = OAuthToken(oauth_map.google_request_token, oauth_map.google_request_token_secret) oauth_request = OAuthRequest.from_consumer_and_token( GoogleOAuthClient.Consumer, token = token, verifier = oauth_map.google_verification_code, http_url = "%s/_ah/OAuthGetAccessToken" % App.realm ) oauth_request.sign_request(OAuthSignatureMethod_HMAC_SHA1(), GoogleOAuthClient.Consumer, token) response = get_response(oauth_request.to_url()) return OAuthToken.from_string(response)
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) """