def setUp(self): """ user_a -> admin user_b -> user2 user_a uploads shared file. We authenticate to the API with user_b. user_b subscribes to user_a's shake.] """ super(APIResourceRequests, self).setUp() self.user_a = User( name='admin', email='*****@*****.**', email_confirmed=1, is_paid=1, about="admin", website='https://mltshp.com') self.user_a.set_password('asdfasdf') self.user_a.save() self.sid = self.sign_in('admin', 'asdfasdf') self.xsrf = self.get_xsrf() self.test_file1_path = os.path.abspath("test/files/1.png") self.test_file1_sha1 = Sourcefile.get_sha1_file_key(self.test_file1_path) self.test_file1_content_type = "image/png" response = self.upload_file(file_path=self.test_file1_path, sha1=self.test_file1_sha1, content_type=self.test_file1_content_type, user_id=self.user_a.id, sid=self.sid, xsrf=self.xsrf) self.user_b = User(name='user2', email='*****@*****.**', email_confirmed=1, is_paid=1) self.user_b.set_password('asdfasdf') self.user_b.save() self.group_shake = self.user_b.create_group_shake(title='Group Shake', name='groupshake', description='This is a group shake.') self.group_shake_2 = self.user_a.create_group_shake(title='Another Group', name='anothergroup') # Add user_b to user_a's group shake, so we get it in user_b's /shakes endpoint. shake_manager = ShakeManager(user_id=self.user_b.id, shake_id=self.group_shake_2.id) shake_manager.save() self.app = App(user_id=self.user_a.id, title='An App', description='Nothing yet.', redirect_url='http://client.example.com/return') self.app.save() self.authorization = Authorizationcode.generate(self.app.id, self.app.redirect_url, self.user_b.id) self.access_token = Accesstoken.generate(self.authorization.id) extra_authorization = Authorizationcode.generate(self.app.id, self.app.redirect_url, self.user_b.id) self.ratelimited_access_token = Accesstoken.generate(extra_authorization.id) now_hour = datetime.utcnow().strftime('%Y-%m-%d %H:00:00') ratelimit = Apihit(accesstoken_id=self.ratelimited_access_token.id, hits=options.api_hits_per_hour - 2, hour_start=now_hour) ratelimit.save() #subscription self.user_b.subscribe(self.user_a.shake())
def test_sending_valid_request_returns_access_token(self): message = "grant_type=password&client_id=%s&client_secret=%s&username=%s&password=%s" % (self.app.key(), self.app.secret, 'admin', 'asdfasdf') response = api_request(self, self.get_url('/api/token'), method='POST', body=message, unsigned=True) access_token = Accesstoken.all() self.assertEqual(len(access_token), 1) self.assertTrue(access_token[0]) # Now clean up so the invalid test will work out of order. for token in access_token: token.delete()
def test_access_token_is_not_deleted_when_new_one_is_requested(self): # First request one. message = "grant_type=authorization_code&code=%s&redirect_uri=%s&client_id=%s&client_secret=%s" % (self.authorization.code, self.app.redirect_url, self.app.key(), self.app.secret) response = api_request(self, self.get_url('/api/token'), method='POST', body=message, unsigned=True) #one access token should have been created: access_token = Accesstoken.get('id=1') self.assertTrue(access_token) self.assertFalse(access_token.deleted) access_tokens = Accesstoken.all() self.assertEqual(len(access_tokens), 1) j_response = json_decode(response.body) self.assertEqual(j_response['token_type'], 'mac') self.assertEqual(j_response['access_token'], access_token.consumer_key) self.assertEqual(j_response['secret'], access_token.consumer_secret) self.assertEqual(j_response['algorithm'], 'hmac-sha-1') # Now request another. other_authorization = Authorizationcode.generate(self.app.id, self.app.redirect_url, self.user_b.id) message = "grant_type=authorization_code&code=%s&redirect_uri=%s&client_id=%s&client_secret=%s" % (other_authorization.code, self.app.redirect_url, self.app.key(), self.app.secret) response = api_request(self, self.get_url('/api/token'), method='POST', body=message, unsigned=True) # A second access token should have been created, but the # first one should be gone. access_token = Accesstoken.get('id=1') self.assertFalse(access_token.deleted) access_token = Accesstoken.get('id=2') self.assertFalse(access_token.deleted) access_tokens = Accesstoken.all() self.assertEqual(len(access_tokens), 2) j_response = json_decode(response.body) self.assertEqual(j_response['token_type'], 'mac') self.assertEqual(j_response['access_token'], access_token.consumer_key) self.assertEqual(j_response['secret'], access_token.consumer_secret) self.assertEqual(j_response['algorithm'], 'hmac-sha-1')
def test_access_token_returned_for_valid_authorization_code_and_credentials(self): message="grant_type=authorization_code&code=%s&redirect_uri=%s&client_id=%s&client_secret=%s" % (self.authorization.code, self.app.redirect_url, self.app.key(), self.app.secret) response = api_request(self, self.get_url('/api/token'), method='POST', body=message, unsigned=True) #one access token should have been created: access_token = Accesstoken.get('id=1') j_response = json_decode(response.body) self.assertEqual(j_response['token_type'], 'mac') self.assertEqual(j_response['access_token'], access_token.consumer_key) self.assertEqual(j_response['secret'], access_token.consumer_secret) self.assertEqual(j_response['algorithm'], 'hmac-sha-1')
def test_sending_invalid_password_returns_error(self): message = "grant_type=password&client_id=%s&client_secret=%s&username=%s&password=%s" % (self.app.key(), self.app.secret, 'admin', 'qwerqwer') response = api_request(self, self.get_url('/api/token'), method='POST', body=message, unsigned=True) access_token = Accesstoken.all() self.assertEqual(len(access_token), 0)
def test_disconnecting_connection(self): """ Sending a post request to /settings/connections/{{app_id}}/disconnect should mark an access token as deleted. """ # app created by Torrez. app = App(user_id=self.user.id,title='Title') app.save() # another user. new_user = User(name='newuser', email='*****@*****.**', email_confirmed=1, is_paid=1) new_user.set_password('asdfasdf') new_user.save() #connect another user and app token = Accesstoken(user_id=new_user.id, app_id=app.id, deleted=0) token.save() # If torrez hits URL, which he should never be able to find, nothing happens # to token. self.post_url("/account/settings/connections/%s/disconnect" % app.id) token_reloaded = Accesstoken.get('id = %s', token.id) self.assertEqual(0, token_reloaded.deleted) # but if the user it's about hits it, the token deletes. self.sign_in('newuser', 'asdfasdf') self.post_url("/account/settings/connections/%s/disconnect" % app.id) token_reloaded = Accesstoken.get('id = %s', token.id) self.assertEqual(1, token_reloaded.deleted) # Even if there are several undeleted access tokens, they all end up deleted. token_two = Accesstoken(user_id=new_user.id, app_id=app.id, deleted=0) token_two.save() token_three = Accesstoken(user_id=new_user.id, app_id=app.id, deleted=0) token_three.save() self.post_url("/account/settings/connections/%s/disconnect" % app.id) token_reloaded = Accesstoken.get('id = %s', token.id) token_two_reloaded = Accesstoken.get('id = %s', token_two.id) token_three_reloaded = Accesstoken.get('id = %s', token_three.id) self.assertEqual(1, token_reloaded.deleted) # still self.assertEqual(1, token_two_reloaded.deleted) self.assertEqual(1, token_three_reloaded.deleted)
def post(self): grant_type = self.get_argument('grant_type', None) code = self.get_argument('code', None) redirect_url = self.get_argument('redirect_uri', None) client_secret = self.get_argument('client_secret', None) client_id = self.get_argument('client_id', None) username = self.get_argument('username', None) password = self.get_argument('password', None) if not grant_type or not client_id or not client_secret: self.set_status(400) return self.write({ 'error': 'invalid_request', 'error_description': "The grant_type, client_id, and client_secret parameters are required." }) if grant_type == 'password': pass elif grant_type == 'authorization_code': if not code or not redirect_url: self.set_status(400) return self.write({ 'error': 'invalid_request', 'error_description': "The code and redirect_url parameters are required." }) else: self.set_status(401) return self.write({'error': 'invalid_grant'}) app = App.by_key(client_id) if not app: self.set_status(401) return self.write({'error': 'invalid_client'}) if app.secret != client_secret: self.set_status(401) return self.write({'error': 'access_denied'}) auth_code = None if grant_type == 'password': #generating one in one fell swoop. #if user password match then make an auth_code check_user = User.authenticate(username, password) if check_user: auth_code = Authorizationcode.generate( app_id=app.id, redirect_url=app.redirect_url, user_id=check_user.id) else: self.set_status(401) return self.write({'error': 'invalid_request'}) else: auth_code = Authorizationcode.get( "code = %s and redirect_url = %s and expires_at > %s", code, redirect_url, datetime.utcnow()) if auth_code: self.set_header("Cache-Control", "no-store") access_token = Accesstoken.generate(auth_code.id) if access_token: response = { "access_token": access_token.consumer_key, "secret": access_token.consumer_secret, "token_type": "mac", "algorithm": "hmac-sha-1" } return self.write(response) else: self.set_status(401) return self.write({'error': 'invalid_grant'}) else: self.set_status(401) return self.write({'error': 'invalid_grant'})
def wrapper(self, *args, **kwargs): #break up header and get access token, timestamp, nonce auth_items = {} if self.request.headers.get('Authorization'): try: h = self.request.headers.get('Authorization').replace( 'MAC ', '') auth_items = dict( item.split('=', 1) for item in h.replace('"', '').split(', ')) except: self.set_status(401) self.set_header( 'WWW-Authenticate', 'MAC realm="mltshp" error="invalid_token", error_description="Cannot parse token."' ) self.finish() return try: access_token_str = auth_items['token'] except KeyError: self.set_status(401) self.set_header( 'WWW-Authenticate', 'MAC realm="mltshp" error="invalid_token", error_description="Missing token."' ) self.finish() return access_token = Accesstoken.get('consumer_key = %s and deleted=0', access_token_str) if not access_token: self.set_status(401) self.set_header( 'WWW-Authenticate', 'MAC realm="mltshp" error="invalid_token", error_description="Invalid access token."' ) self.finish() return api_log = Apilog.get('accesstoken_id=%s and nonce=%s', access_token.id, auth_items['nonce']) if api_log: self.set_status(401) self.set_header( 'WWW-Authenticate', 'MAC realm="mltshp" error="invalid_token", error_description="Duplicate nonce."' ) self.finish() return else: api_log = Apilog(accesstoken_id=access_token.id, nonce=auth_items['nonce']) api_log.save() timestamp = int(auth_items['timestamp']) nowstamp = int(time.mktime(datetime.utcnow().timetuple())) if (nowstamp - timestamp) > 30: self.set_status(401) self.set_header( 'WWW-Authenticate', 'MAC realm="mltshp" error="invalid_token", error_description="Token is expired."' ) self.finish() return hits_per_hour = options.api_hits_per_hour if hits_per_hour is not None: hits = Apihit.hit(access_token.id) #raise ValueError("YESSSS %d HITS PER HOUR AND %d HITS WHAT" % (hits_per_hour, hits)) self.set_header('X-RateLimit-Limit', str(hits_per_hour)) self.set_header('X-RateLimit-Reset', str(nowstamp % 3600 + 3600)) if hits >= hits_per_hour: self.set_status(400) self.set_header('X-RateLimit-Remaining', '0') self.finish() return self.set_header('X-RateLimit-Remaining', hits_per_hour - hits) parsed_url = urlparse(self.request.full_url()) query_array = [] if parsed_url.query: query_array += parsed_url.query.split('&') for i in range(len(query_array)): if query_array[i].find('=') == -1: query_array[i] += '=' query_array.sort() port = 80 if not parsed_url.port: if parsed_url.scheme == 'https': port = 443 elif parsed_url.port: port = parsed_url.port normalized_string = normalize_string(auth_items['token'], auth_items['timestamp'], auth_items['nonce'], self.request.method, parsed_url.hostname, port, parsed_url.path, query_array) digest = hmac.new(access_token.consumer_secret.encode('ascii'), normalized_string, sha1).digest() signature = base64.encodestring(digest).strip() if signature == auth_items['signature']: self.oauth2_user_id = access_token.user_id return method(self, *args, **kwargs) else: self.set_status(401) self.set_header( 'WWW-Authenticate', 'MAC realm="mltshp" error="invalid_token", error_description="Signature doesn\'t match."' ) self.write("NORMALIZED STRING: \n") self.write(normalized_string) self.write('-----\n') self.write('MY SIGNATURE\n') self.write(signature) self.write('\nYOUR SIGNATURE\n') self.write(auth_items['signature']) self.finish() return else: self.set_status(401) self.set_header('WWW-Authenticate', 'Basic realm="mltshp"') self.finish()