class LdapPersistence(BasePersistence):
    def __init__(self, manager):
        self.client = LdapClient(manager)

    def get_auth_config(self):
        entry = self.client.get(
            "ou=jans-auth,ou=configuration,o=jans",
            attributes=["jansRevision", "jansConfWebKeys", "jansConfDyn"],
        )

        if not entry:
            return {}

        config = {
            "id": entry.entry_dn,
            "jansRevision": entry["jansRevision"][0],
            "jansConfWebKeys": entry["jansConfWebKeys"][0],
            "jansConfDyn": entry["jansConfDyn"][0],
        }
        return config

    def modify_auth_config(self, id_, rev, conf_dynamic, conf_webkeys):
        modified, _ = self.client.modify(
            id_, {
                'jansRevision': [(self.client.MODIFY_REPLACE, [str(rev)])],
                'jansConfWebKeys':
                [(self.client.MODIFY_REPLACE, [json.dumps(conf_webkeys)])],
                'jansConfDyn': [
                    (self.client.MODIFY_REPLACE, [json.dumps(conf_dynamic)])
                ],
            })
        return modified
예제 #2
0
def wait_for_ldap_conn(manager, **kwargs):
    """Wait for readiness/availability of LDAP server based on connection status.

    :param manager: An instance of :class:`~jans.pycloudlib.manager._Manager`.
    """

    connected = LdapClient(manager).is_connected()
    if not connected:
        raise WaitError("LDAP is unreachable")
예제 #3
0
class LdapPersistence:
    def __init__(self, manager):
        self.client = LdapClient(manager)

    def get_auth_config(self):
        # base DN for auth config
        dn = "ou=jans-auth,ou=configuration,o=jans"
        entry = self.client.get(dn)

        if not entry:
            return {}
        return entry["jansConfDyn"][0]
예제 #4
0
def wait_for_ldap(manager, **kwargs):
    """Wait for readiness/availability of LDAP server based on existing entry.

    :param manager: An instance of :class:`~jans.pycloudlib.manager._Manager`.
    """

    persistence_type = os.environ.get("CN_PERSISTENCE_TYPE", "ldap")
    ldap_mapping = os.environ.get("CN_PERSISTENCE_LDAP_MAPPING", "default")

    # a minimum service stack is having config-api client
    jca_client_id = manager.config.get("jca_client_id")
    default_search = (
        f"inum={jca_client_id},ou=clients,o=jans",
        "(objectClass=jansClnt)",
    )

    if persistence_type == "hybrid":
        # `cache` and `token` mapping only have base entries
        search_mapping = {
            "default": default_search,
            "user": ("inum=60B7,ou=groups,o=jans", "(objectClass=jansGrp)"),
            "site": ("ou=cache-refresh,o=site", "(ou=cache-refresh)"),
            "cache": ("ou=cache,o=jans", "(ou=cache)"),
            "token": ("ou=tokens,o=jans", "(ou=tokens)"),
            "session": ("ou=sessions,o=jans", "(ou=sessions)"),
        }
        search = search_mapping[ldap_mapping]
    else:
        search = default_search

    client = LdapClient(manager)
    entries = client.search(search[0],
                            search[1],
                            attributes=["objectClass"],
                            limit=1)
    if not entries:
        raise WaitError("LDAP is not fully initialized")
예제 #5
0
 def __init__(self, manager):
     self.client = LdapClient(manager)
     self.manager = manager
예제 #6
0
class LDAPBackend:
    def __init__(self, manager):
        self.client = LdapClient(manager)
        self.manager = manager

    def check_indexes(self, mapping):
        if mapping == "site":
            index_name = "jansScrTyp"
            backend = "site"
        # elif mapping == "statistic":
        #     index_name = "jansMetricTyp"
        #     backend = "metric"
        else:
            index_name = "del"
            backend = "userRoot"

        dn = "ds-cfg-attribute={},cn=Index,ds-cfg-backend-id={}," \
             "cn=Backends,cn=config".format(index_name, backend)

        max_wait_time = 300
        sleep_duration = 10

        for _ in range(0, max_wait_time, sleep_duration):
            try:
                if self.client.get(dn, attributes=["1.1"]):
                    return
                reason = f"Index {dn} is not ready"
            except (LDAPSessionTerminatedByServerError, LDAPSocketOpenError) as exc:
                reason = exc

            logger.warning("Waiting for index to be ready; reason={}; "
                           "retrying in {} seconds".format(reason, sleep_duration))
            time.sleep(sleep_duration)

    def import_ldif(self):
        optional_scopes = json.loads(self.manager.config.get("optional_scopes", "[]"))
        ldif_mappings = get_ldif_mappings(optional_scopes)

        # hybrid means only a subsets of ldif are needed
        persistence_type = os.environ.get("CN_PERSISTENCE_TYPE", "ldap")
        ldap_mapping = os.environ.get("CN_PERSISTENCE_LDAP_MAPPING", "default")
        if persistence_type == "hybrid":
            mapping = ldap_mapping
            ldif_mappings = {mapping: ldif_mappings[mapping]}

            # # these mappings require `base.ldif`
            # opt_mappings = ("user", "token",)

            # `user` mapping requires `o=gluu` which available in `base.ldif`
            # if mapping in opt_mappings and "base.ldif" not in ldif_mappings[mapping]:
            if "base.ldif" not in ldif_mappings[mapping]:
                ldif_mappings[mapping].insert(0, "base.ldif")

        ctx = prepare_template_ctx(self.manager)

        for mapping, files in ldif_mappings.items():
            self.check_indexes(mapping)

            for file_ in files:
                logger.info(f"Importing {file_} file")
                src = f"/app/templates/{file_}"
                dst = f"/app/tmp/{file_}"
                os.makedirs(os.path.dirname(dst), exist_ok=True)

                render_ldif(src, dst, ctx)

                with open(dst, "rb") as fd:
                    parser = LDIFParser(fd)
                    for dn, entry in parser.parse():
                        self.add_entry(dn, entry)

    def add_entry(self, dn, attrs):
        max_wait_time = 300
        sleep_duration = 10

        for _ in range(0, max_wait_time, sleep_duration):
            try:
                added, msg = self.client.add(dn, attributes=attrs)
                if not added and "name already exists" not in msg:
                    logger.warning(f"Unable to add entry with DN {dn}; reason={msg}")
                break
            except (LDAPSessionTerminatedByServerError, LDAPSocketOpenError) as exc:
                logger.warning(f"Unable to add entry with DN {dn}; reason={exc}; retrying in {sleep_duration} seconds")
            time.sleep(sleep_duration)

    def initialize(self):
        def is_initialized():
            persistence_type = os.environ.get("CN_PERSISTENCE_TYPE", "ldap")
            ldap_mapping = os.environ.get("CN_PERSISTENCE_LDAP_MAPPING", "default")

            # a minimum service stack is having oxTrust, hence check whether entry
            # for oxTrust exists in LDAP
            default_search = ("ou=jans-auth,ou=configuration,o=jans",
                              "(objectClass=jansAppConf)")

            if persistence_type == "hybrid":
                # `cache` and `token` mapping only have base entries
                search_mapping = {
                    "default": default_search,
                    "user": ("inum=60B7,ou=groups,o=jans", "(objectClass=jansGrp)"),
                    "site": ("ou=cache-refresh,o=site", "(ou=people)"),
                    "cache": ("o=jans", "(ou=cache)"),
                    "token": ("ou=tokens,o=jans", "(ou=tokens)"),
                    "session": ("ou=sessions,o=jans", "(ou=sessions)"),
                }
                search = search_mapping[ldap_mapping]
            else:
                search = default_search
            return self.client.search(search[0], search[1], attributes=["objectClass"], limit=1)

        should_skip = as_boolean(
            os.environ.get("CN_PERSISTENCE_SKIP_INITIALIZED", False),
        )
        if should_skip and is_initialized():
            logger.info("LDAP backend already initialized")
            return
        self.import_ldif()
예제 #7
0
 def __init__(self, manager):
     super().__init__()
     self.manager = manager
     self.client = LdapClient(manager)
     self.type = "ldap"
예제 #8
0
class LDAPBackend(BaseBackend):
    def __init__(self, manager):
        super().__init__()
        self.manager = manager
        self.client = LdapClient(manager)
        self.type = "ldap"

    def get_entry(self, key, filter_="", attrs=None, **kwargs):
        def format_attrs(attrs):
            _attrs = {}
            for k, v in attrs.items():
                if len(v) < 2:
                    v = v[0]
                _attrs[k] = v
            return _attrs

        filter_ = filter_ or "(objectClass=*)"

        entry = self.client.get(key, filter_=filter_, attributes=attrs)
        if not entry:
            return None
        return Entry(entry.entry_dn,
                     format_attrs(entry.entry_attributes_as_dict))

    def modify_entry(self, key, attrs=None, **kwargs):
        attrs = attrs or {}
        del_flag = kwargs.get("delete_attr", False)

        if del_flag:
            mod = self.client.MODIFY_DELETE
        else:
            mod = self.client.MODIFY_REPLACE

        for k, v in attrs.items():
            if not isinstance(v, list):
                v = [v]
            attrs[k] = [(mod, v)]
        return self.client.modify(key, attrs)

    def update_people_entries(self):
        # add jansAdminUIRole to default admin user
        admin_inum = self.manager.config.get("admin_inum")
        id_ = f"inum={admin_inum},ou=people,o=jans"
        kwargs = {}

        entry = self.get_entry(id_, **kwargs)
        if not entry:
            return

        if "jansAdminUIRole" not in entry.attrs:
            entry.attrs["jansAdminUIRole"] = ["api-admin"]
            self.modify_entry(id_, entry.attrs, **kwargs)

    def update_scopes_entries(self):
        # add jansAdminUIRole claim to profile scope
        kwargs = {}

        entry = self.get_entry(self.jans_admin_ui_role_id, **kwargs)
        if not entry:
            return

        if self.jans_admin_ui_claim not in entry.attrs["jansClaim"]:
            entry.attrs["jansClaim"].append(self.jans_admin_ui_claim)
            self.modify_entry(self.jans_admin_ui_role_id, entry.attrs,
                              **kwargs)

    def update_clients_entries(self):
        jca_client_id = self.manager.config.get("jca_client_id")
        id_ = f"inum={jca_client_id},ou=clients,o=jans"
        kwargs = {}

        entry = self.get_entry(id_, **kwargs)
        if not entry:
            return

        should_update = False

        # modify redirect UI of config-api client
        hostname = self.manager.config.get("hostname")

        if f"https://{hostname}/admin" not in entry.attrs["jansRedirectURI"]:
            entry.attrs["jansRedirectURI"].append(f"https://{hostname}/admin")
            should_update = True

        # add jans_stat, SCIM users.read, SCIM users.write scopes to config-api client
        for scope in (self.jans_scim_scopes + self.jans_stat_scopes):
            if scope not in entry.attrs["jansScope"]:
                entry.attrs["jansScope"].append(scope)
                should_update = True

        if should_update:
            self.modify_entry(id_, entry.attrs, **kwargs)

    def update_scim_scopes_entries(self):
        # add jansAttrs to SCIM users.read and users.write scopes
        ids = self.jans_scim_scopes
        kwargs = {}

        for id_ in ids:
            entry = self.get_entry(id_, **kwargs)
            if not entry:
                continue

            if "jansAttrs" not in entry.attrs:
                entry.attrs["jansAttrs"] = self.jans_attrs
                self.modify_entry(id_, entry.attrs, **kwargs)

    def update_base_entries(self):
        # add jansManagerGrp to base entry
        entry = self.get_entry(JANS_BASE_ID)
        if not entry:
            return

        if not entry.attrs.get("jansManagerGrp"):
            entry.attrs["jansManagerGrp"] = JANS_MANAGER_GROUP
            self.modify_entry(JANS_BASE_ID, entry.attrs)