diff --git a/src/keycloak/keycloak_openid.py b/src/keycloak/keycloak_openid.py index 82e980c..1c81871 100644 --- a/src/keycloak/keycloak_openid.py +++ b/src/keycloak/keycloak_openid.py @@ -81,7 +81,25 @@ class KeycloakOpenID: proxies=None, timeout=60, ): - """Init method.""" + """Init method. + + :param server_url: Keycloak server url + :type server_url: str + :param client_id: client id + :type client_id: str + :param realm_name: realm name + :type realm_name: str + :param client_secret_key: client secret key + :type client_secret_key: str + :param verify: True if want check connection SSL + :type verify: bool + :param custom_headers: dict of custom header to pass to each HTML request + :type custom_headers: dict + :param proxies: dict of proxies to sent the request by. + :type proxies: dict + :param timeout: connection timeout in seconds + :type timeout: int + """ self.client_id = client_id self.client_secret_key = client_secret_key self.realm_name = realm_name @@ -94,7 +112,11 @@ class KeycloakOpenID: @property def client_id(self): - """Get client id.""" + """Get client id. + + :returns: Client id + :rtype: str + """ return self._client_id @client_id.setter @@ -103,7 +125,11 @@ class KeycloakOpenID: @property def client_secret_key(self): - """Get the client secret key.""" + """Get the client secret key. + + :returns: Client secret key + :rtype: str + """ return self._client_secret_key @client_secret_key.setter @@ -112,7 +138,11 @@ class KeycloakOpenID: @property def realm_name(self): - """Get the realm name.""" + """Get the realm name. + + :returns: Realm name + :rtype: str + """ return self._realm_name @realm_name.setter @@ -121,7 +151,11 @@ class KeycloakOpenID: @property def connection(self): - """Get connection.""" + """Get connection. + + :returns: Connection manager object + :rtype: ConnectionManager + """ return self._connection @connection.setter @@ -130,7 +164,11 @@ class KeycloakOpenID: @property def authorization(self): - """Get authorization.""" + """Get authorization. + + :returns: The authorization manager + :rtype: Authorization + """ return self._authorization @authorization.setter @@ -140,8 +178,10 @@ class KeycloakOpenID: def _add_secret_key(self, payload): """Add secret key if exists. - :param payload: - :return: + :param payload: Payload + :type payload: dict + :returns: Payload with the secret key + :rtype: dict """ if self.client_secret_key: payload.update({"client_secret": self.client_secret_key}) @@ -151,18 +191,24 @@ class KeycloakOpenID: def _build_name_role(self, role): """Build name of a role. - :param role: - :return: + :param role: Role name + :type role: str + :returns: Role path + :rtype: str """ return self.client_id + "/" + role def _token_info(self, token, method_token_info, **kwargs): """Getter for the token data. - :param token: - :param method_token_info: - :param kwargs: - :return: + :param token: Token + :type token: str + :param method_token_info: Token info method to use + :type method_token_info: str + :param kwargs: Additional keyword arguments + :type kwargs: dict + :returns: Token info + :rtype: dict """ if method_token_info == "introspect": token_info = self.introspect(token) @@ -178,7 +224,8 @@ class KeycloakOpenID: endpoint. It lists endpoints and other configuration options relevant to the OpenID Connect implementation in Keycloak. - :return It lists endpoints and other configuration options relevant. + :returns: It lists endpoints and other configuration options relevant + :rtype: dict """ params_path = {"realm-name": self.realm_name} data_raw = self.connection.raw_get(URL_WELL_KNOWN.format(**params_path)) @@ -190,9 +237,9 @@ class KeycloakOpenID: :param redirect_uri: Redirect url to receive oauth code :type redirect_uri: str :param scope: Scope of authorization request, split with the blank space - :type: scope: str + :type scope: str :param state: State will be returned to the redirect_uri - :type: str + :type state: str :returns: Authorization URL Full Build :rtype: str """ @@ -224,13 +271,22 @@ class KeycloakOpenID: http://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint - :param username: - :param password: - :param grant_type: - :param code: - :param redirect_uri: - :param totp: - :return: + :param username: Username + :type username: str + :param password: Password + :type password: str + :param grant_type: Grant type + :type grant_type: str + :param code: Code + :type code: str + :param redirect_uri: Redirect URI + :type redirect_uri: str + :param totp: Time-based one-time password + :type totp: int + :param extra: Additional extra arguments + :type extra: dict + :returns: Keycloak token + :rtype: dict """ params_path = {"realm-name": self.realm_name} payload = { @@ -261,9 +317,12 @@ class KeycloakOpenID: http://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint - :param refresh_token: - :param grant_type: - :return: + :param refresh_token: Refresh token from Keycloak + :type refresh_token: str + :param grant_type: Grant type + :type grant_type: str + :returns: New token + :rtype: dict """ params_path = {"realm-name": self.realm_name} payload = { @@ -281,11 +340,16 @@ class KeycloakOpenID: Use a token to obtain an entirely different token. See https://www.keycloak.org/docs/latest/securing_apps/index.html#_token-exchange - :param token: - :param client_id: - :param audience: - :param subject: - :return: + :param token: Access token + :type token: str + :param client_id: Client id + :type client_id: str + :param audience: Audience + :type audience: str + :param subject: Subject + :type subject: str + :returns: Exchanged token + :rtype: dict """ params_path = {"realm-name": self.realm_name} payload = { @@ -308,8 +372,10 @@ class KeycloakOpenID: http://openid.net/specs/openid-connect-core-1_0.html#UserInfo - :param token: - :return: + :param token: Access token + :type token: str + :returns: Userinfo object + :rtype: dict """ self.connection.add_param_headers("Authorization", "Bearer " + token) params_path = {"realm-name": self.realm_name} @@ -319,8 +385,10 @@ class KeycloakOpenID: def logout(self, refresh_token): """Log out the authenticated user. - :param refresh_token: - :return: + :param refresh_token: Refresh token from Keycloak + :type refresh_token: str + :returns: Keycloak server response + :rtype: dict """ params_path = {"realm-name": self.realm_name} payload = {"client_id": self.client_id, "refresh_token": refresh_token} @@ -337,7 +405,8 @@ class KeycloakOpenID: https://tools.ietf.org/html/rfc7517 - :return: + :returns: Certificates + :rtype: dict """ params_path = {"realm-name": self.realm_name} data_raw = self.connection.raw_get(URL_CERTS.format(**params_path)) @@ -348,7 +417,8 @@ class KeycloakOpenID: The public key is exposed by the realm page directly. - :return: + :returns: The public key + :rtype: str """ params_path = {"realm-name": self.realm_name} data_raw = self.connection.raw_get(URL_REALM.format(**params_path)) @@ -363,7 +433,12 @@ class KeycloakOpenID: authorization policies associated with the resources being requested. With an RPT, client applications can gain access to protected resources at the resource server. - :return: + :param token: Access token + :type token: str + :param resource_server_id: Resource server ID + :type resource_server_id: str + :returns: Entitlements + :rtype: dict """ self.connection.add_param_headers("Authorization", "Bearer " + token) params_path = {"realm-name": self.realm_name, "resource-server-id": resource_server_id} @@ -382,11 +457,16 @@ class KeycloakOpenID: https://tools.ietf.org/html/rfc7662 - :param token: - :param rpt: - :param token_type_hint: + :param token: Access token + :type token: str + :param rpt: Requesting party token + :type rpt: str + :param token_type_hint: Token type hint + :type token_type_hint: str - :return: + :returns: Token info + :rtype: dict + :raises KeycloakRPTNotFound: In case of RPT not specified """ params_path = {"realm-name": self.realm_name} payload = {"client_id": self.client_id, "token": token} @@ -415,10 +495,16 @@ class KeycloakOpenID: https://tools.ietf.org/html/rfc7517 - :param token: - :param key: - :param algorithms: - :return: + :param token: Keycloak token + :type token: str + :param key: Decode key + :type key: str + :param algorithms: Algorithms to use for decoding + :type algorithms: list[str] + :param kwargs: Keyword arguments + :type kwargs: dict + :returns: Decoded token + :rtype: dict """ return jwt.decode(token, key, algorithms=algorithms, audience=self.client_id, **kwargs) @@ -426,7 +512,7 @@ class KeycloakOpenID: """Load Keycloak settings (authorization). :param path: settings file (json) - :return: + :type path: str """ with open(path, "r") as fp: authorization_json = json.load(fp) @@ -436,8 +522,16 @@ class KeycloakOpenID: def get_policies(self, token, method_token_info="introspect", **kwargs): """Get policies by user token. - :param token: user token - :return: policies list + :param token: User token + :type token: str + :param method_token_info: Method for token info decoding + :type method_token_info: str + :param kwargs: Additional keyword arguments + :type kwargs: dict + :return: Policies + :rtype: dict + :raises KeycloakAuthorizationConfigError: In case of bad authorization configuration + :raises KeycloakInvalidTokenError: In case of bad token """ if not self.authorization.policies: raise KeycloakAuthorizationConfigError( @@ -467,9 +561,15 @@ class KeycloakOpenID: """Get permission by user token. :param token: user token + :type token: str :param method_token_info: Decode token method + :type method_token_info: str :param kwargs: parameters for decode - :return: permissions list + :type kwargs: dict + :returns: permissions list + :rtype: list + :raises KeycloakAuthorizationConfigError: In case of bad authorization configuration + :raises KeycloakInvalidTokenError: In case of bad token """ if not self.authorization.policies: raise KeycloakAuthorizationConfigError( @@ -504,8 +604,11 @@ class KeycloakOpenID: http://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint :param token: user token + :type token: str :param permissions: list of uma permissions list(resource:scope) requested by the user - :return: permissions list + :type permissions: str + :returns: Keycloak server response + :rtype: dict """ permission = build_permission_param(permissions) @@ -525,8 +628,13 @@ class KeycloakOpenID: """Determine whether user has uma permissions with specified user token. :param token: user token + :type token: str :param permissions: list of uma permissions (resource:scope) - :return: auth status + :type permissions: str + :return: Authentication status + :rtype: AuthStatus + :raises KeycloakAuthenticationError: In case of failed authentication + :raises KeycloakPostError: In case of failed request to Keycloak """ needed = build_permission_param(permissions) try: diff --git a/src/keycloak/uma_permissions.py b/src/keycloak/uma_permissions.py index 94779f1..eadcd72 100644 --- a/src/keycloak/uma_permissions.py +++ b/src/keycloak/uma_permissions.py @@ -48,7 +48,16 @@ class UMAPermission: """ def __init__(self, permission=None, resource="", scope=""): - """Init method.""" + """Init method. + + :param permission: Permission + :type permission: UMAPermission + :param resource: Resource + :type resource: str + :param scope: Scope + :type scope: str + :raises PermissionDefinitionError: In case bad permission definition + """ self.resource = resource self.scope = scope @@ -63,26 +72,55 @@ class UMAPermission: self.scope = str(permission.scope) def __str__(self): - """Str method.""" + """Str method. + + :returns: String representation + :rtype: str + """ scope = self.scope if scope: scope = "#" + scope return "{}{}".format(self.resource, scope) def __eq__(self, __o: object) -> bool: - """Eq method.""" + """Eq method. + + :param __o: The other object + :type __o: object + :returns: Equality boolean + :rtype: bool + """ return str(self) == str(__o) def __repr__(self) -> str: - """Repr method.""" + """Repr method. + + :returns: The object representation + :rtype: str + """ return self.__str__() def __hash__(self) -> int: - """Hash method.""" + """Hash method. + + :returns: Hash of the object + :rtype: int + """ return hash(str(self)) - def __call__(self, permission=None, resource="", scope="") -> object: - """Call method.""" + def __call__(self, permission=None, resource="", scope="") -> "UMAPermission": + """Call method. + + :param permission: Permission + :type permission: UMAPermission + :param resource: Resource + :type resource: str + :param scope: Scope + :type scope: str + :returns: The combined UMA permission + :rtype: UMAPermission + :raises PermissionDefinitionError: In case bad permission definition + """ result_resource = self.resource result_scope = self.scope @@ -114,7 +152,11 @@ class Resource(UMAPermission): """ def __init__(self, resource): - """Init method.""" + """Init method. + + :param resource: Resource + :type resource: str + """ super().__init__(resource=resource) @@ -128,7 +170,11 @@ class Scope(UMAPermission): """ def __init__(self, scope): - """Init method.""" + """Init method. + + :param scope: Scope + :type scope: str + """ super().__init__(scope=scope) @@ -147,17 +193,33 @@ class AuthStatus: """ def __init__(self, is_logged_in, is_authorized, missing_permissions): - """Init method.""" + """Init method. + + :param is_logged_in: Is logged in indicator + :type is_logged_in: bool + :param is_authorized: Is authorized indicator + :type is_authorized: bool + :param missing_permissions: Missing permissions + :type missing_permissions: set + """ self.is_logged_in = is_logged_in self.is_authorized = is_authorized self.missing_permissions = missing_permissions def __bool__(self): - """Bool method.""" + """Bool method. + + :returns: Boolean representation + :rtype: bool + """ return self.is_authorized def __repr__(self): - """Repr method.""" + """Repr method. + + :returns: The object representation + :rtype: str + """ return ( f"AuthStatus(" f"is_authorized={self.is_authorized}, " @@ -169,11 +231,11 @@ class AuthStatus: def build_permission_param(permissions): """Transform permissions to a set, so they are usable for requests. - :param permissions: either str (resource#scope), - iterable[str] (resource#scope), - dict[str,str] (resource: scope), - dict[str,iterable[str]] (resource: scopes) - :return: result bool + :param permissions: Permissions + :type permissions: str | Iterable[str] | dict[str, str] | dict[str, Iterabble[str]] + :returns: Permission parameters + :rtype: set + :raises KeycloakPermissionFormatError: In case of bad permission format """ if permissions is None or permissions == "": return set()