def test_get_pdf_token_allowed(self): """You don't need to be logged in at all, if your token is right.""" (other, teammate, contact, captain, super_admin, team, classroom, report_dict) = self.test_post_team_pdf() # Put in a classroom report for the same team so we know we can # correctly select the team-level one. classReport = Report.create( team_id=team.uid, classroom_id=classroom.uid, filename='classroom.pdf', gcs_path='/mybucket/upload/classroom.pdf', size=1000000, content_type='application/pdf', ) classReport.put() path = '/api/teams/{team_id}/reports/{filename}'.format( team_id=team.uid, filename=report_dict['filename'], ) endpoint_str = util.get_endpoint_str(method='GET', path=path) jwt = jwt_helper.encode({'allowed_endpoints': [endpoint_str]}), url = util.set_query_parameters(path, token=jwt) response = self.testapp.get(url) # asserts 200 self.assert_pdf_response(report_dict, response)
def test_delete_code_allowed(self): code = 'trout viper' path = '/api/codes/{}'.format(code.replace(' ', '-')) pc = ProjectCohort.create( code=code, organization_id='triton', program_label='triton', ) pc.put() pc.key.get() # simulate consistency, code fetches are eventual token = jwt_helper.encode({ 'user_id': 'User_foo', 'email': '*****@*****.**', 'allowed_endpoints': ['DELETE //neptune{}'.format(path)], }) self.testapp.delete( path, headers={'Authorization': 'Bearer ' + token}, status=204, ) # Project cohort AND Unique should be gone self.assertIsNone(pc.key.get()) unique_key = ndb.Key('Unique', ProjectCohort.uniqueness_key(code)) self.assertIsNone(unique_key.get())
def test_precheck_jwt_expired(self): payload = {'user_id': 'User_foo', 'email': '*****@*****.**'} token = jwt_helper.encode(payload, expiration_minutes=-1) response = self.testapp.get( '/api/auth_tokens/{}/user'.format(token), status=410, ) self.assertEqual(json.loads(response.body), 'expired')
def signup(request): """ token content: - email (username) - username (display name | firstname in Django) - password - remember - expire """ status = False msg = '' token = '' if request.method != 'POST': return HttpResponse(status=403) email = request.POST.get('email', None) password = request.POST.get('password', None) username = request.POST.get('username', None) expire = calendar.timegm(datetime.utcnow().utctimetuple()) + 2629743 if None in (email, password, username): return HttpResponse(status=404) # validate if not re.match(r"[^@]+@[^@]+\.[^@]+", email): msg = 'Email not valid' elif len(username) < 2: msg = 'Username too short' elif len(password) < 4: msg = 'Password too short' else: # get the user try: user = User.objects.create_user( username=email, password=password, first_name=username ) status = True except: user = None msg = 'User already exists' # check pswd if user: stuff = { 'email': email, 'expire': expire, } token = jwt_helper.encode(stuff) res = { 'status': status, 'msg': msg, 'token': token, 'email': email, 'username': username, } return JsonResponse(res, safe=False)
def test_delete_code_forbidden(self): """Requires 'allowed_endpoints' in jwt.""" token = jwt_helper.encode({'user_id': 'User_foo', 'email': '*****@*****.**'}) self.testapp.delete( '/api/codes/trout-viper', headers={'Authorization': 'Bearer ' + token}, status=403, )
def signup(request): """ token content: - email (username) - username (display name | firstname in Django) - password - remember - expire """ status = False msg = '' token = '' if request.method != 'POST': return HttpResponse(status=403) email = request.POST.get('email', None) password = request.POST.get('password', None) username = request.POST.get('username', None) expire = calendar.timegm(datetime.utcnow().utctimetuple()) + 2629743 if None in (email, password, username): return HttpResponse(status=404) # validate if not re.match(r"[^@]+@[^@]+\.[^@]+", email): msg = 'Email not valid' elif len(username) < 2: msg = 'Username too short' elif len(password) < 4: msg = 'Password too short' else: # get the user try: user = User.objects.create_user(username=email, password=password, first_name=username) status = True except: user = None msg = 'User already exists' # check pswd if user: stuff = { 'email': email, 'expire': expire, } token = jwt_helper.encode(stuff) res = { 'status': status, 'msg': msg, 'token': token, 'email': email, 'username': username, } return JsonResponse(res, safe=False)
def batch_participation(self, user, pcs): # Successful with allowed endpoints for individual project cohorts # (even if the paths are different). Can use either uid or code. start = datetime.date.today() end = start + datetime.timedelta(days=1) for attr in ('uid', 'code'): ids = [getattr(pc, attr).replace(' ', '-') for pc in pcs] paths = [ '/api/project_cohorts/{}/participation'.format(id) for id in ids ] payload = { 'user_id': user.uid, 'email': user.email, 'allowed_endpoints': [get_endpoint_str('GET', 'neptune', p) for p in paths], } url = ('/api/project_cohorts/participation?' 'uid={}&uid={}&start={start}&end={end}'.format( *ids, start=start.strftime(config.iso_datetime_format), end=end.strftime(config.iso_datetime_format))) result = self.testapp.get( url, headers={ 'Authorization': 'Bearer ' + jwt_helper.encode(payload), }, ) expected = { getattr(pc, attr).replace(' ', '-'): [ { 'project_cohort_id': pc.uid, 'survey_ordinal': 1, 'n': 1, 'value': '1', 'code': pc.code }, { 'project_cohort_id': pc.uid, 'survey_ordinal': 1, 'n': 1, 'value': '100', 'code': pc.code }, ] for pc in pcs } self.assertEqual(json.loads(result.body), expected)
def test_bad_token(self): """Invalid token: decoding error b/c typo or bad secret.""" payload = {'user_id': 'User_foo', 'email': '*****@*****.**'} token = jwt_helper.encode(payload, expiration_minutes=-1) response = self.testapp.post_json( '/api/set_password', {'password': '******'}, headers={'Authorization': 'Bearer ' + token[:-1]}, status=401, ) self.assertEqual(json.loads(response.body), 'not found')
def login(request): """ token content: - email (username) - username (display name | firstname in Django) - password - remember - expire """ status = False msg = '' token = '' e = '' name = '' if request.method != 'POST': return HttpResponse(status=403) email = request.POST.get('email', None) password = request.POST.get('password', None) expire = calendar.timegm(datetime.utcnow().utctimetuple()) + 2629743 if None in (email, password): return HttpResponse(status=404) # get the user try: user = User.objects.get(username=email) e = user.username name = user.first_name except: user = None msg = 'User not exists' # check pswd if user: if user.check_password(password): status = True stuff = { 'email': email, 'expire': expire, } token = jwt_helper.encode(stuff) else: msg = 'Incorrect password' res = { 'status': status, 'msg': msg, 'token': token, 'email': e, 'username': name, } return JsonResponse(res, safe=False)
def test_patch_bad_method(self): """Can only package PUT and DELETE with a bulk PATCH.""" codes = ('trout viper', 'solid snake') token = jwt_helper.encode({ 'user_id': 'User_foo', 'email': '*****@*****.**', 'allowed_endpoints': ['POST //neptune{}'.format(path(c)) for c in codes], }) self.testapp.patch_json( '/api/codes', # doesn't match "/api/other" [{'method': 'POST', 'path': path(c)} for c in codes], headers={'Authorization': 'Bearer ' + token}, status=400, )
def test_get_pdf_bad_token_forbidden(self): (other, teammate, contact, captain, super_admin, team, classroom, classReport1, classReport2, teamReport) = self.create_reports() # Not any old jwt will do. For instance, the typical auth jwt doesn't # work as a token. path = '/api/classrooms/{}/reports/foo.pdf?token={}'.format( classReport1.uid, jwt_helper.encode({ 'user_id': contact.uid, 'email': contact.email })) self.testapp.get( path, headers=self.login_headers(contact), status=403, )
def test_patch_update_codes(self): codes = ('trout viper', 'solid snake') pcs = [] for c in codes: pcs.append(ProjectCohort.create( code=c, organization_id='triton', program_label='triton', )) ndb.put_multi(pcs) for pc in pcs: pc.key.get() # simulate consistency, code fetches are eventual token = jwt_helper.encode({ 'user_id': 'User_foo', 'email': '*****@*****.**', 'allowed_endpoints': ['PUT //neptune{}'.format(path(c)) for c in codes], }) body = {'portal_message': 'hi'} response = self.testapp.patch_json( '/api/codes', [{'method': 'PUT', 'path': path(c), 'body': body} for c in codes], headers={'Authorization': 'Bearer ' + token}, ) task_names = [t['task_name'] for t in json.loads(response.body)] # PATCHing two codes should result in two tasks. for name in task_names: tasks = self.taskqueue.get_filtered_tasks(name=name) self.assertEqual(len(tasks), 1) t = tasks[0] # Running the tasks should update the codes. self.assertEqual(t.method, 'PUT') self.testapp.put_json( t.url, json.loads(t.payload), headers=t.headers, ) for pc in pcs: fetched = pc.key.get() self.assertEqual(fetched.portal_message, 'hi')
def test_new_success(self): """Valid token and password, user is new.""" # User.create() actually hits the db with a Unique entity, so we can't # use that to make a user. payload = {'user_id': 'User_foo', 'email': '*****@*****.**'} token = jwt_helper.encode(payload) response = self.testapp.post_json( '/api/set_password', {'password': '******'}, headers={'Authorization': 'Bearer ' + token}) user_dict = json.loads(response.body) self.assertEqual(type(user_dict), dict) self.assertEqual(user_dict['uid'], payload['user_id']) # should now be in the db self.assertIsNotNone(User.get_by_id(payload['user_id'])) return token
def report_link(self, report): parent_kind = SqlModel.get_url_kind(report.parent_id) short_id = SqlModel.convert_uid(report.parent_id) if report.gcs_path: platform = 'triton' prefix = '' view_path = '/api/{parent_kind}/{id}/reports/{filename}'.format( parent_kind=parent_kind, id=short_id, filename=report.filename, ) elif report.dataset_id: platform = 'neptune' prefix = '{protocol}://{domain}'.format( protocol='http' if util.is_localhost() else 'https', domain=('localhost:8888' if util.is_localhost() else os.environ['NEPTUNE_DOMAIN']), ) view_path = '/datasets/{ds_id}/{template}/{filename}'.format( ds_id=SqlModel.convert_uid(report.dataset_id), # short form template=report.template, filename=report.filename, ) # Permit report clients to query some data about participation. parent_path = '/api/{parent_kind}/{id}'.format(parent_kind=parent_kind, id=short_id) data_path = '/api/{parent_kind}/{id}/report_data'.format( parent_kind=parent_kind, id=short_id) link_jwt = jwt_helper.encode( { 'allowed_endpoints': [ self.get_endpoint_str(platform=platform, path=view_path), self.get_endpoint_str(platform='triton', path=parent_path), self.get_endpoint_str(platform='triton', path=data_path), ] }, expiration_minutes=(30 * 24 * 60), # thirty days ) return util.set_query_parameters(prefix + view_path, token=link_jwt)
def test_patch_bad_scope(self): """You can't request calls from some different scope/collection.""" codes = ('trout viper', 'solid snake') # These paths are for the "other" collection, not "codes". path = lambda code: '/api/other/{}'.format(code.replace(' ', '-')) token = jwt_helper.encode({ 'user_id': 'User_foo', 'email': '*****@*****.**', 'allowed_endpoints': ['DELETE //neptune{}'.format(path(c)) for c in codes], }) self.testapp.patch_json( '/api/codes', # doesn't match "/api/other" [{'method': 'DELETE', 'path': path(c)} for c in codes], headers={'Authorization': 'Bearer ' + token}, status=400, )
def test_update_code_allowed(self): code = 'trout viper' path = '/api/codes/{}'.format(code.replace(' ', '-')) pc = ProjectCohort.create( code=code, organization_id='triton', program_label='triton', ) pc.put() pc.key.get() # simulate consistency, code fetches are eventual token = jwt_helper.encode({ 'user_id': 'User_foo', 'email': '*****@*****.**', 'allowed_endpoints': ['PUT //neptune{}'.format(path)], }) self.testapp.put_json( path, {'portal_message': 'hi'}, headers={'Authorization': 'Bearer ' + token} )
def get_participation(cycle, classrooms): handler = TeamsClassrooms() user = User.create(id='triton', email='') payload = jwt_helper.get_payload(user) payload['allowed_endpoints'] = [ BaseHandler().get_endpoint_str( 'GET', 'neptune', '/api/project_cohorts/{}/participation'.format(c.url_code)) for c in classrooms ] jwt = jwt_helper.encode(payload) result = urlfetch.fetch(url=participation_query_url(cycle, classrooms), method=urlfetch.GET, headers={'Authorization': 'Bearer {}'.format(jwt)}) if not result or result.status_code != 200: raise Exception("Failed to get participation {}".format(result)) return json.loads(result.content)
def login_headers(self, user): payload = {'user_id': user.uid, 'email': user.email} return {'Authorization': 'Bearer ' + jwt_helper.encode(payload)}