diff --git a/libraries/api/openapi.yaml b/libraries/api/openapi.yaml index 280997e4..6fd92e36 100644 --- a/libraries/api/openapi.yaml +++ b/libraries/api/openapi.yaml @@ -609,3 +609,22 @@ components: \ in the Docker image)" example: my_lib.change_pod_manifest additionalProperties: true + UserRole: + type: object + properties: + attributes: + type: object + additionalProperties: true + clientRole: + type: boolean + composite: + type: boolean + containerId: + type: string + description: + type: string + id: + type: string + name: + type: string + additionalProperties: true diff --git a/libraries/cloudharness-common/cloudharness/auth/keycloak.py b/libraries/cloudharness-common/cloudharness/auth/keycloak.py index 4ef77150..1c8bead4 100644 --- a/libraries/cloudharness-common/cloudharness/auth/keycloak.py +++ b/libraries/cloudharness-common/cloudharness/auth/keycloak.py @@ -1,4 +1,5 @@ import os +from typing import List import jwt import json import requests @@ -7,7 +8,7 @@ from cachetools import cached, TTLCache from cloudharness import log from cloudharness.middleware import get_authentication_token - +from cloudharness.models import UserGroup, User, UserRole try: @@ -25,7 +26,8 @@ def __init__(self, secret_name): def get_api_password() -> str: name = "api_user_password" - AUTH_SECRET_PATH = os.environ.get("AUTH_SECRET_PATH", "/opt/cloudharness/resources/auth") + AUTH_SECRET_PATH = os.environ.get( + "AUTH_SECRET_PATH", "/opt/cloudharness/resources/auth") try: with open(os.path.join(AUTH_SECRET_PATH, name)) as fh: return fh.read() @@ -239,7 +241,7 @@ def create_client_role(self, client_id, role): @cached(cache=TTLCache(maxsize=1024, ttl=30)) @with_refreshtoken - def get_group(self, group_id, with_members=False): + def get_group(self, group_id, with_members=False) -> UserGroup: """ Return the group in the application realm @@ -259,10 +261,10 @@ def get_group(self, group_id, with_members=False): user.update( {'realmRoles': admin_client.get_realm_roles_of_user(user['id'])}) group.update({'members': members}) - return group + return UserGroup.from_dict(group) @with_refreshtoken - def get_groups(self, with_members=False): + def get_groups(self, with_members=False) -> List[UserGroup]: """ Return a list of all groups in the application realm @@ -273,13 +275,13 @@ def get_groups(self, with_members=False): :return: List(GroupRepresentation) """ admin_client = self.get_admin_client() - groups = [] - for group in admin_client.get_groups(): - groups.append(self.get_group(group['id'], with_members)) - return groups + return [ + UserGroup.from_dict(self.get_group(group['id'], with_members)) + for group in admin_client.get_groups() + ] @with_refreshtoken - def create_group(self, name: str, parent: str=None): + def create_group(self, name: str, parent: str = None) -> UserGroup: """ Create a group in the application realm @@ -288,18 +290,35 @@ def create_group(self, name: str, parent: str=None): :param name: the name of the group :param parent: parent group's id. Required to create a sub-group. - :return: GroupRepresentation """ admin_client = self.get_admin_client() - return admin_client.create_group( + return UserGroup.from_dict(admin_client.create_group( payload={ "name": name, }, parent=parent, - skip_exists=True) + skip_exists=True)) + + @with_refreshtoken + def add_group(self, group: UserGroup, parent: str) -> UserGroup: + """ + Create a group in the application realm + + GroupRepresentation + https://www.keycloak.org/docs-api/16.0/rest-api/index.html#_grouprepresentation + + :param name: the name of the group + :param parent: parent group's id. Required to create a sub-group. + :return: UserGroup + """ + admin_client = self.get_admin_client() + return UserGroup.from_dict(admin_client.create_group( + payload=group.to_dict(), + parent=parent, + skip_exists=True)) @with_refreshtoken - def update_group(self, group_id: str, name: str): + def update_group(self, group_id: str, name: str) -> UserGroup: """ Updates the group identified by the given group_id in the application realm @@ -308,14 +327,13 @@ def update_group(self, group_id: str, name: str): :param group_id: the id of the group to update :param name: the new name of the group - :return: GroupRepresentation """ admin_client = self.get_admin_client() - return admin_client.update_group( + return UserGroup.from_dict(admin_client.update_group( group_id=group_id, payload={ "name": name - }) + })) @with_refreshtoken def group_user_add(self, user_id, group_id): @@ -342,7 +360,7 @@ def group_user_remove(self, user_id, group_id): return admin_client.group_user_remove(user_id, group_id) @with_refreshtoken - def get_users(self, query=None): + def get_users(self, query=None) -> List[User]: """ Return a list of all users in the application realm @@ -358,11 +376,11 @@ def get_users(self, query=None): admin_client = self.get_admin_client() users = [] for user in admin_client.get_users(query=query): - user.update( - {'userGroups': admin_client.get_user_groups(user['id'])}) - user.update( - {'realmRoles': admin_client.get_realm_roles_of_user(user['id'])}) - users.append(user) + user.update({ + "userGroups": admin_client.get_user_groups(user['id']), + 'realmRoles': admin_client.get_realm_roles_of_user(user['id']) + }) + users.append(User.from_dict(user)) return users @cached(cache=TTLCache(maxsize=1024, ttl=30)) @@ -383,10 +401,11 @@ def get_user(self, user_id): """ admin_client = self.get_admin_client() user = admin_client.get_user(user_id) - user.update({'userGroups': admin_client.get_user_groups(user_id)}) - user.update( - {'realmRoles': admin_client.get_realm_roles_of_user(user_id)}) - return user + user.update({ + "userGroups": admin_client.get_user_groups(user['id']), + 'realmRoles': admin_client.get_realm_roles_of_user(user['id']) + }) + return User.from_dict(user) def get_current_user(self): """ @@ -402,9 +421,9 @@ def get_current_user(self): """ return self.get_user(self._get_keycloak_user_id()) - @cached(cache=TTLCache(maxsize=1024, ttl=30)) + @cached(cache = TTLCache(maxsize=1024, ttl=30)) @with_refreshtoken - def get_user_realm_roles(self, user_id): + def get_user_realm_roles(self, user_id) -> List[str]: """ Get the user realm roles within the current realm @@ -415,10 +434,10 @@ def get_user_realm_roles(self, user_id): :return: (array RoleRepresentation) """ - admin_client = self.get_admin_client() + admin_client=self.get_admin_client() return admin_client.get_realm_roles_of_user(user_id) - def get_current_user_realm_roles(self): + def get_current_user_realm_roles(self) -> List[str]: """ Get the current user realm roles within the current realm @@ -429,9 +448,9 @@ def get_current_user_realm_roles(self): """ return self.get_user_realm_roles(self._get_keycloak_user_id()) - @cached(cache=TTLCache(maxsize=1024, ttl=30)) + @cached(cache = TTLCache(maxsize=1024, ttl=30)) @with_refreshtoken - def get_user_client_roles(self, user_id, client_name): + def get_user_client_roles(self, user_id, client_name) -> List[str]: """ Get the user including the user resource access @@ -439,81 +458,76 @@ def get_user_client_roles(self, user_id, client_name): :param client_name: Client name :return: (array RoleRepresentation) """ - admin_client = self.get_admin_client() - client_id = admin_client.get_client_id(client_name) + admin_client=self.get_admin_client() + client_id=admin_client.get_client_id(client_name) return admin_client.get_client_roles_of_user(user_id, client_id) - def get_current_user_client_roles(self, client_name): + def get_current_user_client_roles(self, client_name) -> List[str]: """ Get the user including the user resource access :param client_name: Client name :return: UserRepresentation + GroupRepresentation """ - cur_user_id = self._get_keycloak_user_id() + cur_user_id=self._get_keycloak_user_id() return self.get_user_client_roles(cur_user_id, client_name) - def user_has_client_role(self, user_id, client_name, role): + def user_has_client_role(self, user_id, client_name, role) -> bool: """ Tests if the user has the given role within the given client :param user_id: User id :param client_name: Name of the client :param role: Name of the role - :return: (array RoleRepresentation) """ - roles = [user_client_role for user_client_role in self.get_user_client_roles( + roles=[user_client_role for user_client_role in self.get_user_client_roles( user_id, client_name) if user_client_role['name'] == role] return roles != [] - def user_has_realm_role(self, user_id, role): + def user_has_realm_role(self, user_id, role) -> bool: """ Tests if the user has the given role within the current realm :param user_id: User id :param role: Name of the role - :return: (array RoleRepresentation) """ - roles = [user_realm_role for user_realm_role in self.get_user_realm_roles( + roles=[user_realm_role for user_realm_role in self.get_user_realm_roles( user_id) if user_realm_role['name'] == role] return roles != [] - def current_user_has_client_role(self, client_name, role): + def current_user_has_client_role(self, client_name, role) -> bool: """ Tests if the current user has the given role within the given client :param client_name: Name of the client :param role: Name of the role - :return: (array RoleRepresentation) """ return self.user_has_client_role( self._get_keycloak_user_id(), client_name, role) - def current_user_has_realm_role(self, role): + def current_user_has_realm_role(self, role) -> bool: """ Tests if the current user has the given role within the current realm :param role: Name of the role - :return: (array RoleRepresentation) """ return self.user_has_realm_role( self._get_keycloak_user_id(), role) @with_refreshtoken - def get_client_role_members(self, client_name, role): + def get_client_role_members(self, client_name, role) -> List[User]: """ Get all users for the specified client and role :param client_name: Client name :param role: Role name - :return: List(UserRepresentation) """ - admin_client = self.get_admin_client() - client_id = admin_client.get_client_id(client_name) - return admin_client.get_client_role_members(client_id, role) + admin_client=self.get_admin_client() + client_id=admin_client.get_client_id(client_name) + return [User.from_dict(u) for u in admin_client.get_client_role_members(client_id, role)] @with_refreshtoken def user_add_update_attribute(self, user_id, attribute_name, attribute_value): @@ -523,18 +537,17 @@ def user_add_update_attribute(self, user_id, attribute_name, attribute_value): param user_id: id of the user param attribute_name: name of the attribute to add/update param attribute_value: value of the attribute - :return: boolean True on success + """ - admin_client = self.get_admin_client() - user = self.get_user(user_id) - attributes = user.get('attributes', {}) - attributes[attribute_name] = attribute_value + admin_client=self.get_admin_client() + user=self.get_user(user_id) + attributes=user.get('attributes', {}) + attributes[attribute_name]=attribute_value admin_client.update_user( user_id, { 'attributes': attributes }) - return True @with_refreshtoken def user_delete_attribute(self, user_id, attribute_name): @@ -545,9 +558,9 @@ def user_delete_attribute(self, user_id, attribute_name): param attribute_name: name of the attribute to delete :return: boolean True on success, False is attribute not in user attributes """ - admin_client = self.get_admin_client() - user = self.get_user(user_id) - attributes = user.get('attributes', None) + admin_client=self.get_admin_client() + user=self.get_user(user_id) + attributes=user.get('attributes', None) if attributes and attribute_name in attributes: del attributes[attribute_name] admin_client.update_user( diff --git a/libraries/cloudharness-common/cloudharness/events/client.py b/libraries/cloudharness-common/cloudharness/events/client.py index a7155106..3a76284d 100644 --- a/libraries/cloudharness-common/cloudharness/events/client.py +++ b/libraries/cloudharness-common/cloudharness/events/client.py @@ -2,7 +2,7 @@ import sys import threading import time -import traceback +from typing import List import logging from time import sleep @@ -18,6 +18,7 @@ from cloudharness.errors import * from cloudharness.utils import env from cloudharness.utils.config import CloudharnessConfig as config +from cloudharness.models import CDCEvent logging.getLogger('kafka').setLevel(logging.ERROR) diff --git a/libraries/models/.openapi-generator-ignore b/libraries/models/.openapi-generator-ignore index 996bece9..26c17133 100644 --- a/libraries/models/.openapi-generator-ignore +++ b/libraries/models/.openapi-generator-ignore @@ -32,4 +32,5 @@ setup.py */__init__.py encoder.py *.sh -util.py \ No newline at end of file +util.py +base_model.py \ No newline at end of file diff --git a/libraries/models/cloudharness_model/models/__init__.py b/libraries/models/cloudharness_model/models/__init__.py index 4fde01a7..9e1b6ab7 100644 --- a/libraries/models/cloudharness_model/models/__init__.py +++ b/libraries/models/cloudharness_model/models/__init__.py @@ -30,3 +30,4 @@ from cloudharness_model.models.user import User from cloudharness_model.models.user_credential import UserCredential from cloudharness_model.models.user_group import UserGroup +from cloudharness_model.models.user_role import UserRole diff --git a/libraries/models/cloudharness_model/models/base_model_.py b/libraries/models/cloudharness_model/models/base_model_.py index 17aea9c2..f3288fda 100644 --- a/libraries/models/cloudharness_model/models/base_model_.py +++ b/libraries/models/cloudharness_model/models/base_model_.py @@ -17,27 +17,10 @@ class Model(object): # value is json key in definition. attribute_map = {} - def __init__(self): - self._raw_dict = {} - @classmethod def from_dict(cls: typing.Type[T], dikt) -> T: """Returns the dict as a model""" - obj = util.deserialize_model(dikt, cls) - return obj - - def __getitem__(self, key): - if hasattr(self, key): - return getattr(self, key) - return self._raw_dict[key] - - - def __contains__(self, key): - if key in self.attribute_map: - return True - elif hasattr(self, "_raw_dict"): - return key in self._raw_dict - return False + return util.deserialize_model(dikt, cls) def to_dict(self): """Returns the model properties as a dict @@ -64,10 +47,6 @@ def to_dict(self): else: result[attr] = value - if hasattr(self, "raw_dict"): - merged = dict(self.raw_dict) - merged.update(result) - return merged return result def to_str(self): diff --git a/libraries/models/cloudharness_model/models/user_role.py b/libraries/models/cloudharness_model/models/user_role.py new file mode 100644 index 00000000..dfa7e529 --- /dev/null +++ b/libraries/models/cloudharness_model/models/user_role.py @@ -0,0 +1,220 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from cloudharness_model.models.base_model_ import Model +from cloudharness_model import util + + +class UserRole(Model): + """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + + Do not edit the class manually. + """ + + def __init__(self, attributes=None, client_role=None, composite=None, container_id=None, description=None, id=None, name=None): # noqa: E501 + """UserRole - a model defined in OpenAPI + + :param attributes: The attributes of this UserRole. # noqa: E501 + :type attributes: Dict[str, object] + :param client_role: The client_role of this UserRole. # noqa: E501 + :type client_role: bool + :param composite: The composite of this UserRole. # noqa: E501 + :type composite: bool + :param container_id: The container_id of this UserRole. # noqa: E501 + :type container_id: str + :param description: The description of this UserRole. # noqa: E501 + :type description: str + :param id: The id of this UserRole. # noqa: E501 + :type id: str + :param name: The name of this UserRole. # noqa: E501 + :type name: str + """ + self.openapi_types = { + 'attributes': Dict[str, object], + 'client_role': bool, + 'composite': bool, + 'container_id': str, + 'description': str, + 'id': str, + 'name': str + } + + self.attribute_map = { + 'attributes': 'attributes', + 'client_role': 'clientRole', + 'composite': 'composite', + 'container_id': 'containerId', + 'description': 'description', + 'id': 'id', + 'name': 'name' + } + + self._attributes = attributes + self._client_role = client_role + self._composite = composite + self._container_id = container_id + self._description = description + self._id = id + self._name = name + + @classmethod + def from_dict(cls, dikt) -> 'UserRole': + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The UserRole of this UserRole. # noqa: E501 + :rtype: UserRole + """ + return util.deserialize_model(dikt, cls) + + @property + def attributes(self): + """Gets the attributes of this UserRole. + + + :return: The attributes of this UserRole. + :rtype: Dict[str, object] + """ + return self._attributes + + @attributes.setter + def attributes(self, attributes): + """Sets the attributes of this UserRole. + + + :param attributes: The attributes of this UserRole. + :type attributes: Dict[str, object] + """ + + self._attributes = attributes + + @property + def client_role(self): + """Gets the client_role of this UserRole. + + + :return: The client_role of this UserRole. + :rtype: bool + """ + return self._client_role + + @client_role.setter + def client_role(self, client_role): + """Sets the client_role of this UserRole. + + + :param client_role: The client_role of this UserRole. + :type client_role: bool + """ + + self._client_role = client_role + + @property + def composite(self): + """Gets the composite of this UserRole. + + + :return: The composite of this UserRole. + :rtype: bool + """ + return self._composite + + @composite.setter + def composite(self, composite): + """Sets the composite of this UserRole. + + + :param composite: The composite of this UserRole. + :type composite: bool + """ + + self._composite = composite + + @property + def container_id(self): + """Gets the container_id of this UserRole. + + + :return: The container_id of this UserRole. + :rtype: str + """ + return self._container_id + + @container_id.setter + def container_id(self, container_id): + """Sets the container_id of this UserRole. + + + :param container_id: The container_id of this UserRole. + :type container_id: str + """ + + self._container_id = container_id + + @property + def description(self): + """Gets the description of this UserRole. + + + :return: The description of this UserRole. + :rtype: str + """ + return self._description + + @description.setter + def description(self, description): + """Sets the description of this UserRole. + + + :param description: The description of this UserRole. + :type description: str + """ + + self._description = description + + @property + def id(self): + """Gets the id of this UserRole. + + + :return: The id of this UserRole. + :rtype: str + """ + return self._id + + @id.setter + def id(self, id): + """Sets the id of this UserRole. + + + :param id: The id of this UserRole. + :type id: str + """ + + self._id = id + + @property + def name(self): + """Gets the name of this UserRole. + + + :return: The name of this UserRole. + :rtype: str + """ + return self._name + + @name.setter + def name(self, name): + """Sets the name of this UserRole. + + + :param name: The name of this UserRole. + :type name: str + """ + + self._name = name diff --git a/libraries/models/cloudharness_model/util.py b/libraries/models/cloudharness_model/util.py index b1c85b86..bdbe5756 100644 --- a/libraries/models/cloudharness_model/util.py +++ b/libraries/models/cloudharness_model/util.py @@ -68,8 +68,8 @@ def deserialize_date(string): :rtype: date """ if string is None: - return None - + return None + try: from dateutil.parser import parse return parse(string).date() @@ -88,8 +88,8 @@ def deserialize_datetime(string): :rtype: datetime """ if string is None: - return None - + return None + try: from dateutil.parser import parse return parse(string) @@ -97,8 +97,6 @@ def deserialize_datetime(string): return string - - def deserialize_model(data, klass): """Deserializes list or dict to model. @@ -108,27 +106,16 @@ def deserialize_model(data, klass): :return: model object. """ instance = klass() - instance._raw_dict = data if not instance.openapi_types: return data - if data is None: - return instance - - if isinstance(data, list): - for attr, attr_type in six.iteritems(instance.openapi_types): - if instance.attribute_map[attr] in data: - value = data[instance.attribute_map[attr]] - setattr(instance, attr, _deserialize(value, attr_type)) - - elif isinstance(data, dict): - - for attr in data: - value = data[attr] - if attr in instance.attribute_map: - setattr(instance, attr, _deserialize(value, instance.openapi_types[attr])) - else: - setattr(instance, attr, value) + + for attr, attr_type in six.iteritems(instance.openapi_types): + if data is not None \ + and instance.attribute_map[attr] in data \ + and isinstance(data, (list, dict)): + value = data[instance.attribute_map[attr]] + setattr(instance, attr, _deserialize(value, attr_type)) return instance diff --git a/libraries/models/test/test_helm_values.py b/libraries/models/test/test_helm_values.py index 2788b96b..cf0cdc88 100644 --- a/libraries/models/test/test_helm_values.py +++ b/libraries/models/test/test_helm_values.py @@ -2,7 +2,7 @@ from os.path import join, dirname as dn, realpath import yaml -from cloudharness_model import HarnessMainConfig, ApplicationConfig +from cloudharness_model import HarnessMainConfig, ApplicationConfig, User HERE = dn(realpath(__file__)) @@ -15,4 +15,10 @@ def test_helm_values_deserialize(): assert v.apps["accounts"].harness.deployment.name == "accounts" app = ApplicationConfig.from_dict(values["apps"]["accounts"]) - assert app.harness.deployment.name == "accounts" \ No newline at end of file + assert app.harness.deployment.name == "accounts" + + assert v.apps["accounts"].webclient.get('id') + + u = User(last_name="a") + assert u.last_name == "a" + assert u["last_name"] == "a" \ No newline at end of file