def __init__(self, sequence_number=None, profile_json=None, **kwargs): self.connection_object = connect.AWS() self.identity_vault_client = None self.config = common.get_config() self.condition = "unknown" self.user_id = kwargs.get("user_id") self.user_uuid = kwargs.get("user_uuid") self.primary_email = kwargs.get("primary_email") self.primary_username = kwargs.get("primary_username") if self.user_id is None: logger.info( "No user_id arg was passed for the payload. This is a new user or batch." ) tmp_user = User(user_structure_json=profile_json) self.user_id = tmp_user.user_id.value self.condition = "create" if sequence_number is not None: self.sequence_number = str(sequence_number) else: self.sequence_number = str(uuid.uuid4().int)
def get(self): """Return a single user with id `user_id`.""" parser = reqparse.RequestParser() parser.add_argument("Authorization", location="headers") parser.add_argument("nextPage", type=str) parser.add_argument("primaryEmail", type=str) parser.add_argument("filterDisplay", type=str) parser.add_argument("active", type=str) args = parser.parse_args() filter_display = args.get("filterDisplay", None) primary_email = args.get("primaryEmail", None) next_page = args.get("nextPage", None) scopes = get_scopes(args.get("Authorization")) if next_page is not None: nextPage = load_dirty_json(next_page) else: nextPage = None if transactions == "false": identity_vault = user.Profile(dynamodb_table, dynamodb_client, transactions=False) if transactions == "true": identity_vault = user.Profile(dynamodb_table, dynamodb_client, transactions=True) next_page_token = None if primary_email is None: result = identity_vault.all_by_page(next_page=nextPage, limit=25) next_page_token = result.get("LastEvaluatedKey") else: result = identity_vault.find_by_email(primary_email) v2_profiles = [] if args.get("active") is not None and args.get( "active").lower() == "false": active = False else: active = True # Support returning only active users by default. for profile in result.get("Items"): vault_profile = json.loads(profile.get("profile")) v2_profile = User(user_structure_json=vault_profile) # This must be a pre filtering check because mutation is real. if v2_profile.active.value == active: allowed_in_list = True else: allowed_in_list = False if "read:fullprofile" in scopes: # Assume someone has asked for all the data. logger.info( "The provided token has access to all of the data.", extra={ "query_args": args, "scopes": scopes }) pass else: # Assume the we are filtering falls back to public with no scopes logger.info("This is a limited scoped query.", extra={ "query_args": args, "scopes": scopes }) v2_profile.filter_scopes( scope_to_mozilla_data_classification(scopes)) if "display:all" in scopes: logger.info("display:all in token not filtering profile.", extra={ "query_args": args, "scopes": scopes }) else: logger.info("display filtering engaged for query.", extra={ "query_args": args, "scopes": scopes }) v2_profile.filter_display(scope_to_display_level(scopes)) if filter_display is not None: v2_profile.filter_display( DisplayLevelParms.map(filter_display)) if allowed_in_list: v2_profiles.append(v2_profile.as_dict()) else: logger.debug( "Skipping adding this profile to the list of profiles because it is: {}" .format(active)) pass response = {"Items": v2_profiles, "nextPage": next_page_token} return jsonify(response)
def getUser(id, find_by): """Return a single user with identifier using find_by.""" id = urllib.parse.unquote(id) parser = reqparse.RequestParser() parser.add_argument("Authorization", location="headers") parser.add_argument("filterDisplay", type=str) parser.add_argument("active", type=str) args = parser.parse_args() scopes = get_scopes(args.get("Authorization")) filter_display = args.get("filterDisplay", None) active = True if args.get("active") is not None and args.get( "active").lower() == "false": active = False if transactions == "false": identity_vault = user.Profile(dynamodb_table, dynamodb_client, transactions=False) if transactions == "true": identity_vault = user.Profile(dynamodb_table, dynamodb_client, transactions=True) result = find_by(identity_vault, id) if len(result["Items"]) > 0: vault_profile = result["Items"][0]["profile"] v2_profile = User(user_structure_json=json.loads(vault_profile)) if v2_profile.active.value == active: if "read:fullprofile" in scopes: logger.info( "read:fullprofile in token not filtering based on scopes.", extra={ "query_args": args, "scopes": scopes }, ) else: v2_profile.filter_scopes( scope_to_mozilla_data_classification(scopes)) if "display:all" in scopes: logger.info( "display:all in token not filtering profile based on display.", extra={ "query_args": args, "scopes": scopes }, ) else: v2_profile.filter_display(scope_to_display_level(scopes)) if filter_display is not None: logger.info( "filter_display argument is passed, applying display level filter.", extra={"query_args": args}) v2_profile.filter_display( DisplayLevelParms.map(filter_display)) return jsonify(v2_profile.as_dict()) logger.info("No user was found for the query", extra={ "query_args": args, "scopes": scopes }) return jsonify({})
def test_profile_env(self): os.environ[ "CIS_DISCOVERY_URL"] = "https://auth.allizom.org/.well-known/mozilla-iam" u = User() assert u._User__well_known.discovery_url == "https://auth.allizom.org/.well-known/mozilla-iam"
def get(self): """Return a single user with id `user_id`.""" parser = reqparse.RequestParser() parser.add_argument("Authorization", location="headers") parser.add_argument("nextPage", type=str) parser.add_argument("primaryEmail", type=str) parser.add_argument("filterDisplay", type=str) args = parser.parse_args() filter_display = args.get("filterDisplay", None) primary_email = args.get("primaryEmail", None) next_page = args.get("nextPage", None) scopes = get_scopes(args.get("Authorization")) if next_page is not None: nextPage = load_dirty_json(next_page) else: nextPage = None if transactions == "false": identity_vault = user.Profile(dynamodb_table, dynamodb_client, transactions=False) if transactions == "true": identity_vault = user.Profile(dynamodb_table, dynamodb_client, transactions=True) next_page_token = None if primary_email is None: result = identity_vault.all_by_page(next_page=nextPage, limit=25) next_page_token = result.get("LastEvaluatedKey") else: result = identity_vault.find_by_email(primary_email) v2_profiles = [] for profile in result.get("Items"): vault_profile = json.loads(profile.get("profile")) v2_profile = User(user_structure_json=vault_profile) if "read:fullprofile" in scopes: # Assume someone has asked for all the data. pass else: # Assume the we are filtering falls back to public with no scopes v2_profile.filter_scopes( scope_to_mozilla_data_classification(scopes)) if "display:all" in scopes: logger.debug("display:all in token not filtering profile.") else: v2_profile.filter_display(scope_to_display_level(scopes)) if filter_display is not None: v2_profile.filter_display( DisplayLevelParms.map(filter_display)) v2_profiles.append(v2_profile.as_dict()) response = {"Items": v2_profiles, "nextPage": next_page_token} return jsonify(response)
def _search_and_merge(self, user_id, cis_profile_object): """ Search for an existing user in the vault for the given profile If one exist, merge the given profile with the existing user If not, return the given profile WARNING: This function also verifies the publishers are valid, as this verification requires knowledge of the incoming user profile, profile in the vault, and resulting merged profile. @cis_profile_object cis_profile.User object of an incoming user @user_id str the user id of cis_profile_object Returns a cis_profile.User object """ try: self._connect() vault = user.Profile(self.identity_vault_client.get("table"), self.identity_vault_client.get("client")) res = vault.find_by_id(user_id) logger.info("Search user in vault results: {}".format( len(res["Items"]))) except Exception as e: logger.error( "Problem finding user profile in identity vault due to: {}". format(e)) res = {"Items": []} if len(res["Items"]) > 0: # This profile exists in the vault and will be merged and it's publishers verified self.condition = "update" logger.info( "A record already exists in the identity vault for user: {}.". format(user_id), extra={"user_id": user_id}, ) old_user_profile = User( user_structure_json=json.loads(res["Items"][0]["profile"])) new_user_profile = copy.deepcopy(old_user_profile) difference = new_user_profile.merge(cis_profile_object) if ((difference == ["user_id"]) or (new_user_profile.active.value == old_user_profile.active.value and difference == ["active"]) or (len(difference) == 0) or ((new_user_profile.active.value == old_user_profile.active.value and (new_user_profile.uuid.value is None and new_user_profile.primary_username.value is None)) and sorted(difference) == sorted( ["active", "uuid", "primary_username"]))): logger.info( "Will not merge user as there were no difference found with the vault instance of the user" .format(extra={"user_id": user_id})) return None else: logger.info( "Differences found during merge: {}".format(difference), extra={"user_id": user_id}) # XXX This is safe but this is not great. Probably should have a route to deactivate since its a CIS # attribute. if difference == ["active"]: logger.info( "Partial update only contains the `active` attribute, bypassing publisher verification as CIS " "will enforce this check on it's own'", extra={"user_id": user_id}, ) return new_user_profile if self.config("verify_publishers", namespace="cis") == "true": logger.info("Verifying publishers", extra={"user_id": user_id}) try: new_user_profile.verify_all_publishers(old_user_profile) except Exception as e: logger.error( "The merged profile failed to pass publisher verification", extra={ "user_id": user_id, "profile": new_user_profile.as_dict(), "reason": e, "trace": format_exc(), }, ) raise VerificationError( { "code": "invalid_publisher", "description": "{}".format(e) }, 403) else: logger.warning( "Bypassing profile publisher verification due to `verify_publishers` setting being false", extra={"user_id": user_id}, ) return new_user_profile else: # This is a new profile, set uuid and primary_username and verify. self.condition = "create" logger.info( "A record does not exist in the identity vault for user: {}.". format(user_id), extra={"user_id": user_id}, ) # We raise an exception if uuid or primary_username is already set. This must not happen. if cis_profile_object.uuid.value is not None or cis_profile_object.primary_username.value is not None: logger.error( "Trying to create profile, but uuid ({}) or primary_username ({}) was already " "set".format(cis_profile_object.uuid.value, cis_profile_object.primary_username.value), extra={ "user_id": user_id, "profile": cis_profile_object.as_dict() }, ) raise VerificationError( { "code": "uuid_or_primary_username_set", "description": "The fields primary_username or uuid have been set in a new profile.", }, 403, ) cis_profile_object.initialize_uuid_and_primary_username() cis_profile_object.sign_attribute("uuid", "cis") cis_profile_object.sign_attribute("primary_username", "cis") if self.config("verify_publishers", namespace="cis") == "true": logger.info("Verifying publishers", extra={"user_id": user_id}) try: cis_profile_object.verify_all_publishers( cis_profile.User()) except Exception as e: logger.error( "The profile failed to pass publisher verification", extra={ "user_id": user_id, "profile": cis_profile_object.as_dict(), "reason": e, "trace": format_exc(), }, ) raise VerificationError( { "code": "invalid_publisher", "description": "{}".format(e) }, 403) else: logger.warning( "Bypassing profile publisher verification due to `verify_publishers` setting being false", extra={"user_id": user_id}, ) return cis_profile_object