{"config":{"indexing":"full","lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"kanidm A Python module for interacting with Kanidm. Currently in very very very early beta, please log an issue for feature requests and bugs. Installation python -m pip install kanidm Documentation Documentation can be generated by cloning the repository and running make docs/pykanidm/build . The documentation will appear in ./pykanidm/site . You'll need make and the poetry package installed. Testing Set up your dev environment using poetry - python -m pip install poetry && poetry install . Pytest it used for testing, if you don't have a live server to test against and config set up, use poetry run pytest -m 'not network' . Changelog Version Date Notes 0.0.1 2022-08-16 Initial release 0.0.2 2022-08-16 Updated license, including test code in package 0.0.3 2022-08-17 Updated test suite to allow skipping of network tests","title":"Home"},{"location":"#kanidm","text":"A Python module for interacting with Kanidm. Currently in very very very early beta, please log an issue for feature requests and bugs.","title":"kanidm"},{"location":"#installation","text":"python -m pip install kanidm","title":"Installation"},{"location":"#documentation","text":"Documentation can be generated by cloning the repository and running make docs/pykanidm/build . The documentation will appear in ./pykanidm/site . You'll need make and the poetry package installed.","title":"Documentation"},{"location":"#testing","text":"Set up your dev environment using poetry - python -m pip install poetry && poetry install . Pytest it used for testing, if you don't have a live server to test against and config set up, use poetry run pytest -m 'not network' .","title":"Testing"},{"location":"#changelog","text":"Version Date Notes 0.0.1 2022-08-16 Initial release 0.0.2 2022-08-16 Updated license, including test code in package 0.0.3 2022-08-17 Updated test suite to allow skipping of network tests","title":"Changelog"},{"location":"kanidmclient/","text":"kanidm.KanidmClient 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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 class KanidmClient : \"\"\"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 \"\"\" # pylint: disable=too-many-instance-attributes,too-many-arguments def __init__ ( self , config : Optional [ KanidmClientConfig ] = None , config_file : Optional [ Union [ Path , str ]] = None , uri : Optional [ str ] = None , verify_hostnames : bool = True , verify_certificate : bool = True , ca_path : Optional [ str ] = None , token : Optional [ str ] = None , ) -> None : \"\"\"Constructor for KanidmClient\"\"\" if config is not None : self . config = config else : self . config = KanidmClientConfig ( uri = uri , verify_hostnames = verify_hostnames , verify_certificate = verify_certificate , ca_path = ca_path , auth_token = token , ) if config_file is not None : if not isinstance ( config_file , Path ): config_file = Path ( config_file ) config_data = load_config ( config_file . expanduser () . resolve ()) self . config = self . config . parse_obj ( config_data ) if self . config . uri is None : raise ValueError ( \"Please intitialize this with a server URI\" ) self . _ssl : Optional [ Union [ bool , ssl . SSLContext ]] = None self . _configure_ssl () def _configure_ssl ( self ) -> None : \"\"\"Sets up SSL configuration for the client\"\"\" if self . config . verify_certificate is False : self . _ssl = False else : if ( self . config . ca_path is not None and not Path ( self . config . ca_path ) . expanduser () . resolve () . exists () ): raise FileNotFoundError ( f \"CA Path not found: { self . config . ca_path } \" ) self . _ssl = ssl . create_default_context ( cafile = self . config . ca_path ) if self . _ssl is not False : # ignoring this for typing because mypy is being weird # ssl.SSLContext.check_hostname is totally a thing # https://docs.python.org/3/library/ssl.html#ssl.SSLContext.check_hostname self . _ssl . check_hostname = self . config . verify_hostnames # type: ignore def parse_config_data ( self , config_data : Dict [ str , Any ], ) -> None : \"\"\"hand it a config dict and it'll configure the client\"\"\" try : self . config . parse_obj ( config_data ) except ValidationError as validation_error : # pylint: disable=raise-missing-from raise ValueError ( f \"Failed to validate configuration: { validation_error } \" ) async def check_token_valid ( self , token : Optional [ str ] = None ) -> bool : \"\"\"checks if a given token is valid, or the local one if you don't pass it\"\"\" url = \"/v1/auth/valid\" if token is not None : headers = { \"authorization\" : f \"Bearer { token } \" , \"content-type\" : \"application/json\" , } else : headers = None result = await self . call_get ( url , headers = headers ) logging . debug ( result ) if result . status_code == 200 : return True return False @lru_cache () def get_path_uri ( self , path : str ) -> str : \"\"\"turns a path into a full URI\"\"\" if path . startswith ( \"/\" ): path = path [ 1 :] return f \" { self . config . uri }{ path } \" @property def _token_headers ( self ) -> Dict [ str , str ]: \"\"\"returns an auth header with the token in it\"\"\" if self . config . auth_token is None : raise ValueError ( \"Token is not set\" ) return { \"authorization\" : f \"Bearer { self . config . auth_token } \" } # pylint: disable=too-many-arguments async def _call ( self , method : str , path : str , headers : Optional [ Dict [ str , str ]] = None , timeout : Optional [ int ] = None , json : Optional [ Dict [ str , str ]] = None , params : Optional [ Dict [ str , str ]] = None , ) -> ClientResponse : if timeout is None : timeout = self . config . connect_timeout async with aiohttp . client . ClientSession () as session : # if we have a token set, we send it. if self . config . auth_token is not None : logging . debug ( \"Found a token internally\" ) if headers is None : headers = self . _token_headers elif \"authorization\" not in headers : logging . debug ( \"Setting auth headers as Authorization not in keys\" ) headers . update ( self . _token_headers ) logging . debug ( \"_call method= %s to %s \" , method , self . get_path_uri ( path )) async with session . request ( method = method , url = self . get_path_uri ( path ), headers = headers , timeout = timeout , json = json , params = params , ssl = self . _ssl , ) as request : content = await request . content . read () try : response_json = json_lib . loads ( content ) if not isinstance ( response_json , dict ): response_json = None except json_lib . JSONDecodeError as json_error : logging . error ( \"Failed to JSON Decode Response: %s \" , json_error ) logging . error ( \"Response data: %s \" , content ) response_json = {} response_input = { \"data\" : response_json , \"content\" : content . decode ( \"utf-8\" ), \"headers\" : request . headers , \"status_code\" : request . status , } logging . debug ( json_lib . dumps ( response_input , default = str , indent = 4 )) response = ClientResponse . parse_obj ( response_input ) return response async def call_get ( self , path : str , headers : Optional [ Dict [ str , str ]] = None , params : Optional [ Dict [ str , str ]] = None , timeout : Optional [ int ] = None , ) -> ClientResponse : \"\"\"does a get call to the server\"\"\" return await self . _call ( \"GET\" , path , headers , timeout , params = params ) async def call_post ( self , path : str , headers : Optional [ Dict [ str , str ]] = None , json : Optional [ Dict [ str , Any ]] = None , timeout : Optional [ int ] = None , ) -> ClientResponse : \"\"\"does a get call to the server\"\"\" return await self . _call ( method = \"POST\" , path = path , headers = headers , json = json , timeout = timeout ) async def auth_init ( self , username : str ) -> AuthInitResponse : \"\"\"init step, starts the auth session, sets the class-local session ID\"\"\" init_auth = { \"step\" : { \"init\" : username }} response = await self . call_post ( path = KANIDMURLS [ \"auth\" ], json = init_auth , ) if response . status_code != 200 : logging . debug ( \"Failed to authenticate, response from server: %s \" , response . content , ) # TODO: mock test auth_init raises AuthInitFailed raise AuthInitFailed ( response . content ) if \"x-kanidm-auth-session-id\" not in response . headers : logging . debug ( \"response.content: %s \" , response . content ) logging . debug ( \"response.headers: %s \" , response . headers ) raise ValueError ( f \"Missing x-kanidm-auth-session-id header in init auth response: { response . headers } \" ) retval = AuthInitResponse . parse_obj ( response . data ) retval . response = response return retval async def auth_begin ( self , method : str , sessionid : str ) -> ClientResponse : \"\"\"the 'begin' step\"\"\" begin_auth = { \"step\" : { \"begin\" : method , }, } headers = self . session_header ( sessionid ) response = await self . call_post ( KANIDMURLS [ \"auth\" ], json = begin_auth , headers = headers , ) if response . status_code != 200 : # TODO: mock test for auth_begin raises AuthBeginFailed raise AuthBeginFailed ( response . content ) retobject = AuthBeginResponse . parse_obj ( response . data ) retobject . response = response return response async def authenticate_password ( self , username : Optional [ str ] = None , password : Optional [ str ] = None , ) -> AuthStepPasswordResponse : \"\"\"authenticates with a username and password, returns the auth token\"\"\" if username is None and password is None : if self . config . username is None or self . config . password is None : # pylint: disable=line-too-long raise ValueError ( \"Need username/password to be in caller or class settings before calling authenticate_password\" ) username = self . config . username password = self . config . password if username is None or password is None : raise ValueError ( \"Username and Password need to be set somewhere!\" ) auth_init : AuthInitResponse = await self . auth_init ( username ) if auth_init . response is None : raise NotImplementedError ( \"This should throw a really cool response\" ) sessionid = auth_init . response . headers [ \"x-kanidm-auth-session-id\" ] if len ( auth_init . state . choose ) == 0 : # there's no mechanisms at all - bail # TODO: write test coverage for authenticate_password raises AuthMechUnknown raise AuthMechUnknown ( f \"No auth mechanisms for { username } \" ) auth_begin = await self . auth_begin ( method = \"password\" , sessionid = sessionid ) # does a little bit of validation auth_begin_object = AuthBeginResponse . parse_obj ( auth_begin . data ) auth_begin_object . response = auth_begin return await self . auth_step_password ( password = password , sessionid = sessionid ) async def auth_step_password ( self , sessionid : str , password : Optional [ str ] = None , ) -> AuthStepPasswordResponse : \"\"\"does the password auth step\"\"\" if password is None : password = self . config . password if password is None : raise ValueError ( \"Password has to be passed to auth_step_password or in self.password!\" ) cred_auth = { \"step\" : { \"cred\" : { \"password\" : password }}} response = await self . call_post ( path = \"/v1/auth\" , json = cred_auth , headers = self . session_header ( sessionid ) ) if response . status_code != 200 : # TODO: write test coverage auth_step_password raises AuthCredFailed logging . debug ( \"Failed to authenticate, response: %s \" , response . content ) raise AuthCredFailed ( \"Failed password authentication!\" ) result = AuthStepPasswordResponse . parse_obj ( response . data ) result . response = response # pull the token out and set it if result . state . success is None : # TODO: write test coverage for AuthCredFailed raise AuthCredFailed result . sessionid = result . state . success return result def session_header ( self , sessionid : str , ) -> Dict [ str , str ]: \"\"\"create a headers dict from a session id\"\"\" # TODO: perhaps allow session_header to take a dict and update it, too? return { \"X-KANIDM-AUTH-SESSION-ID\" : sessionid , } async def get_radius_token ( self , username : str ) -> ClientResponse : \"\"\"does the call to the radius token endpoint\"\"\" path = f \"/v1/account/ { username } /_radius/_token\" response = await self . call_get ( path ) if response . status_code == 404 : raise NoMatchingEntries ( f \"No user found: ' { username } ' { response . headers [ 'x-kanidm-opid' ] } \" ) return response __init__ ( config = None , config_file = None , uri = None , verify_hostnames = True , verify_certificate = True , ca_path = None , token = None ) Constructor for KanidmClient Source code in kanidm/__init__.py 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 def __init__ ( self , config : Optional [ KanidmClientConfig ] = None , config_file : Optional [ Union [ Path , str ]] = None , uri : Optional [ str ] = None , verify_hostnames : bool = True , verify_certificate : bool = True , ca_path : Optional [ str ] = None , token : Optional [ str ] = None , ) -> None : \"\"\"Constructor for KanidmClient\"\"\" if config is not None : self . config = config else : self . config = KanidmClientConfig ( uri = uri , verify_hostnames = verify_hostnames , verify_certificate = verify_certificate , ca_path = ca_path , auth_token = token , ) if config_file is not None : if not isinstance ( config_file , Path ): config_file = Path ( config_file ) config_data = load_config ( config_file . expanduser () . resolve ()) self . config = self . config . parse_obj ( config_data ) if self . config . uri is None : raise ValueError ( \"Please intitialize this with a server URI\" ) self . _ssl : Optional [ Union [ bool , ssl . SSLContext ]] = None self . _configure_ssl () auth_begin ( method , sessionid ) async the 'begin' step Source code in kanidm/__init__.py 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 async def auth_begin ( self , method : str , sessionid : str ) -> ClientResponse : \"\"\"the 'begin' step\"\"\" begin_auth = { \"step\" : { \"begin\" : method , }, } headers = self . session_header ( sessionid ) response = await self . call_post ( KANIDMURLS [ \"auth\" ], json = begin_auth , headers = headers , ) if response . status_code != 200 : # TODO: mock test for auth_begin raises AuthBeginFailed raise AuthBeginFailed ( response . content ) retobject = AuthBeginResponse . parse_obj ( response . data ) retobject . response = response return response auth_init ( username ) async init step, starts the auth session, sets the class-local session ID Source code in kanidm/__init__.py 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 async def auth_init ( self , username : str ) -> AuthInitResponse : \"\"\"init step, starts the auth session, sets the class-local session ID\"\"\" init_auth = { \"step\" : { \"init\" : username }} response = await self . call_post ( path = KANIDMURLS [ \"auth\" ], json = init_auth , ) if response . status_code != 200 : logging . debug ( \"Failed to authenticate, response from server: %s \" , response . content , ) # TODO: mock test auth_init raises AuthInitFailed raise AuthInitFailed ( response . content ) if \"x-kanidm-auth-session-id\" not in response . headers : logging . debug ( \"response.content: %s \" , response . content ) logging . debug ( \"response.headers: %s \" , response . headers ) raise ValueError ( f \"Missing x-kanidm-auth-session-id header in init auth response: { response . headers } \" ) retval = AuthInitResponse . parse_obj ( response . data ) retval . response = response return retval auth_step_password ( sessionid , password = None ) async does the password auth step Source code in kanidm/__init__.py 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 async def auth_step_password ( self , sessionid : str , password : Optional [ str ] = None , ) -> AuthStepPasswordResponse : \"\"\"does the password auth step\"\"\" if password is None : password = self . config . password if password is None : raise ValueError ( \"Password has to be passed to auth_step_password or in self.password!\" ) cred_auth = { \"step\" : { \"cred\" : { \"password\" : password }}} response = await self . call_post ( path = \"/v1/auth\" , json = cred_auth , headers = self . session_header ( sessionid ) ) if response . status_code != 200 : # TODO: write test coverage auth_step_password raises AuthCredFailed logging . debug ( \"Failed to authenticate, response: %s \" , response . content ) raise AuthCredFailed ( \"Failed password authentication!\" ) result = AuthStepPasswordResponse . parse_obj ( response . data ) result . response = response # pull the token out and set it if result . state . success is None : # TODO: write test coverage for AuthCredFailed raise AuthCredFailed result . sessionid = result . state . success return result authenticate_password ( username = None , password = None ) async authenticates with a username and password, returns the auth token Source code in kanidm/__init__.py 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 async def authenticate_password ( self , username : Optional [ str ] = None , password : Optional [ str ] = None , ) -> AuthStepPasswordResponse : \"\"\"authenticates with a username and password, returns the auth token\"\"\" if username is None and password is None : if self . config . username is None or self . config . password is None : # pylint: disable=line-too-long raise ValueError ( \"Need username/password to be in caller or class settings before calling authenticate_password\" ) username = self . config . username password = self . config . password if username is None or password is None : raise ValueError ( \"Username and Password need to be set somewhere!\" ) auth_init : AuthInitResponse = await self . auth_init ( username ) if auth_init . response is None : raise NotImplementedError ( \"This should throw a really cool response\" ) sessionid = auth_init . response . headers [ \"x-kanidm-auth-session-id\" ] if len ( auth_init . state . choose ) == 0 : # there's no mechanisms at all - bail # TODO: write test coverage for authenticate_password raises AuthMechUnknown raise AuthMechUnknown ( f \"No auth mechanisms for { username } \" ) auth_begin = await self . auth_begin ( method = \"password\" , sessionid = sessionid ) # does a little bit of validation auth_begin_object = AuthBeginResponse . parse_obj ( auth_begin . data ) auth_begin_object . response = auth_begin return await self . auth_step_password ( password = password , sessionid = sessionid ) call_get ( path , headers = None , params = None , timeout = None ) async does a get call to the server Source code in kanidm/__init__.py 197 198 199 200 201 202 203 204 205 async def call_get ( self , path : str , headers : Optional [ Dict [ str , str ]] = None , params : Optional [ Dict [ str , str ]] = None , timeout : Optional [ int ] = None , ) -> ClientResponse : \"\"\"does a get call to the server\"\"\" return await self . _call ( \"GET\" , path , headers , timeout , params = params ) call_post ( path , headers = None , json = None , timeout = None ) async does a get call to the server Source code in kanidm/__init__.py 207 208 209 210 211 212 213 214 215 216 217 218 async def call_post ( self , path : str , headers : Optional [ Dict [ str , str ]] = None , json : Optional [ Dict [ str , Any ]] = None , timeout : Optional [ int ] = None , ) -> ClientResponse : \"\"\"does a get call to the server\"\"\" return await self . _call ( method = \"POST\" , path = path , headers = headers , json = json , timeout = timeout ) check_token_valid ( token = None ) async checks if a given token is valid, or the local one if you don't pass it Source code in kanidm/__init__.py 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 async def check_token_valid ( self , token : Optional [ str ] = None ) -> bool : \"\"\"checks if a given token is valid, or the local one if you don't pass it\"\"\" url = \"/v1/auth/valid\" if token is not None : headers = { \"authorization\" : f \"Bearer { token } \" , \"content-type\" : \"application/json\" , } else : headers = None result = await self . call_get ( url , headers = headers ) logging . debug ( result ) if result . status_code == 200 : return True return False get_path_uri ( path ) cached turns a path into a full URI Source code in kanidm/__init__.py 132 133 134 135 136 137 @lru_cache () def get_path_uri ( self , path : str ) -> str : \"\"\"turns a path into a full URI\"\"\" if path . startswith ( \"/\" ): path = path [ 1 :] return f \" { self . config . uri }{ path } \" get_radius_token ( username ) async does the call to the radius token endpoint Source code in kanidm/__init__.py 347 348 349 350 351 352 353 354 355 async def get_radius_token ( self , username : str ) -> ClientResponse : \"\"\"does the call to the radius token endpoint\"\"\" path = f \"/v1/account/ { username } /_radius/_token\" response = await self . call_get ( path ) if response . status_code == 404 : raise NoMatchingEntries ( f \"No user found: ' { username } ' { response . headers [ 'x-kanidm-opid' ] } \" ) return response parse_config_data ( config_data ) hand it a config dict and it'll configure the client Source code in kanidm/__init__.py 105 106 107 108 109 110 111 112 113 114 def parse_config_data ( self , config_data : Dict [ str , Any ], ) -> None : \"\"\"hand it a config dict and it'll configure the client\"\"\" try : self . config . parse_obj ( config_data ) except ValidationError as validation_error : # pylint: disable=raise-missing-from raise ValueError ( f \"Failed to validate configuration: { validation_error } \" ) session_header ( sessionid ) create a headers dict from a session id Source code in kanidm/__init__.py 337 338 339 340 341 342 343 344 345 def session_header ( self , sessionid : str , ) -> Dict [ str , str ]: \"\"\"create a headers dict from a session id\"\"\" # TODO: perhaps allow session_header to take a dict and update it, too? return { \"X-KANIDM-AUTH-SESSION-ID\" : sessionid , }","title":"Client"},{"location":"kanidmclient/#kanidmkanidmclient","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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 class KanidmClient : \"\"\"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 \"\"\" # pylint: disable=too-many-instance-attributes,too-many-arguments def __init__ ( self , config : Optional [ KanidmClientConfig ] = None , config_file : Optional [ Union [ Path , str ]] = None , uri : Optional [ str ] = None , verify_hostnames : bool = True , verify_certificate : bool = True , ca_path : Optional [ str ] = None , token : Optional [ str ] = None , ) -> None : \"\"\"Constructor for KanidmClient\"\"\" if config is not None : self . config = config else : self . config = KanidmClientConfig ( uri = uri , verify_hostnames = verify_hostnames , verify_certificate = verify_certificate , ca_path = ca_path , auth_token = token , ) if config_file is not None : if not isinstance ( config_file , Path ): config_file = Path ( config_file ) config_data = load_config ( config_file . expanduser () . resolve ()) self . config = self . config . parse_obj ( config_data ) if self . config . uri is None : raise ValueError ( \"Please intitialize this with a server URI\" ) self . _ssl : Optional [ Union [ bool , ssl . SSLContext ]] = None self . _configure_ssl () def _configure_ssl ( self ) -> None : \"\"\"Sets up SSL configuration for the client\"\"\" if self . config . verify_certificate is False : self . _ssl = False else : if ( self . config . ca_path is not None and not Path ( self . config . ca_path ) . expanduser () . resolve () . exists () ): raise FileNotFoundError ( f \"CA Path not found: { self . config . ca_path } \" ) self . _ssl = ssl . create_default_context ( cafile = self . config . ca_path ) if self . _ssl is not False : # ignoring this for typing because mypy is being weird # ssl.SSLContext.check_hostname is totally a thing # https://docs.python.org/3/library/ssl.html#ssl.SSLContext.check_hostname self . _ssl . check_hostname = self . config . verify_hostnames # type: ignore def parse_config_data ( self , config_data : Dict [ str , Any ], ) -> None : \"\"\"hand it a config dict and it'll configure the client\"\"\" try : self . config . parse_obj ( config_data ) except ValidationError as validation_error : # pylint: disable=raise-missing-from raise ValueError ( f \"Failed to validate configuration: { validation_error } \" ) async def check_token_valid ( self , token : Optional [ str ] = None ) -> bool : \"\"\"checks if a given token is valid, or the local one if you don't pass it\"\"\" url = \"/v1/auth/valid\" if token is not None : headers = { \"authorization\" : f \"Bearer { token } \" , \"content-type\" : \"application/json\" , } else : headers = None result = await self . call_get ( url , headers = headers ) logging . debug ( result ) if result . status_code == 200 : return True return False @lru_cache () def get_path_uri ( self , path : str ) -> str : \"\"\"turns a path into a full URI\"\"\" if path . startswith ( \"/\" ): path = path [ 1 :] return f \" { self . config . uri }{ path } \" @property def _token_headers ( self ) -> Dict [ str , str ]: \"\"\"returns an auth header with the token in it\"\"\" if self . config . auth_token is None : raise ValueError ( \"Token is not set\" ) return { \"authorization\" : f \"Bearer { self . config . auth_token } \" } # pylint: disable=too-many-arguments async def _call ( self , method : str , path : str , headers : Optional [ Dict [ str , str ]] = None , timeout : Optional [ int ] = None , json : Optional [ Dict [ str , str ]] = None , params : Optional [ Dict [ str , str ]] = None , ) -> ClientResponse : if timeout is None : timeout = self . config . connect_timeout async with aiohttp . client . ClientSession () as session : # if we have a token set, we send it. if self . config . auth_token is not None : logging . debug ( \"Found a token internally\" ) if headers is None : headers = self . _token_headers elif \"authorization\" not in headers : logging . debug ( \"Setting auth headers as Authorization not in keys\" ) headers . update ( self . _token_headers ) logging . debug ( \"_call method= %s to %s \" , method , self . get_path_uri ( path )) async with session . request ( method = method , url = self . get_path_uri ( path ), headers = headers , timeout = timeout , json = json , params = params , ssl = self . _ssl , ) as request : content = await request . content . read () try : response_json = json_lib . loads ( content ) if not isinstance ( response_json , dict ): response_json = None except json_lib . JSONDecodeError as json_error : logging . error ( \"Failed to JSON Decode Response: %s \" , json_error ) logging . error ( \"Response data: %s \" , content ) response_json = {} response_input = { \"data\" : response_json , \"content\" : content . decode ( \"utf-8\" ), \"headers\" : request . headers , \"status_code\" : request . status , } logging . debug ( json_lib . dumps ( response_input , default = str , indent = 4 )) response = ClientResponse . parse_obj ( response_input ) return response async def call_get ( self , path : str , headers : Optional [ Dict [ str , str ]] = None , params : Optional [ Dict [ str , str ]] = None , timeout : Optional [ int ] = None , ) -> ClientResponse : \"\"\"does a get call to the server\"\"\" return await self . _call ( \"GET\" , path , headers , timeout , params = params ) async def call_post ( self , path : str , headers : Optional [ Dict [ str , str ]] = None , json : Optional [ Dict [ str , Any ]] = None , timeout : Optional [ int ] = None , ) -> ClientResponse : \"\"\"does a get call to the server\"\"\" return await self . _call ( method = \"POST\" , path = path , headers = headers , json = json , timeout = timeout ) async def auth_init ( self , username : str ) -> AuthInitResponse : \"\"\"init step, starts the auth session, sets the class-local session ID\"\"\" init_auth = { \"step\" : { \"init\" : username }} response = await self . call_post ( path = KANIDMURLS [ \"auth\" ], json = init_auth , ) if response . status_code != 200 : logging . debug ( \"Failed to authenticate, response from server: %s \" , response . content , ) # TODO: mock test auth_init raises AuthInitFailed raise AuthInitFailed ( response . content ) if \"x-kanidm-auth-session-id\" not in response . headers : logging . debug ( \"response.content: %s \" , response . content ) logging . debug ( \"response.headers: %s \" , response . headers ) raise ValueError ( f \"Missing x-kanidm-auth-session-id header in init auth response: { response . headers } \" ) retval = AuthInitResponse . parse_obj ( response . data ) retval . response = response return retval async def auth_begin ( self , method : str , sessionid : str ) -> ClientResponse : \"\"\"the 'begin' step\"\"\" begin_auth = { \"step\" : { \"begin\" : method , }, } headers = self . session_header ( sessionid ) response = await self . call_post ( KANIDMURLS [ \"auth\" ], json = begin_auth , headers = headers , ) if response . status_code != 200 : # TODO: mock test for auth_begin raises AuthBeginFailed raise AuthBeginFailed ( response . content ) retobject = AuthBeginResponse . parse_obj ( response . data ) retobject . response = response return response async def authenticate_password ( self , username : Optional [ str ] = None , password : Optional [ str ] = None , ) -> AuthStepPasswordResponse : \"\"\"authenticates with a username and password, returns the auth token\"\"\" if username is None and password is None : if self . config . username is None or self . config . password is None : # pylint: disable=line-too-long raise ValueError ( \"Need username/password to be in caller or class settings before calling authenticate_password\" ) username = self . config . username password = self . config . password if username is None or password is None : raise ValueError ( \"Username and Password need to be set somewhere!\" ) auth_init : AuthInitResponse = await self . auth_init ( username ) if auth_init . response is None : raise NotImplementedError ( \"This should throw a really cool response\" ) sessionid = auth_init . response . headers [ \"x-kanidm-auth-session-id\" ] if len ( auth_init . state . choose ) == 0 : # there's no mechanisms at all - bail # TODO: write test coverage for authenticate_password raises AuthMechUnknown raise AuthMechUnknown ( f \"No auth mechanisms for { username } \" ) auth_begin = await self . auth_begin ( method = \"password\" , sessionid = sessionid ) # does a little bit of validation auth_begin_object = AuthBeginResponse . parse_obj ( auth_begin . data ) auth_begin_object . response = auth_begin return await self . auth_step_password ( password = password , sessionid = sessionid ) async def auth_step_password ( self , sessionid : str , password : Optional [ str ] = None , ) -> AuthStepPasswordResponse : \"\"\"does the password auth step\"\"\" if password is None : password = self . config . password if password is None : raise ValueError ( \"Password has to be passed to auth_step_password or in self.password!\" ) cred_auth = { \"step\" : { \"cred\" : { \"password\" : password }}} response = await self . call_post ( path = \"/v1/auth\" , json = cred_auth , headers = self . session_header ( sessionid ) ) if response . status_code != 200 : # TODO: write test coverage auth_step_password raises AuthCredFailed logging . debug ( \"Failed to authenticate, response: %s \" , response . content ) raise AuthCredFailed ( \"Failed password authentication!\" ) result = AuthStepPasswordResponse . parse_obj ( response . data ) result . response = response # pull the token out and set it if result . state . success is None : # TODO: write test coverage for AuthCredFailed raise AuthCredFailed result . sessionid = result . state . success return result def session_header ( self , sessionid : str , ) -> Dict [ str , str ]: \"\"\"create a headers dict from a session id\"\"\" # TODO: perhaps allow session_header to take a dict and update it, too? return { \"X-KANIDM-AUTH-SESSION-ID\" : sessionid , } async def get_radius_token ( self , username : str ) -> ClientResponse : \"\"\"does the call to the radius token endpoint\"\"\" path = f \"/v1/account/ { username } /_radius/_token\" response = await self . call_get ( path ) if response . status_code == 404 : raise NoMatchingEntries ( f \"No user found: ' { username } ' { response . headers [ 'x-kanidm-opid' ] } \" ) return response","title":"kanidm.KanidmClient"},{"location":"kanidmclient/#kanidm.KanidmClient.__init__","text":"Constructor for KanidmClient Source code in kanidm/__init__.py 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 def __init__ ( self , config : Optional [ KanidmClientConfig ] = None , config_file : Optional [ Union [ Path , str ]] = None , uri : Optional [ str ] = None , verify_hostnames : bool = True , verify_certificate : bool = True , ca_path : Optional [ str ] = None , token : Optional [ str ] = None , ) -> None : \"\"\"Constructor for KanidmClient\"\"\" if config is not None : self . config = config else : self . config = KanidmClientConfig ( uri = uri , verify_hostnames = verify_hostnames , verify_certificate = verify_certificate , ca_path = ca_path , auth_token = token , ) if config_file is not None : if not isinstance ( config_file , Path ): config_file = Path ( config_file ) config_data = load_config ( config_file . expanduser () . resolve ()) self . config = self . config . parse_obj ( config_data ) if self . config . uri is None : raise ValueError ( \"Please intitialize this with a server URI\" ) self . _ssl : Optional [ Union [ bool , ssl . SSLContext ]] = None self . _configure_ssl ()","title":"__init__()"},{"location":"kanidmclient/#kanidm.KanidmClient.auth_begin","text":"the 'begin' step Source code in kanidm/__init__.py 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 async def auth_begin ( self , method : str , sessionid : str ) -> ClientResponse : \"\"\"the 'begin' step\"\"\" begin_auth = { \"step\" : { \"begin\" : method , }, } headers = self . session_header ( sessionid ) response = await self . call_post ( KANIDMURLS [ \"auth\" ], json = begin_auth , headers = headers , ) if response . status_code != 200 : # TODO: mock test for auth_begin raises AuthBeginFailed raise AuthBeginFailed ( response . content ) retobject = AuthBeginResponse . parse_obj ( response . data ) retobject . response = response return response","title":"auth_begin()"},{"location":"kanidmclient/#kanidm.KanidmClient.auth_init","text":"init step, starts the auth session, sets the class-local session ID Source code in kanidm/__init__.py 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 async def auth_init ( self , username : str ) -> AuthInitResponse : \"\"\"init step, starts the auth session, sets the class-local session ID\"\"\" init_auth = { \"step\" : { \"init\" : username }} response = await self . call_post ( path = KANIDMURLS [ \"auth\" ], json = init_auth , ) if response . status_code != 200 : logging . debug ( \"Failed to authenticate, response from server: %s \" , response . content , ) # TODO: mock test auth_init raises AuthInitFailed raise AuthInitFailed ( response . content ) if \"x-kanidm-auth-session-id\" not in response . headers : logging . debug ( \"response.content: %s \" , response . content ) logging . debug ( \"response.headers: %s \" , response . headers ) raise ValueError ( f \"Missing x-kanidm-auth-session-id header in init auth response: { response . headers } \" ) retval = AuthInitResponse . parse_obj ( response . data ) retval . response = response return retval","title":"auth_init()"},{"location":"kanidmclient/#kanidm.KanidmClient.auth_step_password","text":"does the password auth step Source code in kanidm/__init__.py 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 async def auth_step_password ( self , sessionid : str , password : Optional [ str ] = None , ) -> AuthStepPasswordResponse : \"\"\"does the password auth step\"\"\" if password is None : password = self . config . password if password is None : raise ValueError ( \"Password has to be passed to auth_step_password or in self.password!\" ) cred_auth = { \"step\" : { \"cred\" : { \"password\" : password }}} response = await self . call_post ( path = \"/v1/auth\" , json = cred_auth , headers = self . session_header ( sessionid ) ) if response . status_code != 200 : # TODO: write test coverage auth_step_password raises AuthCredFailed logging . debug ( \"Failed to authenticate, response: %s \" , response . content ) raise AuthCredFailed ( \"Failed password authentication!\" ) result = AuthStepPasswordResponse . parse_obj ( response . data ) result . response = response # pull the token out and set it if result . state . success is None : # TODO: write test coverage for AuthCredFailed raise AuthCredFailed result . sessionid = result . state . success return result","title":"auth_step_password()"},{"location":"kanidmclient/#kanidm.KanidmClient.authenticate_password","text":"authenticates with a username and password, returns the auth token Source code in kanidm/__init__.py 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 async def authenticate_password ( self , username : Optional [ str ] = None , password : Optional [ str ] = None , ) -> AuthStepPasswordResponse : \"\"\"authenticates with a username and password, returns the auth token\"\"\" if username is None and password is None : if self . config . username is None or self . config . password is None : # pylint: disable=line-too-long raise ValueError ( \"Need username/password to be in caller or class settings before calling authenticate_password\" ) username = self . config . username password = self . config . password if username is None or password is None : raise ValueError ( \"Username and Password need to be set somewhere!\" ) auth_init : AuthInitResponse = await self . auth_init ( username ) if auth_init . response is None : raise NotImplementedError ( \"This should throw a really cool response\" ) sessionid = auth_init . response . headers [ \"x-kanidm-auth-session-id\" ] if len ( auth_init . state . choose ) == 0 : # there's no mechanisms at all - bail # TODO: write test coverage for authenticate_password raises AuthMechUnknown raise AuthMechUnknown ( f \"No auth mechanisms for { username } \" ) auth_begin = await self . auth_begin ( method = \"password\" , sessionid = sessionid ) # does a little bit of validation auth_begin_object = AuthBeginResponse . parse_obj ( auth_begin . data ) auth_begin_object . response = auth_begin return await self . auth_step_password ( password = password , sessionid = sessionid )","title":"authenticate_password()"},{"location":"kanidmclient/#kanidm.KanidmClient.call_get","text":"does a get call to the server Source code in kanidm/__init__.py 197 198 199 200 201 202 203 204 205 async def call_get ( self , path : str , headers : Optional [ Dict [ str , str ]] = None , params : Optional [ Dict [ str , str ]] = None , timeout : Optional [ int ] = None , ) -> ClientResponse : \"\"\"does a get call to the server\"\"\" return await self . _call ( \"GET\" , path , headers , timeout , params = params )","title":"call_get()"},{"location":"kanidmclient/#kanidm.KanidmClient.call_post","text":"does a get call to the server Source code in kanidm/__init__.py 207 208 209 210 211 212 213 214 215 216 217 218 async def call_post ( self , path : str , headers : Optional [ Dict [ str , str ]] = None , json : Optional [ Dict [ str , Any ]] = None , timeout : Optional [ int ] = None , ) -> ClientResponse : \"\"\"does a get call to the server\"\"\" return await self . _call ( method = \"POST\" , path = path , headers = headers , json = json , timeout = timeout )","title":"call_post()"},{"location":"kanidmclient/#kanidm.KanidmClient.check_token_valid","text":"checks if a given token is valid, or the local one if you don't pass it Source code in kanidm/__init__.py 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 async def check_token_valid ( self , token : Optional [ str ] = None ) -> bool : \"\"\"checks if a given token is valid, or the local one if you don't pass it\"\"\" url = \"/v1/auth/valid\" if token is not None : headers = { \"authorization\" : f \"Bearer { token } \" , \"content-type\" : \"application/json\" , } else : headers = None result = await self . call_get ( url , headers = headers ) logging . debug ( result ) if result . status_code == 200 : return True return False","title":"check_token_valid()"},{"location":"kanidmclient/#kanidm.KanidmClient.get_path_uri","text":"turns a path into a full URI Source code in kanidm/__init__.py 132 133 134 135 136 137 @lru_cache () def get_path_uri ( self , path : str ) -> str : \"\"\"turns a path into a full URI\"\"\" if path . startswith ( \"/\" ): path = path [ 1 :] return f \" { self . config . uri }{ path } \"","title":"get_path_uri()"},{"location":"kanidmclient/#kanidm.KanidmClient.get_radius_token","text":"does the call to the radius token endpoint Source code in kanidm/__init__.py 347 348 349 350 351 352 353 354 355 async def get_radius_token ( self , username : str ) -> ClientResponse : \"\"\"does the call to the radius token endpoint\"\"\" path = f \"/v1/account/ { username } /_radius/_token\" response = await self . call_get ( path ) if response . status_code == 404 : raise NoMatchingEntries ( f \"No user found: ' { username } ' { response . headers [ 'x-kanidm-opid' ] } \" ) return response","title":"get_radius_token()"},{"location":"kanidmclient/#kanidm.KanidmClient.parse_config_data","text":"hand it a config dict and it'll configure the client Source code in kanidm/__init__.py 105 106 107 108 109 110 111 112 113 114 def parse_config_data ( self , config_data : Dict [ str , Any ], ) -> None : \"\"\"hand it a config dict and it'll configure the client\"\"\" try : self . config . parse_obj ( config_data ) except ValidationError as validation_error : # pylint: disable=raise-missing-from raise ValueError ( f \"Failed to validate configuration: { validation_error } \" )","title":"parse_config_data()"},{"location":"kanidmclient/#kanidm.KanidmClient.session_header","text":"create a headers dict from a session id Source code in kanidm/__init__.py 337 338 339 340 341 342 343 344 345 def session_header ( self , sessionid : str , ) -> Dict [ str , str ]: \"\"\"create a headers dict from a session id\"\"\" # TODO: perhaps allow session_header to take a dict and update it, too? return { \"X-KANIDM-AUTH-SESSION-ID\" : sessionid , }","title":"session_header()"},{"location":"kanidmclientconfig/","text":"kanidm.types.KanidmClientConfig 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 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 class KanidmClientConfig ( BaseModel ): \"\"\"Configuration file definition for Kanidm client config Based on struct KanidmClientConfig in kanidm_client/src/lib.rs See source code for fields \"\"\" uri : Optional [ str ] = None auth_token : Optional [ str ] = None verify_hostnames : bool = True verify_certificate : bool = True ca_path : Optional [ str ] = None username : Optional [ str ] = None password : Optional [ str ] = None radius_cert_path : str = \"/data/cert.pem\" radius_key_path : str = \"/data/key.pem\" # the signing key for radius TLS radius_dh_path : str = \"/data/dh.pem\" # the diffie-hellman output radius_ca_path : Optional [ str ] = None radius_ca_dir : Optional [ str ] = None radius_required_groups : List [ str ] = [] radius_default_vlan : int = 1 radius_groups : List [ RadiusGroup ] = [] radius_clients : List [ RadiusClient ] = [] connect_timeout : int = 30 @classmethod def parse_toml ( cls , input_string : str ) -> Any : \"\"\"loads from a string\"\"\" return super () . parse_obj ( toml . loads ( input_string )) @validator ( \"uri\" ) def validate_uri ( cls , value : Optional [ str ]) -> Optional [ str ]: \"\"\"validator for the uri field\"\"\" if value is not None : uri = urlparse ( value ) valid_schemes = [ \"http\" , \"https\" ] if uri . scheme not in valid_schemes : raise ValueError ( f \"Invalid URL Scheme for uri=' { value } ': ' { uri . scheme } ' - expected one of { valid_schemes } \" ) # make sure the URI ends with a / if not value . endswith ( \"/\" ): value = f \" { value } /\" return value parse_toml ( input_string ) classmethod loads from a string Source code in kanidm/types.py 198 199 200 201 @classmethod def parse_toml ( cls , input_string : str ) -> Any : \"\"\"loads from a string\"\"\" return super () . parse_obj ( toml . loads ( input_string )) validate_uri ( value ) validator for the uri field Source code in kanidm/types.py 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 @validator ( \"uri\" ) def validate_uri ( cls , value : Optional [ str ]) -> Optional [ str ]: \"\"\"validator for the uri field\"\"\" if value is not None : uri = urlparse ( value ) valid_schemes = [ \"http\" , \"https\" ] if uri . scheme not in valid_schemes : raise ValueError ( f \"Invalid URL Scheme for uri=' { value } ': ' { uri . scheme } ' - expected one of { valid_schemes } \" ) # make sure the URI ends with a / if not value . endswith ( \"/\" ): value = f \" { value } /\" return value","title":"Client Configuration"},{"location":"kanidmclientconfig/#kanidmtypeskanidmclientconfig","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 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 class KanidmClientConfig ( BaseModel ): \"\"\"Configuration file definition for Kanidm client config Based on struct KanidmClientConfig in kanidm_client/src/lib.rs See source code for fields \"\"\" uri : Optional [ str ] = None auth_token : Optional [ str ] = None verify_hostnames : bool = True verify_certificate : bool = True ca_path : Optional [ str ] = None username : Optional [ str ] = None password : Optional [ str ] = None radius_cert_path : str = \"/data/cert.pem\" radius_key_path : str = \"/data/key.pem\" # the signing key for radius TLS radius_dh_path : str = \"/data/dh.pem\" # the diffie-hellman output radius_ca_path : Optional [ str ] = None radius_ca_dir : Optional [ str ] = None radius_required_groups : List [ str ] = [] radius_default_vlan : int = 1 radius_groups : List [ RadiusGroup ] = [] radius_clients : List [ RadiusClient ] = [] connect_timeout : int = 30 @classmethod def parse_toml ( cls , input_string : str ) -> Any : \"\"\"loads from a string\"\"\" return super () . parse_obj ( toml . loads ( input_string )) @validator ( \"uri\" ) def validate_uri ( cls , value : Optional [ str ]) -> Optional [ str ]: \"\"\"validator for the uri field\"\"\" if value is not None : uri = urlparse ( value ) valid_schemes = [ \"http\" , \"https\" ] if uri . scheme not in valid_schemes : raise ValueError ( f \"Invalid URL Scheme for uri=' { value } ': ' { uri . scheme } ' - expected one of { valid_schemes } \" ) # make sure the URI ends with a / if not value . endswith ( \"/\" ): value = f \" { value } /\" return value","title":"kanidm.types.KanidmClientConfig"},{"location":"kanidmclientconfig/#kanidm.types.KanidmClientConfig.parse_toml","text":"loads from a string Source code in kanidm/types.py 198 199 200 201 @classmethod def parse_toml ( cls , input_string : str ) -> Any : \"\"\"loads from a string\"\"\" return super () . parse_obj ( toml . loads ( input_string ))","title":"parse_toml()"},{"location":"kanidmclientconfig/#kanidm.types.KanidmClientConfig.validate_uri","text":"validator for the uri field Source code in kanidm/types.py 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 @validator ( \"uri\" ) def validate_uri ( cls , value : Optional [ str ]) -> Optional [ str ]: \"\"\"validator for the uri field\"\"\" if value is not None : uri = urlparse ( value ) valid_schemes = [ \"http\" , \"https\" ] if uri . scheme not in valid_schemes : raise ValueError ( f \"Invalid URL Scheme for uri=' { value } ': ' { uri . scheme } ' - expected one of { valid_schemes } \" ) # make sure the URI ends with a / if not value . endswith ( \"/\" ): value = f \" { value } /\" return value","title":"validate_uri()"},{"location":"radiusclient/","text":"kanidm.types.RadiusClient 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 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 class RadiusClient ( 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. \"\"\" name : str ipaddr : str secret : str # TODO: this should probably be renamed to token @validator ( \"ipaddr\" ) def validate_ipaddr ( cls , value : str ) -> str : \"\"\"validates the ipaddr field is an IP address, CIDR or valid hostname\"\"\" for typedef in ( IPv6Network , IPv6Address , IPv4Address , IPv4Network ): try : typedef ( value ) return value except ValueError : pass try : socket . gethostbyname ( value ) return value except socket . gaierror as error : raise ValueError ( f \"ipaddr value ( { value } ) wasn't an IP Address, Network or valid hostname: { error } \" ) validate_ipaddr ( value ) validates the ipaddr field is an IP address, CIDR or valid hostname Source code in kanidm/types.py 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 @validator ( \"ipaddr\" ) def validate_ipaddr ( cls , value : str ) -> str : \"\"\"validates the ipaddr field is an IP address, CIDR or valid hostname\"\"\" for typedef in ( IPv6Network , IPv6Address , IPv4Address , IPv4Network ): try : typedef ( value ) return value except ValueError : pass try : socket . gethostbyname ( value ) return value except socket . gaierror as error : raise ValueError ( f \"ipaddr value ( { value } ) wasn't an IP Address, Network or valid hostname: { error } \" )","title":"RADIUS Client"},{"location":"radiusclient/#kanidmtypesradiusclient","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 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 class RadiusClient ( 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. \"\"\" name : str ipaddr : str secret : str # TODO: this should probably be renamed to token @validator ( \"ipaddr\" ) def validate_ipaddr ( cls , value : str ) -> str : \"\"\"validates the ipaddr field is an IP address, CIDR or valid hostname\"\"\" for typedef in ( IPv6Network , IPv6Address , IPv4Address , IPv4Network ): try : typedef ( value ) return value except ValueError : pass try : socket . gethostbyname ( value ) return value except socket . gaierror as error : raise ValueError ( f \"ipaddr value ( { value } ) wasn't an IP Address, Network or valid hostname: { error } \" )","title":"kanidm.types.RadiusClient"},{"location":"radiusclient/#kanidm.types.RadiusClient.validate_ipaddr","text":"validates the ipaddr field is an IP address, CIDR or valid hostname Source code in kanidm/types.py 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 @validator ( \"ipaddr\" ) def validate_ipaddr ( cls , value : str ) -> str : \"\"\"validates the ipaddr field is an IP address, CIDR or valid hostname\"\"\" for typedef in ( IPv6Network , IPv6Address , IPv4Address , IPv4Network ): try : typedef ( value ) return value except ValueError : pass try : socket . gethostbyname ( value ) return value except socket . gaierror as error : raise ValueError ( f \"ipaddr value ( { value } ) wasn't an IP Address, Network or valid hostname: { error } \" )","title":"validate_ipaddr()"},{"location":"tokenstore/","text":"User Auth Token related widgets JWS JWS parser Source code in kanidm/tokens.py 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 class JWS : \"\"\"JWS parser\"\"\" def __init__ ( self , raw : str ) -> None : \"\"\"raw is the raw string version of the JWS\"\"\" data = self . parse ( raw ) self . header = data [ 0 ] self . payload = data [ 1 ] self . signature = data [ 2 ] @classmethod def parse ( cls , raw : str ) -> Tuple [ JWSHeader , JWSPayload , bytes ]: \"\"\"parse a raw JWS\"\"\" if \".\" not in raw : raise ValueError ( \"Invalid number of segments, there's no . in the raw JWS\" ) split_raw = raw . split ( \".\" ) if len ( split_raw ) != 3 : raise ValueError ( \"Invalid number of segments\" ) raw_header = split_raw [ 0 ] logging . debug ( \"Parsing header: %s \" , raw_header ) padded_header = raw_header + \"=\" * divmod ( len ( raw_header ), 4 )[ 0 ] decoded_header = base64 . urlsafe_b64decode ( padded_header ) logging . debug ( \"decoded_header= %s \" , decoded_header ) header = JWSHeader . parse_obj ( json . loads ( decoded_header . decode ( \"utf-8\" ))) logging . debug ( \"header: %s \" , header ) raw_payload = split_raw [ 1 ] logging . debug ( \"Parsing payload: %s \" , raw_payload ) padded_payload = raw_payload + \"=\" * divmod ( len ( raw_payload ), 4 )[ 1 ] payload = JWSPayload . parse_raw ( base64 . urlsafe_b64decode ( padded_payload )) raw_signature = split_raw [ 2 ] logging . debug ( \"Parsing signature: %s \" , raw_signature ) padded_signature = raw_signature + \"=\" * divmod ( len ( raw_signature ), 4 )[ 1 ] signature = base64 . urlsafe_b64decode ( padded_signature ) return header , payload , signature __init__ ( raw ) raw is the raw string version of the JWS Source code in kanidm/tokens.py 74 75 76 77 78 79 80 def __init__ ( self , raw : str ) -> None : \"\"\"raw is the raw string version of the JWS\"\"\" data = self . parse ( raw ) self . header = data [ 0 ] self . payload = data [ 1 ] self . signature = data [ 2 ] parse ( raw ) classmethod parse a raw JWS Source code in kanidm/tokens.py 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 @classmethod def parse ( cls , raw : str ) -> Tuple [ JWSHeader , JWSPayload , bytes ]: \"\"\"parse a raw JWS\"\"\" if \".\" not in raw : raise ValueError ( \"Invalid number of segments, there's no . in the raw JWS\" ) split_raw = raw . split ( \".\" ) if len ( split_raw ) != 3 : raise ValueError ( \"Invalid number of segments\" ) raw_header = split_raw [ 0 ] logging . debug ( \"Parsing header: %s \" , raw_header ) padded_header = raw_header + \"=\" * divmod ( len ( raw_header ), 4 )[ 0 ] decoded_header = base64 . urlsafe_b64decode ( padded_header ) logging . debug ( \"decoded_header= %s \" , decoded_header ) header = JWSHeader . parse_obj ( json . loads ( decoded_header . decode ( \"utf-8\" ))) logging . debug ( \"header: %s \" , header ) raw_payload = split_raw [ 1 ] logging . debug ( \"Parsing payload: %s \" , raw_payload ) padded_payload = raw_payload + \"=\" * divmod ( len ( raw_payload ), 4 )[ 1 ] payload = JWSPayload . parse_raw ( base64 . urlsafe_b64decode ( padded_payload )) raw_signature = split_raw [ 2 ] logging . debug ( \"Parsing signature: %s \" , raw_signature ) padded_signature = raw_signature + \"=\" * divmod ( len ( raw_signature ), 4 )[ 1 ] signature = base64 . urlsafe_b64decode ( padded_signature ) return header , payload , signature JWSHeader Bases: BaseModel JWS Header Parser Source code in kanidm/tokens.py 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class JWSHeader ( BaseModel ): \"\"\"JWS Header Parser\"\"\" class JWSHeaderJWK ( BaseModel ): \"\"\"JWS Header Sub-bit\"\"\" kty : str crv : str x : str y : str alg : str use : str alg : str typ : str jwk : JWSHeaderJWK class Config : \"\"\"Configure the pydantic class\"\"\" arbitrary_types_allowed = True Config Configure the pydantic class Source code in kanidm/tokens.py 35 36 37 38 class Config : \"\"\"Configure the pydantic class\"\"\" arbitrary_types_allowed = True JWSHeaderJWK Bases: BaseModel JWS Header Sub-bit Source code in kanidm/tokens.py 21 22 23 24 25 26 27 28 29 class JWSHeaderJWK ( BaseModel ): \"\"\"JWS Header Sub-bit\"\"\" kty : str crv : str x : str y : str alg : str use : str JWSPayload Bases: BaseModel JWS Payload parser Source code in kanidm/tokens.py 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 class JWSPayload ( BaseModel ): \"\"\"JWS Payload parser\"\"\" session_id : str auth_type : str # TODO: work out the format of the expiry # example expiry: 2022,265,28366,802525000 expiry : List [ int ] # [year, day of year, something?] uuid : str name : str displayname : str spn : str mail_primary : Optional [ str ] lim_uidx : bool lim_rmax : int lim_pmax : int lim_fmax : int @property def expiry_datetime ( self ) -> datetime : \"\"\"parse the expiry and return a datetime object\"\"\" year , day , seconds , _ = self . expiry retval = datetime ( year = year , month = 1 , day = 1 , second = 0 , hour = 0 , tzinfo = timezone . utc ) # day - 1 because we're already starting at day 1 retval += timedelta ( days = day - 1 , seconds = seconds ) return retval expiry_datetime () property parse the expiry and return a datetime object Source code in kanidm/tokens.py 59 60 61 62 63 64 65 66 67 68 @property def expiry_datetime ( self ) -> datetime : \"\"\"parse the expiry and return a datetime object\"\"\" year , day , seconds , _ = self . expiry retval = datetime ( year = year , month = 1 , day = 1 , second = 0 , hour = 0 , tzinfo = timezone . utc ) # day - 1 because we're already starting at day 1 retval += timedelta ( days = day - 1 , seconds = seconds ) return retval TokenStore Bases: BaseModel Represents the user auth tokens, can load them from the user store Source code in kanidm/tokens.py 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 class TokenStore ( BaseModel ): \"\"\"Represents the user auth tokens, can load them from the user store\"\"\" __root__ : Dict [ str , str ] = {} # TODO: one day work out how to type the __iter__ on TokenStore properly. It's some kind of iter() that makes mypy unhappy. def __iter__ ( self ) -> Any : \"\"\"overloading the default function\"\"\" for key in self . __root__ . keys (): yield key def __getitem__ ( self , item : str ) -> str : \"\"\"overloading the default function\"\"\" return self . __root__ [ item ] def __delitem__ ( self , item : str ) -> None : \"\"\"overloading the default function\"\"\" del self . __root__ [ item ] def __setitem__ ( self , key : str , value : str ) -> None : \"\"\"overloading the default function\"\"\" self . __root__ [ key ] = value def save ( self , filepath : Path = TOKEN_PATH ) -> None : \"\"\"saves the cached tokens to disk\"\"\" data = json . dumps ( self . __root__ , indent = 2 ) with filepath . expanduser () . resolve () . open ( mode = \"w\" , encoding = \"utf-8\" ) as file_handle : file_handle . write ( data ) def load ( self , overwrite : bool = True , filepath : Path = TOKEN_PATH ) -> Dict [ str , str ]: \"\"\"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\"\"\" token_path = filepath . expanduser () . resolve () if not token_path . exists (): tokens : Dict [ str , str ] = {} else : with token_path . open ( encoding = \"utf-8\" ) as file_handle : tokens = json . load ( file_handle ) if overwrite : self . __root__ = tokens else : for user in tokens : self . __root__ [ user ] = tokens [ user ] self . validate_tokens () logging . debug ( json . dumps ( tokens , indent = 4 )) return self . __root__ def validate_tokens ( self ) -> None : \"\"\"validates the JWS tokens for format, not their signature - PRs welcome\"\"\" for username in self . __root__ : logging . debug ( \"Parsing %s \" , username ) # TODO: Work out how to get the validation working. We probably shouldn't be worried about this since we're using it for auth... logging . debug ( JsonWebSignature () . deserialize_compact ( s = self [ username ], key = None ) ) def token_info ( self , username : str ) -> Optional [ JWSPayload ]: \"\"\"grabs a token and returns a complex object object\"\"\" if username not in self : return None parsed_object = JsonWebSignature () . deserialize_compact ( s = self [ username ], key = None ) logging . debug ( parsed_object ) return JWSPayload . parse_raw ( parsed_object . payload ) __delitem__ ( item ) overloading the default function Source code in kanidm/tokens.py 127 128 129 def __delitem__ ( self , item : str ) -> None : \"\"\"overloading the default function\"\"\" del self . __root__ [ item ] __getitem__ ( item ) overloading the default function Source code in kanidm/tokens.py 123 124 125 def __getitem__ ( self , item : str ) -> str : \"\"\"overloading the default function\"\"\" return self . __root__ [ item ] __iter__ () overloading the default function Source code in kanidm/tokens.py 118 119 120 121 def __iter__ ( self ) -> Any : \"\"\"overloading the default function\"\"\" for key in self . __root__ . keys (): yield key __setitem__ ( key , value ) overloading the default function Source code in kanidm/tokens.py 131 132 133 def __setitem__ ( self , key : str , value : str ) -> None : \"\"\"overloading the default function\"\"\" self . __root__ [ key ] = value load ( overwrite = True , filepath = TOKEN_PATH ) 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 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 def load ( self , overwrite : bool = True , filepath : Path = TOKEN_PATH ) -> Dict [ str , str ]: \"\"\"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\"\"\" token_path = filepath . expanduser () . resolve () if not token_path . exists (): tokens : Dict [ str , str ] = {} else : with token_path . open ( encoding = \"utf-8\" ) as file_handle : tokens = json . load ( file_handle ) if overwrite : self . __root__ = tokens else : for user in tokens : self . __root__ [ user ] = tokens [ user ] self . validate_tokens () logging . debug ( json . dumps ( tokens , indent = 4 )) return self . __root__ save ( filepath = TOKEN_PATH ) saves the cached tokens to disk Source code in kanidm/tokens.py 135 136 137 138 139 140 141 def save ( self , filepath : Path = TOKEN_PATH ) -> None : \"\"\"saves the cached tokens to disk\"\"\" data = json . dumps ( self . __root__ , indent = 2 ) with filepath . expanduser () . resolve () . open ( mode = \"w\" , encoding = \"utf-8\" ) as file_handle : file_handle . write ( data ) token_info ( username ) grabs a token and returns a complex object object Source code in kanidm/tokens.py 179 180 181 182 183 184 185 186 187 def token_info ( self , username : str ) -> Optional [ JWSPayload ]: \"\"\"grabs a token and returns a complex object object\"\"\" if username not in self : return None parsed_object = JsonWebSignature () . deserialize_compact ( s = self [ username ], key = None ) logging . debug ( parsed_object ) return JWSPayload . parse_raw ( parsed_object . payload ) validate_tokens () validates the JWS tokens for format, not their signature - PRs welcome Source code in kanidm/tokens.py 170 171 172 173 174 175 176 177 def validate_tokens ( self ) -> None : \"\"\"validates the JWS tokens for format, not their signature - PRs welcome\"\"\" for username in self . __root__ : logging . debug ( \"Parsing %s \" , username ) # TODO: Work out how to get the validation working. We probably shouldn't be worried about this since we're using it for auth... logging . debug ( JsonWebSignature () . deserialize_compact ( s = self [ username ], key = None ) )","title":"Token Storage"},{"location":"tokenstore/#kanidm.tokens.JWS","text":"JWS parser Source code in kanidm/tokens.py 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 class JWS : \"\"\"JWS parser\"\"\" def __init__ ( self , raw : str ) -> None : \"\"\"raw is the raw string version of the JWS\"\"\" data = self . parse ( raw ) self . header = data [ 0 ] self . payload = data [ 1 ] self . signature = data [ 2 ] @classmethod def parse ( cls , raw : str ) -> Tuple [ JWSHeader , JWSPayload , bytes ]: \"\"\"parse a raw JWS\"\"\" if \".\" not in raw : raise ValueError ( \"Invalid number of segments, there's no . in the raw JWS\" ) split_raw = raw . split ( \".\" ) if len ( split_raw ) != 3 : raise ValueError ( \"Invalid number of segments\" ) raw_header = split_raw [ 0 ] logging . debug ( \"Parsing header: %s \" , raw_header ) padded_header = raw_header + \"=\" * divmod ( len ( raw_header ), 4 )[ 0 ] decoded_header = base64 . urlsafe_b64decode ( padded_header ) logging . debug ( \"decoded_header= %s \" , decoded_header ) header = JWSHeader . parse_obj ( json . loads ( decoded_header . decode ( \"utf-8\" ))) logging . debug ( \"header: %s \" , header ) raw_payload = split_raw [ 1 ] logging . debug ( \"Parsing payload: %s \" , raw_payload ) padded_payload = raw_payload + \"=\" * divmod ( len ( raw_payload ), 4 )[ 1 ] payload = JWSPayload . parse_raw ( base64 . urlsafe_b64decode ( padded_payload )) raw_signature = split_raw [ 2 ] logging . debug ( \"Parsing signature: %s \" , raw_signature ) padded_signature = raw_signature + \"=\" * divmod ( len ( raw_signature ), 4 )[ 1 ] signature = base64 . urlsafe_b64decode ( padded_signature ) return header , payload , signature","title":"JWS"},{"location":"tokenstore/#kanidm.tokens.JWS.__init__","text":"raw is the raw string version of the JWS Source code in kanidm/tokens.py 74 75 76 77 78 79 80 def __init__ ( self , raw : str ) -> None : \"\"\"raw is the raw string version of the JWS\"\"\" data = self . parse ( raw ) self . header = data [ 0 ] self . payload = data [ 1 ] self . signature = data [ 2 ]","title":"__init__()"},{"location":"tokenstore/#kanidm.tokens.JWS.parse","text":"parse a raw JWS Source code in kanidm/tokens.py 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 @classmethod def parse ( cls , raw : str ) -> Tuple [ JWSHeader , JWSPayload , bytes ]: \"\"\"parse a raw JWS\"\"\" if \".\" not in raw : raise ValueError ( \"Invalid number of segments, there's no . in the raw JWS\" ) split_raw = raw . split ( \".\" ) if len ( split_raw ) != 3 : raise ValueError ( \"Invalid number of segments\" ) raw_header = split_raw [ 0 ] logging . debug ( \"Parsing header: %s \" , raw_header ) padded_header = raw_header + \"=\" * divmod ( len ( raw_header ), 4 )[ 0 ] decoded_header = base64 . urlsafe_b64decode ( padded_header ) logging . debug ( \"decoded_header= %s \" , decoded_header ) header = JWSHeader . parse_obj ( json . loads ( decoded_header . decode ( \"utf-8\" ))) logging . debug ( \"header: %s \" , header ) raw_payload = split_raw [ 1 ] logging . debug ( \"Parsing payload: %s \" , raw_payload ) padded_payload = raw_payload + \"=\" * divmod ( len ( raw_payload ), 4 )[ 1 ] payload = JWSPayload . parse_raw ( base64 . urlsafe_b64decode ( padded_payload )) raw_signature = split_raw [ 2 ] logging . debug ( \"Parsing signature: %s \" , raw_signature ) padded_signature = raw_signature + \"=\" * divmod ( len ( raw_signature ), 4 )[ 1 ] signature = base64 . urlsafe_b64decode ( padded_signature ) return header , payload , signature","title":"parse()"},{"location":"tokenstore/#kanidm.tokens.JWSHeader","text":"Bases: BaseModel JWS Header Parser Source code in kanidm/tokens.py 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class JWSHeader ( BaseModel ): \"\"\"JWS Header Parser\"\"\" class JWSHeaderJWK ( BaseModel ): \"\"\"JWS Header Sub-bit\"\"\" kty : str crv : str x : str y : str alg : str use : str alg : str typ : str jwk : JWSHeaderJWK class Config : \"\"\"Configure the pydantic class\"\"\" arbitrary_types_allowed = True","title":"JWSHeader"},{"location":"tokenstore/#kanidm.tokens.JWSHeader.Config","text":"Configure the pydantic class Source code in kanidm/tokens.py 35 36 37 38 class Config : \"\"\"Configure the pydantic class\"\"\" arbitrary_types_allowed = True","title":"Config"},{"location":"tokenstore/#kanidm.tokens.JWSHeader.JWSHeaderJWK","text":"Bases: BaseModel JWS Header Sub-bit Source code in kanidm/tokens.py 21 22 23 24 25 26 27 28 29 class JWSHeaderJWK ( BaseModel ): \"\"\"JWS Header Sub-bit\"\"\" kty : str crv : str x : str y : str alg : str use : str","title":"JWSHeaderJWK"},{"location":"tokenstore/#kanidm.tokens.JWSPayload","text":"Bases: BaseModel JWS Payload parser Source code in kanidm/tokens.py 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 class JWSPayload ( BaseModel ): \"\"\"JWS Payload parser\"\"\" session_id : str auth_type : str # TODO: work out the format of the expiry # example expiry: 2022,265,28366,802525000 expiry : List [ int ] # [year, day of year, something?] uuid : str name : str displayname : str spn : str mail_primary : Optional [ str ] lim_uidx : bool lim_rmax : int lim_pmax : int lim_fmax : int @property def expiry_datetime ( self ) -> datetime : \"\"\"parse the expiry and return a datetime object\"\"\" year , day , seconds , _ = self . expiry retval = datetime ( year = year , month = 1 , day = 1 , second = 0 , hour = 0 , tzinfo = timezone . utc ) # day - 1 because we're already starting at day 1 retval += timedelta ( days = day - 1 , seconds = seconds ) return retval","title":"JWSPayload"},{"location":"tokenstore/#kanidm.tokens.JWSPayload.expiry_datetime","text":"parse the expiry and return a datetime object Source code in kanidm/tokens.py 59 60 61 62 63 64 65 66 67 68 @property def expiry_datetime ( self ) -> datetime : \"\"\"parse the expiry and return a datetime object\"\"\" year , day , seconds , _ = self . expiry retval = datetime ( year = year , month = 1 , day = 1 , second = 0 , hour = 0 , tzinfo = timezone . utc ) # day - 1 because we're already starting at day 1 retval += timedelta ( days = day - 1 , seconds = seconds ) return retval","title":"expiry_datetime()"},{"location":"tokenstore/#kanidm.tokens.TokenStore","text":"Bases: BaseModel Represents the user auth tokens, can load them from the user store Source code in kanidm/tokens.py 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 class TokenStore ( BaseModel ): \"\"\"Represents the user auth tokens, can load them from the user store\"\"\" __root__ : Dict [ str , str ] = {} # TODO: one day work out how to type the __iter__ on TokenStore properly. It's some kind of iter() that makes mypy unhappy. def __iter__ ( self ) -> Any : \"\"\"overloading the default function\"\"\" for key in self . __root__ . keys (): yield key def __getitem__ ( self , item : str ) -> str : \"\"\"overloading the default function\"\"\" return self . __root__ [ item ] def __delitem__ ( self , item : str ) -> None : \"\"\"overloading the default function\"\"\" del self . __root__ [ item ] def __setitem__ ( self , key : str , value : str ) -> None : \"\"\"overloading the default function\"\"\" self . __root__ [ key ] = value def save ( self , filepath : Path = TOKEN_PATH ) -> None : \"\"\"saves the cached tokens to disk\"\"\" data = json . dumps ( self . __root__ , indent = 2 ) with filepath . expanduser () . resolve () . open ( mode = \"w\" , encoding = \"utf-8\" ) as file_handle : file_handle . write ( data ) def load ( self , overwrite : bool = True , filepath : Path = TOKEN_PATH ) -> Dict [ str , str ]: \"\"\"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\"\"\" token_path = filepath . expanduser () . resolve () if not token_path . exists (): tokens : Dict [ str , str ] = {} else : with token_path . open ( encoding = \"utf-8\" ) as file_handle : tokens = json . load ( file_handle ) if overwrite : self . __root__ = tokens else : for user in tokens : self . __root__ [ user ] = tokens [ user ] self . validate_tokens () logging . debug ( json . dumps ( tokens , indent = 4 )) return self . __root__ def validate_tokens ( self ) -> None : \"\"\"validates the JWS tokens for format, not their signature - PRs welcome\"\"\" for username in self . __root__ : logging . debug ( \"Parsing %s \" , username ) # TODO: Work out how to get the validation working. We probably shouldn't be worried about this since we're using it for auth... logging . debug ( JsonWebSignature () . deserialize_compact ( s = self [ username ], key = None ) ) def token_info ( self , username : str ) -> Optional [ JWSPayload ]: \"\"\"grabs a token and returns a complex object object\"\"\" if username not in self : return None parsed_object = JsonWebSignature () . deserialize_compact ( s = self [ username ], key = None ) logging . debug ( parsed_object ) return JWSPayload . parse_raw ( parsed_object . payload )","title":"TokenStore"},{"location":"tokenstore/#kanidm.tokens.TokenStore.__delitem__","text":"overloading the default function Source code in kanidm/tokens.py 127 128 129 def __delitem__ ( self , item : str ) -> None : \"\"\"overloading the default function\"\"\" del self . __root__ [ item ]","title":"__delitem__()"},{"location":"tokenstore/#kanidm.tokens.TokenStore.__getitem__","text":"overloading the default function Source code in kanidm/tokens.py 123 124 125 def __getitem__ ( self , item : str ) -> str : \"\"\"overloading the default function\"\"\" return self . __root__ [ item ]","title":"__getitem__()"},{"location":"tokenstore/#kanidm.tokens.TokenStore.__iter__","text":"overloading the default function Source code in kanidm/tokens.py 118 119 120 121 def __iter__ ( self ) -> Any : \"\"\"overloading the default function\"\"\" for key in self . __root__ . keys (): yield key","title":"__iter__()"},{"location":"tokenstore/#kanidm.tokens.TokenStore.__setitem__","text":"overloading the default function Source code in kanidm/tokens.py 131 132 133 def __setitem__ ( self , key : str , value : str ) -> None : \"\"\"overloading the default function\"\"\" self . __root__ [ key ] = value","title":"__setitem__()"},{"location":"tokenstore/#kanidm.tokens.TokenStore.load","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 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 def load ( self , overwrite : bool = True , filepath : Path = TOKEN_PATH ) -> Dict [ str , str ]: \"\"\"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\"\"\" token_path = filepath . expanduser () . resolve () if not token_path . exists (): tokens : Dict [ str , str ] = {} else : with token_path . open ( encoding = \"utf-8\" ) as file_handle : tokens = json . load ( file_handle ) if overwrite : self . __root__ = tokens else : for user in tokens : self . __root__ [ user ] = tokens [ user ] self . validate_tokens () logging . debug ( json . dumps ( tokens , indent = 4 )) return self . __root__","title":"load()"},{"location":"tokenstore/#kanidm.tokens.TokenStore.save","text":"saves the cached tokens to disk Source code in kanidm/tokens.py 135 136 137 138 139 140 141 def save ( self , filepath : Path = TOKEN_PATH ) -> None : \"\"\"saves the cached tokens to disk\"\"\" data = json . dumps ( self . __root__ , indent = 2 ) with filepath . expanduser () . resolve () . open ( mode = \"w\" , encoding = \"utf-8\" ) as file_handle : file_handle . write ( data )","title":"save()"},{"location":"tokenstore/#kanidm.tokens.TokenStore.token_info","text":"grabs a token and returns a complex object object Source code in kanidm/tokens.py 179 180 181 182 183 184 185 186 187 def token_info ( self , username : str ) -> Optional [ JWSPayload ]: \"\"\"grabs a token and returns a complex object object\"\"\" if username not in self : return None parsed_object = JsonWebSignature () . deserialize_compact ( s = self [ username ], key = None ) logging . debug ( parsed_object ) return JWSPayload . parse_raw ( parsed_object . payload )","title":"token_info()"},{"location":"tokenstore/#kanidm.tokens.TokenStore.validate_tokens","text":"validates the JWS tokens for format, not their signature - PRs welcome Source code in kanidm/tokens.py 170 171 172 173 174 175 176 177 def validate_tokens ( self ) -> None : \"\"\"validates the JWS tokens for format, not their signature - PRs welcome\"\"\" for username in self . __root__ : logging . debug ( \"Parsing %s \" , username ) # TODO: Work out how to get the validation working. We probably shouldn't be worried about this since we're using it for auth... logging . debug ( JsonWebSignature () . deserialize_compact ( s = self [ username ], key = None ) )","title":"validate_tokens()"}]}