def _remote_survey_url_vioscreen(transaction, account_id, source_id, language_tag, survey_redirect_url, vioscreen_ext_sample_id): # assumes an instance of Transaction is already available acct_repo = AccountRepo(transaction) survey_template_repo = SurveyTemplateRepo(transaction) if vioscreen_ext_sample_id: # User is about to start a vioscreen survey for this sample # record this in the database. db_vioscreen_id = survey_template_repo.create_vioscreen_id( account_id, source_id, vioscreen_ext_sample_id) else: raise ValueError("Vioscreen Template requires " "vioscreen_ext_sample_id parameter.") (birth_year, gender, height, weight) = \ survey_template_repo.fetch_user_basic_physiology( account_id, source_id) account = acct_repo.get_account(account_id) country_code = account.address.country_code url = vioscreen.gen_survey_url(db_vioscreen_id, language_tag, survey_redirect_url, birth_year=birth_year, gender=gender, height=height, weight=weight, country_code=country_code) return url
def setup_test_data(): teardown_test_data() with Transaction() as t: acct_repo = AccountRepo(t) acc = Account(ACCT_ID_1, "*****@*****.**", "admin", ACCT_MOCK_ISS, ACCT_MOCK_SUB, "Dan", "H", Address( "456 Dan Lane", "Danville", "CA", 12345, "US" ), "fakekit", "en_US") acct_repo.create_account(acc) with t.cursor() as cur: cur.execute("UPDATE barcodes.project" " SET is_active = FALSE" " WHERE project_id = 2") t.commit()
def validate_admin_access(token_info): with Transaction() as t: account_repo = AccountRepo(t) account = account_repo.find_linked_account(token_info['iss'], token_info['sub']) if account is None or account.account_type != 'admin': raise Unauthorized()
def teardown_test_data(): with Transaction() as t: acct_repo = AccountRepo(t) admin_repo = AdminRepo(t) acct_repo.delete_account(ACCT_ID_1) admin_repo.delete_project_by_name(DUMMY_PROJ_NAME) t.commit()
def _validate_account_access(token_info, account_id): with Transaction() as t: account_repo = AccountRepo(t) token_associated_account = account_repo.find_linked_account( token_info['iss'], token_info['sub']) account = account_repo.get_account(account_id) if account is None: raise NotFound(ACCT_NOT_FOUND_MSG) else: # Whether or not the token_info is associated with an admin acct token_authenticates_admin = \ token_associated_account is not None and \ token_associated_account.account_type == 'admin' # Enum of how closely token info matches requested account_id auth_match = account.account_matches_auth( token_info[JWT_EMAIL_CLAIM_KEY], token_info[JWT_ISS_CLAIM_KEY], token_info[JWT_SUB_CLAIM_KEY]) # If token doesn't match requested account id, and doesn't grant # admin access to the system, deny. if auth_match == AuthorizationMatch.NO_MATCH and \ not token_authenticates_admin: raise Unauthorized() return account
def test_email_stats(self): with Transaction() as t: accts = AccountRepo(t) acct1 = accts.get_account("65dcd6c8-69fa-4de8-a33a-3de4957a0c79") acct2 = accts.get_account("556f5dc4-8cf2-49ae-876c-32fbdfb005dd") # execute articles get for project in [None, "American Gut Project", "NotAProj"]: response = self.client.post( "/api/admin/account_email_summary", headers=MOCK_HEADERS, content_type='application/json', data=json.dumps({ "emails": [acct1.email, acct2.email], "project": project }) ) self.assertEqual(200, response.status_code) result = json.loads(response.data) self.assertEqual(result[0]["account_id"], "65dcd6c8-69fa-4de8-a33a-3de4957a0c79") self.assertEqual(result[1]["account_id"], "556f5dc4-8cf2-49ae-876c-32fbdfb005dd") if project is None or project == "American Gut Project": self.assertEqual(result[0]["sample-is-valid"], 1) else: self.assertEqual(result[0].get("sample-is-valid", 0), 0)
def get_samples_by_source(self, account_id, source_id, allow_revoked=False): sql = "{0}{1}".format( self.PARTIAL_SQL, " WHERE" " source.account_id = %s" " AND source.id = %s" " ORDER BY ag_kit_barcodes.barcode asc") with self._transaction.cursor() as cur: acct_repo = AccountRepo(self._transaction) if acct_repo.get_account(account_id) is None: raise NotFound("No such account") source_repo = SourceRepo(self._transaction) if source_repo.get_source(account_id, source_id, allow_revoked=allow_revoked) is None: raise NotFound("No such source") cur.execute(sql, (account_id, source_id)) samples = [] for sample_row in cur.fetchall(): samples.append(self._create_sample_obj(sample_row)) return samples
def retrieve_diagnostics_by_email(self, email): acct_repo = AccountRepo(self._transaction) ids = acct_repo.get_account_ids_by_email(email) accts = [acct_repo.get_account(acct_id) for acct_id in ids] diagnostic = { "accounts": accts } return diagnostic
def setup_test_data(): teardown_test_data() with Transaction() as t: acct_repo = AccountRepo(t) acc = Account(ACCT_ID_1, "*****@*****.**", "admin", ACCT_MOCK_ISS, ACCT_MOCK_SUB, "Dan", "H", Address("456 Dan Lane", "Danville", "CA", 12345, "US"), "fakekit") acct_repo.create_account(acc) t.commit()
def find_accounts_for_login(token_info): # Note: Returns an array of accounts accessible by token_info because # we'll use that functionality when we add in administrator accounts. with Transaction() as t: acct_repo = AccountRepo(t) acct = acct_repo.find_linked_account( token_info[JWT_ISS_CLAIM_KEY], token_info[JWT_SUB_CLAIM_KEY]) if acct is None: return jsonify([]), 200 return jsonify([acct.to_api()]), 200
def teardown_test_data(): with Transaction() as t: acct_repo = AccountRepo(t) admin_repo = AdminRepo(t) acct_repo.delete_account(ACCT_ID_1) admin_repo.delete_project_by_name(DUMMY_PROJ_NAME) with t.cursor() as cur: cur.execute("UPDATE barcodes.project" " SET is_active = TRUE" " WHERE project_id = 2") t.commit()
def _validate_has_account(token_info): # WARNING: this does NOT authenticate a user but tests for the # presence of an account with Transaction() as t: account_repo = AccountRepo(t) token_associated_account = account_repo.find_linked_account( token_info['iss'], token_info['sub']) if token_associated_account is None: raise Unauthorized() else: return
def create_source(self, source): with self._transaction.cursor() as cur: acct_repo = AccountRepo(self._transaction) if acct_repo.get_account(source.account_id) is None: raise NotFound("No such account_id") cur.execute( "INSERT INTO source (" + SourceRepo.write_cols + ") " "VALUES(" "%s, %s, %s, " "%s, %s, " "%s, %s, %s, " "%s, %s, %s, " "%s, %s, %s)", _source_to_row(source)) return cur.rowcount == 1
def _create_dummy_acct_from_t(t, create_dummy_1=True, iss=ACCT_MOCK_ISS, sub=ACCT_MOCK_SUB): if create_dummy_1: dummy_acct_id = ACCT_ID_1 dict_to_copy = DUMMY_ACCT_INFO else: dummy_acct_id = ACCT_ID_2 dict_to_copy = DUMMY_ACCT_INFO_2 input_obj = copy.deepcopy(dict_to_copy) input_obj["id"] = dummy_acct_id acct_repo = AccountRepo(t) acct_repo.create_account(Account.from_dict(input_obj, iss, sub)) return dummy_acct_id
def update_account(account_id, body, token_info): acc = _validate_account_access(token_info, account_id) with Transaction() as t: acct_repo = AccountRepo(t) acc.first_name = body['first_name'] acc.last_name = body['last_name'] acc.email = body['email'] acc.address = Address(body['address']['street'], body['address']['city'], body['address']['state'], body['address']['post_code'], body['address']['country_code']) # 422 handling is done inside acct_repo acct_repo.update_account(acc) t.commit() return jsonify(acc.to_api()), 200
def register_account(body, token_info): # First register with AuthRocket, then come here to make the account new_acct_id = str(uuid.uuid4()) body["id"] = new_acct_id # Account.from_dict requires a kit_name, even if blank kit_name = body.get("kit_name", "") body["kit_name"] = kit_name code = body.get("code", "") body["code"] = code account_obj = Account.from_dict(body, token_info[JWT_ISS_CLAIM_KEY], token_info[JWT_SUB_CLAIM_KEY]) if kit_name == "" and code == "": return jsonify(code=400, message="Account registration requires " "valid kit ID or activation code"), 400 with Transaction() as t: activation_repo = ActivationRepo(t) if code != "": can_activate, cause = activation_repo.can_activate_with_cause( body["email"], code) if not can_activate: return jsonify(code=404, message=cause), 404 else: activation_repo.use_activation_code(body["email"], code) if kit_name != "": kit_repo = KitRepo(t) kit = kit_repo.get_kit_all_samples(kit_name) if kit is None: return jsonify(code=404, message="Kit name not found"), 404 acct_repo = AccountRepo(t) acct_repo.create_account(account_obj) new_acct = acct_repo.get_account(new_acct_id) t.commit() response = jsonify(new_acct.to_api()) response.status_code = 201 response.headers['Location'] = '/api/accounts/%s' % new_acct_id return response
def claim_legacy_acct(token_info): # If there exists a legacy account for the email in the token, which the # user represented by the token does not already own but can claim, this # claims the legacy account for the user and returns a 200 code with json # list containing the object for the claimed account. Otherwise, this # returns an empty json list. This function can also trigger a 422 from the # repo layer in the case of inconsistent account data. email = token_info[JWT_EMAIL_CLAIM_KEY] auth_iss = token_info[JWT_ISS_CLAIM_KEY] auth_sub = token_info[JWT_SUB_CLAIM_KEY] with Transaction() as t: acct_repo = AccountRepo(t) acct = acct_repo.claim_legacy_account(email, auth_iss, auth_sub) t.commit() if acct is None: return jsonify([]), 200 return jsonify([acct.to_api()]), 200
def test_scrub(self): with Transaction() as t: ar = AccountRepo(t) ar.scrub(ACCOUNT_ID) obs = ar.get_account(ACCOUNT_ID) self.assertEqual(obs.id, self.untouched.id) self.assertNotEqual(obs.email, self.untouched.email) self.assertEqual(obs.account_type, 'deleted') self.assertEqual(obs.auth_issuer, None) self.assertEqual(obs.auth_sub, None) self.assertEqual(obs.first_name, 'scrubbed') self.assertEqual(obs.last_name, 'scrubbed') self.assertEqual(obs.address.street, 'scrubbed') self.assertEqual(obs.address.city, 'scrubbed') self.assertEqual(obs.address.state, 'NA') self.assertEqual(obs.address.post_code, 'scrubbed') # keeping country is reasonable as it's so broad self.assertEqual(obs.address.country_code, self.untouched.address.country_code) self.assertEqual(obs.created_with_kit_id, self.untouched.created_with_kit_id) self.assertEqual(obs.creation_time, self.untouched.creation_time) self.assertNotEqual(obs.update_time, self.untouched.update_time) self.assertEqual(obs.language, self.untouched.language) email = obs.email date, remainder = email.split('@', 1) date = date.split('T')[0].strip('"') obs_date = datetime.datetime.strptime(date, "%Y-%m-%d") today = datetime.datetime.now() today = datetime.datetime(year=today.year, month=today.month, day=today.day) self.assertEqual(obs_date, today) self.assertEqual(remainder, 'microsetta.ucsd.edu')
def retrieve_diagnostics_by_kit_id(self, supplied_kit_id): kit_repo = KitRepo(self._transaction) kit = kit_repo.get_kit_all_samples(supplied_kit_id) if kit is None: return None sample_assoc = [] for sample in kit.samples: sample_assoc.append( self.retrieve_diagnostics_by_barcode(sample.barcode, grab_kit=False)) with self._transaction.dict_cursor() as cur: cur.execute( "SELECT " "ag_login_id as account_id " "FROM " "ag_kit " "WHERE " "supplied_kit_id = %s", (supplied_kit_id,)) row = cur.fetchone() pre_microsetta_acct = None if row['account_id'] is not None: acct_repo = AccountRepo(self._transaction) # This kit predated the microsetta migration, let's pull in the # account info associated with it pre_microsetta_acct = acct_repo.get_account(row['account_id']) diagnostic = { 'kit_id': kit.id, 'supplied_kit_id': supplied_kit_id, 'kit': kit, 'pre_microsetta_acct': pre_microsetta_acct, 'sample_diagnostic_info': sample_assoc } return diagnostic
def delete_dummy_accts(): all_sample_ids = [] acct_ids = [ACCT_ID_1, ACCT_ID_2] with Transaction() as t: acct_repo = AccountRepo(t) source_repo = SourceRepo(t) survey_answers_repo = SurveyAnswersRepo(t) sample_repo = SampleRepo(t) for curr_acct_id in acct_ids: sources = source_repo.get_sources_in_account(curr_acct_id) for curr_source in sources: source_samples = sample_repo.get_samples_by_source( curr_acct_id, curr_source.id) sample_ids = [x.id for x in source_samples] all_sample_ids.extend(sample_ids) # Dissociate all samples linked to this source from all # answered surveys linked to this source, then delete all # answered surveys delete_dummy_answered_surveys_from_source_with_t( t, curr_acct_id, curr_source.id, sample_ids, survey_answers_repo) # Now dissociate all the samples from this source for curr_sample_id in sample_ids: sample_repo.dissociate_sample(curr_acct_id, curr_source.id, curr_sample_id) # Finally, delete the source source_repo.delete_source(curr_acct_id, curr_source.id) # Delete the account acct_repo.delete_account(curr_acct_id) # Belt and suspenders: these test emails are used by some tests outside # of this module as well, so can't be sure they are paired with the # above dummy account ids acct_repo.delete_account_by_email(TEST_EMAIL) acct_repo.delete_account_by_email(TEST_EMAIL_2) # Delete the kit and all samples that were attached to any sources # NB: This won't clean up any samples that were created but NOT # attached to any sources ... if len(all_sample_ids) == 0: all_sample_ids = None _remove_mock_kit(t, mock_sample_ids=all_sample_ids) t.commit()
def test_geocode_accounts_valid(self, test_verify_address): test_verify_address.return_value = { "address_1": DUMMY_ACCT_INFO_1['address']['street'], "address_2": "", "city": DUMMY_ACCT_INFO_1['address']['city'], "state": DUMMY_ACCT_INFO_1['address']['state'], "postal": DUMMY_ACCT_INFO_1['address']['post_code'], "country": DUMMY_ACCT_INFO_1['address']['country_code'], "latitude": RESULT_LAT, "longitude": RESULT_LONG, "valid": True } with Transaction() as t: ar = AccountRepo(t) acct_1 = Account.from_dict(DUMMY_ACCT_INFO_1, ACCT_MOCK_ISS_1, ACCT_MOCK_SUB_1) ar.create_account(acct_1) ar.geocode_accounts() with t.dict_cursor() as cur: cur.execute("SELECT latitude, longitude, address_verified, " "cannot_geocode FROM ag.account " "WHERE id = %s", (ACCT_ID_1,)) r = cur.fetchone() self.assertAlmostEqual(r['latitude'], RESULT_LAT, 9) self.assertAlmostEqual(r['longitude'], RESULT_LONG, 9) self.assertTrue(r['address_verified']) self.assertFalse(r['cannot_geocode'])
def delete_account(account_id, token_info): validate_admin_access(token_info) with Transaction() as t: acct_repo = AccountRepo(t) src_repo = SourceRepo(t) samp_repo = SampleRepo(t) sar_repo = SurveyAnswersRepo(t) acct = acct_repo.get_account(account_id) if acct is None: return jsonify(message="Account not found", code=404), 404 else: # the account is already scrubbed so let's stop early if acct.account_type == 'deleted': return None, 204 sample_count = 0 sources = src_repo.get_sources_in_account(account_id) for source in sources: samples = samp_repo.get_samples_by_source(account_id, source.id) has_samples = len(samples) > 0 sample_count += len(samples) for sample in samples: # we scrub rather than disassociate in the event that the # sample is in our freezers but not with an up-to-date scan samp_repo.scrub(account_id, source.id, sample.id) surveys = sar_repo.list_answered_surveys(account_id, source.id) if has_samples: # if we have samples, we need to scrub survey / source # free text for survey_id in surveys: sar_repo.scrub(account_id, source.id, survey_id) src_repo.scrub(account_id, source.id) else: # if we do not have associated samples, then the source # is safe to delete for survey_id in surveys: sar_repo.delete_answered_survey(account_id, survey_id) src_repo.delete_source(account_id, source.id) # an account is safe to delete if there are no associated samples if sample_count > 0: acct_repo.scrub(account_id) else: acct_repo.delete_account(account_id) t.commit() return None, 204
def register_account(body, token_info): # First register with AuthRocket, then come here to make the account new_acct_id = str(uuid.uuid4()) body["id"] = new_acct_id account_obj = Account.from_dict(body, token_info[JWT_ISS_CLAIM_KEY], token_info[JWT_SUB_CLAIM_KEY]) with Transaction() as t: kit_repo = KitRepo(t) kit = kit_repo.get_kit_all_samples(body['kit_name']) if kit is None: return jsonify(code=404, message="Kit name not found"), 404 acct_repo = AccountRepo(t) acct_repo.create_account(account_obj) new_acct = acct_repo.get_account(new_acct_id) t.commit() response = jsonify(new_acct.to_api()) response.status_code = 201 response.headers['Location'] = '/api/accounts/%s' % new_acct_id return response
def get_samples_by_source(self, account_id, source_id): with self._transaction.cursor() as cur: acct_repo = AccountRepo(self._transaction) if acct_repo.get_account(account_id) is None: raise NotFound("No such account") source_repo = SourceRepo(self._transaction) if source_repo.get_source(account_id, source_id) is None: raise NotFound("No such source") cur.execute( "SELECT " "ag_kit_barcodes.ag_kit_barcode_id, " "ag_kit_barcodes.sample_date, " "ag_kit_barcodes.sample_time, " "ag_kit_barcodes.site_sampled, " "ag_kit_barcodes.notes, " "ag_kit_barcodes.barcode, " "barcode.scan_date " "FROM ag_kit_barcodes " "LEFT JOIN barcode " "USING (barcode) " "LEFT JOIN source " "ON ag_kit_barcodes.source_id = source.id " "WHERE " "source.account_id = %s AND " "source.id = %s " "ORDER BY barcode.barcode asc", (account_id, source_id)) samples = [] for sample_row in cur.fetchall(): barcode = sample_row[5] sample_projects = self._retrieve_projects(barcode) s = Sample.from_db(*sample_row, sample_projects) samples.append(s) return samples
def create_daklapack_orders(body, token_info): validate_admin_access(token_info) ADDRESSES_KEY = 'addresses' extended_body = body.copy() results = [] with Transaction() as t: account_repo = AccountRepo(t) extended_body[SUBMITTER_ACCT_KEY] = account_repo.find_linked_account( token_info['iss'], token_info['sub']) for curr_address in extended_body[ADDRESSES_KEY]: curr_order_dict = extended_body.copy() curr_order_dict.pop(ADDRESSES_KEY) curr_order_dict[ADDR_DICT_KEY] = curr_address result_info = _create_daklapack_order(curr_order_dict) results.append(result_info) # return response to caller response = jsonify({"order_submissions": results}) response.status_code = 200 return response
def delete_dummy_accts(): with Transaction() as t: source_repo = SourceRepo(t) survey_answers_repo = SurveyAnswersRepo(t) sources = source_repo.get_sources_in_account(ACCT_ID_1) for curr_source in sources: answers = survey_answers_repo.list_answered_surveys( ACCT_ID_1, curr_source.id) for survey_id in answers: survey_answers_repo.delete_answered_survey( ACCT_ID_1, survey_id) source_repo.delete_source(ACCT_ID_1, curr_source.id) acct_repo = AccountRepo(t) acct_repo.delete_account(ACCT_ID_1) acct_repo.delete_account(ACCT_ID_2) # Belt and suspenders: these test emails are used by some tests outside # of this module as well, so can't be sure they are paired with the # above dummy account ids acct_repo.delete_account_by_email(TEST_EMAIL) acct_repo.delete_account_by_email(TEST_EMAIL_2) t.commit()
def setup_test_data(): AdminTests.teardown_test_data() with Transaction() as t: acct_repo = AccountRepo(t) acc = Account(STANDARD_ACCT_ID, "*****@*****.**", "standard", "https://MOCKUNITTEST.com", "1234ThisIsNotARealSub", "NotDan", "NotH", Address( "123 Dan Lane", "NotDanville", "CA", 12345, "US" ), "fakekit") acct_repo.create_account(acc) acc = Account(ADMIN_ACCT_ID, "*****@*****.**", "admin", "https://MOCKUNITTEST.com", "5678ThisIsNotARealAdminSub", "Dan", "H", Address( "456 Dan Lane", "Danville", "CA", 12345, "US" ), "fakekit") acct_repo.create_account(acc) t.commit()
def token_grants_admin_access(token_info): with Transaction() as t: account_repo = AccountRepo(t) account = account_repo.find_linked_account(token_info['iss'], token_info['sub']) return account is not None and account.account_type == 'admin'
def teardown_test_data(): with Transaction() as t: acct_repo = AccountRepo(t) acct_repo.delete_account(STANDARD_ACCT_ID) acct_repo.delete_account(ADMIN_ACCT_ID) t.commit()
def get_survey_metadata(self, sample_barcode, survey_template_id=None): ids = self._get_ids_relevant_to_barcode(sample_barcode) if ids is None: raise NotFound("No such barcode") account_id = ids.get('account_id') source_id = ids.get('source_id') sample_id = ids.get('sample_id') account = None source = None sample = None if sample_id is not None: sample_repo = SampleRepo(self._transaction) sample = sample_repo._get_sample_by_id(sample_id) if source_id is not None and account_id is not None: source_repo = SourceRepo(self._transaction) account_repo = AccountRepo(self._transaction) account = account_repo.get_account(account_id) source = source_repo.get_source(account_id, source_id) if source is None: raise RepoException("Barcode is not associated with a source") # TODO: This is my best understanding of how the data must be # transformed to get the host_subject_id, needs verification that it # generates the expected values for preexisting samples. prehash = account_id + source.name.lower() host_subject_id = sha512(prehash.encode()).hexdigest() survey_answers_repo = SurveyAnswersRepo(self._transaction) answer_ids = survey_answers_repo.list_answered_surveys_by_sample( account_id, source_id, sample_id) answer_to_template_map = {} for answer_id in answer_ids: template_id = survey_answers_repo.find_survey_template_id( answer_id) answer_to_template_map[answer_id] = template_id # if a survey template is specified, filter the returned surveys if survey_template_id is not None: # TODO: This schema is so awkward for this type of query... answers = [] for answer_id in answer_ids: if answer_to_template_map[answer_id] == survey_template_id: answers.append(answer_id) if len(answers) == 0: raise NotFound("This barcode is not associated with any " "surveys matching this template id") if len(answers) > 1: # I really hope this can't happen. (x . x) raise RepoException("This barcode is associated with more " "than one survey matching this template" " id") answer_ids = answers metadata_map = survey_answers_repo.build_metadata_map() all_survey_answers = [] for answer_id in answer_ids: answer_model = survey_answers_repo.get_answered_survey( account_id, source_id, answer_id, "en-US") survey_answers = {} for k in answer_model: new_k = metadata_map[int(k)] survey_answers[k] = [new_k, answer_model[k]] all_survey_answers.append({ "template": answer_to_template_map[answer_id], "response": survey_answers }) pulldown = { "sample_barcode": sample_barcode, "host_subject_id": host_subject_id, "account": account, "source": source, "sample": sample, "survey_answers": all_survey_answers } return pulldown