From 9cf3c376763c10468a682655a54b0633c1e4e73e Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Wed, 23 Aug 2017 09:42:47 -0300 Subject: [PATCH] Added policies, permissions, roles, scopes and resources. --- keycloak/__init__.py | 34 +++++++++++++++++++++++++--- keycloak/authorization/__init__.py | 9 +++++++- keycloak/authorization/permission.py | 16 +++++++++++++ keycloak/authorization/policy.py | 23 +++++++++++++++++++ keycloak/authorization/role.py | 12 ++++++++++ keycloak/exceptions.py | 20 ++++------------ 6 files changed, 94 insertions(+), 20 deletions(-) diff --git a/keycloak/__init__.py b/keycloak/__init__.py index 79e3761..af937c2 100644 --- a/keycloak/__init__.py +++ b/keycloak/__init__.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from keycloak.authorization import Authorization -from keycloak.exceptions import KeycloakAuthorizationConfigError +from keycloak.exceptions import KeycloakAuthorizationConfigError, KeycloakInvalidTokenError from .exceptions import raise_error_from_response, KeycloakGetError, KeycloakSecretNotFound, \ KeycloakRPTNotFound from .urls_patterns import ( @@ -98,6 +98,9 @@ class Keycloak: return payload + def _build_name_role(self, role): + return self.client_id + "/" + role + def well_know(self): """ The most important endpoint to understand is the well-known configuration endpoint. It lists endpoints and other configuration options relevant to @@ -269,14 +272,39 @@ class Keycloak: self.authorization.load_config(authorization_json) authorization_file.close() - def get_permissions(self): + def get_permissions(self, token): + """ + Get permission by user token + + :param token: user token + :return: permissions list + """ if not self.authorization.policies: raise KeycloakAuthorizationConfigError( "Keycloak settings not found. Load Authorization Keycloak settings." ) - return + token_info = self.instropect(token) + + if not token_info['active']: + raise KeycloakInvalidTokenError( + "Token expired or invalid." + ) + + user_resources = token_info['resource_access'].get(self.client_id) + + if not user_resources: + return None + + permissions = [] + + for policy_name, policy in self.authorization.policies.items(): + for role in user_resources['roles']: + if self._build_name_role(role) in policy.roles: + permissions += policy.permissions + + return list(set(permissions)) diff --git a/keycloak/authorization/__init__.py b/keycloak/authorization/__init__.py index e5ab8bd..95bbb3a 100644 --- a/keycloak/authorization/__init__.py +++ b/keycloak/authorization/__init__.py @@ -24,6 +24,12 @@ from keycloak.authorization.role import Role class Authorization: + """ + Keycloak Authorization (policies, roles, scopes and resources). + + https://keycloak.gitbooks.io/documentation/authorization_services/index.html + + """ def __init__(self): self._policies = {} @@ -38,8 +44,9 @@ class Authorization: def load_config(self, data): """ + Load policies, roles and permissions (scope/resources). - :param data: + :param data: keycloak authorization data (dict) :return: """ for pol in data['policies']: diff --git a/keycloak/authorization/permission.py b/keycloak/authorization/permission.py index d67e396..94eca77 100644 --- a/keycloak/authorization/permission.py +++ b/keycloak/authorization/permission.py @@ -17,6 +17,22 @@ class Permission: + """ + Consider this simple and very common permission: + + A permission associates the object being protected with the policies that must be evaluated to determine whether access is granted. + + X CAN DO Y ON RESOURCE Z + + where … + X represents one or more users, roles, or groups, or a combination of them. You can + also use claims and context here. + Y represents an action to be performed, for example, write, view, and so on. + Z represents a protected resource, for example, "/accounts". + + https://keycloak.gitbooks.io/documentation/authorization_services/topics/permission/overview.html + + """ def __init__(self, name, type, logic, decision_strategy): self._name = name diff --git a/keycloak/authorization/policy.py b/keycloak/authorization/policy.py index 2abdf5e..2504fd1 100644 --- a/keycloak/authorization/policy.py +++ b/keycloak/authorization/policy.py @@ -19,6 +19,17 @@ from keycloak.exceptions import KeycloakAuthorizationConfigError class Policy: + """ + A policy defines the conditions that must be satisfied to grant access to an object. + Unlike permissions, you do not specify the object being protected but rather the conditions + that must be satisfied for access to a given object (for example, resource, scope, or both). + Policies are strongly related to the different access control mechanisms (ACMs) that you can use to + protect your resources. With policies, you can implement strategies for attribute-based access control + (ABAC), role-based access control (RBAC), context-based access control, or any combination of these. + + https://keycloak.gitbooks.io/documentation/authorization_services/topics/policy/overview.html + + """ def __init__(self, name, type, logic, decision_strategy): self._name = name @@ -75,10 +86,22 @@ class Policy: return self._permissions def add_role(self, role): + """ + Add keycloak role in policy. + + :param role: keycloak role. + :return: + """ if self.type != 'role': raise KeycloakAuthorizationConfigError( "Can't add role. Policy type is different of role") self._roles.append(role) def add_permission(self, permission): + """ + Add keycloak permission in policy. + + :param permission: keycloak permission. + :return: + """ self._permissions.append(permission) diff --git a/keycloak/authorization/role.py b/keycloak/authorization/role.py index ff7efde..8d398b0 100644 --- a/keycloak/authorization/role.py +++ b/keycloak/authorization/role.py @@ -17,6 +17,13 @@ class Role: + """ + Roles identify a type or category of user. Admin, user, + manager, and employee are all typical roles that may exist in an organization. + + https://keycloak.gitbooks.io/documentation/server_admin/topics/roles.html + + """ def __init__(self, name, required=False): self.name = name @@ -25,3 +32,8 @@ class Role: @property def get_name(self): return self.name + + def __eq__(self, other): + if isinstance(other, str): + return self.name == other + return NotImplemented diff --git a/keycloak/exceptions.py b/keycloak/exceptions.py index 0ce69bb..7576352 100644 --- a/keycloak/exceptions.py +++ b/keycloak/exceptions.py @@ -48,26 +48,10 @@ class KeycloakOperationError(KeycloakError): pass -class KeycloakListError(KeycloakOperationError): - pass - - class KeycloakGetError(KeycloakOperationError): pass -class KeycloakCreateError(KeycloakOperationError): - pass - - -class KeycloakUpdateError(KeycloakOperationError): - pass - - -class KeycloakDeleteError(KeycloakOperationError): - pass - - class KeycloakSecretNotFound(KeycloakOperationError): pass @@ -80,6 +64,10 @@ class KeycloakAuthorizationConfigError(KeycloakOperationError): pass +class KeycloakInvalidTokenError(KeycloakOperationError): + pass + + def raise_error_from_response(response, error, expected_code=200): if expected_code == response.status_code: