mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-24 13:07:00 +01:00
1 line
44 KiB
JSON
1 line
44 KiB
JSON
{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"kanidmclient/","title":"kanidm.KanidmClient","text":"<p>Kanidm client module</p> <p>config: a <code>KanidmClientConfig</code> object, if this is set, everything else is ignored config_file: a <code>pathlib.Path</code> object pointing to a configuration file uri: kanidm base URL session: a <code>aiohttp.client.ClientSession</code> 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</p> Source code in <code>kanidm/__init__.py</code> <pre><code>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</code></pre>"},{"location":"kanidmclient/#kanidm.KanidmClient.__init__","title":"<code>__init__(config=None, config_file=None, uri=None, verify_hostnames=True, verify_certificate=True, ca_path=None, token=None)</code>","text":"<p>Constructor for KanidmClient</p> Source code in <code>kanidm/__init__.py</code> <pre><code>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</code></pre>"},{"location":"kanidmclient/#kanidm.KanidmClient.auth_begin","title":"<code>auth_begin(method, sessionid)</code> <code>async</code>","text":"<p>the 'begin' step</p> Source code in <code>kanidm/__init__.py</code> <pre><code>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</code></pre>"},{"location":"kanidmclient/#kanidm.KanidmClient.auth_init","title":"<code>auth_init(username)</code> <code>async</code>","text":"<p>init step, starts the auth session, sets the class-local session ID</p> Source code in <code>kanidm/__init__.py</code> <pre><code>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</code></pre>"},{"location":"kanidmclient/#kanidm.KanidmClient.auth_step_password","title":"<code>auth_step_password(sessionid, password=None)</code> <code>async</code>","text":"<p>does the password auth step</p> Source code in <code>kanidm/__init__.py</code> <pre><code>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</code></pre>"},{"location":"kanidmclient/#kanidm.KanidmClient.authenticate_password","title":"<code>authenticate_password(username=None, password=None)</code> <code>async</code>","text":"<p>authenticates with a username and password, returns the auth token</p> Source code in <code>kanidm/__init__.py</code> <pre><code>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</code></pre>"},{"location":"kanidmclient/#kanidm.KanidmClient.call_get","title":"<code>call_get(path, headers=None, params=None, timeout=None)</code> <code>async</code>","text":"<p>does a get call to the server</p> Source code in <code>kanidm/__init__.py</code> <pre><code>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</code></pre>"},{"location":"kanidmclient/#kanidm.KanidmClient.call_post","title":"<code>call_post(path, headers=None, json=None, timeout=None)</code> <code>async</code>","text":"<p>does a get call to the server</p> Source code in <code>kanidm/__init__.py</code> <pre><code>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</code></pre>"},{"location":"kanidmclient/#kanidm.KanidmClient.check_token_valid","title":"<code>check_token_valid(token=None)</code> <code>async</code>","text":"<p>checks if a given token is valid, or the local one if you don't pass it</p> Source code in <code>kanidm/__init__.py</code> <pre><code>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</code></pre>"},{"location":"kanidmclient/#kanidm.KanidmClient.get_path_uri","title":"<code>get_path_uri(path)</code> <code>cached</code>","text":"<p>turns a path into a full URI</p> Source code in <code>kanidm/__init__.py</code> <pre><code>@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</code></pre>"},{"location":"kanidmclient/#kanidm.KanidmClient.get_radius_token","title":"<code>get_radius_token(username)</code> <code>async</code>","text":"<p>does the call to the radius token endpoint</p> Source code in <code>kanidm/__init__.py</code> <pre><code>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</code></pre>"},{"location":"kanidmclient/#kanidm.KanidmClient.parse_config_data","title":"<code>parse_config_data(config_data)</code>","text":"<p>hand it a config dict and it'll configure the client</p> Source code in <code>kanidm/__init__.py</code> <pre><code>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</code></pre>"},{"location":"kanidmclient/#kanidm.KanidmClient.session_header","title":"<code>session_header(sessionid)</code>","text":"<p>create a headers dict from a session id</p> Source code in <code>kanidm/__init__.py</code> <pre><code>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</code></pre>"},{"location":"kanidmclientconfig/","title":"kanidm.types.KanidmClientConfig","text":"<p> Bases: <code>BaseModel</code></p> <p>Configuration file definition for Kanidm client config Based on struct KanidmClientConfig in kanidm_client/src/lib.rs</p> <p>See source code for fields</p> Source code in <code>kanidm/types.py</code> <pre><code>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</code></pre>"},{"location":"kanidmclientconfig/#kanidm.types.KanidmClientConfig.parse_toml","title":"<code>parse_toml(input_string)</code> <code>classmethod</code>","text":"<p>loads from a string</p> Source code in <code>kanidm/types.py</code> <pre><code>@classmethod\ndef parse_toml(cls, input_string: str) -> Any:\n\"\"\"loads from a string\"\"\"\n return super().parse_obj(toml.loads(input_string))\n</code></pre>"},{"location":"kanidmclientconfig/#kanidm.types.KanidmClientConfig.validate_uri","title":"<code>validate_uri(value)</code>","text":"<p>validator for the uri field</p> Source code in <code>kanidm/types.py</code> <pre><code>@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</code></pre>"},{"location":"radiusclient/","title":"kanidm.types.RadiusClient","text":"<p> Bases: <code>BaseModel</code></p> <p>Client config for Kanidm FreeRADIUS integration, this is a pydantic model.</p> <p>name: (str) An identifier for the client definition</p> <p>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.</p> <p>secret: (str) The password the client should use to authenticate.</p> Source code in <code>kanidm/types.py</code> <pre><code>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</code></pre>"},{"location":"radiusclient/#kanidm.types.RadiusClient.validate_ipaddr","title":"<code>validate_ipaddr(value)</code>","text":"<p>validates the ipaddr field is an IP address, CIDR or valid hostname</p> Source code in <code>kanidm/types.py</code> <pre><code>@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</code></pre>"},{"location":"tokenstore/","title":"Token Storage","text":"<p>User Auth Token related widgets</p>"},{"location":"tokenstore/#kanidm.tokens.JWS","title":"<code>JWS</code>","text":"<p>JWS parser</p> Source code in <code>kanidm/tokens.py</code> <pre><code>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</code></pre>"},{"location":"tokenstore/#kanidm.tokens.JWS.__init__","title":"<code>__init__(raw)</code>","text":"<p>raw is the raw string version of the JWS</p> Source code in <code>kanidm/tokens.py</code> <pre><code>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</code></pre>"},{"location":"tokenstore/#kanidm.tokens.JWS.parse","title":"<code>parse(raw)</code> <code>classmethod</code>","text":"<p>parse a raw JWS</p> Source code in <code>kanidm/tokens.py</code> <pre><code>@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</code></pre>"},{"location":"tokenstore/#kanidm.tokens.JWSHeader","title":"<code>JWSHeader</code>","text":"<p> Bases: <code>BaseModel</code></p> <p>JWS Header Parser</p> Source code in <code>kanidm/tokens.py</code> <pre><code>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</code></pre>"},{"location":"tokenstore/#kanidm.tokens.JWSHeader.Config","title":"<code>Config</code>","text":"<p>Configure the pydantic class</p> Source code in <code>kanidm/tokens.py</code> <pre><code>class Config:\n\"\"\"Configure the pydantic class\"\"\"\n\n arbitrary_types_allowed = True\n</code></pre>"},{"location":"tokenstore/#kanidm.tokens.JWSHeader.JWSHeaderJWK","title":"<code>JWSHeaderJWK</code>","text":"<p> Bases: <code>BaseModel</code></p> <p>JWS Header Sub-bit</p> Source code in <code>kanidm/tokens.py</code> <pre><code>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</code></pre>"},{"location":"tokenstore/#kanidm.tokens.JWSPayload","title":"<code>JWSPayload</code>","text":"<p> Bases: <code>BaseModel</code></p> <p>JWS Payload parser</p> Source code in <code>kanidm/tokens.py</code> <pre><code>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</code></pre>"},{"location":"tokenstore/#kanidm.tokens.JWSPayload.expiry_datetime","title":"<code>expiry_datetime: datetime</code> <code>property</code>","text":"<p>parse the expiry and return a datetime object</p>"},{"location":"tokenstore/#kanidm.tokens.TokenStore","title":"<code>TokenStore</code>","text":"<p> Bases: <code>BaseModel</code></p> <p>Represents the user auth tokens, can load them from the user store</p> Source code in <code>kanidm/tokens.py</code> <pre><code>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</code></pre>"},{"location":"tokenstore/#kanidm.tokens.TokenStore.__delitem__","title":"<code>__delitem__(item)</code>","text":"<p>overloading the default function</p> Source code in <code>kanidm/tokens.py</code> <pre><code>def __delitem__(self, item: str) -> None:\n\"\"\"overloading the default function\"\"\"\n del self.__root__[item]\n</code></pre>"},{"location":"tokenstore/#kanidm.tokens.TokenStore.__getitem__","title":"<code>__getitem__(item)</code>","text":"<p>overloading the default function</p> Source code in <code>kanidm/tokens.py</code> <pre><code>def __getitem__(self, item: str) -> str:\n\"\"\"overloading the default function\"\"\"\n return self.__root__[item]\n</code></pre>"},{"location":"tokenstore/#kanidm.tokens.TokenStore.__iter__","title":"<code>__iter__()</code>","text":"<p>overloading the default function</p> Source code in <code>kanidm/tokens.py</code> <pre><code>def __iter__(self) -> Any:\n\"\"\"overloading the default function\"\"\"\n for key in self.__root__.keys():\n yield key\n</code></pre>"},{"location":"tokenstore/#kanidm.tokens.TokenStore.__setitem__","title":"<code>__setitem__(key, value)</code>","text":"<p>overloading the default function</p> Source code in <code>kanidm/tokens.py</code> <pre><code>def __setitem__(self, key: str, value: str) -> None:\n\"\"\"overloading the default function\"\"\"\n self.__root__[key] = value\n</code></pre>"},{"location":"tokenstore/#kanidm.tokens.TokenStore.load","title":"<code>load(overwrite=True, filepath=TOKEN_PATH)</code>","text":"<p>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.</p> <p>Will return the current cached store.</p> <p>If overwrite=False, then it will add them to the existing in-memory store</p> Source code in <code>kanidm/tokens.py</code> <pre><code>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</code></pre>"},{"location":"tokenstore/#kanidm.tokens.TokenStore.save","title":"<code>save(filepath=TOKEN_PATH)</code>","text":"<p>saves the cached tokens to disk</p> Source code in <code>kanidm/tokens.py</code> <pre><code>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</code></pre>"},{"location":"tokenstore/#kanidm.tokens.TokenStore.token_info","title":"<code>token_info(username)</code>","text":"<p>grabs a token and returns a complex object object</p> Source code in <code>kanidm/tokens.py</code> <pre><code>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</code></pre>"},{"location":"tokenstore/#kanidm.tokens.TokenStore.validate_tokens","title":"<code>validate_tokens()</code>","text":"<p>validates the JWS tokens for format, not their signature - PRs welcome</p> Source code in <code>kanidm/tokens.py</code> <pre><code>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</code></pre>"}]} |