def test_user_authentication_success_if_user_already_exists(self): """ Tests the end point of a user authenticating their ADS 2.0 credentials via the web app, assuming that they have previously set something before """ # Stub out the user in the database user = Users( absolute_uid=10, twopointoh_email='*****@*****.**', ) with self.app.session_scope() as session: session.add(user) session.commit() # 1. The user fills in their credentials # 2. The user submits their credentials to the end point url = url_for('authenticateusertwopointoh') with HTTMock(ads_classic_200): r = self.client.post(url, data=self.stub_user_data_2p0, headers={USER_ID_KEYWORD: 10}) self.assertStatus(r, 200) self.assertEqual(r.json['twopointoh_email'], self.stub_user_data_2p0['twopointoh_email']) self.assertTrue(r.json['twopointoh_authed']) r_user = session.query(Users).filter( Users.absolute_uid == 10).one() self.assertEqual(r_user.twopointoh_email, self.stub_user_data_2p0['twopointoh_email'])
def test_user_authentication_fails_when_user_already_exists(self): """ Tests the end point of a user authenticating their ADS credentials via the web app, assuming that they have previously set something before. This is the scenario in which there is a failure, the credentials remain the same in the database """ # Stub out the user in the database user = Users(absolute_uid=10, twopointoh_email='*****@*****.**') with self.app.session_scope() as session: session.add(user) session.commit() # 1. The user fills in their credentials # 2. The user submits their credentials to the end point url = url_for('authenticateusertwopointoh') possible_failures = [ ads_classic_fail, ads_classic_unknown_user, ads_classic_wrong_password ] for fail_response in possible_failures: with HTTMock(fail_response): self.client.post(url, data=self.stub_user_data_2p0, headers={USER_ID_KEYWORD: 10}) r_user = session.query(Users).filter( Users.absolute_uid == 10).one() self.assertEqual(r_user.twopointoh_email, '*****@*****.**')
def test_user_authentication_success_if_user_already_exists(self): """ Tests the end point of a user authenticating their ADS credentials via the web app, assuming that they have previously set something before """ # Stub out the user in the database user = Users(absolute_uid=10, classic_email='*****@*****.**', classic_cookie='some cookie', classic_mirror='other.mirror.com') db.session.add(user) db.session.commit() # 1. The user fills in their credentials # 2. The user submits their credentials to the end point url = url_for('authenticateuserclassic') with HTTMock(ads_classic_200): r = self.client.post(url, data=self.stub_user_data, headers={USER_ID_KEYWORD: 10}) self.assertStatus(r, 200) self.assertEqual(r.json['classic_email'], self.stub_user_data['classic_email']) self.assertTrue(r.json['classic_authed']) r_user = Users.query.filter(Users.absolute_uid == 10).one() self.assertEqual(r_user.classic_email, self.stub_user_data['classic_email']) self.assertEqual(r_user.classic_mirror, self.stub_user_data['classic_mirror']) self.assertIsInstance(r_user.classic_cookie, unicode)
def test_get_libraries_end_point(self): """ Test the workflow of successfully retrieving a set of ADS 2.0 libraries """ # Setup S3 mock data TestADSTwoPointOhLibraries.helper_s3_mock_setup() # Expected stub data stub_get_libraries = { 'libraries': [{ 'name': 'Name', 'description': 'Description', 'documents': [ '2015MNRAS.446.4239E', '2015A&C....10...61E', '2014A&A...562A.100E', '2013A&A...556A..23E' ] }] } # 1. The user is identified via header information # - Generate dummy user in database # - Give the header the correct information user = Users(absolute_uid=10, twopointoh_email='*****@*****.**') with self.app.session_scope() as session: session.add(user) session.commit() url = url_for('twopointohlibraries', uid=10) r = self.client.get(url) self.assertStatus(r, 200) self.assertEqual(r.json['libraries'], stub_get_libraries['libraries'])
def test_get_myads_end_point(self): """ Test the workflow of successfully retrieving myADS settings """ stub_get_myads = { u'pre_aut,': u'Henneken, Edwin\r\nKurtz, Michael J.\r\nHernquist, L.\r\nIcke, V.\r\nMellema, G.\r\nRosvall, Martin\r\nBergstrom, Carl\r\nNewman, Mark', u'firstname': u'Edwin', u'phy_t1,': u'bibliometrics\r\ninformetrics\r\neigenfactor\r\nrecommendation\r\n=citation\r\n"digital library"', u'ast_t1,': u'bibliometrics\r\ninformetrics\r\neigenfactor\r\nrecommendation\r\n=citation\r\n"digital library"', u'phy_aut,': u'Henneken, Edwin\r\nKurtz, Michael J.\r\nHernquist, L.\r\nIcke, V.\r\nMellema, G.\r\nRosvall, Martin\r\nBergstrom, Carl\r\nNewman, Mark', u'ast_aut,': u'Henneken, Edwin\r\nKurtz, Michael J.\r\nHernquist, L.\r\nIcke, V.\r\nMellema, G.\r\nRosvall, Martin\r\nBergstrom, Carl\r\nNewman, Mark', u'phy_t2,': u'"digital library"\r\n"search engine"\r\n"information retrieval"\r\n', u'email': u'*****@*****.**', u'pre_t1,': u'bibliometrics\r\ninformetrics\r\neigenfactor\r\nrecommendation\r\n=citation\r\n"digital library"', u'daily_t1,': u'"gamma ray bursts"', u'groups': [u'astro-ph', u'cs', u'gr-qc', u'math-ph'], u'lastname': u'Henneken', u'ast_t2,': u'"digital library"\r\n"search engine"\r\n"information retrieval"\r\n', u'pre_t2,': u'"digital library"\r\n"search engine"\r\n"information retrieval"\r\n', u'id': 2093057 } # 1. The user is identified via header information # - Generate dummy user in database # - Give the header the correct information user = Users(absolute_uid=10, classic_cookie='ef9df8ds', classic_mirror='mirror.com', classic_email='*****@*****.**') with self.app.session_scope() as session: session.add(user) session.commit() # 2. The database is checked to see if the user exists, and the cookie # retrieved # 3. The ADS Classic end point is contacted, returning a 200, and the # content returned url = url_for('classicmyads', uid=10) with HTTMock(ads_classic_myads_200): r = self.client.get(url) self.assertStatus(r, 200) self.assertEqual(r.json, stub_get_myads)
def test_get_export_end_point_when_aws_s3_error(self, mock_resource): """ Test when there is an issue loading/accessing S3 storage """ mock_resource.side_effect = Exception('Custom Error') user = Users(absolute_uid=10, twopointoh_email='*****@*****.**') db.session.add(user) db.session.commit() url = url_for('exporttwopointohlibraries', export='zotero') r = self.client.get(url, headers={USER_ID_KEYWORD: user.absolute_uid}) self.assertStatus(r, TWOPOINTOH_AWS_PROBLEM['code']) self.assertEqual(r.json['error'], TWOPOINTOH_AWS_PROBLEM['message'])
def test_get_libraries_end_point_when_aws_s3_error(self, mock_resource): """ Test when this user has not associated any ADS 2.0 (classic) account """ mock_resource.side_effect = Exception('Custom Error') user = Users(absolute_uid=10, twopointoh_email='*****@*****.**') db.session.add(user) db.session.commit() url = url_for('twopointohlibraries', uid=10) r = self.client.get(url) self.assertStatus(r, TWOPOINTOH_AWS_PROBLEM['code']) self.assertEqual(r.json['error'], TWOPOINTOH_AWS_PROBLEM['message'])
def test_get_libraries_end_point_when_no_user_but_is_twopointoh(self): """ Test when this user does not have a Classic account, but a 2.0 account """ # 1. The user is identified via header information # - Generate dummy user in database # - Give the header the correct information user = Users(absolute_uid=10, twopointoh_email='*****@*****.**') db.session.add(user) db.session.commit() url = url_for('classiclibraries', uid=10) r = self.client.get(url) self.assertStatus(r, NO_CLASSIC_ACCOUNT['code']) self.assertEqual(r.json['error'], NO_CLASSIC_ACCOUNT['message'])
def test_get_libraries_end_point_when_no_user(self): """ Test when this user does not have any libraries """ # 1. The user is identified via header information # - Generate dummy user in database # - Give the header the correct information user = Users(absolute_uid=10, twopointoh_email='*****@*****.**') db.session.add(user) db.session.commit() url = url_for('twopointohlibraries', uid=10) r = self.client.get(url) self.assertStatus(r, NO_TWOPOINTOH_LIBRARIES['code']) self.assertEqual(r.json['error'], NO_TWOPOINTOH_LIBRARIES['message'])
def test_get_zotero_export_successfully_when_no_keyword(self): """ Test that a user can get the expected zotero export. They press a button, which results in a zip file being returned. Within the zip file is a list of files, each one is a .bib file that corresponds to a library that a user had. """ # Stub out the user in the database user = Users(absolute_uid=10, twopointoh_email='*****@*****.**') with self.app.session_scope() as session: session.add(user) session.commit() # Setup S3 storage TestExportADSTwoPointOhLibraries.helper_s3_mock_setup() url = url_for('exporttwopointohlibraries', export='zotero') with HTTMock(export_success_no_keyword): r = self.client.get( url, headers={USER_ID_KEYWORD: user.absolute_uid}) self.assertStatus(r, 200) self.assertIn('Content-Disposition', r.headers) self.assertEqual(r.headers['Content-Disposition'], 'attachment; filename=user_zotero.zip') zip_file = ZipFile(BytesIO(r.get_data())) zip_content = { name: zip_file.read(name) for name in zip_file.namelist() } self.assertEqual(zip_content.keys(), ['Name.bib', 'Name2.bib']) self.assertIn( 'keywords = {tag1, tag2}', zip_content['Name.bib'], ) self.assertIn('notes = {note1, note2}', zip_content['Name.bib']) self.assertNotIn( 'tag1', zip_content['Name2.bib'], ) self.assertNotIn('notes =', zip_content['Name2.bib'])
def test_get_temporary_url_on_export(self): """ The user should receive a temporary url when asking for an export """ # Stub out the user in the database user = Users(absolute_uid=10, twopointoh_email='*****@*****.**') db.session.add(user) db.session.commit() # Setup S3 storage TestExportADSTwoPointOhLibraries.helper_s3_mock_setup() url = url_for('exporttwopointohlibraries', export='zotero') r = self.client.get(url, headers={USER_ID_KEYWORD: 10}) self.assertIn( 'https://adsabs-mongogut.s3.amazonaws.com/cb16a523-cdba-406b-bfff-edfd428248be.zotero.zip', r.json['url'], )
def test_get_libraries_when_ads_classic_returns_non_200(self): """ Tests that the expected response is returned when ADS classic returns a non-200 response """ user = Users(absolute_uid=10, classic_cookie='ef9df8ds', classic_mirror='mirror.com', classic_email='*****@*****.**') db.session.add(user) db.session.commit() url = url_for('classiclibraries', uid=10) with HTTMock(ads_classic_fail): r = self.client.get(url, headers={USER_ID_KEYWORD: 10}) self.assertStatus(r, CLASSIC_UNKNOWN_ERROR['code']) self.assertEqual(r.json['error'], CLASSIC_UNKNOWN_ERROR['message'])
def test_get_libraries_end_point_when_no_user_but_is_classic(self): """ Test when this user does not have any libraries, but has Classic account """ # 1. The user is identified via header information # - Generate dummy user in database # - Give the header the correct information user = Users(absolute_uid=10, classic_cookie='ef9df8ds', classic_mirror='mirror.com', classic_email='*****@*****.**') db.session.add(user) db.session.commit() url = url_for('twopointohlibraries', uid=10) r = self.client.get(url) self.assertStatus(r, NO_TWOPOINTOH_ACCOUNT['code']) self.assertEqual(r.json['error'], NO_TWOPOINTOH_ACCOUNT['message'])
def test_get_libraries_when_ads_classic_timesout(self, mocked_get): """ Test that if ADS Classic times out before finishing the request, that the libraries end point returns a known error """ user = Users(absolute_uid=10, classic_cookie='ef9df8ds', classic_mirror='mirror.com', classic_email='*****@*****.**') db.session.add(user) db.session.commit() mocked_get.side_effect = Timeout url = url_for('classiclibraries', uid=10) r = self.client.get(url) self.assertStatus(r, CLASSIC_TIMEOUT['code']) self.assertEqual(r.json['error'], CLASSIC_TIMEOUT['message'])
def test_user_successfully_retrieves_ads_classic_settings(self): """ Tests that the user successfully retrieves their settings for the ADS Classic service """ # Stub data user = Users(absolute_uid=10, classic_email='*****@*****.**', classic_cookie='some cookie', classic_mirror='mirror.com') db.session.add(user) db.session.commit() # Ask the end point url = url_for('classicuser') r = self.client.get(url, headers={USER_ID_KEYWORD: 10}) # Check we get what we expected self.assertStatus(r, 200) self.assertEqual(r.json['classic_email'], user.classic_email) self.assertEqual(r.json['classic_mirror'], user.classic_mirror) self.assertEqual(r.json['twopointoh_email'], '')
def test_get_libraries_end_point(self): """ Test the workflow of successfully retrieving a set of classic libraries """ stub_get_libraries = { 'libraries': [{ 'name': 'Name', 'description': 'Description', 'documents': [ '2015MNRAS.446.4239E', '2015A&C....10...61E', '2014A&A...562A.100E', '2013A&A...556A..23E' ] }] } # 1. The user is identified via header information # - Generate dummy user in database # - Give the header the correct information user = Users(absolute_uid=10, classic_cookie='ef9df8ds', classic_mirror='mirror.com', classic_email='*****@*****.**') with self.app.session_scope() as session: session.add(user) session.commit() # 2. The database is checked to see if the user exists, and the cookie # retrieved # 3. The ADS Classic end point is contacted, returning a 200, and the # content returned url = url_for('classiclibraries', uid=10) with HTTMock(ads_classic_libraries_200): r = self.client.get(url) self.assertStatus(r, 200) self.assertEqual(r.json['libraries'], stub_get_libraries['libraries'])
def post(self): """ HTTP POST request that receives the user's ADS 2.0 credentials, and then contacts the Classic system to check that what the user provided is indeed valid. If valid, the users ID is stored. Post body: ---------- KEYWORD, VALUE twopointoh_email: <string> ADS 2.0 e-mail of the user twopointoh_password: <string> ADS 2.0 password of the user Return data (on success): ------------------------- twopointoh_authed: <boolean> were they authenticated twopointoh_email: <string> e-mail that authenticated correctly HTTP Responses: -------------- Succeed authentication: 200 Bad/malformed data: 400 User unknown/wrong password/failed authentication: 404 ADS Classic give unknown messages: 500 ADS Classic times out: 504 Any other responses will be default Flask errors """ post_data = get_post_data(request) # Collect the username, password from the request try: twopointoh_email = post_data['twopointoh_email'] twopointoh_password = post_data['twopointoh_password'] except KeyError: current_app.logger.warning( 'User did not provide a required key: {}'.format( traceback.print_exc())) return err(CLASSIC_DATA_MALFORMED) # Create the correct URL url = current_app.config['ADS_CLASSIC_URL'].format( mirror=current_app.config['ADS_TWO_POINT_OH_MIRROR'], ) params = { 'man_cmd': 'elogin', 'man_email': twopointoh_email, 'man_passwd': twopointoh_password } # Authenticate current_app.logger.info( 'User "{email}" trying to authenticate"'.format( email=twopointoh_email)) try: response = current_app.client.post(url, params=params) except requests.exceptions.Timeout: current_app.logger.warning( 'ADS Classic end point timed out, returning to user') return err(CLASSIC_TIMEOUT) if response.status_code >= 500: message, status_code = err(CLASSIC_UNKNOWN_ERROR) message['ads_classic'] = { 'message': response.text, 'status_code': response.status_code } current_app.logger.warning( 'ADS Classic has responded with an unknown error: {}'.format( response.text)) return message, status_code # Sanity check the response email = response.json()['email'] if email != twopointoh_email: current_app.logger.warning( 'User email "{}" does not match ADS return email "{}"'.format( twopointoh_email, email)) return err(CLASSIC_AUTH_FAILED) # Respond to the user based on whether they were successful or not if response.status_code == 200 \ and response.json()['message'] == 'LOGGED_IN' \ and int(response.json()['loggedin']): current_app.logger.info( 'Authenticated successfully "{email}"'.format( email=twopointoh_email)) absolute_uid = self.helper_get_user_id() with current_app.session_scope() as session: try: user = session.query(Users).filter( Users.absolute_uid == absolute_uid).one() current_app.logger.info('User already exists in database') user.twopointoh_email = twopointoh_email except NoResultFound: current_app.logger.info( 'Creating entry in database for user') user = Users(absolute_uid=absolute_uid, twopointoh_email=twopointoh_email) session.add(user) session.commit() current_app.logger.info( 'Successfully saved content for "{}" to database'.format( twopointoh_email)) return { 'twopointoh_email': email, 'twopointoh_authed': True }, 200 return err(HARBOUR_SERVICE_FAIL) else: current_app.logger.warning( 'ADS 2.0 credentials for "{email}" did not succeed"'.format( email=twopointoh_email)) return err(CLASSIC_AUTH_FAILED)
def post(self): """ HTTP POST request that receives the user's ADS Classic credentials, and then contacts the Classic system to check that what the user provided is indeed valid. If valid, the users ID is stored within the myADS service store. Post body: ---------- KEYWORD, VALUE classic_email: <string> ADS Classic e-mail of the user classic_password: <string> ADS Classic password of the user classic_mirror: <string> ADS Classic mirror this user belongs to Return data (on success): ------------------------- classic_authed: <boolean> were they authenticated classic_email: <string> e-mail that authenticated correctly classic_mirror: <string> ADS Classic mirror this user selected HTTP Responses: -------------- Succeed authentication: 200 Bad/malformed data: 400 User unknown/wrong password/failed authentication: 404 ADS Classic give unknown messages: 500 ADS Classic times out: 504 Any other responses will be default Flask errors """ post_data = get_post_data(request) with current_app.session_scope() as session: # Collect the username, password from the request try: classic_email = post_data['classic_email'] classic_password = post_data['classic_password'] classic_mirror = post_data['classic_mirror'] except KeyError: current_app.logger.warning( 'User did not provide a required key: {}'.format( traceback.print_exc())) return err(CLASSIC_DATA_MALFORMED) # Check that the mirror exists and not man-in-the-middle if classic_mirror not in current_app.config[ 'ADS_CLASSIC_MIRROR_LIST']: current_app.logger.warning( 'User "{}" tried to use a mirror that does not exist: "{}"' .format(classic_email, classic_mirror)) return err(CLASSIC_BAD_MIRROR) # Create the correct URL url = current_app.config['ADS_CLASSIC_URL'].format( mirror=classic_mirror, ) params = { 'man_cmd': 'elogin', 'man_email': classic_email, 'man_passwd': classic_password } # Authenticate current_app.logger.info( 'User "{email}" trying to authenticate at mirror "{mirror}"'. format(email=classic_email, mirror=classic_mirror)) try: response = current_app.client.post(url, params=params) except requests.exceptions.Timeout: current_app.logger.warning( 'ADS Classic end point timed out, returning to user') return err(CLASSIC_TIMEOUT) if response.status_code >= 500: message, status_code = err(CLASSIC_UNKNOWN_ERROR) message['ads_classic'] = { 'message': response.text, 'status_code': response.status_code } current_app.logger.warning( 'ADS Classic has responded with an unknown error: {}'. format(response.text)) return message, status_code # Sanity check the response email = response.json()['email'] if email != classic_email: current_app.logger.warning( 'User email "{}" does not match ADS return email "{}"'. format(classic_email, email)) return err(CLASSIC_AUTH_FAILED) # Respond to the user based on whether they were successful or not if response.status_code == 200 \ and response.json()['message'] == 'LOGGED_IN' \ and int(response.json()['loggedin']): current_app.logger.info( 'Authenticated successfully "{email}" at mirror "{mirror}"' .format(email=classic_email, mirror=classic_mirror)) # Save cookie in myADS try: cookie = response.json()['cookie'] except KeyError: current_app.logger.warning( 'Classic returned no cookie, cannot continue: {}'. format(response.json())) return err(CLASSIC_NO_COOKIE) absolute_uid = self.helper_get_user_id() try: user = session.query(Users).filter( Users.absolute_uid == absolute_uid).one() current_app.logger.info('User already exists in database') user.classic_mirror = classic_mirror user.classic_cookie = cookie user.classic_email = classic_email except NoResultFound: current_app.logger.info( 'Creating entry in database for user') user = Users(absolute_uid=absolute_uid, classic_cookie=cookie, classic_email=classic_email, classic_mirror=classic_mirror) session.add(user) session.commit() current_app.logger.info( 'Successfully saved content for "{}" to database: {{"cookie": "{}"}}' .format(classic_email, '*' * len(user.classic_cookie))) return { 'classic_email': email, 'classic_mirror': classic_mirror, 'classic_authed': True }, 200 else: current_app.logger.warning( 'Credentials for "{email}" did not succeed at mirror "{mirror}"' .format(email=classic_email, mirror=classic_mirror)) return err(CLASSIC_AUTH_FAILED)