{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"kanidmclient/","title":"kanidm.KanidmClient","text":"

Kanidm client module

config: a KanidmClientConfig object, if this is set, everything else is ignored config_file: a pathlib.Path object pointing to a configuration file uri: kanidm base URL session: a aiohttp.client.ClientSession verify_hostnames: verify the hostname is correct verify_certificate: verify the validity of the certificate and its CA ca_path: set this to a trusted CA certificate (PEM format) token: a JWS from an authentication session

Source code in kanidm/__init__.py
class KanidmClient:\n\"\"\"Kanidm client module\n\n    config: a `KanidmClientConfig` object, if this is set, everything else is ignored\n    config_file: a `pathlib.Path` object pointing to a configuration file\n    uri: kanidm base URL\n    session: a `aiohttp.client.ClientSession`\n    verify_hostnames: verify the hostname is correct\n    verify_certificate: verify the validity of the certificate and its CA\n    ca_path: set this to a trusted CA certificate (PEM format)\n    token: a JWS from an authentication session\n    \"\"\"\n\n    # pylint: disable=too-many-instance-attributes,too-many-arguments\n    def __init__(\n        self,\n        config: Optional[KanidmClientConfig] = None,\n        config_file: Optional[Union[Path, str]] = None,\n        uri: Optional[str] = None,\n        verify_hostnames: bool = True,\n        verify_certificate: bool = True,\n        ca_path: Optional[str] = None,\n        token: Optional[str] = None,\n    ) -> None:\n\"\"\"Constructor for KanidmClient\"\"\"\n\n        if config is not None:\n            self.config = config\n\n        else:\n            self.config = KanidmClientConfig(\n                uri=uri,\n                verify_hostnames=verify_hostnames,\n                verify_certificate=verify_certificate,\n                ca_path=ca_path,\n                auth_token=token,\n            )\n\n            if config_file is not None:\n                if not isinstance(config_file, Path):\n                    config_file = Path(config_file)\n                config_data = load_config(config_file.expanduser().resolve())\n                self.config = self.config.parse_obj(config_data)\n\n        if self.config.uri is None:\n            raise ValueError(\"Please initialize this with a server URI\")\n\n        self._ssl: Optional[Union[bool, ssl.SSLContext]] = None\n        self._configure_ssl()\n\n    def _configure_ssl(self) -> None:\n\"\"\"Sets up SSL configuration for the client\"\"\"\n        if self.config.verify_certificate is False:\n            self._ssl = False\n        else:\n            if (\n                self.config.ca_path is not None\n                and not Path(self.config.ca_path).expanduser().resolve().exists()\n            ):\n                raise FileNotFoundError(f\"CA Path not found: {self.config.ca_path}\")\n            self._ssl = ssl.create_default_context(cafile=self.config.ca_path)\n        if self._ssl is not False:\n            # ignoring this for typing because mypy is being weird\n            # ssl.SSLContext.check_hostname is totally a thing\n            # https://docs.python.org/3/library/ssl.html#ssl.SSLContext.check_hostname\n            self._ssl.check_hostname = self.config.verify_hostnames  # type: ignore\n\n    def parse_config_data(\n        self,\n        config_data: Dict[str, Any],\n    ) -> None:\n\"\"\"hand it a config dict and it'll configure the client\"\"\"\n        try:\n            self.config.parse_obj(config_data)\n        except ValidationError as validation_error:\n            # pylint: disable=raise-missing-from\n            raise ValueError(f\"Failed to validate configuration: {validation_error}\")\n\n    async def check_token_valid(self, token: Optional[str] = None) -> bool:\n\"\"\"checks if a given token is valid, or the local one if you don't pass it\"\"\"\n        url = \"/v1/auth/valid\"\n        if token is not None:\n            headers = {\n                \"authorization\": f\"Bearer {token}\",\n                \"content-type\": \"application/json\",\n            }\n        else:\n            headers = None\n        result = await self.call_get(url, headers=headers)\n        logging.debug(result)\n        if result.status_code == 200:\n            return True\n        return False\n\n    @lru_cache()\n    def get_path_uri(self, path: str) -> str:\n\"\"\"turns a path into a full URI\"\"\"\n        if path.startswith(\"/\"):\n            path = path[1:]\n        return f\"{self.config.uri}{path}\"\n\n    @property\n    def _token_headers(self) -> Dict[str, str]:\n\"\"\"returns an auth header with the token in it\"\"\"\n        if self.config.auth_token is None:\n            raise ValueError(\"Token is not set\")\n        return {\"authorization\": f\"Bearer {self.config.auth_token}\"}\n\n    # pylint: disable=too-many-arguments\n    async def _call(\n        self,\n        method: str,\n        path: str,\n        headers: Optional[Dict[str, str]] = None,\n        timeout: Optional[int] = None,\n        json: Optional[Dict[str, str]] = None,\n        params: Optional[Dict[str, str]] = None,\n    ) -> ClientResponse:\n\n        if timeout is None:\n            timeout = self.config.connect_timeout\n        async with aiohttp.client.ClientSession() as session:\n            # if we have a token set, we send it.\n            if self.config.auth_token is not None:\n                logging.debug(\"Found a token internally\")\n                if headers is None:\n                    headers = self._token_headers\n                elif \"authorization\" not in headers:\n                    logging.debug(\"Setting auth headers as Authorization not in keys\")\n                    headers.update(self._token_headers)\n            logging.debug(\"_call method=%s to %s\", method, self.get_path_uri(path))\n            async with session.request(\n                method=method,\n                url=self.get_path_uri(path),\n                headers=headers,\n                timeout=timeout,\n                json=json,\n                params=params,\n                ssl=self._ssl,\n            ) as request:\n                content = await request.content.read()\n                try:\n                    response_json = json_lib.loads(content)\n                    if not isinstance(response_json, dict):\n                        response_json = None\n                except json_lib.JSONDecodeError as json_error:\n                    logging.error(\"Failed to JSON Decode Response: %s\", json_error)\n                    logging.error(\"Response data: %s\", content)\n                    response_json = {}\n                response_input = {\n                    \"data\": response_json,\n                    \"content\": content.decode(\"utf-8\"),\n                    \"headers\": request.headers,\n                    \"status_code\": request.status,\n                }\n                logging.debug(json_lib.dumps(response_input, default=str, indent=4))\n                response = ClientResponse.parse_obj(response_input)\n            return response\n\n    async def call_get(\n        self,\n        path: str,\n        headers: Optional[Dict[str, str]] = None,\n        params: Optional[Dict[str, str]] = None,\n        timeout: Optional[int] = None,\n    ) -> ClientResponse:\n\"\"\"does a get call to the server\"\"\"\n        return await self._call(\"GET\", path, headers, timeout, params=params)\n\n    async def call_post(\n        self,\n        path: str,\n        headers: Optional[Dict[str, str]] = None,\n        json: Optional[Dict[str, Any]] = None,\n        timeout: Optional[int] = None,\n    ) -> ClientResponse:\n\"\"\"does a get call to the server\"\"\"\n\n        return await self._call(\n            method=\"POST\", path=path, headers=headers, json=json, timeout=timeout\n        )\n\n    async def auth_init(self, username: str) -> AuthInitResponse:\n\"\"\"init step, starts the auth session, sets the class-local session ID\"\"\"\n        init_auth = {\"step\": {\"init\": username}}\n\n        response = await self.call_post(\n            path=KANIDMURLS[\"auth\"],\n            json=init_auth,\n        )\n        if response.status_code != 200:\n            logging.debug(\n                \"Failed to authenticate, response from server: %s\",\n                response.content,\n            )\n            # TODO: mock test auth_init raises AuthInitFailed\n            raise AuthInitFailed(response.content)\n\n        if \"x-kanidm-auth-session-id\" not in response.headers:\n            logging.debug(\"response.content: %s\", response.content)\n            logging.debug(\"response.headers: %s\", response.headers)\n            raise ValueError(\n                f\"Missing x-kanidm-auth-session-id header in init auth response: {response.headers}\"\n            )\n        retval = AuthInitResponse.parse_obj(response.data)\n        retval.response = response\n        return retval\n\n    async def auth_begin(self, method: str, sessionid: str) -> ClientResponse:\n\"\"\"the 'begin' step\"\"\"\n\n        begin_auth = {\n            \"step\": {\n                \"begin\": method,\n            },\n        }\n        headers = self.session_header(sessionid)\n\n        response = await self.call_post(\n            KANIDMURLS[\"auth\"],\n            json=begin_auth,\n            headers=headers,\n        )\n        if response.status_code != 200:\n            # TODO: mock test for auth_begin raises AuthBeginFailed\n            raise AuthBeginFailed(response.content)\n\n        retobject = AuthBeginResponse.parse_obj(response.data)\n        retobject.response = response\n        return response\n\n    async def authenticate_password(\n        self,\n        username: Optional[str] = None,\n        password: Optional[str] = None,\n    ) -> AuthStepPasswordResponse:\n\"\"\"authenticates with a username and password, returns the auth token\"\"\"\n        if username is None and password is None:\n            if self.config.username is None or self.config.password is None:\n                # pylint: disable=line-too-long\n                raise ValueError(\n                    \"Need username/password to be in caller or class settings before calling authenticate_password\"\n                )\n            username = self.config.username\n            password = self.config.password\n        if username is None or password is None:\n            raise ValueError(\"Username and Password need to be set somewhere!\")\n\n        auth_init: AuthInitResponse = await self.auth_init(username)\n\n        if auth_init.response is None:\n            raise NotImplementedError(\"This should throw a really cool response\")\n\n        sessionid = auth_init.response.headers[\"x-kanidm-auth-session-id\"]\n\n        if len(auth_init.state.choose) == 0:\n            # there's no mechanisms at all - bail\n            # TODO: write test coverage for authenticate_password raises AuthMechUnknown\n            raise AuthMechUnknown(f\"No auth mechanisms for {username}\")\n        auth_begin = await self.auth_begin(method=\"password\", sessionid=sessionid)\n        # does a little bit of validation\n        auth_begin_object = AuthBeginResponse.parse_obj(auth_begin.data)\n        auth_begin_object.response = auth_begin\n        return await self.auth_step_password(password=password, sessionid=sessionid)\n\n    async def auth_step_password(\n        self,\n        sessionid: str,\n        password: Optional[str] = None,\n    ) -> AuthStepPasswordResponse:\n\"\"\"does the password auth step\"\"\"\n\n        if password is None:\n            password = self.config.password\n        if password is None:\n            raise ValueError(\n                \"Password has to be passed to auth_step_password or in self.password!\"\n            )\n\n        cred_auth = {\"step\": {\"cred\": {\"password\": password}}}\n        response = await self.call_post(\n            path=\"/v1/auth\", json=cred_auth, headers=self.session_header(sessionid)\n        )\n\n        if response.status_code != 200:\n            # TODO: write test coverage auth_step_password raises AuthCredFailed\n            logging.debug(\"Failed to authenticate, response: %s\", response.content)\n            raise AuthCredFailed(\"Failed password authentication!\")\n\n        result = AuthStepPasswordResponse.parse_obj(response.data)\n        result.response = response\n\n        # pull the token out and set it\n        if result.state.success is None:\n            # TODO: write test coverage for AuthCredFailed\n            raise AuthCredFailed\n        result.sessionid = result.state.success\n        return result\n\n    def session_header(\n        self,\n        sessionid: str,\n    ) -> Dict[str, str]:\n\"\"\"create a headers dict from a session id\"\"\"\n        # TODO: perhaps allow session_header to take a dict and update it, too?\n        return {\n            \"X-KANIDM-AUTH-SESSION-ID\": sessionid,\n        }\n\n    async def get_radius_token(self, username: str) -> ClientResponse:\n\"\"\"does the call to the radius token endpoint\"\"\"\n        path = f\"/v1/account/{username}/_radius/_token\"\n        response = await self.call_get(path)\n        if response.status_code == 404:\n            raise NoMatchingEntries(\n                f\"No user found: '{username}' {response.headers['x-kanidm-opid']}\"\n            )\n        return response\n
"},{"location":"kanidmclient/#kanidm.KanidmClient.__init__","title":"__init__(config=None, config_file=None, uri=None, verify_hostnames=True, verify_certificate=True, ca_path=None, token=None)","text":"

Constructor for KanidmClient

Source code in kanidm/__init__.py
def __init__(\n    self,\n    config: Optional[KanidmClientConfig] = None,\n    config_file: Optional[Union[Path, str]] = None,\n    uri: Optional[str] = None,\n    verify_hostnames: bool = True,\n    verify_certificate: bool = True,\n    ca_path: Optional[str] = None,\n    token: Optional[str] = None,\n) -> None:\n\"\"\"Constructor for KanidmClient\"\"\"\n\n    if config is not None:\n        self.config = config\n\n    else:\n        self.config = KanidmClientConfig(\n            uri=uri,\n            verify_hostnames=verify_hostnames,\n            verify_certificate=verify_certificate,\n            ca_path=ca_path,\n            auth_token=token,\n        )\n\n        if config_file is not None:\n            if not isinstance(config_file, Path):\n                config_file = Path(config_file)\n            config_data = load_config(config_file.expanduser().resolve())\n            self.config = self.config.parse_obj(config_data)\n\n    if self.config.uri is None:\n        raise ValueError(\"Please initialize this with a server URI\")\n\n    self._ssl: Optional[Union[bool, ssl.SSLContext]] = None\n    self._configure_ssl()\n
"},{"location":"kanidmclient/#kanidm.KanidmClient.auth_begin","title":"auth_begin(method, sessionid) async","text":"

the 'begin' step

Source code in kanidm/__init__.py
async def auth_begin(self, method: str, sessionid: str) -> ClientResponse:\n\"\"\"the 'begin' step\"\"\"\n\n    begin_auth = {\n        \"step\": {\n            \"begin\": method,\n        },\n    }\n    headers = self.session_header(sessionid)\n\n    response = await self.call_post(\n        KANIDMURLS[\"auth\"],\n        json=begin_auth,\n        headers=headers,\n    )\n    if response.status_code != 200:\n        # TODO: mock test for auth_begin raises AuthBeginFailed\n        raise AuthBeginFailed(response.content)\n\n    retobject = AuthBeginResponse.parse_obj(response.data)\n    retobject.response = response\n    return response\n
"},{"location":"kanidmclient/#kanidm.KanidmClient.auth_init","title":"auth_init(username) async","text":"

init step, starts the auth session, sets the class-local session ID

Source code in kanidm/__init__.py
async def auth_init(self, username: str) -> AuthInitResponse:\n\"\"\"init step, starts the auth session, sets the class-local session ID\"\"\"\n    init_auth = {\"step\": {\"init\": username}}\n\n    response = await self.call_post(\n        path=KANIDMURLS[\"auth\"],\n        json=init_auth,\n    )\n    if response.status_code != 200:\n        logging.debug(\n            \"Failed to authenticate, response from server: %s\",\n            response.content,\n        )\n        # TODO: mock test auth_init raises AuthInitFailed\n        raise AuthInitFailed(response.content)\n\n    if \"x-kanidm-auth-session-id\" not in response.headers:\n        logging.debug(\"response.content: %s\", response.content)\n        logging.debug(\"response.headers: %s\", response.headers)\n        raise ValueError(\n            f\"Missing x-kanidm-auth-session-id header in init auth response: {response.headers}\"\n        )\n    retval = AuthInitResponse.parse_obj(response.data)\n    retval.response = response\n    return retval\n
"},{"location":"kanidmclient/#kanidm.KanidmClient.auth_step_password","title":"auth_step_password(sessionid, password=None) async","text":"

does the password auth step

Source code in kanidm/__init__.py
async def auth_step_password(\n    self,\n    sessionid: str,\n    password: Optional[str] = None,\n) -> AuthStepPasswordResponse:\n\"\"\"does the password auth step\"\"\"\n\n    if password is None:\n        password = self.config.password\n    if password is None:\n        raise ValueError(\n            \"Password has to be passed to auth_step_password or in self.password!\"\n        )\n\n    cred_auth = {\"step\": {\"cred\": {\"password\": password}}}\n    response = await self.call_post(\n        path=\"/v1/auth\", json=cred_auth, headers=self.session_header(sessionid)\n    )\n\n    if response.status_code != 200:\n        # TODO: write test coverage auth_step_password raises AuthCredFailed\n        logging.debug(\"Failed to authenticate, response: %s\", response.content)\n        raise AuthCredFailed(\"Failed password authentication!\")\n\n    result = AuthStepPasswordResponse.parse_obj(response.data)\n    result.response = response\n\n    # pull the token out and set it\n    if result.state.success is None:\n        # TODO: write test coverage for AuthCredFailed\n        raise AuthCredFailed\n    result.sessionid = result.state.success\n    return result\n
"},{"location":"kanidmclient/#kanidm.KanidmClient.authenticate_password","title":"authenticate_password(username=None, password=None) async","text":"

authenticates with a username and password, returns the auth token

Source code in kanidm/__init__.py
async def authenticate_password(\n    self,\n    username: Optional[str] = None,\n    password: Optional[str] = None,\n) -> AuthStepPasswordResponse:\n\"\"\"authenticates with a username and password, returns the auth token\"\"\"\n    if username is None and password is None:\n        if self.config.username is None or self.config.password is None:\n            # pylint: disable=line-too-long\n            raise ValueError(\n                \"Need username/password to be in caller or class settings before calling authenticate_password\"\n            )\n        username = self.config.username\n        password = self.config.password\n    if username is None or password is None:\n        raise ValueError(\"Username and Password need to be set somewhere!\")\n\n    auth_init: AuthInitResponse = await self.auth_init(username)\n\n    if auth_init.response is None:\n        raise NotImplementedError(\"This should throw a really cool response\")\n\n    sessionid = auth_init.response.headers[\"x-kanidm-auth-session-id\"]\n\n    if len(auth_init.state.choose) == 0:\n        # there's no mechanisms at all - bail\n        # TODO: write test coverage for authenticate_password raises AuthMechUnknown\n        raise AuthMechUnknown(f\"No auth mechanisms for {username}\")\n    auth_begin = await self.auth_begin(method=\"password\", sessionid=sessionid)\n    # does a little bit of validation\n    auth_begin_object = AuthBeginResponse.parse_obj(auth_begin.data)\n    auth_begin_object.response = auth_begin\n    return await self.auth_step_password(password=password, sessionid=sessionid)\n
"},{"location":"kanidmclient/#kanidm.KanidmClient.call_get","title":"call_get(path, headers=None, params=None, timeout=None) async","text":"

does a get call to the server

Source code in kanidm/__init__.py
async def call_get(\n    self,\n    path: str,\n    headers: Optional[Dict[str, str]] = None,\n    params: Optional[Dict[str, str]] = None,\n    timeout: Optional[int] = None,\n) -> ClientResponse:\n\"\"\"does a get call to the server\"\"\"\n    return await self._call(\"GET\", path, headers, timeout, params=params)\n
"},{"location":"kanidmclient/#kanidm.KanidmClient.call_post","title":"call_post(path, headers=None, json=None, timeout=None) async","text":"

does a get call to the server

Source code in kanidm/__init__.py
async def call_post(\n    self,\n    path: str,\n    headers: Optional[Dict[str, str]] = None,\n    json: Optional[Dict[str, Any]] = None,\n    timeout: Optional[int] = None,\n) -> ClientResponse:\n\"\"\"does a get call to the server\"\"\"\n\n    return await self._call(\n        method=\"POST\", path=path, headers=headers, json=json, timeout=timeout\n    )\n
"},{"location":"kanidmclient/#kanidm.KanidmClient.check_token_valid","title":"check_token_valid(token=None) async","text":"

checks if a given token is valid, or the local one if you don't pass it

Source code in kanidm/__init__.py
async def check_token_valid(self, token: Optional[str] = None) -> bool:\n\"\"\"checks if a given token is valid, or the local one if you don't pass it\"\"\"\n    url = \"/v1/auth/valid\"\n    if token is not None:\n        headers = {\n            \"authorization\": f\"Bearer {token}\",\n            \"content-type\": \"application/json\",\n        }\n    else:\n        headers = None\n    result = await self.call_get(url, headers=headers)\n    logging.debug(result)\n    if result.status_code == 200:\n        return True\n    return False\n
"},{"location":"kanidmclient/#kanidm.KanidmClient.get_path_uri","title":"get_path_uri(path) cached","text":"

turns a path into a full URI

Source code in kanidm/__init__.py
@lru_cache()\ndef get_path_uri(self, path: str) -> str:\n\"\"\"turns a path into a full URI\"\"\"\n    if path.startswith(\"/\"):\n        path = path[1:]\n    return f\"{self.config.uri}{path}\"\n
"},{"location":"kanidmclient/#kanidm.KanidmClient.get_radius_token","title":"get_radius_token(username) async","text":"

does the call to the radius token endpoint

Source code in kanidm/__init__.py
async def get_radius_token(self, username: str) -> ClientResponse:\n\"\"\"does the call to the radius token endpoint\"\"\"\n    path = f\"/v1/account/{username}/_radius/_token\"\n    response = await self.call_get(path)\n    if response.status_code == 404:\n        raise NoMatchingEntries(\n            f\"No user found: '{username}' {response.headers['x-kanidm-opid']}\"\n        )\n    return response\n
"},{"location":"kanidmclient/#kanidm.KanidmClient.parse_config_data","title":"parse_config_data(config_data)","text":"

hand it a config dict and it'll configure the client

Source code in kanidm/__init__.py
def parse_config_data(\n    self,\n    config_data: Dict[str, Any],\n) -> None:\n\"\"\"hand it a config dict and it'll configure the client\"\"\"\n    try:\n        self.config.parse_obj(config_data)\n    except ValidationError as validation_error:\n        # pylint: disable=raise-missing-from\n        raise ValueError(f\"Failed to validate configuration: {validation_error}\")\n
"},{"location":"kanidmclient/#kanidm.KanidmClient.session_header","title":"session_header(sessionid)","text":"

create a headers dict from a session id

Source code in kanidm/__init__.py
def session_header(\n    self,\n    sessionid: str,\n) -> Dict[str, str]:\n\"\"\"create a headers dict from a session id\"\"\"\n    # TODO: perhaps allow session_header to take a dict and update it, too?\n    return {\n        \"X-KANIDM-AUTH-SESSION-ID\": sessionid,\n    }\n
"},{"location":"kanidmclientconfig/","title":"kanidm.types.KanidmClientConfig","text":"

Bases: BaseModel

Configuration file definition for Kanidm client config Based on struct KanidmClientConfig in kanidm_client/src/lib.rs

See source code for fields

Source code in kanidm/types.py
class KanidmClientConfig(BaseModel):\n\"\"\"Configuration file definition for Kanidm client config\n    Based on struct KanidmClientConfig in kanidm_client/src/lib.rs\n\n    See source code for fields\n    \"\"\"\n\n    uri: Optional[str] = None\n\n    auth_token: Optional[str] = None\n\n    verify_hostnames: bool = True\n    verify_certificate: bool = True\n    ca_path: Optional[str] = None\n\n    username: Optional[str] = None\n    password: Optional[str] = None\n\n    radius_cert_path: str = \"/data/cert.pem\"\n    radius_key_path: str = \"/data/key.pem\"  # the signing key for radius TLS\n    radius_dh_path: str = \"/data/dh.pem\"  # the diffie-hellman output\n    radius_ca_path: Optional[str] = None\n    radius_ca_dir: Optional[str] = None\n\n    radius_required_groups: List[str] = []\n    radius_default_vlan: int = 1\n    radius_groups: List[RadiusGroup] = []\n    radius_clients: List[RadiusClient] = []\n\n    connect_timeout: int = 30\n\n    @classmethod\n    def parse_toml(cls, input_string: str) -> Any:\n\"\"\"loads from a string\"\"\"\n        return super().parse_obj(toml.loads(input_string))\n\n    @validator(\"uri\")\n    def validate_uri(cls, value: Optional[str]) -> Optional[str]:\n\"\"\"validator for the uri field\"\"\"\n        if value is not None:\n            uri = urlparse(value)\n            valid_schemes = [\"http\", \"https\"]\n            if uri.scheme not in valid_schemes:\n                raise ValueError(\n                    f\"Invalid URL Scheme for uri='{value}': '{uri.scheme}' - expected one of {valid_schemes}\"\n                )\n\n            # make sure the URI ends with a /\n            if not value.endswith(\"/\"):\n                value = f\"{value}/\"\n\n        return value\n
"},{"location":"kanidmclientconfig/#kanidm.types.KanidmClientConfig.parse_toml","title":"parse_toml(input_string) classmethod","text":"

loads from a string

Source code in kanidm/types.py
@classmethod\ndef parse_toml(cls, input_string: str) -> Any:\n\"\"\"loads from a string\"\"\"\n    return super().parse_obj(toml.loads(input_string))\n
"},{"location":"kanidmclientconfig/#kanidm.types.KanidmClientConfig.validate_uri","title":"validate_uri(value)","text":"

validator for the uri field

Source code in kanidm/types.py
@validator(\"uri\")\ndef validate_uri(cls, value: Optional[str]) -> Optional[str]:\n\"\"\"validator for the uri field\"\"\"\n    if value is not None:\n        uri = urlparse(value)\n        valid_schemes = [\"http\", \"https\"]\n        if uri.scheme not in valid_schemes:\n            raise ValueError(\n                f\"Invalid URL Scheme for uri='{value}': '{uri.scheme}' - expected one of {valid_schemes}\"\n            )\n\n        # make sure the URI ends with a /\n        if not value.endswith(\"/\"):\n            value = f\"{value}/\"\n\n    return value\n
"},{"location":"radiusclient/","title":"kanidm.types.RadiusClient","text":"

Bases: BaseModel

Client config for Kanidm FreeRADIUS integration, this is a pydantic model.

name: (str) An identifier for the client definition

ipaddr: (str) A single IP Address, CIDR or DNS hostname (which will be resolved on startup, preferring A records over AAAA). FreeRADIUS doesn't recommend using DNS.

secret: (str) The password the client should use to authenticate.

Source code in kanidm/types.py
class RadiusClient(BaseModel):\n\"\"\"Client config for Kanidm FreeRADIUS integration,\n    this is a pydantic model.\n\n    name: (str) An identifier for the client definition\n\n    ipaddr: (str) A single IP Address, CIDR or\n    DNS hostname (which will be resolved on startup,\n    preferring A records over AAAA).\n    FreeRADIUS doesn't recommend using DNS.\n\n    secret: (str) The password the client should use to\n    authenticate.\n    \"\"\"\n\n    name: str\n    ipaddr: str\n    secret: str  # TODO: this should probably be renamed to token\n\n    @validator(\"ipaddr\")\n    def validate_ipaddr(cls, value: str) -> str:\n\"\"\"validates the ipaddr field is an IP address, CIDR or valid hostname\"\"\"\n        for typedef in (IPv6Network, IPv6Address, IPv4Address, IPv4Network):\n            try:\n                typedef(value)\n                return value\n            except ValueError:\n                pass\n        try:\n            socket.gethostbyname(value)\n            return value\n        except socket.gaierror as error:\n            raise ValueError(\n                f\"ipaddr value ({value}) wasn't an IP Address, Network or valid hostname: {error}\"\n            )\n
"},{"location":"radiusclient/#kanidm.types.RadiusClient.validate_ipaddr","title":"validate_ipaddr(value)","text":"

validates the ipaddr field is an IP address, CIDR or valid hostname

Source code in kanidm/types.py
@validator(\"ipaddr\")\ndef validate_ipaddr(cls, value: str) -> str:\n\"\"\"validates the ipaddr field is an IP address, CIDR or valid hostname\"\"\"\n    for typedef in (IPv6Network, IPv6Address, IPv4Address, IPv4Network):\n        try:\n            typedef(value)\n            return value\n        except ValueError:\n            pass\n    try:\n        socket.gethostbyname(value)\n        return value\n    except socket.gaierror as error:\n        raise ValueError(\n            f\"ipaddr value ({value}) wasn't an IP Address, Network or valid hostname: {error}\"\n        )\n
"},{"location":"tokenstore/","title":"Token Storage","text":"

User Auth Token related widgets

"},{"location":"tokenstore/#kanidm.tokens.JWS","title":"JWS","text":"

JWS parser

Source code in kanidm/tokens.py
class JWS:\n\"\"\"JWS parser\"\"\"\n\n    def __init__(self, raw: str) -> None:\n\"\"\"raw is the raw string version of the JWS\"\"\"\n\n        data = self.parse(raw)\n        self.header = data[0]\n        self.payload = data[1]\n        self.signature = data[2]\n\n    @classmethod\n    def parse(cls, raw: str) -> Tuple[JWSHeader, JWSPayload, bytes]:\n\"\"\"parse a raw JWS\"\"\"\n        if \".\" not in raw:\n            raise ValueError(\"Invalid number of segments, there's no . in the raw JWS\")\n        split_raw = raw.split(\".\")\n        if len(split_raw) != 3:\n            raise ValueError(\"Invalid number of segments\")\n\n        raw_header = split_raw[0]\n        logging.debug(\"Parsing header: %s\", raw_header)\n        padded_header = raw_header + \"=\" * divmod(len(raw_header), 4)[0]\n        decoded_header = base64.urlsafe_b64decode(padded_header)\n        logging.debug(\"decoded_header=%s\", decoded_header)\n        header = JWSHeader.parse_obj(json.loads(decoded_header.decode(\"utf-8\")))\n        logging.debug(\"header: %s\", header)\n\n        raw_payload = split_raw[1]\n        logging.debug(\"Parsing payload: %s\", raw_payload)\n        padded_payload = raw_payload + \"=\" * divmod(len(raw_payload), 4)[1]\n        payload = JWSPayload.parse_raw(base64.urlsafe_b64decode(padded_payload))\n\n        raw_signature = split_raw[2]\n        logging.debug(\"Parsing signature: %s\", raw_signature)\n        padded_signature = raw_signature + \"=\" * divmod(len(raw_signature), 4)[1]\n        signature = base64.urlsafe_b64decode(padded_signature)\n\n        return header, payload, signature\n
"},{"location":"tokenstore/#kanidm.tokens.JWS.__init__","title":"__init__(raw)","text":"

raw is the raw string version of the JWS

Source code in kanidm/tokens.py
def __init__(self, raw: str) -> None:\n\"\"\"raw is the raw string version of the JWS\"\"\"\n\n    data = self.parse(raw)\n    self.header = data[0]\n    self.payload = data[1]\n    self.signature = data[2]\n
"},{"location":"tokenstore/#kanidm.tokens.JWS.parse","title":"parse(raw) classmethod","text":"

parse a raw JWS

Source code in kanidm/tokens.py
@classmethod\ndef parse(cls, raw: str) -> Tuple[JWSHeader, JWSPayload, bytes]:\n\"\"\"parse a raw JWS\"\"\"\n    if \".\" not in raw:\n        raise ValueError(\"Invalid number of segments, there's no . in the raw JWS\")\n    split_raw = raw.split(\".\")\n    if len(split_raw) != 3:\n        raise ValueError(\"Invalid number of segments\")\n\n    raw_header = split_raw[0]\n    logging.debug(\"Parsing header: %s\", raw_header)\n    padded_header = raw_header + \"=\" * divmod(len(raw_header), 4)[0]\n    decoded_header = base64.urlsafe_b64decode(padded_header)\n    logging.debug(\"decoded_header=%s\", decoded_header)\n    header = JWSHeader.parse_obj(json.loads(decoded_header.decode(\"utf-8\")))\n    logging.debug(\"header: %s\", header)\n\n    raw_payload = split_raw[1]\n    logging.debug(\"Parsing payload: %s\", raw_payload)\n    padded_payload = raw_payload + \"=\" * divmod(len(raw_payload), 4)[1]\n    payload = JWSPayload.parse_raw(base64.urlsafe_b64decode(padded_payload))\n\n    raw_signature = split_raw[2]\n    logging.debug(\"Parsing signature: %s\", raw_signature)\n    padded_signature = raw_signature + \"=\" * divmod(len(raw_signature), 4)[1]\n    signature = base64.urlsafe_b64decode(padded_signature)\n\n    return header, payload, signature\n
"},{"location":"tokenstore/#kanidm.tokens.JWSHeader","title":"JWSHeader","text":"

Bases: BaseModel

JWS Header Parser

Source code in kanidm/tokens.py
class JWSHeader(BaseModel):\n\"\"\"JWS Header Parser\"\"\"\n\n    class JWSHeaderJWK(BaseModel):\n\"\"\"JWS Header Sub-bit\"\"\"\n\n        kty: str\n        crv: str\n        x: str\n        y: str\n        alg: str\n        use: str\n\n    alg: str\n    typ: str\n    jwk: JWSHeaderJWK\n\n    class Config:\n\"\"\"Configure the pydantic class\"\"\"\n\n        arbitrary_types_allowed = True\n
"},{"location":"tokenstore/#kanidm.tokens.JWSHeader.Config","title":"Config","text":"

Configure the pydantic class

Source code in kanidm/tokens.py
class Config:\n\"\"\"Configure the pydantic class\"\"\"\n\n    arbitrary_types_allowed = True\n
"},{"location":"tokenstore/#kanidm.tokens.JWSHeader.JWSHeaderJWK","title":"JWSHeaderJWK","text":"

Bases: BaseModel

JWS Header Sub-bit

Source code in kanidm/tokens.py
class JWSHeaderJWK(BaseModel):\n\"\"\"JWS Header Sub-bit\"\"\"\n\n    kty: str\n    crv: str\n    x: str\n    y: str\n    alg: str\n    use: str\n
"},{"location":"tokenstore/#kanidm.tokens.JWSPayload","title":"JWSPayload","text":"

Bases: BaseModel

JWS Payload parser

Source code in kanidm/tokens.py
class JWSPayload(BaseModel):\n\"\"\"JWS Payload parser\"\"\"\n\n    session_id: str\n    auth_type: str\n    # TODO: work out the format of the expiry\n    # example expiry: 2022,265,28366,802525000\n    expiry: List[int]  # [year, day of year, something?]\n    uuid: str\n    name: str\n    displayname: str\n    spn: str\n    mail_primary: Optional[str]\n    lim_uidx: bool\n    lim_rmax: int\n    lim_pmax: int\n    lim_fmax: int\n\n    @property\n    def expiry_datetime(self) -> datetime:\n\"\"\"parse the expiry and return a datetime object\"\"\"\n        year, day, seconds, _ = self.expiry\n        retval = datetime(\n            year=year, month=1, day=1, second=0, hour=0, tzinfo=timezone.utc\n        )\n        # day - 1 because we're already starting at day 1\n        retval += timedelta(days=day - 1, seconds=seconds)\n        return retval\n
"},{"location":"tokenstore/#kanidm.tokens.JWSPayload.expiry_datetime","title":"expiry_datetime: datetime property","text":"

parse the expiry and return a datetime object

"},{"location":"tokenstore/#kanidm.tokens.TokenStore","title":"TokenStore","text":"

Bases: BaseModel

Represents the user auth tokens, can load them from the user store

Source code in kanidm/tokens.py
class TokenStore(BaseModel):\n\"\"\"Represents the user auth tokens, can load them from the user store\"\"\"\n\n    __root__: Dict[str, str] = {}\n\n    # TODO: one day work out how to type the __iter__ on TokenStore properly. It's some kind of iter() that makes mypy unhappy.\n    def __iter__(self) -> Any:\n\"\"\"overloading the default function\"\"\"\n        for key in self.__root__.keys():\n            yield key\n\n    def __getitem__(self, item: str) -> str:\n\"\"\"overloading the default function\"\"\"\n        return self.__root__[item]\n\n    def __delitem__(self, item: str) -> None:\n\"\"\"overloading the default function\"\"\"\n        del self.__root__[item]\n\n    def __setitem__(self, key: str, value: str) -> None:\n\"\"\"overloading the default function\"\"\"\n        self.__root__[key] = value\n\n    def save(self, filepath: Path = TOKEN_PATH) -> None:\n\"\"\"saves the cached tokens to disk\"\"\"\n        data = json.dumps(self.__root__, indent=2)\n        with filepath.expanduser().resolve().open(\n            mode=\"w\", encoding=\"utf-8\"\n        ) as file_handle:\n            file_handle.write(data)\n\n    def load(\n        self, overwrite: bool = True, filepath: Path = TOKEN_PATH\n    ) -> Dict[str, str]:\n\"\"\"Loads the tokens from from the store and caches them in memory - by default\n        from the local user's store path, but you can point it at any file path.\n\n        Will return the current cached store.\n\n        If overwrite=False, then it will add them to the existing in-memory store\"\"\"\n        token_path = filepath.expanduser().resolve()\n        if not token_path.exists():\n            tokens: Dict[str, str] = {}\n        else:\n            with token_path.open(encoding=\"utf-8\") as file_handle:\n                tokens = json.load(file_handle)\n\n        if overwrite:\n            self.__root__ = tokens\n        else:\n            for user in tokens:\n                self.__root__[user] = tokens[user]\n\n        self.validate_tokens()\n\n        logging.debug(json.dumps(tokens, indent=4))\n        return self.__root__\n\n    def validate_tokens(self) -> None:\n\"\"\"validates the JWS tokens for format, not their signature - PRs welcome\"\"\"\n        for username in self.__root__:\n            logging.debug(\"Parsing %s\", username)\n            # TODO: Work out how to get the validation working. We probably shouldn't be worried about this since we're using it for auth...\n            logging.debug(\n                JsonWebSignature().deserialize_compact(s=self[username], key=None)\n            )\n\n    def token_info(self, username: str) -> Optional[JWSPayload]:\n\"\"\"grabs a token and returns a complex object object\"\"\"\n        if username not in self:\n            return None\n        parsed_object = JsonWebSignature().deserialize_compact(\n            s=self[username], key=None\n        )\n        logging.debug(parsed_object)\n        return JWSPayload.parse_raw(parsed_object.payload)\n
"},{"location":"tokenstore/#kanidm.tokens.TokenStore.__delitem__","title":"__delitem__(item)","text":"

overloading the default function

Source code in kanidm/tokens.py
def __delitem__(self, item: str) -> None:\n\"\"\"overloading the default function\"\"\"\n    del self.__root__[item]\n
"},{"location":"tokenstore/#kanidm.tokens.TokenStore.__getitem__","title":"__getitem__(item)","text":"

overloading the default function

Source code in kanidm/tokens.py
def __getitem__(self, item: str) -> str:\n\"\"\"overloading the default function\"\"\"\n    return self.__root__[item]\n
"},{"location":"tokenstore/#kanidm.tokens.TokenStore.__iter__","title":"__iter__()","text":"

overloading the default function

Source code in kanidm/tokens.py
def __iter__(self) -> Any:\n\"\"\"overloading the default function\"\"\"\n    for key in self.__root__.keys():\n        yield key\n
"},{"location":"tokenstore/#kanidm.tokens.TokenStore.__setitem__","title":"__setitem__(key, value)","text":"

overloading the default function

Source code in kanidm/tokens.py
def __setitem__(self, key: str, value: str) -> None:\n\"\"\"overloading the default function\"\"\"\n    self.__root__[key] = value\n
"},{"location":"tokenstore/#kanidm.tokens.TokenStore.load","title":"load(overwrite=True, filepath=TOKEN_PATH)","text":"

Loads the tokens from from the store and caches them in memory - by default from the local user's store path, but you can point it at any file path.

Will return the current cached store.

If overwrite=False, then it will add them to the existing in-memory store

Source code in kanidm/tokens.py
def load(\n    self, overwrite: bool = True, filepath: Path = TOKEN_PATH\n) -> Dict[str, str]:\n\"\"\"Loads the tokens from from the store and caches them in memory - by default\n    from the local user's store path, but you can point it at any file path.\n\n    Will return the current cached store.\n\n    If overwrite=False, then it will add them to the existing in-memory store\"\"\"\n    token_path = filepath.expanduser().resolve()\n    if not token_path.exists():\n        tokens: Dict[str, str] = {}\n    else:\n        with token_path.open(encoding=\"utf-8\") as file_handle:\n            tokens = json.load(file_handle)\n\n    if overwrite:\n        self.__root__ = tokens\n    else:\n        for user in tokens:\n            self.__root__[user] = tokens[user]\n\n    self.validate_tokens()\n\n    logging.debug(json.dumps(tokens, indent=4))\n    return self.__root__\n
"},{"location":"tokenstore/#kanidm.tokens.TokenStore.save","title":"save(filepath=TOKEN_PATH)","text":"

saves the cached tokens to disk

Source code in kanidm/tokens.py
def save(self, filepath: Path = TOKEN_PATH) -> None:\n\"\"\"saves the cached tokens to disk\"\"\"\n    data = json.dumps(self.__root__, indent=2)\n    with filepath.expanduser().resolve().open(\n        mode=\"w\", encoding=\"utf-8\"\n    ) as file_handle:\n        file_handle.write(data)\n
"},{"location":"tokenstore/#kanidm.tokens.TokenStore.token_info","title":"token_info(username)","text":"

grabs a token and returns a complex object object

Source code in kanidm/tokens.py
def token_info(self, username: str) -> Optional[JWSPayload]:\n\"\"\"grabs a token and returns a complex object object\"\"\"\n    if username not in self:\n        return None\n    parsed_object = JsonWebSignature().deserialize_compact(\n        s=self[username], key=None\n    )\n    logging.debug(parsed_object)\n    return JWSPayload.parse_raw(parsed_object.payload)\n
"},{"location":"tokenstore/#kanidm.tokens.TokenStore.validate_tokens","title":"validate_tokens()","text":"

validates the JWS tokens for format, not their signature - PRs welcome

Source code in kanidm/tokens.py
def validate_tokens(self) -> None:\n\"\"\"validates the JWS tokens for format, not their signature - PRs welcome\"\"\"\n    for username in self.__root__:\n        logging.debug(\"Parsing %s\", username)\n        # TODO: Work out how to get the validation working. We probably shouldn't be worried about this since we're using it for auth...\n        logging.debug(\n            JsonWebSignature().deserialize_compact(s=self[username], key=None)\n        )\n
"}]}