Beispiel #1
0
    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)
Beispiel #2
0
    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)
Beispiel #3
0
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({})
Beispiel #4
0
 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"
Beispiel #5
0
    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)
Beispiel #6
0
    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