Ejemplo n.º 1
0
    def put_profile(self, _profile):
        """
        Wrapper for a single profile, calls the batch put_profiles method
        """
        res = self.put_profiles([_profile])
        if res["status"] == 202:
            # 202 means everything went OK but no processing was performed (ie no results and this normal)
            condition = "noop"
            sequence_number = None
        elif res["creates"] is not None and len(
                res["creates"]["sequence_numbers"]) == 1:
            sequence_number = res["creates"]["sequence_numbers"][0]
            condition = "create"
        elif res["updates"] is not None and len(
                res["updates"]["sequence_numbers"]) == 1:
            sequence_number = res["updates"]["sequence_numbers"][0]
            condition = "update"
        else:
            raise IntegrationError(
                {
                    "code": "integration_exception",
                    "description": "No operation occurred: {}".format(res)
                }, 500)

        return dict(sequence_number=sequence_number,
                    status_code=res["status"],
                    condition=condition)
Ejemplo n.º 2
0
    def delete_profile(self, profile_json):
        # XXX This method should be refactored to look like put_profiles() / put_profile()
        self.condition = "delete"

        try:
            user_profile = dict(
                id=profile_json["user_id"]["value"],
                primary_email=profile_json["primary_email"]["value"],
                user_uuid=profile_json["uuid"]["value"],
                primary_username=profile_json["primary_username"]["value"],
                sequence_number=self.sequence_number,
                profile=json.dumps(profile_json),
            )

            if self.config("dynamodb_transactions", namespace="cis") == "true":
                logger.debug(
                    "Attempting to put batch of profiles using transactions.")
                vault = user.Profile(self.identity_vault_client.get("table"),
                                     self.identity_vault_client.get("client"),
                                     transactions=True)
            else:
                logger.info(
                    "Attempting to put batch of profiles without transactions."
                )
                vault = user.Profile(
                    self.identity_vault_client.get("table"),
                    self.identity_vault_client.get("client"),
                    transactions=False,
                )

            vault.delete(user_profile)
        except ClientError as e:
            logger.error(
                "An error occured removing this profile from dynamodb",
                extra={
                    "profile": profile_json,
                    "error": e,
                    "trace": format_exc()
                },
            )
            logger.error(e)
            raise IntegrationError(
                {
                    "code": "integration_exception",
                    "description": "{}".format(e)
                }, 500)
        return {
            "status":
            200,
            "message":
            "user profile deleted for user: {}".format(
                profile_json["user_id"]["value"]),
            "condition":
            self.condition,
        }
Ejemplo n.º 3
0
    def _store_in_vault(self, profiles):
        """
        Actually store profiles in the vault
        All profiles must have been merged and verified correctly before calling this method

        @profiles list of cis_profiles.User

        Returns dict {"creates": result_of_users_created, "updates": result_of_users_updates}
        """

        # Vault profiles (not cis_profile.User objects)
        vault_profiles = []

        try:
            self._connect()

            if self.config("dynamodb_transactions", namespace="cis") == "true":
                logger.debug("Attempting to put batch of profiles ({}) using transactions.".format(len(profiles)))
                vault = user.Profile(
                    self.identity_vault_client.get("table"), self.identity_vault_client.get("client"), transactions=True
                )
            else:
                logger.debug(
                    "Attempting to put batch of profiles ({}) without using transactions.".format(len(profiles))
                )
                vault = user.Profile(
                    self.identity_vault_client.get("table"),
                    self.identity_vault_client.get("client"),
                    transactions=False,
                )

            # transform cis_profiles.User profiles to vault profiles
            for user_profile in profiles:
                vault_profile = dict(
                    id=user_profile.user_id.value,
                    primary_email=user_profile.primary_email.value,
                    user_uuid=user_profile.uuid.value,
                    primary_username=user_profile.primary_username.value,
                    sequence_number=self.sequence_number,
                    profile=user_profile.as_json(),
                )
                vault_profiles.append(vault_profile)

            result = vault.find_or_create_batch(vault_profiles)
        except ClientError as e:
            logger.error(
                "An error occured writing these profiles to dynamodb",
                extra={"profiles": profiles, "error": e, "trace": format_exc()},
            )
            raise IntegrationError({"code": "integration_exception", "description": "{}".format(e)}, 500)
        # The result looks something like this:
        # result = {'creates': {'status': '200',
        # 'sequence_numbers': ['285229813155718975995433494324446866394']}, 'updates': None, 'status': 200}"}
        return {"creates": result[0], "updates": result[1], "status": 200}
Ejemplo n.º 4
0
    def put_profiles(self, profiles):
        """
        Merge profile data as necessary with existing profile data for a given user
        Verify profile data is correctly signed and published by allowed publishers
        Write back the result to the identity vault.
        @profiles list of str or cis_profile.User object

        Returns a dictionary containing vault results
        """

        # User profiles that have been verified, validated, merged, etc.
        profiles_to_store = []

        for user_profile in profiles:
            # Ensure we always have a cis_profile.User at this point (compat)
            if isinstance(user_profile, str):
                user_profile = cis_profile.User(
                    user_structure_json=user_profile)
            elif isinstance(user_profile, dict):
                user_profile = cis_profile.User(
                    user_structure_json=json.dumps(user_profile))

            # For single put_profile events the user_id is passed as argument
            if self.user_id:
                user_id = self.user_id
                # Verify that we're passing the same as the signed user_id for safety reasons
                if user_profile._attribute_value_set(
                        user_profile.user_id) and (user_id !=
                                                   user_profile.user_id.value):
                    raise IntegrationError(
                        {
                            "code":
                            "integration_exception",
                            "description":
                            "user_id query parameter does not match profile, that looks wrong",
                        },
                        400,
                    )

            else:
                user_id = user_profile.user_id.value
            logger.info(
                "Attempting integration of profile data into the vault",
                extra={"user_id": user_id})

            # Ensure we merge user_profile data when we have an existing user in the vault
            # This also does publisher verification
            current_user = self._search_and_merge(user_id, user_profile)
            # No difference found, no merging occured, skip!
            if current_user is None:
                logger.info(
                    "User {} already exists and proposed update has no difference, skipping"
                    .format(user_id),
                    extra={"user_id": user_id},
                )
                continue

            # Check profile signatures
            if self.config("verify_signatures", namespace="cis") == "true":
                try:
                    current_user.verify_all_signatures()
                except Exception as e:
                    logger.error(
                        "The profile failed to pass signature verification for user_id: {}"
                        .format(user_id),
                        extra={
                            "user_id": user_id,
                            "profile": current_user.as_dict(),
                            "reason": e,
                            "trace": format_exc(),
                        },
                    )
                    raise VerificationError(
                        {
                            "code": "invalid_signature",
                            "description": "{}".format(e)
                        }, 403)
            else:
                logger.warning(
                    "Bypassing profile signature verification (`verify_signatures` setting is false)"
                )

            # Update any CIS-owned attributes
            current_user = self._update_attr_owned_by_cis(
                user_id, current_user)

            profiles_to_store.append(current_user)

        if len(profiles_to_store) == 0:
            logger.info("No profiles to store in vault")
            return {"creates": None, "updates": None, "status": 202}

        # Store resulting user in the vault
        logger.info("Will store {} verified profiles".format(
            len(profiles_to_store)))
        return self._store_in_vault(profiles_to_store)