Exemple #1
0
    def test_known_users(self, mock_request_get, mock_authzero, mock_secrets):
        mock_secrets.return_value = "hi"
        mock_authzero.return_value = "hi"

        class FakeResponse:
            def __init__(self, fake={}):
                self.fake = fake
                self.text = str(fake)

            def json(self):
                return self.fake

            def ok(self):
                return True

        mu = [{
            "user_id": "auser",
            "uuid": "093249324",
            "primary_email": "*****@*****.**"
        }]
        mock_request_get.return_value = FakeResponse(fake=mu)

        profiles = [cis_profile.User()]
        publisher = cis_publisher.Publish(profiles,
                                          login_method="ad",
                                          publisher_name="ldap")
        u = publisher.get_known_cis_users()
        assert u == mu
Exemple #2
0
    def test_create_bad(self, mock_authzero, mock_secrets, mock_request_get, mock_request_post):
        """
        test that we'll correctly fixup profiles on creation
        """
        mock_authzero.return_value = "dinopark"
        mock_secrets.return_value = "is_pretty_cool"

        class FakeResponse:
            def __init__(self, fake={}):
                self.fake = fake
                self.text = str(fake)

            def json(self):
                return self.fake

            def ok(self):
                return True

        mock_request_post.return_value = FakeResponse()
        mock_request_get.return_value = FakeResponse(fake=self.mu)

        u = cis_profile.User()
        # Bad bad
        u.user_id.value = "anotherauser"
        u.fun_title.metadata.display = "private"
        u.fun_title.value = None
        profiles = [u]
        publisher = cis_publisher.Publish(profiles, login_method="ad", publisher_name="ldap")
        publisher.filter_known_cis_users(profiles)
        assert publisher.profiles[0].fun_title.metadata.display != "private"
Exemple #3
0
 def side_effect(*args, **kwargs):
     fake_user = cis_profile.User(user_id=args[0])
     if args[0] == "ad|Mozilla-LDAP|community":
         fake_user.staff_information.staff.value = False
     else:
         fake_user.staff_information.staff.value = True
     return fake_user
Exemple #4
0
    def test_post_specific_user(self, mock_validate, mock_authzero,
                                mock_secrets, mock_request_get,
                                mock_request_post):
        mock_authzero.return_value = "dinopark"
        mock_secrets.return_value = "is_pretty_cool"
        mock_validate.return_value = True

        class FakeResponse:
            def __init__(self, fake={}):
                self.fake = fake
                self.text = str(fake)

            def json(self):
                return self.fake

            def ok(self):
                return True

        mock_request_post.return_value = FakeResponse()
        mock_request_get.return_value = FakeResponse()
        profiles = [cis_profile.User(user_id="test")]
        publisher = cis_publisher.Publish(profiles,
                                          login_method="ad",
                                          publisher_name="ldap")
        publisher.post_all(user_ids=["test"])
        assert publisher.profiles[0].user_id.value == "test"
Exemple #5
0
    def test_user_deactivate(self, mock_cis_user):
        hris_data = {}
        with open("tests/fixture/workday.json") as fd:
            hris_data = json.load(fd)

        def side_effect(*args, **kwargs):
            fake_user = cis_profile.User(user_id=args[0])
            if args[0] == "ad|Mozilla-LDAP|community":
                fake_user.staff_information.staff.value = False
            else:
                fake_user.staff_information.staff.value = True
            return fake_user

        mock_cis_user.side_effect = side_effect

        hris = cis_publisher.hris.HRISPublisher()
        publisher = cis_publisher.Publish([],
                                          login_method="ad",
                                          publisher_name="hris")
        # we pass 2 fake users, ndonna is in the fixture, nolongerexist is not in the fixture but "CIS" "has it"
        publisher.known_cis_users_by_user_id = {
            "ad|Mozilla-LDAP|NDonna": "*****@*****.**",
            "ad|Mozilla-LDAP|notexist": "*****@*****.**",
            "ad|Mozilla-LDAP|community": "*****@*****.**",
        }
        publisher.known_cis_users_by_email = {
            "*****@*****.**": "ad|Mozilla-LDAP|NDonna",
            "*****@*****.**": "ad|Mozilla-LDAP|notexist",
            "*****@*****.**": "ad|Mozilla-LDAP|community",
        }
        for uid in publisher.known_cis_users_by_user_id:
            p = cis_profile.User(
                user_id=uid,
                primary_email=publisher.known_cis_users_by_user_id[uid])
            if uid == "ad|Mozilla-LDAP|community":
                p.staff_information.staff.value = False
            else:
                p.staff_information.staff.value = True
            publisher.all_known_profiles[uid] = p

        profiles = hris.convert_hris_to_cis_profiles(
            hris_data,
            publisher.known_cis_users_by_user_id,
            publisher.known_cis_users_by_email,
            user_ids=[
                "ad|Mozilla-LDAP|NDonna", "ad|Mozilla-LDAP|notexist",
                "ad|Mozilla-LDAP|community"
            ],
        )
        profiles = hris.deactivate_users(publisher, profiles, hris_data)
        # nolongerexist is returned by fake cis reply, but is not in hris workday fixture, so it should be active.value
        # = false
        # Community doesnt exist in HRIS but should not be touched so we should have 2 profiles back (ie community is
        # excluded)
        assert len(profiles) == 2
        assert profiles[1].active.value is False
        assert profiles[1].primary_email.value == "*****@*****.**"
        assert profiles[0].active.value is True
        assert profiles[0].primary_email.value == "*****@*****.**"
Exemple #6
0
    def test_post(self, mock_authzero, mock_secrets, mock_request_post):
        mock_authzero.return_value = "dinopark"
        mock_secrets.return_value = "is_pretty_cool"

        class FakeResponse:
            def ok(self):
                return True

        mock_request_post.return_value = FakeResponse()
        profiles = [cis_profile.User()]
        cis_publisher.Publish(profiles, login_method="ad", publisher_name="ldap")
Exemple #7
0
    def test_filter_cis_users(self, mock_authzero, mock_secrets,
                              mock_request_get, mock_request_post):
        mock_authzero.return_value = "dinopark"
        mock_secrets.return_value = "is_pretty_cool"

        class FakeResponse:
            def __init__(self, fake={}):
                self.fake = fake
                self.text = str(fake)

            def json(self):
                return self.fake

            def ok(self):
                return True

        mock_request_post.return_value = FakeResponse()
        mu = [{
            "user_id": "auser",
            "uuid": "0932493241",
            "primary_email": "*****@*****.**"
        }]
        mock_request_get.return_value = FakeResponse(fake=mu)

        profiles = [cis_profile.User()]
        profiles[0].user_id.value = "auser"

        profiles[0].first_name.value = "firstname"
        profiles[0].first_name.signature.publisher.name = "wrong"

        profiles[0].access_information.hris.values = {"test": "test"}
        profiles[0].access_information.hris.signature.publisher.name = "wrong"

        profiles[0].access_information.ldap.values = {"test": "test"}
        profiles[0].access_information.ldap.signature.publisher.name = "ldap"

        publisher = cis_publisher.Publish(profiles,
                                          login_method="ad",
                                          publisher_name="ldap")
        publisher.filter_known_cis_users()

        # Should be filtered out because publisher = "wrong"
        assert publisher.profiles[0].first_name.value != "firstname"
        # Should not because publisher = "ldap" and thats "us"
        assert publisher.profiles[0].as_dict(
        )["access_information"]["ldap"]["values"] == {
            "test": "test"
        }
        # Should be filtered out because publisher = "wrong"
        assert publisher.profiles[0].as_dict(
        )["access_information"]["hris"]["values"] is None
    def publish(self, user_ids=None):
        """
        Glue to create or fetch cis_profile.User profiles for this publisher
        Then pass everything over to the Publisher class
        None, ALL profiles are sent.
        @user_ids: list of str - user ids to publish. If None, all users are published.
        """
        logger.info("Starting LDAP Publisher")
        profiles_xz = self.fetch_from_s3()
        # If there are memory issues here, use lzma.LZMADecompressor() instead
        raw = lzma.decompress(profiles_xz)
        profiles_json = json.loads(raw)
        # Free some memory
        del profiles_xz
        del raw

        profiles = []
        logger.info("Processing {} profiles".format(len(profiles_json)))
        for p in profiles_json:
            str_p = json.dumps(profiles_json[p])
            if (user_ids is None) or (profiles_json[p]["user_id"]["value"]
                                      in user_ids):
                profiles.append(cis_profile.User(user_structure_json=str_p))

        logger.info("Will publish {} profiles".format(len(profiles)))
        publisher = cis_publisher.Publish(profiles,
                                          publisher_name="ldap",
                                          login_method="ad")
        failures = []
        try:
            publisher.filter_known_cis_users()
            failures = publisher.post_all(user_ids=user_ids)
        except Exception as e:
            logger.error(
                "Failed to post_all() LDAP profiles. Trace: {}".format(
                    format_exc()))
            raise e

        if len(failures) > 0:
            logger.error("Failed to post {} profiles: {}".format(
                len(failures), failures))
Exemple #9
0
    def test_known_users(self, mock_request_get, mock_authzero, mock_secrets):
        mock_secrets.return_value = "hi"
        mock_authzero.return_value = "hi"

        class FakeResponse:
            def __init__(self, fake={}):
                self.fake = fake
                self.text = str(fake)

            def json(self):
                return self.fake

            def ok(self):
                return True

        mock_request_get.return_value = FakeResponse(fake=self.mu)

        profiles = [cis_profile.User()]
        publisher = cis_publisher.Publish(profiles, login_method="ad", publisher_name="ldap")
        u = publisher.get_known_cis_users()
        assert u == self.mu["users"]
Exemple #10
0
    def dedup_changes(self, publisher, profiles):
        """
        Remove profiles that have no HRIS changes so that we don't send them for no reason
        XXX this function is not currently used (check!)

        @publisher cis_publisher.Publisher object
        @profiles list of cis_profile.User
        """
        dedup = []
        for p in profiles:
            user_id = publisher.known_cis_users_by_email[p.primary_email.value]
            if user_id is None:
                dedup.append(p)
                continue
            cis_p = cis_profile.User(publisher.all_known_profiles[user_id])
            if p.timezone.value is not None:
                if ((p.staff_information == cis_p.staff_information)
                        and (p.access_information.hris
                             == cis_p.access_information.hris)
                        and (p.active == cis_p.active)):
                    continue
            dedup.append(p)
        return dedup
Exemple #11
0
    def test_known_users_by_attribute(self, mock_request_get, mock_authzero, mock_secrets):
        mock_secrets.return_value = "hi"
        mock_authzero.return_value = "hi"

        class FakeResponse:
            def __init__(self, fake={}):
                self.fake = fake
                self.text = str(fake)

            def json(self):
                return self.fake

            def ok(self):
                return True

        mock_request_get.return_value = FakeResponse(fake=self.mu2)

        profiles = [cis_profile.User()]
        publisher = cis_publisher.Publish(profiles, login_method="ad", publisher_name="ldap")
        attributes = {"staff_information.staff": True, "active": True}
        u = publisher.get_known_cis_user_by_attribute_paginated(attributes)
        print(u)
        assert u["auser"] == self.mu2["users"][0]
Exemple #12
0
    def test_wrong_publisher(self, fake_jwks):
        """
        This verifies a wrong-publisher can't update
        it creates a valid user, then wrongly modify an attribute its not allowed to
        """
        os.environ["CIS_CONFIG_INI"] = "tests/mozilla-cis-verify.ini"
        os.environ["AWS_XRAY_SDK_ENABLED"] = "false"
        os.environ["CIS_ENVIRONMENT"] = "local"
        os.environ["CIS_DYNALITE_PORT"] = self.dynalite_port
        os.environ["CIS_REGION_NAME"] = "us-east-1"
        os.environ["AWS_ACCESS_KEY_ID"] = "foo"
        os.environ["AWS_SECRET_ACCESS_KEY"] = "bar"
        os.environ["DEFAULT_AWS_REGION"] = "us-east-1"
        os.environ["CIS_VERIFY_SIGNATURES"] = "true"
        os.environ["CIS_VERIFY_PUBLISHERS"] = "true"
        from cis_change_service import api

        fake_new_user = FakeUser(
            config=FakeProfileConfig().minimal().no_display())
        # Create a brand new user
        patched_user_profile = ensure_appropriate_publishers_and_sign(
            fake_new_user.as_dict(), self.publisher_rules, "create")
        # Ensure a first_name is set as we'll use that for testing
        patched_user_profile.first_name.value = "test"
        patched_user_profile.first_name.signature.publisher.name = "ldap"
        patched_user_profile.first_name.metadata.display = "public"
        patched_user_profile.sign_attribute("first_name", "ldap")

        f = FakeBearer()
        fake_jwks.return_value = json_form_of_pk
        token = f.generate_bearer_without_scope()
        api.app.testing = True
        self.app = api.app.test_client()
        result = self.app.post(
            "/v2/user",
            headers={"Authorization": "Bearer " + token},
            data=json.dumps(patched_user_profile.as_json()),
            content_type="application/json",
            follow_redirects=True,
        )

        response = json.loads(result.get_data())
        assert result.status_code == 200
        assert response["condition"] == "create"

        # sign first_name again but with wrong publisher (but same value as before)
        new_user = cis_profile.User(user_id=patched_user_profile.user_id.value)
        new_user.first_name = patched_user_profile.first_name
        new_user.sign_attribute("first_name", "access_provider")
        result = self.app.post(
            "/v2/user",
            headers={"Authorization": "Bearer " + token},
            data=json.dumps(new_user.as_json()),
            content_type="application/json",
            follow_redirects=True,
        )
        response = json.loads(result.get_data())
        assert response["status_code"] == 202

        # sign first_name again but with wrong publisher and different display (but same value as before)
        new_user = cis_profile.User(user_id=patched_user_profile.user_id.value)
        new_user.first_name = patched_user_profile.first_name
        new_user.first_name.metadata.display = "staff"
        new_user.sign_attribute("first_name", "access_provider")
        result = self.app.post(
            "/v2/user",
            headers={"Authorization": "Bearer " + token},
            data=json.dumps(new_user.as_json()),
            content_type="application/json",
            follow_redirects=True,
        )
        response = json.loads(result.get_data())
        assert response["code"] == "invalid_publisher"

        # sign first_name again but with wrong publisher and wrong value (it should fail)
        new_user.first_name.value = "new-test"
        new_user.sign_attribute("first_name", "access_provider")
        result = self.app.post(
            "/v2/user",
            headers={"Authorization": "Bearer " + token},
            data=json.dumps(new_user.as_json()),
            content_type="application/json",
            follow_redirects=True,
        )
        response = json.loads(result.get_data())
        assert result.status_code != 200
Exemple #13
0
    def convert_hris_to_cis_profiles(self, hris_data):
        """
        @hris_data list dict of HRIS data

        returns: cis_profile.Profile
        """
        def tz_convert(hris_tz):
            tzmap = {
                "GMT United Kingdom Time (London)": "UTC+0000 Europe/London",
                "GMT Western European Time (Casablanca)":
                "UTC+0100 Africa/Casablanca",
                "GMT+01:00 Central European Time (Amsterdam)":
                "UTC+0200 Europe/Amsterdam",
                "GMT+01:00 Central European Time (Berlin)":
                "UTC+01:00 Europe/Berlin",
                "GMT+01:00 Central European Time (Oslo)":
                "UTC+01:00 Europe/Oslo",
                "GMT+01:00 Central European Time (Paris)":
                "UTC+01:00 Europe/Paris",
                "GMT+01:00 Central European Time (Prague)":
                "UTC+01:00 Europe/Prague",
                "GMT+01:00 Central European Time (Stockholm)":
                "UTC+01:00 Europe/Stockholm",
                "GMT+02:00 Eastern European Time (Athens)":
                "UTC+02:00 Europe/Athens",
                "GMT+02:00 Eastern European Time (Bucharest)":
                "UTC+02:00 Europe/Bucharest",
                "GMT+02:00 Eastern European Time (Helsinki)":
                "UTC+02:00 Europe/Helsinki",
                "GMT+02:00 South Africa Standard Time (Johannesburg)":
                "UTC+02:00 Africa/Johannesburg",
                "GMT+03:00 East Africa Time (Nairobi)":
                "UTC+03:00 Africa/Nairobi",
                "GMT+03:00 Moscow Standard Time (Moscow)":
                "UTC+03:00 Europe/Moscow",
                "GMT+05:00 Pakistan Standard Time (Karachi)":
                "UTC+05:00 Pakistan/Karachi",
                "GMT+05:30 India Standard Time (Kolkata)":
                "UTC+05:30 Asia/Kolkata",
                "GMT+07:00 Western Indonesia Time (Jakarta)":
                "UTC+07:00 Asia/Jakarta",
                "GMT+08:00 Australian Western Standard Time (Perth)":
                "UTC+08:00 Australia/Perth",
                "GMT+08:00 China Standard Time (Shanghai)":
                "UTC+08:00 Asia/Shanghai",
                "GMT+08:00 Taipei Standard Time (Taipei)":
                "UTC+08:00 Asia/Taipei",
                "GMT+09:00 Japan Standard Time (Tokyo)":
                "UTC+09:00 Asia/Tokyo",
                "GMT+10:00 Australian Eastern Standard Time (Brisbane)":
                "UTC+10:00 Australia/Brisbane",
                "GMT+12:00 New Zealand Time (Auckland)":
                "UTC+12:00 Pacific/Auckland",
                "GMT-03:00 Argentina Standard Time (Buenos Aires)":
                "UTC-0300 America/Buenos_Aires",
                "GMT-03:00 Brasilia Standard Time (Recife)":
                "UTC-0300 America/Recife",
                "GMT-04:00 Atlantic Time (Halifax)":
                "UTC-0400 America/Halifax",
                "GMT-05:00 Eastern Time": "UTC-0500 US/Eastern",
                "GMT-06:00 Central Standard Time (Regina)":
                "UTC-0600 America/Regina",
                "GMT-06:00 Central Time (Chicago)": "UTC-0600 America/Chicago",
                "GMT-06:00 Central Time": "UTC-0600 US/Central",
                "GMT-07:00 Mountain Time": "UTC-0700 US/Mountain",
                "GMT-08:00 Pacific Time (Los Angeles)":
                "UTC-0800 America/Los_Angeles",
                "GMT-08:00 Pacific Time (Tijuana)": "UTC-0800 America/Tijuana",
                "GMT-08:00 Pacific Time": "UTC-0800 US/Pacific",
            }
            try:
                tzmap[hris_tz]
            except KeyError:
                self.logger.warning(
                    "Unknown timezone in workday, defaulting to UTC. Timezone from HRIS was"
                    " {}.".format(hris_tz))
                return "UTC+0000 Europe/London"
            return tzmap[hris_tz]

        def cost_center_convert(cc):
            """
            Cost centers can have decimal points
            So it's a float
            """
            return str(float(cc.split(" ")[0]))

        def strbool_convert(v):
            return v.lower() in ("yes", "true", "t", "1")

        user_array = []
        for hruser in hris_data.get("Report_Entry"):
            p = cis_profile.User()
            # Note: Never use non-preferred names here
            p.primary_email.value = hruser.get("PrimaryWorkEmail")
            p.last_name.value = hruser.get("Preferred_Name_-_Last_Name")
            p.first_name.value = hruser.get("PreferredFirstName")
            p.timezone.value = tz_convert(hruser.get("Time_Zone"))
            p.staff_information.manager.value = strbool_convert(
                hruser.get("IsManager"))
            p.staff_information.director.value = strbool_convert(
                hruser.get("isDirectorOrAbove"))
            if len(hruser.get("EmployeeID")) > 0:
                p.staff_information.staff.value = True
            else:
                p.staff_information.staff.value = False
            p.staff_information.title.value = hruser.get("businessTitle")
            p.staff_information.team.value = hruser.get("Team")
            p.staff_information.cost_center.value = cost_center_convert(
                hruser.get("Cost_Center"))
            p.staff_information.worker_type.value = hruser.get("WorkerType")
            p.staff_information.wpr_desk_number.value = hruser.get(
                "WPRDeskNumber")
            p.staff_information.office_location.value = hruser.get(
                "LocationDescription")

            p.access_information.hris["values"]["employee_id"] = hruser.get(
                "EmployeeID")
            p.access_information.hris["values"]["worker_type"] = hruser.get(
                "WorkerType")
            p.access_information.hris["values"][
                "manager_employee_id"] = hruser.get(
                    "WorkersManagersEmployeeID")
            p.access_information.hris["values"][
                "egencia_pos_country"] = hruser.get("EgenciaPOSCountry")

            # Typical required user values
            p.active.value = True
            p.initialize_timestamps()
            try:
                p.sign_all(publisher_name="hris")
            except Exception as e:
                self.logger.critical(
                    "Profile data signing failed for user {} - skipped signing, verification "
                    "WILL FAIL ({})".format(p.primary_email.value, e))
            try:
                p.validate()
            except Exception as e:
                self.logger.critical(
                    "Profile schema validation failed for user {} - skipped validation, verification "
                    "WILL FAIL({})".format(p.primary_email.value, e))

            try:
                p.verify_all_publishers(cis_profile.User())
            except Exception as e:
                self.logger.critical(
                    "Profile signing failed for user {} - skipped signing, verification "
                    "WILL FAIL ({})".format(p.primary_email.value, e))
            user_array.append(p)

        return user_array
Exemple #14
0
 def test_profile_validate(self):
     profiles = [cis_profile.User()]
     publisher = cis_publisher.Publish(profiles,
                                       login_method="ad",
                                       publisher_name="ldap")
     publisher.validate()
Exemple #15
0
 def test_obj(self):
     profiles = [cis_profile.User()]
     publisher = cis_publisher.Publish(profiles,
                                       login_method="ad",
                                       publisher_name="ldap")
     assert isinstance(publisher, object)
Exemple #16
0
    def deactivate_users(self, publisher, profiles, report):
        """
        Deactivate users present in CIS but not in HRIS
        @publisher cis_publisher.Publisher object
        @profiles list of cis_profile.User that were converted
        @report HRIS report
        """
        user_ids_in_hris = []
        for hruser in report.get("Report_Entry"):
            hruser_work_email = hruser.get("PrimaryWorkEmail").lower()
            try:
                current_user_id = publisher.known_cis_users_by_email[
                    hruser_work_email]
            except KeyError:
                logger.critical(
                    "Repeated: There is no user_id in CIS Person API for HRIS User:{}"
                    .format(hruser_work_email))
                continue
            user_ids_in_hris.append(current_user_id)

        delta = set(publisher.known_cis_users_by_user_id.keys()) - set(
            user_ids_in_hris)

        # XXX this is a slow work-around to figure out who is staff
        # This data should eventually be directly queriable from Person API with a filter (this does not currently
        # exist)
        user_ids_to_deactivate = []
        for potential_user_id in delta:
            if potential_user_id in publisher.all_known_profiles:
                profile = publisher.all_known_profiles[potential_user_id]
                # Convert as needed to a dict
                try:
                    profile = profile.as_dict()
                except (TypeError, AttributeError):
                    pass
                # Check if it has the attributes
                try:
                    ldap_groups = profile["access_information"]["ldap"][
                        "values"]
                    is_staff = profile["staff_information"]["staff"]["value"]
                # Not a staff user f these fields don't exist
                except KeyError:
                    logger.debug(
                        "staff_information.staff or access_information.ldap fields are null,"
                        " won't deactivate {}".format(potential_user_id))
                    continue

                if (is_staff is
                        True) or (is_staff is False and
                                  (ldap_groups is not None
                                   and "admin_accounts" not in ldap_groups)):
                    user_ids_to_deactivate.append(potential_user_id)

        if len(user_ids_to_deactivate) > 0:
            logger.info(
                "Will deactivate {} users because they're in CIS but not in HRIS"
                .format(user_ids_to_deactivate))
            for user in user_ids_to_deactivate:
                logger.info("User selected for deactivation: {}".format(user))
                # user from cis
                try:
                    p = cis_profile.User(publisher.all_known_profiles[user])
                except TypeError:
                    p = publisher.all_known_profiles[user]

                # our partial update
                newp = cis_profile.User()
                newp.user_id = p.user_id
                newp.primary_email = p.primary_email
                newp.active.value = False
                newp.active.signature.publisher.name = "hris"
                try:
                    newp.sign_attribute("active", publisher_name="hris")
                except Exception as e:
                    logger.critical(
                        "Profile data signing failed for user {} - skipped signing, verification "
                        "WILL FAIL ({})".format(newp.primary_email.value, e))
                    logger.debug("Profile data {}".format(newp.as_dict()))

                profiles.append(newp)
        return profiles
    def convert_hris_to_cis_profiles(self, hris_data, user_ids=None):
        """
        @hris_data list dict of HRIS data
        @user_ids list of user ids to convert

        returns: cis_profile.Profile
        """

        def tz_convert(hris_tz):
            tzmap = {
                "GMT United Kingdom Time (London)": "UTC+0000 Europe/London",
                "GMT Western European Time (Casablanca)": "UTC+0100 Africa/Casablanca",
                "GMT+01:00 Central European Time (Amsterdam)": "UTC+0200 Europe/Amsterdam",
                "GMT+01:00 Central European Time (Berlin)": "UTC+01:00 Europe/Berlin",
                "GMT+01:00 Central European Time (Oslo)": "UTC+01:00 Europe/Oslo",
                "GMT+01:00 Central European Time (Paris)": "UTC+01:00 Europe/Paris",
                "GMT+01:00 Central European Time (Prague)": "UTC+01:00 Europe/Prague",
                "GMT+01:00 Central European Time (Stockholm)": "UTC+01:00 Europe/Stockholm",
                "GMT+02:00 Eastern European Time (Athens)": "UTC+02:00 Europe/Athens",
                "GMT+02:00 Eastern European Time (Bucharest)": "UTC+02:00 Europe/Bucharest",
                "GMT+02:00 Eastern European Time (Helsinki)": "UTC+02:00 Europe/Helsinki",
                "GMT+02:00 South Africa Standard Time (Johannesburg)": "UTC+02:00 Africa/Johannesburg",
                "GMT+03:00 East Africa Time (Nairobi)": "UTC+03:00 Africa/Nairobi",
                "GMT+03:00 Moscow Standard Time (Moscow)": "UTC+03:00 Europe/Moscow",
                "GMT+05:00 Pakistan Standard Time (Karachi)": "UTC+05:00 Pakistan/Karachi",
                "GMT+05:30 India Standard Time (Kolkata)": "UTC+05:30 Asia/Kolkata",
                "GMT+07:00 Western Indonesia Time (Jakarta)": "UTC+07:00 Asia/Jakarta",
                "GMT+08:00 Australian Western Standard Time (Perth)": "UTC+08:00 Australia/Perth",
                "GMT+08:00 China Standard Time (Shanghai)": "UTC+08:00 Asia/Shanghai",
                "GMT+08:00 Taipei Standard Time (Taipei)": "UTC+08:00 Asia/Taipei",
                "GMT+09:00 Japan Standard Time (Tokyo)": "UTC+09:00 Asia/Tokyo",
                "GMT+10:00 Australian Eastern Standard Time (Brisbane)": "UTC+10:00 Australia/Brisbane",
                "GMT+12:00 New Zealand Time (Auckland)": "UTC+12:00 Pacific/Auckland",
                "GMT-03:00 Argentina Standard Time (Buenos Aires)": "UTC-0300 America/Buenos_Aires",
                "GMT-03:00 Brasilia Standard Time (Recife)": "UTC-0300 America/Recife",
                "GMT-04:00 Atlantic Time (Halifax)": "UTC-0400 America/Halifax",
                "GMT-05:00 Eastern Time": "UTC-0500 US/Eastern",
                "GMT-06:00 Central Standard Time (Regina)": "UTC-0600 America/Regina",
                "GMT-06:00 Central Time (Chicago)": "UTC-0600 America/Chicago",
                "GMT-06:00 Central Time": "UTC-0600 US/Central",
                "GMT-07:00 Mountain Time": "UTC-0700 US/Mountain",
                "GMT-08:00 Pacific Time (Los Angeles)": "UTC-0800 America/Los_Angeles",
                "GMT-08:00 Pacific Time (Tijuana)": "UTC-0800 America/Tijuana",
                "GMT-08:00 Pacific Time": "UTC-0800 US/Pacific",
            }
            try:
                tzmap[hris_tz]
            except KeyError:
                logger.warning(
                    "Unknown timezone in workday, defaulting to UTC. Timezone from HRIS was" " {}.".format(hris_tz)
                )
                return "UTC+0000 Europe/London"
            return tzmap[hris_tz]

        def cost_center_convert(cc):
            """
            Cost centers can have decimal points
            So it's a float
            """
            return str(float(cc.split(" ")[0]))

        def strbool_convert(v):
            return v.lower() in ("yes", "true", "t", "1")

        user_array = []
        for hruser in hris_data.get("Report_Entry"):
            # Attempt a rough guess at the user-id. this may not match all user ids correctly
            # We assume this is ok as user_ids are only passed for testing purposes or fixing purposes
            if user_ids is not None:
                if "ad|Mozilla-LDAP|" + hruser.get("PrimaryWorkEmail").split("@")[0] not in user_ids:
                    if "ad|Mozilla-LDAP-Dev|" + hruser.get("PrimaryWorkEmail").split("@")[0] not in user_ids:
                        # Skip user
                        continue
            p = cis_profile.User()
            # Note: Never use non-preferred names here
            # Uncomment when LDAP is no longer the creator for these
            # When that happens, code to disable the user will also be necessary!
            # p.active.value = True
            #            p.last_name.value = hruser.get("Preferred_Name_-_Last_Name")
            #            p.last_name.signature.publisher.name = "hris"
            #            p.first_name.value = hruser.get("PreferredFirstName")
            #            p.first_name.signature.publisher.name = "hris"
            p.primary_email.value = hruser.get("PrimaryWorkEmail")
            p.primary_email.signature.publisher.name = "hris"

            p.timezone.value = tz_convert(hruser.get("Time_Zone"))
            p.timezone.signature.publisher.name = "hris"
            p.timezone.metadata.display = "staff"

            p.staff_information.manager.value = strbool_convert(hruser.get("IsManager"))
            p.staff_information.manager.signature.publisher.name = "hris"
            p.staff_information.manager.metadata.display = "staff"

            p.staff_information.director.value = strbool_convert(hruser.get("isDirectorOrAbove"))
            p.staff_information.director.signature.publisher.name = "hris"
            p.staff_information.director.metadata.display = "staff"
            if len(hruser.get("EmployeeID")) > 0:
                p.staff_information.staff.value = True
            else:
                p.staff_information.staff.value = False
            p.staff_information.staff.signature.publisher.name = "hris"
            p.staff_information.staff.metadata.display = "staff"

            p.staff_information.title.value = hruser.get("businessTitle")
            p.staff_information.title.signature.publisher.name = "hris"
            p.staff_information.title.metadata.display = "staff"

            p.staff_information.team.value = hruser.get("Team")
            p.staff_information.team.signature.publisher.name = "hris"
            p.staff_information.team.metadata.display = "staff"

            p.staff_information.cost_center.value = cost_center_convert(hruser.get("Cost_Center"))
            p.staff_information.cost_center.signature.publisher.name = "hris"
            p.staff_information.cost_center.metadata.display = "staff"

            p.staff_information.worker_type.value = hruser.get("WorkerType")
            p.staff_information.worker_type.signature.publisher.name = "hris"
            p.staff_information.worker_type.metadata.display = "staff"

            p.staff_information.wpr_desk_number.value = hruser.get("WPRDeskNumber")
            p.staff_information.wpr_desk_number.signature.publisher.name = "hris"
            p.staff_information.wpr_desk_number.metadata.display = "staff"

            p.staff_information.office_location.value = hruser.get("LocationDescription")
            p.staff_information.office_location.signature.publisher.name = "hris"
            p.staff_information.office_location.metadata.display = "staff"

            p.access_information.hris["values"] = {}
            p.access_information.hris.signature.publisher.name = "hris"
            p.access_information.hris["values"]["employee_id"] = hruser.get("EmployeeID")
            p.access_information.hris["values"]["worker_type"] = hruser.get("WorkerType")
            p.access_information.hris["values"]["primary_work_email"] = hruser.get("PrimaryWorkEmail")
            p.access_information.hris["values"]["managers_primary_work_email"] = hruser.get(
                "Worker_s_Manager_s_Email_Address"
            )
            p.access_information.hris["values"]["egencia_pos_country"] = hruser.get("EgenciaPOSCountry")

            try:
                p.sign_all(publisher_name="hris")
            except Exception as e:
                logger.critical(
                    "Profile data signing failed for user {} - skipped signing, verification "
                    "WILL FAIL ({})".format(p.primary_email.value, e)
                )
                logger.debug("Profile data {}".format(p.as_dict()))
            try:
                p.validate()
            except Exception as e:
                logger.critical(
                    "Profile schema validation failed for user {} - skipped validation, verification "
                    "WILL FAIL({})".format(p.primary_email.value, e)
                )
                logger.debug("Profile data {}".format(p.as_dict()))

            try:
                p.verify_all_publishers(cis_profile.User())
            except Exception as e:
                logger.critical(
                    "Profile publisher verification failed for user {} - skipped signing, verification "
                    "WILL FAIL ({})".format(p.primary_email.value, e)
                )
                logger.debug("Profile data {}".format(p.as_dict()))

            logger.info("Processed (signed and verified) HRIS report's user {}".format(p.primary_email.value))
            user_array.append(p)

        return user_array
Exemple #18
0
    def convert_hris_to_cis_profiles(self, hris_data, cis_users_by_user_id,
                                     cis_users_by_email, user_ids):
        """
        @hris_data list dict of HRIS data
        @cis_users_by_user_id dict of Person API known users by user_id=>email
        @cis_users_by_email dict of Person API known users by email=>user_id to convert to user_ids
        @user_ids list of user ids to convert

        returns: list of cis_profile.Profile
        """
        def tz_convert(hris_tz):
            tzmap = {
                "GMT United Kingdom Time (London)": "UTC+0000 Europe/London",
                "GMT Western European Time (Casablanca)":
                "UTC+0100 Africa/Casablanca",
                "GMT+01:00 Central European Time (Amsterdam)":
                "UTC+0200 Europe/Amsterdam",
                "GMT+01:00 Central European Time (Berlin)":
                "UTC+01:00 Europe/Berlin",
                "GMT+01:00 Central European Time (Oslo)":
                "UTC+01:00 Europe/Oslo",
                "GMT+01:00 Central European Time (Paris)":
                "UTC+01:00 Europe/Paris",
                "GMT+01:00 Central European Time (Prague)":
                "UTC+01:00 Europe/Prague",
                "GMT+01:00 Central European Time (Stockholm)":
                "UTC+01:00 Europe/Stockholm",
                "GMT+02:00 Eastern European Time (Athens)":
                "UTC+02:00 Europe/Athens",
                "GMT+02:00 Eastern European Time (Bucharest)":
                "UTC+02:00 Europe/Bucharest",
                "GMT+02:00 Eastern European Time (Helsinki)":
                "UTC+02:00 Europe/Helsinki",
                "GMT+02:00 South Africa Standard Time (Johannesburg)":
                "UTC+02:00 Africa/Johannesburg",
                "GMT+03:00 East Africa Time (Nairobi)":
                "UTC+03:00 Africa/Nairobi",
                "GMT+03:00 Moscow Standard Time (Moscow)":
                "UTC+03:00 Europe/Moscow",
                "GMT+05:00 Pakistan Standard Time (Karachi)":
                "UTC+05:00 Pakistan/Karachi",
                "GMT+05:30 India Standard Time (Kolkata)":
                "UTC+05:30 Asia/Kolkata",
                "GMT+07:00 Western Indonesia Time (Jakarta)":
                "UTC+07:00 Asia/Jakarta",
                "GMT+08:00 Australian Western Standard Time (Perth)":
                "UTC+08:00 Australia/Perth",
                "GMT+08:00 China Standard Time (Shanghai)":
                "UTC+08:00 Asia/Shanghai",
                "GMT+08:00 Taipei Standard Time (Taipei)":
                "UTC+08:00 Asia/Taipei",
                "GMT+09:00 Japan Standard Time (Tokyo)":
                "UTC+09:00 Asia/Tokyo",
                "GMT+10:00 Australian Eastern Standard Time (Brisbane)":
                "UTC+10:00 Australia/Brisbane",
                "GMT+12:00 New Zealand Time (Auckland)":
                "UTC+12:00 Pacific/Auckland",
                "GMT-03:00 Argentina Standard Time (Buenos Aires)":
                "UTC-0300 America/Buenos_Aires",
                "GMT-03:00 Brasilia Standard Time (Recife)":
                "UTC-0300 America/Recife",
                "GMT-04:00 Atlantic Time (Halifax)":
                "UTC-0400 America/Halifax",
                "GMT-05:00 Eastern Time": "UTC-0500 US/Eastern",
                "GMT-06:00 Central Standard Time (Regina)":
                "UTC-0600 America/Regina",
                "GMT-06:00 Central Time (Chicago)": "UTC-0600 America/Chicago",
                "GMT-06:00 Central Time": "UTC-0600 US/Central",
                "GMT-07:00 Mountain Time": "UTC-0700 US/Mountain",
                "GMT-08:00 Pacific Time (Los Angeles)":
                "UTC-0800 America/Los_Angeles",
                "GMT-08:00 Pacific Time (Tijuana)": "UTC-0800 America/Tijuana",
                "GMT-08:00 Pacific Time": "UTC-0800 US/Pacific",
            }
            try:
                tzmap[hris_tz]
            except KeyError:
                logger.warning(
                    "Unknown timezone in workday, defaulting to UTC. Timezone from HRIS was"
                    " {}.".format(hris_tz))
                return "UTC+0000 Europe/London"
            return tzmap[hris_tz]

        def cost_center_convert(cc):
            """
            Cost centers can have decimal points
            So it's a float
            """
            return str(float(cc.split(" ")[0]))

        def strbool_convert(v):
            return v.lower() in ("yes", "true", "t", "1")

        # Convert
        user_array = []
        for hruser in hris_data.get("Report_Entry"):
            hruser_work_email = hruser.get("PrimaryWorkEmail").lower()
            logger.debug(
                "filtering fields for user email {}".format(hruser_work_email))

            # NOTE:
            # The HRIS setup will DELETE users when they need to be deactivated and removed. It will set INACTIVE when
            # users may not yet be provisioned. This is important as if this changes the code here would have to also
            # change.
            hruser_active_state = int(hruser.get("CurrentlyActive"))
            if hruser_active_state == 0:
                logger.debug(
                    "User {} is currently set to inactive in HRIS Report, skipping integration"
                    .format(hruser_work_email))
                continue

            current_user_id = None
            try:
                current_user_id = cis_users_by_email[hruser_work_email]
            except KeyError:
                logger.critical(
                    "There is no user_id in CIS Person API for HRIS User {}."
                    " This user may not be created by HRIS yet?".format(
                        hruser_work_email))
                continue
            user_ids_lower_case = [x.lower() for x in user_ids]

            if current_user_id.lower() not in user_ids_lower_case:
                # Skip this user, it's not in the list requested to convert
                logger.debug(
                    "skipping user {}, not in requested conversion list".
                    format(current_user_id))
                continue

            p = cis_profile.User()
            # Note: Never use non-preferred names here
            #            p.last_name.value = hruser.get("Preferred_Name_-_Last_Name")
            #            p.last_name.signature.publisher.name = "hris"
            #            p.first_name.value = hruser.get("PreferredFirstName")
            #            p.first_name.signature.publisher.name = "hris"
            p.active.value = True
            p.active.signature.publisher.name = "hris"

            p.primary_email.value = hruser_work_email
            p.primary_email.signature.publisher.name = "hris"

            p.timezone.value = tz_convert(hruser.get("Time_Zone"))
            p.timezone.signature.publisher.name = "hris"
            p.timezone.metadata.display = "staff"

            p.staff_information.manager.value = strbool_convert(
                hruser.get("IsManager"))
            p.staff_information.manager.signature.publisher.name = "hris"
            p.staff_information.manager.metadata.display = "staff"

            p.staff_information.director.value = strbool_convert(
                hruser.get("isDirectorOrAbove"))
            p.staff_information.director.signature.publisher.name = "hris"
            p.staff_information.director.metadata.display = "staff"
            if len(hruser.get("EmployeeID")) > 0:
                p.staff_information.staff.value = True
            else:
                p.staff_information.staff.value = False
            p.staff_information.staff.signature.publisher.name = "hris"
            p.staff_information.staff.metadata.display = "staff"

            p.staff_information.title.value = hruser.get("businessTitle")
            p.staff_information.title.signature.publisher.name = "hris"
            p.staff_information.title.metadata.display = "staff"

            p.staff_information.team.value = hruser.get("Team")
            p.staff_information.team.signature.publisher.name = "hris"
            p.staff_information.team.metadata.display = "staff"

            p.staff_information.cost_center.value = cost_center_convert(
                hruser.get("Cost_Center"))
            p.staff_information.cost_center.signature.publisher.name = "hris"
            p.staff_information.cost_center.metadata.display = "staff"

            p.staff_information.worker_type.value = hruser.get("WorkerType")
            p.staff_information.worker_type.signature.publisher.name = "hris"
            p.staff_information.worker_type.metadata.display = "staff"

            p.staff_information.wpr_desk_number.value = hruser.get(
                "WPRDeskNumber")
            p.staff_information.wpr_desk_number.signature.publisher.name = "hris"
            p.staff_information.wpr_desk_number.metadata.display = "staff"

            p.staff_information.office_location.value = hruser.get(
                "LocationDescription")
            p.staff_information.office_location.signature.publisher.name = "hris"
            p.staff_information.office_location.metadata.display = "staff"

            p.access_information.hris["values"] = {}
            p.access_information.hris.signature.publisher.name = "hris"
            p.access_information.hris["values"]["employee_id"] = hruser.get(
                "EmployeeID")
            p.access_information.hris["values"]["worker_type"] = hruser.get(
                "WorkerType")
            p.access_information.hris["values"][
                "primary_work_email"] = hruser_work_email
            p.access_information.hris["values"][
                "managers_primary_work_email"] = hruser.get(
                    "Worker_s_Manager_s_Email_Address")
            p.access_information.hris["values"][
                "egencia_pos_country"] = hruser.get("EgenciaPOSCountry")
            try:
                p.sign_all(publisher_name="hris")
            except Exception as e:
                logger.critical(
                    "Profile data signing failed for user {} - skipped signing, verification "
                    "WILL FAIL ({})".format(p.primary_email.value, e))
                logger.debug("Profile data {}".format(p.as_dict()))
            try:
                p.validate()
            except Exception as e:
                logger.critical(
                    "Profile schema validation failed for user {} - skipped validation, verification "
                    "WILL FAIL({})".format(p.primary_email.value, e))
                logger.debug("Profile data {}".format(p.as_dict()))

            try:
                p.verify_all_publishers(cis_profile.User())
            except Exception as e:
                logger.critical(
                    "Profile publisher verification failed for user {} - skipped signing, verification "
                    "WILL FAIL ({})".format(p.primary_email.value, e))
                logger.debug("Profile data {}".format(p.as_dict()))

            logger.info(
                "Processed (signed and verified) HRIS report's user {}".format(
                    p.primary_email.value))
            user_array.append(p)
        return user_array
Exemple #19
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)
Exemple #20
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
Exemple #21
0
    def convert_az_users(self, az_users):
        """
        Convert a list of auth0 user fields to cis_profile Users
        @az_users list of dicts with user attributes
        Returns [cis_profile.Users]
        """
        profiles = []
        logger.info(
            "Converting auth0 users into CIS Profiles ({} user(s))".format(
                len(az_users)))

        for u in az_users:
            p = cis_profile.User()
            # Must have fields
            p.user_id.value = u["user_id"]
            p.user_id.signature.publisher.name = "access_provider"
            p.update_timestamp("user_id")
            if "blocked" in u.keys():
                if u["blocked"]:
                    p.active.value = False
            else:
                p.active.value = True
            p.active.signature.publisher.name = "access_provider"
            p.update_timestamp("active")

            p.primary_email.value = u["email"]
            p.primary_email.metadata.display = "private"
            p.primary_email.signature.publisher.name = "access_provider"
            p.update_timestamp("primary_email")
            try:
                p.login_method.value = u["identities"][0]["connection"]
                p.update_timestamp("login_method")
            except IndexError:
                logger.critical(
                    "Could not find login method for user {}, skipping integration"
                    .format(p.user_id.value))
                continue

            # Should have fields (cannot be "None" or "" but can be " ")
            tmp = u.get(
                "given_name",
                u.get("name", u.get("family_name", u.get("nickname", " "))))
            p.first_name.value = tmp
            p.first_name.metadata.display = "private"
            p.first_name.signature.publisher.name = "access_provider"
            p.update_timestamp("first_name")

            tmp = u.get("family_name", " ")
            p.last_name.value = tmp
            p.last_name.metadata.display = "private"
            p.last_name.signature.publisher.name = "access_provider"
            p.update_timestamp("last_name")

            # May have fields (its ok if these are not set)
            tmp = u.get("node_id", None)
            if tmp is not None:
                p.identities.github_id_v4.value = tmp
                p.identities.github_id_v4.display = "private"
                p.identities.github_id_v4.signature.publisher.name = "access_provider"
                p.update_timestamp("identities.github_id_v4")
            if "identities" in u.keys():
                for ident in u["identities"]:
                    if ident.get(
                            "provider") in self.az_blacklisted_connections:
                        logger.warning(
                            "ad/LDAP account returned from search - this should not happen. User will be skipped."
                            " User_id: {}".format(p.user_id.value))
                        continue
                    elif ident.get("provider") == "google-oauth2":
                        p.identities.google_oauth2_id.value = ident.get(
                            "user_id")
                        p.identities.google_oauth2_id.metadata.display = "private"
                        p.identities.google_oauth2_id.signature.publisher.name = "access_provider"
                        p.update_timestamp("identities.google_oauth2_id")
                        p.identities.google_primary_email.value = p.primary_email.value
                        p.identities.google_primary_email.metadata.display = "private"
                        p.identities.google_primary_email.signature.publisher.name = "access_provider"
                        p.update_timestamp("identities.google_primary_email")
                    elif ident.get("provider") == "oauth2" and ident.get(
                            "connection") == "firefoxaccounts":
                        p.identities.firefox_accounts_id.value = ident.get(
                            "user_id")
                        p.identities.firefox_accounts_id.metadata.display = "private"
                        p.identities.firefox_accounts_id.signature.publisher.name = "access_provider"
                        p.update_timestamp("identities.firefox_accounts_id")
                        p.identities.firefox_accounts_primary_email.value = p.primary_email.value
                        p.identities.firefox_accounts_primary_email.metadata.display = "private"
                        p.identities.firefox_accounts_primary_email.signature.publisher.name = "access_provider"
                        p.update_timestamp(
                            "identities.firefox_accounts_primary_email")
                    elif ident.get("provider") == "github":
                        p.identities.github_id_v3.value = ident.get("user_id")
                        p.identities.github_id_v3.metadata.display = "private"
                        p.identities.github_id_v3.signature.publisher.name = "access_provider"
                        p.update_timestamp("identities.github_id_v3")
                        if "profileData" in ident.keys():
                            p.identities.github_primary_email.value = ident[
                                "profileData"].get("email")
                            p.identities.github_primary_email.metadata.verified = ident[
                                "profileData"].get("email_verified", False)
                            p.identities.github_primary_email.metadata.display = "private"
                            p.identities.github_primary_email.signature.publisher.name = "access_provider"
                            p.update_timestamp(
                                "identities.github_primary_email")
                            p.identities.github_id_v4.value = ident[
                                "profileData"].get("node_id")
                            p.identities.github_id_v4.metadata.display = "private"
                            p.identities.github_id_v4.signature.publisher.name = "access_provider"
                            p.update_timestamp("identities.github_id_v4")

            # Sign and verify everything
            try:
                p.sign_all(publisher_name="access_provider")
            except Exception as e:
                logger.critical(
                    "Profile data signing failed for user {} - skipped signing, verification "
                    "WILL FAIL ({})".format(p.primary_email.value, e))
                logger.debug("Profile data {}".format(p.as_dict()))

            try:
                p.validate()
            except Exception as e:
                logger.critical(
                    "Profile schema validation failed for user {} - skipped validation, verification "
                    "WILL FAIL({})".format(p.primary_email.value, e))
                logger.debug("Profile data {}".format(p.as_dict()))

            try:
                p.verify_all_publishers(cis_profile.User())
            except Exception as e:
                logger.critical(
                    "Profile publisher verification failed for user {} - skipped signing, verification "
                    "WILL FAIL ({})".format(p.primary_email.value, e))
                logger.debug("Profile data {}".format(p.as_dict()))

            logger.debug(
                "Profile signed and ready to publish for user_id {}".format(
                    p.user_id.value))
            profiles.append(p)
        logger.info(
            "All profiles in this request were converted to CIS Profiles")
        return profiles