{"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
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 inkanidm/__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 inkanidm/__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 inkanidm/__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 inkanidm/__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 inkanidm/__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 inkanidm/__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 inkanidm/__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 inkanidm/__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 inkanidm/__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 inkanidm/__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 inkanidm/__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 inkanidm/__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 inkanidm/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 inkanidm/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 inkanidm/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 inkanidm/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 inkanidm/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 inkanidm/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 inkanidm/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 inkanidm/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 inkanidm/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 inkanidm/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 inkanidm/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 inkanidm/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 inkanidm/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 inkanidm/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 inkanidm/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 inkanidm/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 inkanidm/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 inkanidm/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 inkanidm/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 inkanidm/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 inkanidm/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
"}]}