From 0a73223b02fa5bd68e2b22282c4684974bf64a65 Mon Sep 17 00:00:00 2001 From: ImMin5 Date: Tue, 14 Nov 2023 03:24:30 +0900 Subject: [PATCH 01/45] feat: add all interface and model (#64) Signed-off-by: ImMin5 --- src/spaceone/identity/__init__.py | 2 +- src/spaceone/identity/conf/global_conf.py | 118 ++- .../identity/interface/grpc/__init__.py | 41 +- .../identity/interface/grpc/api_key.py | 56 +- .../identity/interface/grpc/authorization.py | 12 +- .../identity/interface/grpc/domain.py | 98 +-- .../identity/interface/grpc/endpoint.py | 12 +- .../identity/interface/grpc/policy.py | 45 +- .../identity/interface/grpc/project.py | 94 +-- .../identity/interface/grpc/project_group.py | 83 +-- .../identity/interface/grpc/provider.py | 48 +- src/spaceone/identity/interface/grpc/role.py | 45 +- .../identity/interface/grpc/role_binding.py | 47 +- .../interface/grpc/service_account.py | 58 +- src/spaceone/identity/interface/grpc/token.py | 18 +- .../interface/grpc/trusted_service_account.py | 48 ++ src/spaceone/identity/interface/grpc/user.py | 115 ++- .../identity/interface/grpc/user_group.py | 56 ++ .../identity/interface/grpc/workspace.py | 56 ++ src/spaceone/identity/model/__init__.py | 13 +- .../identity/model/api_key_request.py | 58 ++ .../identity/model/api_key_response.py | 20 + .../identity/model/authorization_request.py | 41 ++ .../identity/model/authorization_response.py | 10 + src/spaceone/identity/model/domain_request.py | 44 ++ .../identity/model/domain_response.py | 40 + .../identity/model/endpoint_request.py | 10 + .../identity/model/endpoint_response.py | 17 + src/spaceone/identity/model/policy_request.py | 48 ++ .../identity/model/policy_response.py | 19 + .../identity/model/project_group_request.py | 62 ++ .../identity/model/project_group_response.py | 20 + .../identity/model/project_request.py | 108 +++ .../identity/model/project_response.py | 23 + .../identity/model/provider_request.py | 55 ++ .../identity/model/provider_response.py | 22 + .../identity/model/role_binding_request.py | 57 ++ .../identity/model/role_binding_response.py | 24 + src/spaceone/identity/model/role_request.py | 55 ++ src/spaceone/identity/model/role_response.py | 23 + .../identity/model/service_account_request.py | 78 ++ .../model/service_account_response.py | 25 + src/spaceone/identity/model/token_request.py | 15 + src/spaceone/identity/model/token_response.py | 8 + .../model/trusted_service_account_request.py | 60 ++ .../model/trusted_service_account_response.py | 24 + .../identity/model/user_group_request.py | 69 ++ .../identity/model/user_group_response.py | 19 + src/spaceone/identity/model/user_request.py | 139 ++++ src/spaceone/identity/model/user_response.py | 30 + .../identity/model/workspace_request.py | 59 ++ .../identity/model/workspace_response.py | 22 + src/spaceone/identity/service/__init__.py | 14 - .../identity/service/api_key_service.py | 192 ++--- .../identity/service/authorization_service.py | 251 +------ src/spaceone/identity/service/base_service.py | 39 + .../identity/service/domain_service.py | 318 ++------ .../identity/service/endpoint_service.py | 37 +- .../identity/service/policy_service.py | 175 +---- .../identity/service/project_group_service.py | 571 ++------------- .../identity/service/project_service.py | 447 ++---------- src/spaceone/identity/service/provider.py | 41 ++ .../identity/service/role_binding_service.py | 183 ++--- src/spaceone/identity/service/role_service.py | 204 +----- .../service/service_account_service.py | 334 ++------- .../identity/service/token_service.py | 177 +---- .../trusted_service_account_service.py | 47 ++ .../identity/service/user_group_service.py | 55 ++ src/spaceone/identity/service/user_service.py | 682 ++---------------- .../identity/service/workspace_service.py | 53 ++ 70 files changed, 2597 insertions(+), 3592 deletions(-) create mode 100644 src/spaceone/identity/interface/grpc/trusted_service_account.py create mode 100644 src/spaceone/identity/interface/grpc/user_group.py create mode 100644 src/spaceone/identity/interface/grpc/workspace.py create mode 100644 src/spaceone/identity/model/api_key_request.py create mode 100644 src/spaceone/identity/model/api_key_response.py create mode 100644 src/spaceone/identity/model/authorization_request.py create mode 100644 src/spaceone/identity/model/authorization_response.py create mode 100644 src/spaceone/identity/model/domain_request.py create mode 100644 src/spaceone/identity/model/domain_response.py create mode 100644 src/spaceone/identity/model/endpoint_request.py create mode 100644 src/spaceone/identity/model/endpoint_response.py create mode 100644 src/spaceone/identity/model/policy_request.py create mode 100644 src/spaceone/identity/model/policy_response.py create mode 100644 src/spaceone/identity/model/project_group_request.py create mode 100644 src/spaceone/identity/model/project_group_response.py create mode 100644 src/spaceone/identity/model/project_request.py create mode 100644 src/spaceone/identity/model/project_response.py create mode 100644 src/spaceone/identity/model/provider_request.py create mode 100644 src/spaceone/identity/model/provider_response.py create mode 100644 src/spaceone/identity/model/role_binding_request.py create mode 100644 src/spaceone/identity/model/role_binding_response.py create mode 100644 src/spaceone/identity/model/role_request.py create mode 100644 src/spaceone/identity/model/role_response.py create mode 100644 src/spaceone/identity/model/service_account_request.py create mode 100644 src/spaceone/identity/model/service_account_response.py create mode 100644 src/spaceone/identity/model/token_request.py create mode 100644 src/spaceone/identity/model/token_response.py create mode 100644 src/spaceone/identity/model/trusted_service_account_request.py create mode 100644 src/spaceone/identity/model/trusted_service_account_response.py create mode 100644 src/spaceone/identity/model/user_group_request.py create mode 100644 src/spaceone/identity/model/user_group_response.py create mode 100644 src/spaceone/identity/model/user_request.py create mode 100644 src/spaceone/identity/model/user_response.py create mode 100644 src/spaceone/identity/model/workspace_request.py create mode 100644 src/spaceone/identity/model/workspace_response.py create mode 100644 src/spaceone/identity/service/base_service.py create mode 100644 src/spaceone/identity/service/provider.py create mode 100644 src/spaceone/identity/service/trusted_service_account_service.py create mode 100644 src/spaceone/identity/service/user_group_service.py create mode 100644 src/spaceone/identity/service/workspace_service.py diff --git a/src/spaceone/identity/__init__.py b/src/spaceone/identity/__init__.py index 387de8b7..f8305229 100644 --- a/src/spaceone/identity/__init__.py +++ b/src/spaceone/identity/__init__.py @@ -1 +1 @@ -name = 'identity' +__name__ = "identity" diff --git a/src/spaceone/identity/conf/global_conf.py b/src/spaceone/identity/conf/global_conf.py index cb04c79e..3b7badbc 100644 --- a/src/spaceone/identity/conf/global_conf.py +++ b/src/spaceone/identity/conf/global_conf.py @@ -1,11 +1,11 @@ -ROOT_DOMAIN_NAME = 'root' -EMAIL_CONSOLE_DOMAIN = '' -EMAIL_SERVICE_NAME = 'Cloudforet' +ROOT_DOMAIN_NAME = "root" +EMAIL_CONSOLE_DOMAIN = "" +EMAIL_SERVICE_NAME = "Cloudforet" # ACCESS_TOKEN (default) / PASSWORD -RESET_PASSWORD_TYPE = 'ACCESS_TOKEN' +RESET_PASSWORD_TYPE = "ACCESS_TOKEN" DATABASE_AUTO_CREATE_INDEX = True DATABASES = { - 'default': { + "default": { # 'db': '', # 'host': '', # 'port': 0, @@ -17,26 +17,24 @@ } CACHES = { - 'default': {}, - 'local': { - 'backend': 'spaceone.core.cache.local_cache.LocalCache', - 'max_size': 128, - 'ttl': 300 - } + "default": {}, + "local": { + "backend": "spaceone.core.cache.local_cache.LocalCache", + "max_size": 128, + "ttl": 300, + }, } IDENTITY = { - 'token': { - 'verify_code_timeout': 3600, - 'temporary_token_timeout': 86400, # 24 hours - 'token_timeout': 1200, - 'refresh_timeout': 1800, - 'refresh_ttl': 18, - 'refresh_once': False + "token": { + "verify_code_timeout": 3600, + "temporary_token_timeout": 86400, # 24 hours + "token_timeout": 1200, + "refresh_timeout": 1800, + "refresh_ttl": 18, + "refresh_once": False, }, - 'mfa': { - 'mfa_verify_code_timeout': 300 - } + "mfa": {"mfa_verify_code_timeout": 300}, } HANDLERS = { @@ -55,21 +53,21 @@ } CONNECTORS = { - 'SpaceConnector': { - 'backend': 'spaceone.core.connector.space_connector.SpaceConnector', - 'endpoints': { - 'plugin': 'grpc://plugin:50051', - 'secret': 'grpc://secret:50051', - 'repository': 'grpc://repository:50051' - } + "SpaceConnector": { + "backend": "spaceone.core.connector.space_connector.SpaceConnector", + "endpoints": { + "plugin": "grpc://plugin:50051", + "secret": "grpc://secret:50051", + "repository": "grpc://repository:50051", + }, + }, + "SMTPConnector": { + "host": "smtp.mail.com", + "port": "1234", + "user": "cloudforet", + "password": "1234", + "from_email": "support@cloudforet.com", }, - 'SMTPConnector': { - 'host': 'smtp.mail.com', - 'port': '1234', - 'user': 'cloudforet', - 'password': '1234', - 'from_email': 'support@cloudforet.com' - } } ENDPOINTS = [ @@ -83,43 +81,43 @@ # Internal Endpoint INTERNAL_ENDPOINTS = [ { - 'service': 'identity', - 'name': 'Identity Service', - 'endpoint': 'grpc://identity.spaceone.svc.cluster.local:50051/v1' + "service": "identity", + "name": "Identity Service", + "endpoint": "grpc://identity.spaceone.svc.cluster.local:50051/v1", }, { - 'service': 'secret', - 'name': 'Secret Service', - 'endpoint': 'grpc://secret.spaceone.svc.cluster.local:50051/v1' + "service": "secret", + "name": "Secret Service", + "endpoint": "grpc://secret.spaceone.svc.cluster.local:50051/v1", }, { - 'service': 'repository', - 'name': 'Repository Service', - 'endpoint': 'grpc://repository.spaceone.svc.cluster.local:50051/v1' + "service": "repository", + "name": "Repository Service", + "endpoint": "grpc://repository.spaceone.svc.cluster.local:50051/v1", }, { - 'service': 'plugin', - 'name': 'Plugin Service', - 'endpoint': 'grpc://plugin.spaceone.svc.cluster.local:50051/v1' + "service": "plugin", + "name": "Plugin Service", + "endpoint": "grpc://plugin.spaceone.svc.cluster.local:50051/v1", }, { - 'service': 'config', - 'name': 'Config Service', - 'endpoint': 'grpc://config.spaceone.svc.cluster.local:50051/v1' + "service": "config", + "name": "Config Service", + "endpoint": "grpc://config.spaceone.svc.cluster.local:50051/v1", }, { - 'service': 'inventory', - 'name': 'Inventory Service', - 'endpoint': 'grpc://inventory.spaceone.svc.cluster.local:50051/v1' + "service": "inventory", + "name": "Inventory Service", + "endpoint": "grpc://inventory.spaceone.svc.cluster.local:50051/v1", }, { - 'service': 'monitoring', - 'name': 'Monitoring Service', - 'endpoint': 'grpc://monitoring.spaceone.svc.cluster.local:50051/v1' + "service": "monitoring", + "name": "Monitoring Service", + "endpoint": "grpc://monitoring.spaceone.svc.cluster.local:50051/v1", }, { - 'service': 'statistics', - 'name': 'Statistics Service', - 'endpoint': 'grpc://statistics.spaceone.svc.cluster.local:50051/v1' - } + "service": "statistics", + "name": "Statistics Service", + "endpoint": "grpc://statistics.spaceone.svc.cluster.local:50051/v1", + }, ] diff --git a/src/spaceone/identity/interface/grpc/__init__.py b/src/spaceone/identity/interface/grpc/__init__.py index 49f8fa67..acd5406b 100644 --- a/src/spaceone/identity/interface/grpc/__init__.py +++ b/src/spaceone/identity/interface/grpc/__init__.py @@ -1,34 +1,39 @@ from spaceone.core.pygrpc.server import GRPCServer -from spaceone.identity.interface.grpc.api_key import APIKey -from spaceone.identity.interface.grpc.authorization import Authorization from spaceone.identity.interface.grpc.domain import Domain -from spaceone.identity.interface.grpc.domain_owner import DomainOwner from spaceone.identity.interface.grpc.endpoint import Endpoint -from spaceone.identity.interface.grpc.policy import Policy -from spaceone.identity.interface.grpc.project import Project -from spaceone.identity.interface.grpc.project_group import ProjectGroup from spaceone.identity.interface.grpc.provider import Provider +from spaceone.identity.interface.grpc.workspace import Workspace +from spaceone.identity.interface.grpc.project_group import ProjectGroup +from spaceone.identity.interface.grpc.project import Project +from spaceone.identity.interface.grpc.trusted_service_account import ( + TrustedServiceAccount, +) +from spaceone.identity.interface.grpc.service_account import ServiceAccount +from spaceone.identity.interface.grpc.policy import Policy from spaceone.identity.interface.grpc.role import Role from spaceone.identity.interface.grpc.role_binding import RoleBinding -from spaceone.identity.interface.grpc.service_account import ServiceAccount -from spaceone.identity.interface.grpc.token import Token from spaceone.identity.interface.grpc.user import User +from spaceone.identity.interface.grpc.user_group import UserGroup +from spaceone.identity.interface.grpc.api_key import APIKey +from spaceone.identity.interface.grpc.token import Token +from spaceone.identity.interface.grpc.authorization import Authorization -_all_ = ['app'] - +_all_ = ["app"] app = GRPCServer() -app.add_service(APIKey) -app.add_service(Authorization) app.add_service(Domain) -app.add_service(DomainOwner) app.add_service(Endpoint) -app.add_service(Policy) -app.add_service(Project) -app.add_service(ProjectGroup) app.add_service(Provider) +app.add_service(Workspace) +app.add_service(ProjectGroup) +app.add_service(Project) +app.add_service(TrustedServiceAccount) +app.add_service(ServiceAccount) +app.add_service(Policy) app.add_service(Role) app.add_service(RoleBinding) -app.add_service(ServiceAccount) +app.add_service(User) +app.add_service(UserGroup) +app.add_service(APIKey) app.add_service(Token) -app.add_service(User) \ No newline at end of file +app.add_service(Authorization) diff --git a/src/spaceone/identity/interface/grpc/api_key.py b/src/spaceone/identity/interface/grpc/api_key.py index 1f3c0e6d..5ed905db 100644 --- a/src/spaceone/identity/interface/grpc/api_key.py +++ b/src/spaceone/identity/interface/grpc/api_key.py @@ -1,56 +1,56 @@ -from spaceone.api.identity.v1 import api_key_pb2, api_key_pb2_grpc from spaceone.core.pygrpc import BaseAPI +from spaceone.api.identity.v2 import api_key_pb2, api_key_pb2_grpc +from spaceone.identity.service.api_key_service import APIKeyService class APIKey(BaseAPI, api_key_pb2_grpc.APIKeyServicer): - pb2 = api_key_pb2 pb2_grpc = api_key_pb2_grpc def create(self, request, context): params, metadata = self.parse_request(request, context) + api_key_svc = APIKeyService(metadata) + response: dict = api_key_svc.create(params) + return self.dict_to_message(response) - with self.locator.get_service('APIKeyService', metadata) as api_key_svc: - api_key_vo, api_key = api_key_svc.create(params) - return self.locator.get_info('APIKeyInfo', api_key_vo, api_key=api_key) + def update(self, request, context): + params, metadata = self.parse_request(request, context) + api_key_svc = APIKeyService(metadata) + response: dict = api_key_svc.update(params) + return self.dict_to_message(response) def enable(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('APIKeyService', metadata) as api_key_svc: - data = api_key_svc.enable(params) - return self.locator.get_info('APIKeyInfo', data) + api_key_svc = APIKeyService(metadata) + response: dict = api_key_svc.enable(params) + return self.dict_to_message(response) def disable(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('APIKeyService', metadata) as api_key_svc: - data = api_key_svc.disable(params) - return self.locator.get_info('APIKeyInfo', data) + api_key_svc = APIKeyService(metadata) + response: dict = api_key_svc.disable(params) + return self.dict_to_message(response) def delete(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('APIKeyService', metadata) as api_key_svc: - api_key_svc.delete(params) - return self.locator.get_info('EmptyInfo') + api_key_svc = APIKeyService(metadata) + api_key_svc.delete(params) + return self.empty() def get(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('APIKeyService', metadata) as api_key_svc: - data = api_key_svc.get(params) - return self.locator.get_info('APIKeyInfo', data) + api_key_svc = APIKeyService(metadata) + response: dict = api_key_svc.get(params) + return self.dict_to_message(response) def list(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('APIKeyService', metadata) as api_key_svc: - api_key_vos, total_count = api_key_svc.list(params) - return self.locator.get_info('APIKeysInfo', api_key_vos, total_count, minimal=self.get_minimal(params)) + api_key_svc = APIKeyService(metadata) + response: dict = api_key_svc.list(params) + return self.dict_to_message(response) def stat(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('APIKeyService', metadata) as api_key_svc: - return self.locator.get_info('StatisticsInfo', api_key_svc.stat(params)) + api_key_svc = APIKeyService(metadata) + response: dict = api_key_svc.stat(params) + return self.dict_to_message(response) diff --git a/src/spaceone/identity/interface/grpc/authorization.py b/src/spaceone/identity/interface/grpc/authorization.py index aa7c3019..95b4097a 100644 --- a/src/spaceone/identity/interface/grpc/authorization.py +++ b/src/spaceone/identity/interface/grpc/authorization.py @@ -1,16 +1,14 @@ -from spaceone.api.identity.v1 import authorization_pb2, authorization_pb2_grpc from spaceone.core.pygrpc import BaseAPI +from spaceone.api.identity.v2 import authorization_pb2, authorization_pb2_grpc +from spaceone.identity.service.authorization_service import AuthorizationService class Authorization(BaseAPI, authorization_pb2_grpc.AuthorizationServicer): - pb2 = authorization_pb2 pb2_grpc = authorization_pb2_grpc def verify(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('AuthorizationService', metadata) as auth_service: - auth_data = auth_service.verify(params) - return self.locator.get_info('AuthorizationResponse', auth_data) - + authorization_svc = AuthorizationService(metadata) + response: dict = authorization_svc.verify(params) + return self.dict_to_message(response) diff --git a/src/spaceone/identity/interface/grpc/domain.py b/src/spaceone/identity/interface/grpc/domain.py index 4f873fe7..06e8051e 100644 --- a/src/spaceone/identity/interface/grpc/domain.py +++ b/src/spaceone/identity/interface/grpc/domain.py @@ -1,92 +1,68 @@ -from spaceone.api.identity.v1 import domain_pb2, domain_pb2_grpc from spaceone.core.pygrpc import BaseAPI +from spaceone.api.identity.v2 import domain_pb2, domain_pb2_grpc +from spaceone.identity.service.domain_service import DomainService class Domain(BaseAPI, domain_pb2_grpc.DomainServicer): - pb2 = domain_pb2 pb2_grpc = domain_pb2_grpc def create(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('DomainService', metadata) as domain_svc: - data = domain_svc.create(params) - return self.locator.get_info('DomainInfo', data) + domain_svc = DomainService(metadata) + response: dict = domain_svc.create(params) + return self.dict_to_message(response) def update(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('DomainService', metadata) as domain_svc: - data = domain_svc.update(params) - return self.locator.get_info('DomainInfo', data) - - def change_auth_plugin(self, request, context): - params, metadata = self.parse_request(request, context) - - with self.locator.get_service('DomainService', metadata) as domain_svc: - data = domain_svc.change_auth_plugin(params) - return self.locator.get_info('DomainInfo', data) - - def update_plugin(self, request, context): - params, metadata = self.parse_request(request, context) - - with self.locator.get_service('DomainService', metadata) as domain_svc: - data = domain_svc.update_plugin(params) - return self.locator.get_info('DomainInfo', data) - - def verify_plugin(self, request, context): - params, metadata = self.parse_request(request, context) - - with self.locator.get_service('DomainService', metadata) as domain_svc: - data = domain_svc.verify_plugin(params) - return self.locator.get_info('EmptyInfo') - + domain_svc = DomainService(metadata) + response: dict = domain_svc.update(params) + return self.dict_to_message(response) def delete(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('DomainService', metadata) as domain_svc: - domain_svc.delete(params) - return self.locator.get_info('EmptyInfo') + domain_svc = DomainService(metadata) + domain_svc.delete(params) + return self.empty() def enable(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('DomainService', metadata) as domain_svc: - data = domain_svc.enable(params) - return self.locator.get_info('DomainInfo', data) + domain_svc = DomainService(metadata) + response: dict = domain_svc.enable(params) + return self.dict_to_message(response) def disable(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('DomainService', metadata) as domain_svc: - data = domain_svc.disable(params) - return self.locator.get_info('DomainInfo', data) + domain_svc = DomainService(metadata) + response: dict = domain_svc.disable(params) + return self.dict_to_message(response) def get(self, request, context): params, metadata = self.parse_request(request, context) + domain_svc = DomainService(metadata) + response: dict = domain_svc.get(params) + return self.dict_to_message(response) - with self.locator.get_service('DomainService', metadata) as domain_svc: - data = domain_svc.get(params) - return self.locator.get_info('DomainInfo', data) - - def list(self, request, context): + def get_metadata(self, request, context): params, metadata = self.parse_request(request, context) + domain_svc = DomainService(metadata) + response: dict = domain_svc.get_metadata(params) + return self.dict_to_message(response) - with self.locator.get_service('DomainService', metadata) as domain_svc: - data, total_count = domain_svc.list(params) - return self.locator.get_info('DomainsInfo', data, total_count, minimal=self.get_minimal(params)) - - def stat(self, request, context): + def get_public_key(self, request, context): params, metadata = self.parse_request(request, context) + domain_svc = DomainService(metadata) + response: dict = domain_svc.get_public_key(params) + return self.dict_to_message(response) - with self.locator.get_service('DomainService', metadata) as domain_svc: - return self.locator.get_info('StatisticsInfo', domain_svc.stat(params)) - - def get_public_key(self, request, context): + def list(self, request, context): params, metadata = self.parse_request(request, context) + domain_svc = DomainService(metadata) + response: dict = domain_svc.list(params) + return self.dict_to_message(response) - with self.locator.get_service('DomainService', metadata) as domain_svc: - data = domain_svc.get_public_key(params) - return self.locator.get_info('DomainPublicKeyInfo', data['pub_jwk'], data['domain_id']) + def stat(self, request, context): + params, metadata = self.parse_request(request, context) + domain_svc = DomainService(metadata) + response: dict = domain_svc.stat(params) + return self.dict_to_message(response) diff --git a/src/spaceone/identity/interface/grpc/endpoint.py b/src/spaceone/identity/interface/grpc/endpoint.py index 18a7db37..6a10066f 100644 --- a/src/spaceone/identity/interface/grpc/endpoint.py +++ b/src/spaceone/identity/interface/grpc/endpoint.py @@ -1,16 +1,14 @@ -from spaceone.api.identity.v1 import endpoint_pb2, endpoint_pb2_grpc from spaceone.core.pygrpc import BaseAPI +from spaceone.api.identity.v2 import endpoint_pb2, endpoint_pb2_grpc +from spaceone.identity.service.endpoint_service import EndpointService class Endpoint(BaseAPI, endpoint_pb2_grpc.EndpointServicer): - pb2 = endpoint_pb2 pb2_grpc = endpoint_pb2_grpc def list(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('EndpointService', metadata) as endpoint_svc: - endpoint_vos, total_count = endpoint_svc.list(params) - return self.locator.get_info('EndpointsInfo', endpoint_vos, - total_count, minimal=self.get_minimal(params)) + endpoint_svc = EndpointService(metadata) + response: dict = endpoint_svc.list(params) + return self.dict_to_message(response) diff --git a/src/spaceone/identity/interface/grpc/policy.py b/src/spaceone/identity/interface/grpc/policy.py index c81765df..d95b5d70 100644 --- a/src/spaceone/identity/interface/grpc/policy.py +++ b/src/spaceone/identity/interface/grpc/policy.py @@ -1,49 +1,44 @@ -from spaceone.api.identity.v1 import policy_pb2, policy_pb2_grpc from spaceone.core.pygrpc import BaseAPI +from spaceone.api.identity.v2 import policy_pb2, policy_pb2_grpc +from spaceone.identity.service.policy_service import PolicyService class Policy(BaseAPI, policy_pb2_grpc.PolicyServicer): - pb2 = policy_pb2 pb2_grpc = policy_pb2_grpc def create(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('PolicyService', metadata) as policy_svc: - data = policy_svc.create(params) - return self.locator.get_info('PolicyInfo', data) + policy_svc = PolicyService(metadata) + response: dict = policy_svc.create(params) + return self.dict_to_message(response) def update(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('PolicyService', metadata) as policy_svc: - data = policy_svc.update(params) - return self.locator.get_info('PolicyInfo', data) + policy_svc = PolicyService(metadata) + response: dict = policy_svc.update(params) + return self.dict_to_message(response) def delete(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('PolicyService', metadata) as policy_svc: - policy_svc.delete(params) - return self.locator.get_info('EmptyInfo') + policy_svc = PolicyService(metadata) + policy_svc.delete(params) + return self.empty() def get(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('PolicyService', metadata) as policy_svc: - data = policy_svc.get(params) - return self.locator.get_info('PolicyInfo', data) + policy_svc = PolicyService(metadata) + response: dict = policy_svc.get(params) + return self.dict_to_message(response) def list(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('PolicyService', metadata) as policy_svc: - policy_vos, total_count = policy_svc.list(params) - return self.locator.get_info('PoliciesInfo', policy_vos, total_count, minimal=self.get_minimal(params)) + policy_svc = PolicyService(metadata) + response: dict = policy_svc.list(params) + return self.dict_to_message(response) def stat(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('PolicyService', metadata) as policy_svc: - return self.locator.get_info('StatisticsInfo', policy_svc.stat(params)) + policy_svc = PolicyService(metadata) + response: dict = policy_svc.stat(params) + return self.dict_to_message(response) diff --git a/src/spaceone/identity/interface/grpc/project.py b/src/spaceone/identity/interface/grpc/project.py index a7013e54..59ad82d4 100644 --- a/src/spaceone/identity/interface/grpc/project.py +++ b/src/spaceone/identity/interface/grpc/project.py @@ -1,74 +1,80 @@ -from spaceone.api.identity.v1 import project_pb2, project_pb2_grpc from spaceone.core.pygrpc import BaseAPI +from spaceone.api.identity.v2 import project_pb2, project_pb2_grpc +from spaceone.identity.service.project_service import ProjectService class Project(BaseAPI, project_pb2_grpc.ProjectServicer): - pb2 = project_pb2 pb2_grpc = project_pb2_grpc def create(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('ProjectService', metadata) as project_svc: - return self.locator.get_info('ProjectInfo', project_svc.create(params)) + project_svc = ProjectService(metadata) + response: dict = project_svc.create(params) + return self.dict_to_message(response) def update(self, request, context): params, metadata = self.parse_request(request, context) + project_svc = ProjectService(metadata) + response: dict = project_svc.update(params) + return self.dict_to_message(response) - with self.locator.get_service('ProjectService', metadata) as project_svc: - return self.locator.get_info('ProjectInfo', project_svc.update(params)) - - def delete(self, request, context): + def update_project_type(self, request, context): params, metadata = self.parse_request(request, context) + project_svc = ProjectService(metadata) + response: dict = project_svc.update_project_type(params) + return self.dict_to_message(response) - with self.locator.get_service('ProjectService', metadata) as project_svc: - project_svc.delete(params) - return self.locator.get_info('EmptyInfo') - - def get(self, request, context): + def change_project_group(self, request, context): params, metadata = self.parse_request(request, context) + project_svc = ProjectService(metadata) + response: dict = project_svc.change_project_group(params) + return self.dict_to_message(response) - with self.locator.get_service('ProjectService', metadata) as project_svc: - return self.locator.get_info('ProjectInfo', project_svc.get(params)) - - def list(self, request, context): + def delete(self, request, context): params, metadata = self.parse_request(request, context) + project_svc = ProjectService(metadata) + project_svc.delete(params) + return self.empty() - with self.locator.get_service('ProjectService', metadata) as project_svc: - project_vos, total_count, project_groups_info = project_svc.list(params) - return self.locator.get_info('ProjectsInfo', project_vos, total_count, - minimal=self.get_minimal(params), project_groups_info=project_groups_info) - - def stat(self, request, context): + def add_users(self, request, context): params, metadata = self.parse_request(request, context) + project_svc = ProjectService(metadata) + response: dict = project_svc.add_users(params) + return self.dict_to_message(response) - with self.locator.get_service('ProjectService', metadata) as project_svc: - return self.locator.get_info('StatisticsInfo', project_svc.stat(params)) - - def add_member(self, request, context): + def remove_users(self, request, context): params, metadata = self.parse_request(request, context) + project_svc = ProjectService(metadata) + response: dict = project_svc.remove_users(params) + return self.dict_to_message(response) - with self.locator.get_service('ProjectService', metadata) as project_svc: - return self.locator.get_info('ProjectRoleBindingInfo', project_svc.add_member(params)) - - def modify_member(self, request, context): + def add_user_groups(self, request, context): params, metadata = self.parse_request(request, context) + project_svc = ProjectService(metadata) + response: dict = project_svc.add_user_groups(params) + return self.dict_to_message(response) - with self.locator.get_service('ProjectService', metadata) as project_svc: - return self.locator.get_info('ProjectRoleBindingInfo', project_svc.modify_member(params)) - - def remove_member(self, request, context): + def remove_user_groups(self, request, context): params, metadata = self.parse_request(request, context) + project_svc = ProjectService(metadata) + response: dict = project_svc.remove_user_groups(params) + return self.dict_to_message(response) - with self.locator.get_service('ProjectService', metadata) as project_svc: - project_svc.remove_member(params) - return self.locator.get_info('EmptyInfo') + def get(self, request, context): + params, metadata = self.parse_request(request, context) + project_svc = ProjectService(metadata) + response: dict = project_svc.get(params) + return self.dict_to_message(response) - def list_members(self, request, context): + def list(self, request, context): params, metadata = self.parse_request(request, context) + project_svc = ProjectService(metadata) + response: dict = project_svc.list(params) + return self.dict_to_message(response) - with self.locator.get_service('ProjectService', metadata) as project_svc: - project_map_vos, total_count = project_svc.list_members(params) - return self.locator.get_info('ProjectRoleBindingsInfo', project_map_vos, total_count, - minimal=self.get_minimal(params)) + def stat(self, request, context): + params, metadata = self.parse_request(request, context) + project_svc = ProjectService(metadata) + response: dict = project_svc.stat(params) + return self.dict_to_message(response) diff --git a/src/spaceone/identity/interface/grpc/project_group.py b/src/spaceone/identity/interface/grpc/project_group.py index 707cb373..f4936099 100644 --- a/src/spaceone/identity/interface/grpc/project_group.py +++ b/src/spaceone/identity/interface/grpc/project_group.py @@ -1,83 +1,50 @@ -from spaceone.api.identity.v1 import project_group_pb2, project_group_pb2_grpc from spaceone.core.pygrpc import BaseAPI +from spaceone.api.identity.v2 import project_group_pb2, project_group_pb2_grpc +from spaceone.identity.service.project_group_service import ProjectGroupService class ProjectGroup(BaseAPI, project_group_pb2_grpc.ProjectGroupServicer): - pb2 = project_group_pb2 pb2_grpc = project_group_pb2_grpc def create(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('ProjectGroupService', metadata) as project_group_svc: - return self.locator.get_info('ProjectGroupInfo', project_group_svc.create(params)) + project_svc = ProjectGroupService(metadata) + response: dict = project_svc.create(params) + return self.dict_to_message(response) def update(self, request, context): params, metadata = self.parse_request(request, context) + project_svc = ProjectGroupService(metadata) + response: dict = project_svc.update(params) + return self.dict_to_message(response) - with self.locator.get_service('ProjectGroupService', metadata) as project_group_svc: - return self.locator.get_info('ProjectGroupInfo', project_group_svc.update(params)) + def change_parent_group(self, request, context): + params, metadata = self.parse_request(request, context) + project_svc = ProjectGroupService(metadata) + response: dict = project_svc.change_parent_group(params) + return self.dict_to_message(response) def delete(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('ProjectGroupService', metadata) as project_group_svc: - project_group_svc.delete(params) - return self.locator.get_info('EmptyInfo') + project_svc = ProjectGroupService(metadata) + project_svc.delete(params) + return self.empty() def get(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('ProjectGroupService', metadata) as project_group_svc: - return self.locator.get_info('ProjectGroupInfo', project_group_svc.get(params)) + project_svc = ProjectGroupService(metadata) + response: dict = project_svc.get(params) + return self.dict_to_message(response) def list(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('ProjectGroupService', metadata) as project_group_svc: - project_group_vos, total_count, parent_project_groups_info = project_group_svc.list(params) - return self.locator.get_info('ProjectGroupsInfo', project_group_vos, total_count, - minimal=self.get_minimal(params), - parent_project_groups_info=parent_project_groups_info) + project_svc = ProjectGroupService(metadata) + response: dict = project_svc.list(params) + return self.dict_to_message(response) def stat(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('ProjectGroupService', metadata) as project_group_svc: - return self.locator.get_info('StatisticsInfo', project_group_svc.stat(params)) - - def add_member(self, request, context): - params, metadata = self.parse_request(request, context) - - with self.locator.get_service('ProjectGroupService', metadata) as project_group_svc: - return self.locator.get_info('ProjectGroupRoleBindingInfo', project_group_svc.add_member(params)) - - def modify_member(self, request, context): - params, metadata = self.parse_request(request, context) - - with self.locator.get_service('ProjectGroupService', metadata) as project_group_svc: - return self.locator.get_info('ProjectGroupRoleBindingInfo', project_group_svc.modify_member(params)) - - def remove_member(self, request, context): - params, metadata = self.parse_request(request, context) - - with self.locator.get_service('ProjectGroupService', metadata) as project_group_svc: - project_group_svc.remove_member(params) - return self.locator.get_info('EmptyInfo') - - def list_members(self, request, context): - params, metadata = self.parse_request(request, context) - - with self.locator.get_service('ProjectGroupService', metadata) as project_group_svc: - project_group_map_vos, total_count = project_group_svc.list_members(params) - return self.locator.get_info('ProjectGroupRoleBindingsInfo', project_group_map_vos, total_count, - minimal=self.get_minimal(params)) - - def list_projects(self, request, context): - params, metadata = self.parse_request(request, context) - - with self.locator.get_service('ProjectGroupService', metadata) as project_group_svc: - project_vos, total_count, project_groups_info = project_group_svc.list_projects(params) - return self.locator.get_info('ProjectGroupProjectsInfo', project_vos, total_count, - minimal=self.get_minimal(params), project_groups_info=project_groups_info) + project_svc = ProjectGroupService(metadata) + response: dict = project_svc.stat(params) + return self.dict_to_message(response) diff --git a/src/spaceone/identity/interface/grpc/provider.py b/src/spaceone/identity/interface/grpc/provider.py index 0fdc9004..62e99195 100644 --- a/src/spaceone/identity/interface/grpc/provider.py +++ b/src/spaceone/identity/interface/grpc/provider.py @@ -1,50 +1,46 @@ -from spaceone.api.identity.v1 import provider_pb2, provider_pb2_grpc from spaceone.core.pygrpc import BaseAPI +from spaceone.api.identity.v2 import provider_pb2, provider_pb2_grpc +from spaceone.identity.service.service_account_service import ( + ServiceAccountService, +) class Provider(BaseAPI, provider_pb2_grpc.ProviderServicer): - pb2 = provider_pb2 pb2_grpc = provider_pb2_grpc def create(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('ProviderService', metadata) as provider_svc: - data = provider_svc.create(params) - return self.locator.get_info('ProviderInfo', data) + service_account_svc = ServiceAccountService(metadata) + response: dict = service_account_svc.create(params) + return self.dict_to_message(response) def update(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('ProviderService', metadata) as provider_svc: - data = provider_svc.update(params) - return self.locator.get_info('ProviderInfo', data) + service_account_svc = ServiceAccountService(metadata) + response: dict = service_account_svc.update(params) + return self.dict_to_message(response) def delete(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('ProviderService', metadata) as provider_svc: - provider_svc.delete(params) - return self.locator.get_info('EmptyInfo') + service_account_svc = ServiceAccountService(metadata) + service_account_svc.delete(params) + return self.empty() def get(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('ProviderService', metadata) as provider_svc: - data = provider_svc.get(params) - return self.locator.get_info('ProviderInfo', data) + service_account_svc = ServiceAccountService(metadata) + response: dict = service_account_svc.get(params) + return self.dict_to_message(response) def list(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('ProviderService', metadata) as provider_svc: - provider_vos, total_count = provider_svc.list(params) - return self.locator.get_info('ProvidersInfo', provider_vos, - total_count, minimal=self.get_minimal(params)) + service_account_svc = ServiceAccountService(metadata) + response: dict = service_account_svc.list(params) + return self.dict_to_message(response) def stat(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('ProviderService', metadata) as provider_svc: - return self.locator.get_info('StatisticsInfo', provider_svc.stat(params)) + service_account_svc = ServiceAccountService(metadata) + response: dict = service_account_svc.stat(params) + return self.dict_to_message(response) diff --git a/src/spaceone/identity/interface/grpc/role.py b/src/spaceone/identity/interface/grpc/role.py index 1ead7ef7..6f02925b 100644 --- a/src/spaceone/identity/interface/grpc/role.py +++ b/src/spaceone/identity/interface/grpc/role.py @@ -1,49 +1,44 @@ -from spaceone.api.identity.v1 import role_pb2, role_pb2_grpc from spaceone.core.pygrpc import BaseAPI +from spaceone.api.identity.v2 import role_pb2, role_pb2_grpc +from spaceone.identity.service.role_service import RoleService class Role(BaseAPI, role_pb2_grpc.RoleServicer): - pb2 = role_pb2 pb2_grpc = role_pb2_grpc def create(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('RoleService', metadata) as role_svc: - data = role_svc.create(params) - return self.locator.get_info('RoleInfo', data) + endpoint_svc = RoleService(metadata) + response: dict = endpoint_svc.create(params) + return self.dict_to_message(response) def update(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('RoleService', metadata) as role_svc: - data = role_svc.update(params) - return self.locator.get_info('RoleInfo', data) + endpoint_svc = RoleService(metadata) + response: dict = endpoint_svc.update(params) + return self.dict_to_message(response) def delete(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('RoleService', metadata) as role_svc: - role_svc.delete(params) - return self.locator.get_info('EmptyInfo') + endpoint_svc = RoleService(metadata) + endpoint_svc.delete(params) + return self.empty() def get(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('RoleService', metadata) as role_svc: - data = role_svc.get(params) - return self.locator.get_info('RoleInfo', data) + endpoint_svc = RoleService(metadata) + response: dict = endpoint_svc.get(params) + return self.dict_to_message(response) def list(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('RoleService', metadata) as role_svc: - role_vos, total_count = role_svc.list(params) - return self.locator.get_info('RolesInfo', role_vos, total_count, minimal=self.get_minimal(params)) + endpoint_svc = RoleService(metadata) + response: dict = endpoint_svc.list(params) + return self.dict_to_message(response) def stat(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('RoleService', metadata) as role_svc: - return self.locator.get_info('StatisticsInfo', role_svc.stat(params)) + endpoint_svc = RoleService(metadata) + response: dict = endpoint_svc.stat(params) + return self.dict_to_message(response) diff --git a/src/spaceone/identity/interface/grpc/role_binding.py b/src/spaceone/identity/interface/grpc/role_binding.py index 13536fe5..f18d9f39 100644 --- a/src/spaceone/identity/interface/grpc/role_binding.py +++ b/src/spaceone/identity/interface/grpc/role_binding.py @@ -1,49 +1,44 @@ -from spaceone.api.identity.v1 import role_binding_pb2, role_binding_pb2_grpc from spaceone.core.pygrpc import BaseAPI +from spaceone.api.identity.v2 import role_binding_pb2, role_binding_pb2_grpc +from spaceone.identity.service.role_binding_service import RoleBindingService class RoleBinding(BaseAPI, role_binding_pb2_grpc.RoleBindingServicer): - pb2 = role_binding_pb2 pb2_grpc = role_binding_pb2_grpc def create(self, request, context): params, metadata = self.parse_request(request, context) + role_binding_svc = RoleBindingService(metadata) + response: dict = role_binding_svc.create(params) + return self.dict_to_message(response) - with self.locator.get_service('RoleBindingService', metadata) as role_binding_svc: - data = role_binding_svc.create(params) - return self.locator.get_info('RoleBindingInfo', data) - - def update(self, request, context): + def update_role(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('RoleBindingService', metadata) as role_binding_svc: - data = role_binding_svc.update(params) - return self.locator.get_info('RoleBindingInfo', data) + role_binding_svc = RoleBindingService(metadata) + response: dict = role_binding_svc.update_role(params) + return self.dict_to_message(response) def delete(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('RoleBindingService', metadata) as role_binding_svc: - role_binding_svc.delete(params) - return self.locator.get_info('EmptyInfo') + role_binding_svc = RoleBindingService(metadata) + role_binding_svc.delete(params) + return self.empty() def get(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('RoleBindingService', metadata) as role_binding_svc: - data = role_binding_svc.get(params) - return self.locator.get_info('RoleBindingInfo', data) + role_binding_svc = RoleBindingService(metadata) + response: dict = role_binding_svc.get(params) + return self.dict_to_message(response) def list(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('RoleBindingService', metadata) as role_binding_svc: - role_binding_vos, total_count = role_binding_svc.list(params) - return self.locator.get_info('RoleBindingsInfo', role_binding_vos, total_count, minimal=self.get_minimal(params)) + role_binding_svc = RoleBindingService(metadata) + response: dict = role_binding_svc.list(params) + return self.dict_to_message(response) def stat(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('RoleBindingService', metadata) as role_binding_svc: - return self.locator.get_info('StatisticsInfo', role_binding_svc.stat(params)) + role_binding_svc = RoleBindingService(metadata) + response: dict = role_binding_svc.stat(params) + return self.dict_to_message(response) diff --git a/src/spaceone/identity/interface/grpc/service_account.py b/src/spaceone/identity/interface/grpc/service_account.py index b9e33a55..f42f5ea1 100644 --- a/src/spaceone/identity/interface/grpc/service_account.py +++ b/src/spaceone/identity/interface/grpc/service_account.py @@ -1,50 +1,58 @@ -from spaceone.api.identity.v1 import service_account_pb2, service_account_pb2_grpc from spaceone.core.pygrpc import BaseAPI +from spaceone.api.identity.v2 import service_account_pb2, service_account_pb2_grpc +from spaceone.identity.service.service_account_service import ( + ServiceAccountService, +) class ServiceAccount(BaseAPI, service_account_pb2_grpc.ServiceAccountServicer): - pb2 = service_account_pb2 pb2_grpc = service_account_pb2_grpc def create(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('ServiceAccountService', metadata) as service_account_svc: - data = service_account_svc.create(params) - return self.locator.get_info('ServiceAccountInfo', data) + service_account_svc = ServiceAccountService(metadata) + response: dict = service_account_svc.create(params) + return self.dict_to_message(response) def update(self, request, context): params, metadata = self.parse_request(request, context) + service_account_svc = ServiceAccountService(metadata) + response: dict = service_account_svc.update(params) + return self.dict_to_message(response) - with self.locator.get_service('ServiceAccountService', metadata) as service_account_svc: - data = service_account_svc.update(params) - return self.locator.get_info('ServiceAccountInfo', data) + def change_trusted_service_account(self, request, context): + params, metadata = self.parse_request(request, context) + service_account_svc = ServiceAccountService(metadata) + response: dict = service_account_svc.change_trusted_service_account(params) + return self.dict_to_message(response) - def delete(self, request, context): + def change_project(self, request, context): params, metadata = self.parse_request(request, context) + service_account_svc = ServiceAccountService(metadata) + response: dict = service_account_svc.change_project(params) + return self.dict_to_message(response) - with self.locator.get_service('ServiceAccountService', metadata) as service_account_svc: - service_account_svc.delete(params) - return self.locator.get_info('EmptyInfo') + def delete(self, request, context): + params, metadata = self.parse_request(request, context) + service_account_svc = ServiceAccountService(metadata) + service_account_svc.delete(params) + return self.empty() def get(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('ServiceAccountService', metadata) as service_account_svc: - data = service_account_svc.get(params) - return self.locator.get_info('ServiceAccountInfo', data) + service_account_svc = ServiceAccountService(metadata) + response: dict = service_account_svc.get(params) + return self.dict_to_message(response) def list(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('ServiceAccountService', metadata) as service_account_svc: - service_account_vos, total_count, projects_info = service_account_svc.list(params) - return self.locator.get_info('ServiceAccountsInfo', service_account_vos, total_count, - minimal=self.get_minimal(params), projects_info=projects_info) + service_account_svc = ServiceAccountService(metadata) + response: dict = service_account_svc.list(params) + return self.dict_to_message(response) def stat(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('ServiceAccountService', metadata) as service_account_svc: - return self.locator.get_info('StatisticsInfo', service_account_svc.stat(params)) + service_account_svc = ServiceAccountService(metadata) + response: dict = service_account_svc.stat(params) + return self.dict_to_message(response) diff --git a/src/spaceone/identity/interface/grpc/token.py b/src/spaceone/identity/interface/grpc/token.py index 611dae2e..4686b011 100644 --- a/src/spaceone/identity/interface/grpc/token.py +++ b/src/spaceone/identity/interface/grpc/token.py @@ -1,22 +1,20 @@ -from spaceone.api.identity.v1 import token_pb2, token_pb2_grpc from spaceone.core.pygrpc import BaseAPI +from spaceone.api.identity.v2 import token_pb2, token_pb2_grpc +from spaceone.identity.service.token_service import TokenService class Token(BaseAPI, token_pb2_grpc.TokenServicer): - pb2 = token_pb2 pb2_grpc = token_pb2_grpc def issue(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('TokenService', metadata) as token_svc: - data = token_svc.issue(params) - return self.locator.get_info('TokenInfo', data) + token_svc = TokenService(metadata) + response: dict = token_svc.issue(params) + return self.dict_to_message(response) def refresh(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('TokenService', metadata) as token_svc: - data = token_svc.refresh(params) - return self.locator.get_info('TokenInfo', data) + token_svc = TokenService(metadata) + response: dict = token_svc.refresh(params) + return self.dict_to_message(response) diff --git a/src/spaceone/identity/interface/grpc/trusted_service_account.py b/src/spaceone/identity/interface/grpc/trusted_service_account.py new file mode 100644 index 00000000..979dc8db --- /dev/null +++ b/src/spaceone/identity/interface/grpc/trusted_service_account.py @@ -0,0 +1,48 @@ +from spaceone.core.pygrpc import BaseAPI +from spaceone.api.identity.v2 import trusted_account_pb2, trusted_account_pb2_grpc +from spaceone.identity.service.trusted_service_account_service import ( + TrustedServiceAccountService, +) + + +class TrustedServiceAccount( + BaseAPI, trusted_account_pb2_grpc.TrustedServiceAccountServicer +): + pb2 = trusted_account_pb2 + pb2_grpc = trusted_account_pb2_grpc + + def create(self, request, context): + params, metadata = self.parse_request(request, context) + trusted_service_account_svc = TrustedServiceAccountService(metadata) + response: dict = trusted_service_account_svc.create(params) + return self.dict_to_message(response) + + def update(self, request, context): + params, metadata = self.parse_request(request, context) + trusted_service_account_svc = TrustedServiceAccountService(metadata) + response: dict = trusted_service_account_svc.update(params) + return self.dict_to_message(response) + + def delete(self, request, context): + params, metadata = self.parse_request(request, context) + trusted_service_account_svc = TrustedServiceAccountService(metadata) + trusted_service_account_svc.delete(params) + return self.empty() + + def get(self, request, context): + params, metadata = self.parse_request(request, context) + trusted_service_account_svc = TrustedServiceAccountService(metadata) + response: dict = trusted_service_account_svc.get(params) + return self.dict_to_message(response) + + def list(self, request, context): + params, metadata = self.parse_request(request, context) + trusted_service_account_svc = TrustedServiceAccountService(metadata) + response: dict = trusted_service_account_svc.list(params) + return self.dict_to_message(response) + + def stat(self, request, context): + params, metadata = self.parse_request(request, context) + trusted_service_account_svc = TrustedServiceAccountService(metadata) + response: dict = trusted_service_account_svc.stat(params) + return self.dict_to_message(response) diff --git a/src/spaceone/identity/interface/grpc/user.py b/src/spaceone/identity/interface/grpc/user.py index f75a8a4d..1c06e4a0 100644 --- a/src/spaceone/identity/interface/grpc/user.py +++ b/src/spaceone/identity/interface/grpc/user.py @@ -1,119 +1,98 @@ -from spaceone.api.identity.v1 import user_pb2, user_pb2_grpc from spaceone.core.pygrpc import BaseAPI +from spaceone.api.identity.v2 import user_pb2, user_pb2_grpc +from spaceone.identity.service.user_service import UserService class User(BaseAPI, user_pb2_grpc.UserServicer): - pb2 = user_pb2 pb2_grpc = user_pb2_grpc def create(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('UserService', metadata) as user_svc: - return self.locator.get_info('UserInfo', user_svc.create(params)) + user_svc = UserService(metadata) + response: dict = user_svc.create(params) + return self.dict_to_message(response) def update(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('UserService', metadata) as user_svc: - return self.locator.get_info('UserInfo', user_svc.update(params)) + user_svc = UserService(metadata) + response: dict = user_svc.update(params) + return self.dict_to_message(response) def verify_email(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('UserService', metadata) as user_svc: - user_svc.verify_email(params) - return self.locator.get_info('EmptyInfo') + user_svc = UserService(metadata) + user_svc.verify_email(params) + return self.empty() def confirm_email(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('UserService', metadata) as user_svc: - return self.locator.get_info('UserInfo', user_svc.confirm_email(params)) + user_svc = UserService(metadata) + response: dict = user_svc.confirm_email(params) + return self.dict_to_message(response) def reset_password(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('UserService', metadata) as user_svc: - user_svc.reset_password(params) - return self.locator.get_info('EmptyInfo') + user_svc = UserService(metadata) + user_svc.reset_password(params) + return self.empty() def set_required_actions(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('UserService', metadata) as user_svc: - return self.locator.get_info('UserInfo', user_svc.set_required_actions(params)) + user_svc = UserService(metadata) + response: dict = user_svc.set_required_actions(params) + return self.dict_to_message(response) def enable_mfa(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('UserService', metadata) as user_svc: - return self.locator.get_info('UserInfo', user_svc.enable_mfa(params)) + user_svc = UserService(metadata) + response: dict = user_svc.enable_mfa(params) + return self.dict_to_message(response) def disable_mfa(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('UserService', metadata) as user_svc: - user_svc.disable_mfa(params) - return self.locator.get_info('EmptyInfo') + user_svc = UserService(metadata) + response: dict = user_svc.disable_mfa(params) + return self.dict_to_message(response) def confirm_mfa(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('UserService', metadata) as user_svc: - return self.locator.get_info('UserInfo', user_svc.confirm_mfa(params)) + user_svc = UserService(metadata) + response: dict = user_svc.confirm_mfa(params) + return self.dict_to_message(response) def delete(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('UserService', metadata) as user_svc: - user_svc.delete(params) - return self.locator.get_info('EmptyInfo') + user_svc = UserService(metadata) + user_svc.delete(params) + return self.empty() def enable(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('UserService', metadata) as user_svc: - return self.locator.get_info('UserInfo', user_svc.enable(params)) + user_svc = UserService(metadata) + response: dict = user_svc.enable(params) + return self.dict_to_message(response) def disable(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('UserService', metadata) as user_svc: - return self.locator.get_info('UserInfo', user_svc.disable(params)) - - def find(self, request, context): - params, metadata = self.parse_request(request, context) - - with self.locator.get_service('UserService', metadata) as user_svc: - users, total_count = user_svc.find(params) - return self.locator.get_info('FindUsersInfo', users, total_count) - - def sync(self, request, context): - params, metadata = self.parse_request(request, context) - - with self.locator.get_service('UserService', metadata) as user_svc: - pass - # data = user_service.find_user(params) - # return self.locator.get_info('UserInfo', data) + user_svc = UserService(metadata) + response: dict = user_svc.disable(params) + return self.dict_to_message(response) def get(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('UserService', metadata) as user_svc: - return self.locator.get_info('UserInfo', user_svc.get(params)) + user_svc = UserService(metadata) + response: dict = user_svc.get(params) + return self.dict_to_message(response) def list(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('UserService', metadata) as user_svc: - users, total_count = user_svc.list(params) - return self.locator.get_info('UsersInfo', users, total_count, - minimal=self.get_minimal(params)) + user_svc = UserService(metadata) + response: dict = user_svc.list(params) + return self.dict_to_message(response) def stat(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('UserService', metadata) as user_svc: - return self.locator.get_info('StatisticsInfo', user_svc.stat(params)) + user_svc = UserService(metadata) + response: dict = user_svc.stat(params) + return self.dict_to_message(response) diff --git a/src/spaceone/identity/interface/grpc/user_group.py b/src/spaceone/identity/interface/grpc/user_group.py new file mode 100644 index 00000000..c073bf3b --- /dev/null +++ b/src/spaceone/identity/interface/grpc/user_group.py @@ -0,0 +1,56 @@ +from spaceone.core.pygrpc import BaseAPI +from spaceone.api.identity.v2 import user_group_pb2, user_group_pb2_grpc +from spaceone.identity.service.user_group_service import UserGroupService + + +class UserGroup(BaseAPI, user_group_pb2_grpc.UserGroupServicer): + pb2 = user_group_pb2 + pb2_grpc = user_group_pb2_grpc + + def create(self, request, context): + params, metadata = self.parse_request(request, context) + user_group_svc = UserGroupService(metadata) + response: dict = user_group_svc.create(params) + return self.dict_to_message(response) + + def update(self, request, context): + params, metadata = self.parse_request(request, context) + user_group_svc = UserGroupService(metadata) + response: dict = user_group_svc.update(params) + return self.dict_to_message(response) + + def delete(self, request, context): + params, metadata = self.parse_request(request, context) + user_group_svc = UserGroupService(metadata) + user_group_svc.delete(params) + return self.empty() + + def add_users(self, request, context): + params, metadata = self.parse_request(request, context) + user_group_svc = UserGroupService(metadata) + response: dict = user_group_svc.add_users(params) + return self.dict_to_message(response) + + def remove_users(self, request, context): + params, metadata = self.parse_request(request, context) + user_group_svc = UserGroupService(metadata) + response: dict = user_group_svc.remove_users(params) + return self.dict_to_message(response) + + def get(self, request, context): + params, metadata = self.parse_request(request, context) + user_group_svc = UserGroupService(metadata) + response: dict = user_group_svc.get(params) + return self.dict_to_message(response) + + def list(self, request, context): + params, metadata = self.parse_request(request, context) + user_group_svc = UserGroupService(metadata) + response: dict = user_group_svc.list(params) + return self.dict_to_message(response) + + def stat(self, request, context): + params, metadata = self.parse_request(request, context) + user_group_svc = UserGroupService(metadata) + response: dict = user_group_svc.stat(params) + return self.dict_to_message(response) diff --git a/src/spaceone/identity/interface/grpc/workspace.py b/src/spaceone/identity/interface/grpc/workspace.py new file mode 100644 index 00000000..6d01a421 --- /dev/null +++ b/src/spaceone/identity/interface/grpc/workspace.py @@ -0,0 +1,56 @@ +from spaceone.core.pygrpc import BaseAPI +from spaceone.api.identity.v2 import workspace_pb2, workspace_pb2_grpc +from spaceone.identity.service.workspace_service import WorkspaceService + + +class Workspace(BaseAPI, workspace_pb2_grpc.WorkspaceServicer): + pb2 = workspace_pb2 + pb2_grpc = workspace_pb2_grpc + + def create(self, request, context): + params, metadata = self.parse_request(request, context) + workspace_svc = WorkspaceService(metadata) + response: dict = workspace_svc.create(params) + return self.dict_to_message(response) + + def update(self, request, context): + params, metadata = self.parse_request(request, context) + workspace_svc = WorkspaceService(metadata) + response: dict = workspace_svc.update(params) + return self.dict_to_message(response) + + def delete(self, request, context): + params, metadata = self.parse_request(request, context) + workspace_svc = WorkspaceService(metadata) + workspace_svc.delete(params) + return self.empty() + + def enable(self, request, context): + params, metadata = self.parse_request(request, context) + workspace_svc = WorkspaceService(metadata) + response: dict = workspace_svc.enable(params) + return self.dict_to_message(response) + + def disable(self, request, context): + params, metadata = self.parse_request(request, context) + workspace_svc = WorkspaceService(metadata) + response: dict = workspace_svc.disable(params) + return self.dict_to_message(response) + + def get(self, request, context): + params, metadata = self.parse_request(request, context) + workspace_svc = WorkspaceService(metadata) + response: dict = workspace_svc.get(params) + return self.dict_to_message(response) + + def list(self, request, context): + params, metadata = self.parse_request(request, context) + workspace_svc = WorkspaceService(metadata) + response: dict = workspace_svc.list(params) + return self.dict_to_message(response) + + def stat(self, request, context): + params, metadata = self.parse_request(request, context) + workspace_svc = WorkspaceService(metadata) + response: dict = workspace_svc.stat(params) + return self.dict_to_message(response) diff --git a/src/spaceone/identity/model/__init__.py b/src/spaceone/identity/model/__init__.py index 77a503b4..8b137891 100644 --- a/src/spaceone/identity/model/__init__.py +++ b/src/spaceone/identity/model/__init__.py @@ -1,12 +1 @@ -from spaceone.identity.model.api_key_model import APIKey -from spaceone.identity.model.domain_model import Domain -from spaceone.identity.model.domain_owner_model import DomainOwner -from spaceone.identity.model.project_group_model import ProjectGroup -from spaceone.identity.model.project_model import Project -from spaceone.identity.model.provider_model import Provider -from spaceone.identity.model.service_account_model import ServiceAccount -from spaceone.identity.model.domain_secret_model import DomainSecret -from spaceone.identity.model.policy_model import Policy -from spaceone.identity.model.role_model import Role -from spaceone.identity.model.role_binding_model import RoleBinding -from spaceone.identity.model.user_model import User + diff --git a/src/spaceone/identity/model/api_key_request.py b/src/spaceone/identity/model/api_key_request.py new file mode 100644 index 00000000..a901b6b9 --- /dev/null +++ b/src/spaceone/identity/model/api_key_request.py @@ -0,0 +1,58 @@ +from typing import Union +from pydantic import BaseModel + +__all__ = [ + "APIKeyCreateRequest", + "APIKeyUpdateRequest", + "APIKeyDeleteRequest", + "APIKeyGetRequest", + "APIKeySearchQueryRequest", + "APIKeyEnableRequest", + "APIKeyDisableRequest", + "APIKeyStatQueryRequest", +] + + +class APIKeyCreateRequest(BaseModel): + user_id: str + name: Union[str, None] = None + domain_id: str + + +class APIKeyUpdateRequest(BaseModel): + api_key_id: str + name: Union[str, None] = None + domain_id: str + + +class APIKeyEnableRequest(BaseModel): + api_key_id: str + domain_id: str + + +class APIKeyDisableRequest(BaseModel): + api_key_id: str + domain_id: str + + +class APIKeyDeleteRequest(BaseModel): + api_key_id: str + domain_id: str + + +class APIKeyGetRequest(BaseModel): + api_key_id: str + domain_id: str + + +class APIKeySearchQueryRequest(BaseModel): + query: Union[dict, None] = None + api_key_id: Union[str, None] = None + user_id: Union[str, None] = None + state: Union[str, None] = None + domain_id: str + + +class APIKeyStatQueryRequest(BaseModel): + query: dict + domain_id: str diff --git a/src/spaceone/identity/model/api_key_response.py b/src/spaceone/identity/model/api_key_response.py new file mode 100644 index 00000000..bf1a1768 --- /dev/null +++ b/src/spaceone/identity/model/api_key_response.py @@ -0,0 +1,20 @@ +from typing import Union, List +from pydantic import BaseModel + +__all__ = ["APIKeyResponse", "APIKeysResponse"] + + +class APIKeyResponse(BaseModel): + api_key_id: Union[str, None] = None + api_key: Union[str, None] = None + name: Union[str, None] = None + state: Union[str, None] = None + user_id: Union[str, None] = None + domain_id: Union[str, None] = None + created_at: Union[str, None] = None + last_accessed_at: Union[str, None] = None + + +class APIKeysResponse(BaseModel): + results: List[APIKeyResponse] + total_count: int diff --git a/src/spaceone/identity/model/authorization_request.py b/src/spaceone/identity/model/authorization_request.py new file mode 100644 index 00000000..563180b5 --- /dev/null +++ b/src/spaceone/identity/model/authorization_request.py @@ -0,0 +1,41 @@ +from typing import Union, Literal +from pydantic import BaseModel + +__all__ = ["AuthorizationVerifyRequest", "RoleType"] + +Scope = Literal[ + "PUBLIC", + "SYSTEM", + "DOMAIN", + "DOMAIN_READ", + "WORKSPACE", + "WORKSPACE_READ", + "DOMAIN_OR_WORKSPACE", + "DOMAIN_OR_WORKSPACE_READ", + "PROJECT", + "PROJECT_READ", + "DOMAIN_OR_USER", + "DOMAIN_OR_USER_READ", + "USER", +] + +RoleType = Literal[ + "SYSTEM_ADMIN", "DOMAIN_ADMIN", "WORKSPACE_OWNER", "WORKSPACE_MEMBER", "NO_ROLE" +] + + +class AuthorizationVerifyRequest(BaseModel): + service: str + resource: str + verb: str + scope: Scope + request_user_id: Union[str, None] = None + request_project_id: Union[str, None] = None + request_workspace_id: Union[str, None] = None + request_domain_id: Union[str, None] = None + require_user_id: Union[bool, None] = None + require_project_id: Union[bool, None] = None + require_workspace_id: Union[bool, None] = None + require_domain_id: Union[bool, None] = None + user_id: str + domain_id: str diff --git a/src/spaceone/identity/model/authorization_response.py b/src/spaceone/identity/model/authorization_response.py new file mode 100644 index 00000000..206ae239 --- /dev/null +++ b/src/spaceone/identity/model/authorization_response.py @@ -0,0 +1,10 @@ +from typing import Union, Literal, List +from pydantic import BaseModel + +from spaceone.identity.model.authorization_request import RoleType + + +class AuthorizationResponse(BaseModel): + role_type: Union[RoleType, None] = None + projects: Union[List[str], None] = None + workspaces: Union[List[str], None] = None diff --git a/src/spaceone/identity/model/domain_request.py b/src/spaceone/identity/model/domain_request.py new file mode 100644 index 00000000..06be66db --- /dev/null +++ b/src/spaceone/identity/model/domain_request.py @@ -0,0 +1,44 @@ +from typing import Union, Literal +from pydantic import BaseModel + +__all__ = [ + "DomainCreateRequest", + "DomainUpdateRequest", + "DomainRequest", + "DomainGetMetadataRequest", + "DomainSearchQueryRequest", + "DomainStatQuery", + "State", +] + +State = Literal["ENABLED", "DISABLED"] + + +class DomainCreateRequest(BaseModel): + name: str + admin: dict + tags: Union[dict, None] = {} + + +class DomainUpdateRequest(BaseModel): + domain_id: str + tags: Union[dict, None] = {} + + +class DomainRequest(BaseModel): + domain_id: str + + +class DomainGetMetadataRequest(BaseModel): + name: str + + +class DomainSearchQueryRequest(BaseModel): + query: Union[dict, None] = None + domain_id: Union[str, None] = None + name: Union[str, None] = None + state: Union[State, None] = None + + +class DomainStatQuery(BaseModel): + query: dict diff --git a/src/spaceone/identity/model/domain_response.py b/src/spaceone/identity/model/domain_response.py new file mode 100644 index 00000000..453ba4dd --- /dev/null +++ b/src/spaceone/identity/model/domain_response.py @@ -0,0 +1,40 @@ +from datetime import datetime +from typing import List, Union, Literal +from pydantic import BaseModel + +from spaceone.identity.model.domain_request import State + +__all__ = [ + "DomainResponse", + "DomainsResponse", + "DomainMetadataResponse", + "DomainSecretResponse", +] + +ExternalAuthState = Literal["ENABLED", "DISABLED"] + + +class DomainResponse(BaseModel): + domain_id: Union[str, None] = None + name: Union[str, None] = None + state: Union[State, None] = None + tags: Union[dict, None] = {} + created_at: Union[datetime, None] = None + + +class DomainMetadataResponse(BaseModel): + domain_id: Union[str, None] = None + name: Union[str, None] = None + external_auth_state: ExternalAuthState + metadata_info: Union[dict, None] = None + + +class DomainSecretResponse(BaseModel): + domain_id: Union[str, None] = None + key: Union[str, None] = None + key_type: Union[str, None] = None + + +class DomainsResponse(BaseModel): + results: List[DomainResponse] + total_count: int diff --git a/src/spaceone/identity/model/endpoint_request.py b/src/spaceone/identity/model/endpoint_request.py new file mode 100644 index 00000000..1a2a773c --- /dev/null +++ b/src/spaceone/identity/model/endpoint_request.py @@ -0,0 +1,10 @@ +from typing import Union +from pydantic import BaseModel + +__all__ = ["EndpointSearchQueryRequest"] + + +class EndpointSearchQueryRequest(BaseModel): + query: Union[dict, None] = None + service: Union[str, None] = None + endpoint_type: Union[str, None] = None diff --git a/src/spaceone/identity/model/endpoint_response.py b/src/spaceone/identity/model/endpoint_response.py new file mode 100644 index 00000000..295951d3 --- /dev/null +++ b/src/spaceone/identity/model/endpoint_response.py @@ -0,0 +1,17 @@ +from typing import Union, List +from pydantic import BaseModel + +__all__ = ["EndpointsResponse"] + + +class EndpointResponse(BaseModel): + name: Union[str, None] = None + service: Union[str, None] = None + endpoint: Union[str, None] = None + state: Union[str, None] = None + version: Union[str, None] = None + + +class EndpointsResponse(BaseModel): + results: List[EndpointResponse] + total_count: int diff --git a/src/spaceone/identity/model/policy_request.py b/src/spaceone/identity/model/policy_request.py new file mode 100644 index 00000000..f60f6a66 --- /dev/null +++ b/src/spaceone/identity/model/policy_request.py @@ -0,0 +1,48 @@ +from typing import Union, List +from pydantic import BaseModel + +__all__ = [ + "PolicyCreateRequest", + "PolicyUpdateRequest", + "PolicyDeleteRequest", + "PolicyGetRequest", + "PolicySearchQueryRequest", + "PolicyStatQueryRequest", +] + + +class PolicyCreateRequest(BaseModel): + name: str + permissions: List[str] + tags: Union[dict, None] = None + domain_id: str + + +class PolicyUpdateRequest(BaseModel): + policy_id: str + name: str + permissions: Union[List[str], None] = None + tags: Union[dict, None] = None + domain_id: str + + +class PolicyDeleteRequest(BaseModel): + policy_id: str + domain_id: str + + +class PolicyGetRequest(BaseModel): + policy_id: str + domain_id: str + + +class PolicySearchQueryRequest(BaseModel): + query: Union[dict, None] = None + policy_id: Union[str, None] = None + name: Union[str, None] = None + domain_id: str + + +class PolicyStatQueryRequest(BaseModel): + query: Union[dict, None] = None + domain_id: str diff --git a/src/spaceone/identity/model/policy_response.py b/src/spaceone/identity/model/policy_response.py new file mode 100644 index 00000000..0c333bf6 --- /dev/null +++ b/src/spaceone/identity/model/policy_response.py @@ -0,0 +1,19 @@ +from datetime import datetime +from typing import Union, List +from pydantic import BaseModel + +__all__ = ["PolicyResponse", "PoliciesResponse"] + + +class PolicyResponse(BaseModel): + policy_id: Union[str, None] + name: Union[str, None] + permissions: Union[List[str], None] + tags: Union[dict, None] + domain_id: Union[str, None] + created_at: Union[datetime, None] + + +class PoliciesResponse(BaseModel): + results: List[PolicyResponse] + total_count: int diff --git a/src/spaceone/identity/model/project_group_request.py b/src/spaceone/identity/model/project_group_request.py new file mode 100644 index 00000000..693305a5 --- /dev/null +++ b/src/spaceone/identity/model/project_group_request.py @@ -0,0 +1,62 @@ +from typing import Union, Literal +from pydantic import BaseModel + +__all__ = [ + "ProjectGroupCreateRequest", + "ProjectGroupUpdateRequest", + "ProjectChangeParentGroupRequest", + "ProjectGroupDeleteRequest", + "ProjectGroupGetRequest", + "ProjectGroupSearchQueryRequest", + "ProjectGroupStatQueryRequest", +] + + +class ProjectGroupCreateRequest(BaseModel): + name: str + tags: Union[dict, None] = {} + parent_group_id: Union[str, None] = None + workspace_id: str + domain_id: str + + +class ProjectGroupUpdateRequest(BaseModel): + project_group_id: str + name: Union[str, None] = None + tags: Union[dict, None] = {} + workspace_id: str + domain_id: str + + +class ProjectChangeParentGroupRequest(BaseModel): + project_group_id: str + parent_group_id: str + workspace_id: str + domain_id: str + + +class ProjectGroupDeleteRequest(BaseModel): + project_group_id: str + workspace_id: str + domain_id: str + + +class ProjectGroupGetRequest(BaseModel): + projectGroup_id: str + workspace_id: str + domain_id: str + + +class ProjectGroupSearchQueryRequest(BaseModel): + query: Union[dict, None] = None + project_group_id: Union[str, None] = None + name: Union[str, None] = None + parent_group_id: Union[str, None] = None + workspace_id: Union[str, None] = None + domain_id: str + + +class ProjectGroupStatQueryRequest(BaseModel): + query: dict + workspace_id: Union[str, None] = None + domain_id: str diff --git a/src/spaceone/identity/model/project_group_response.py b/src/spaceone/identity/model/project_group_response.py new file mode 100644 index 00000000..e2b1f0f1 --- /dev/null +++ b/src/spaceone/identity/model/project_group_response.py @@ -0,0 +1,20 @@ +from datetime import datetime +from typing import Union, List +from pydantic import BaseModel + +__all__ = ["ProjectGroupResponse", "ProjectGroupsResponse"] + + +class ProjectGroupResponse(BaseModel): + project_group_id: Union[str, None] = None + name: Union[str, None] = None + tags: Union[dict, None] = None + parent_group_id: Union[str, None] = None + workspace_id: Union[str, None] = None + domain_id: Union[str, None] = None + created_at: Union[datetime, None] = None + + +class ProjectGroupsResponse(BaseModel): + results: List[ProjectGroupResponse] = [] + total_count: int diff --git a/src/spaceone/identity/model/project_request.py b/src/spaceone/identity/model/project_request.py new file mode 100644 index 00000000..67ae76fb --- /dev/null +++ b/src/spaceone/identity/model/project_request.py @@ -0,0 +1,108 @@ +from typing import Union, Literal, List +from pydantic import BaseModel + +__all__ = [ + "ProjectCreateRequest", + "ProjectUpdateRequest", + "ProjectUpdateProjectTypeRequest", + "ProjectDeleteRequest", + "ProjectAddUsersRequest", + "ProjectRemoveUsersRequest", + "ProjectAddUserGroupsRequest", + "ProjectRemoveUserGroupsRequest", + "ProjectGetRequest", + "ProjectSearchQueryRequest", + "ProjectStatQueryRequest", + "ProjectChangeProjectGroupRequest", + "ProjectType", +] + +ProjectType = Literal["PRIVATE", "PUBLIC"] + + +class ProjectCreateRequest(BaseModel): + name: str + project_type: str + project_group_id: str + tags: Union[dict, None] = {} + workspace_id: str + domain_id: str + + +class ProjectUpdateRequest(BaseModel): + project_id: str + name: Union[str, None] = None + tags: Union[dict, None] = {} + workspace_id: str + domain_id: str + + +class ProjectUpdateProjectTypeRequest(BaseModel): + project_id: str + project_type: ProjectType + workspace_id: str + domain_id: str + + +class ProjectChangeProjectGroupRequest(BaseModel): + project_id: str + project_group_id: str + workspace_id: str + domain_id: str + + +class ProjectDeleteRequest(BaseModel): + project_id: str + workspace_id: str + domain_id: str + + +class ProjectAddUsersRequest(BaseModel): + project_id: str + users: List[str] + workspace_id: str + domain_id: str + + +class ProjectRemoveUsersRequest(BaseModel): + project_id: str + users: List[str] + workspace_id: str + domain_id: str + + +class ProjectAddUserGroupsRequest(BaseModel): + project_id: str + user_groups: List[str] + workspace_id: str + domain_id: str + + +class ProjectRemoveUserGroupsRequest(BaseModel): + project_id: str + user_groups: List[str] + workspace_id: str + domain_id: str + + +class ProjectGetRequest(BaseModel): + project_id: str + workspace_id: str + domain_id: str + + +class ProjectSearchQueryRequest(BaseModel): + query: Union[dict, None] = None + project_id: Union[str, None] = None + name: Union[str, None] = None + user_id: Union[str, None] = None + user_group_id: Union[str, None] = None + workspace_id: Union[str, None] = None + domain_id: str + + +class ProjectStatQueryRequest(BaseModel): + query: dict + project_id: Union[str, None] = None + workspace_id: Union[str, None] = None + domain_id: str diff --git a/src/spaceone/identity/model/project_response.py b/src/spaceone/identity/model/project_response.py new file mode 100644 index 00000000..0502314a --- /dev/null +++ b/src/spaceone/identity/model/project_response.py @@ -0,0 +1,23 @@ +from datetime import datetime +from typing import Union, List +from pydantic import BaseModel +from spaceone.identity.model.project_request import ProjectType + +__all__ = ["ProjectResponse", "ProjectsResponse"] + + +class ProjectResponse(BaseModel): + project_id: Union[str, None] = None + name: Union[str, None] = None + project_type: Union[ProjectType, None] = None + tags: Union[dict, None] = {} + users: Union[List[str], None] = None + user_groups: Union[List[str], None] = None + workspace_id: Union[str, None] = None + domain_id: Union[str, None] = None + created_at: Union[datetime, None] = None + + +class ProjectsResponse(BaseModel): + results: List[ProjectResponse] = [] + total_count: int diff --git a/src/spaceone/identity/model/provider_request.py b/src/spaceone/identity/model/provider_request.py new file mode 100644 index 00000000..6192c009 --- /dev/null +++ b/src/spaceone/identity/model/provider_request.py @@ -0,0 +1,55 @@ +from typing import Union +from pydantic import BaseModel + +__all__ = [ + "ProviderCreateRequest", + "ProviderUpdateRequest", + "ProviderDeleteRequest", + "ProviderGetRequest", + "ProviderSearchQueryRequest", + "ProviderStatQueryRequest", +] + + +class ProviderCreateRequest(BaseModel): + provider: str + name: str + order: Union[int, None] = None + template: Union[dict, None] = None + metadata: Union[dict, None] = None + capability: Union[dict, None] = None + tags: Union[dict, None] = None + domain_id: str + + +class ProviderUpdateRequest(BaseModel): + provider_id: str + name: Union[str, None] = None + order: Union[int, None] = None + template: Union[dict, None] = None + metadata: Union[dict, None] = None + capability: Union[dict, None] = None + tags: Union[dict, None] = None + domain_id: str + + +class ProviderDeleteRequest(BaseModel): + provider_id: str + domain_id: str + + +class ProviderGetRequest(BaseModel): + provider_id: str + domain_id: str + + +class ProviderSearchQueryRequest(BaseModel): + query: Union[dict, None] = None + provider: Union[str, None] = None + name: Union[str, None] = None + domain_id: str + + +class ProviderStatQueryRequest(BaseModel): + query: dict = None + domain_id: str diff --git a/src/spaceone/identity/model/provider_response.py b/src/spaceone/identity/model/provider_response.py new file mode 100644 index 00000000..3237c6ab --- /dev/null +++ b/src/spaceone/identity/model/provider_response.py @@ -0,0 +1,22 @@ +from datetime import datetime +from typing import Union, List +from pydantic import BaseModel + +__all__ = ["ProviderResponse", "ProvidersResponse"] + + +class ProviderResponse(BaseModel): + provider: Union[str, None] = None + name: Union[str, None] = None + order: Union[int, None] = None + template: Union[dict, None] = None + metadata: Union[dict, None] = None + capability: Union[dict, None] = None + tags: Union[dict, None] = None + domain_id: str + created_at: Union[datetime, None] = None + + +class ProvidersResponse(BaseModel): + results: List[ProviderResponse] = [] + total_count: int diff --git a/src/spaceone/identity/model/role_binding_request.py b/src/spaceone/identity/model/role_binding_request.py new file mode 100644 index 00000000..b56d1068 --- /dev/null +++ b/src/spaceone/identity/model/role_binding_request.py @@ -0,0 +1,57 @@ +from typing import Union, List, Literal +from pydantic import BaseModel + +__all__ = [ + "RoleBindingCreateRequest", + "RoleBindingUpdateRoleRequest", + "RoleBindingDeleteRequest", + "RoleBindingGetRequest", + "RoleBindingSearchQueryRequest", + "RoleBindingStatQueryRequest", + "Scope", +] + +Scope = Literal["DOMAIN", "WORKSPACE"] + + +class RoleBindingCreateRequest(BaseModel): + user_id: str + role_id: str + is_managed_role: bool + scope: Scope + workspace_id: Union[str, None] = None + domain_id: str + + +class RoleBindingUpdateRoleRequest(BaseModel): + role_binding_id: str + role_id: str + is_managed_role: bool + domain_id: str + + +class RoleBindingDeleteRequest(BaseModel): + role_binding_id: str + workspace_id: Union[str, None] = None + domain_id: str + + +class RoleBindingGetRequest(BaseModel): + role_binding_id: str + workspace_id: Union[str, None] = None + domain_id: str + + +class RoleBindingSearchQueryRequest(BaseModel): + query: Union[dict, None] = None + role_binding_id: Union[str, None] = None + scope: Union[Scope, None] = None + user_id: Union[str, None] = None + role_id: Union[str, None] = None + workspace_id: Union[str, None] = None + domain_id: str + + +class RoleBindingStatQueryRequest(BaseModel): + query: dict + domain_id: str diff --git a/src/spaceone/identity/model/role_binding_response.py b/src/spaceone/identity/model/role_binding_response.py new file mode 100644 index 00000000..b096d646 --- /dev/null +++ b/src/spaceone/identity/model/role_binding_response.py @@ -0,0 +1,24 @@ +from datetime import datetime +from typing import Union, List +from pydantic import BaseModel + +from spaceone.identity.model.role_binding_request import Scope + +__all__ = ["RoleBindingResponse", "RoleBindingsResponse"] + + +class RoleBindingResponse(BaseModel): + role_binding_id: Union[str, None] = None + is_managed_role: Union[bool, None] = None + scope: Union[Scope, None] = None + user_id: Union[str, None] = None + role_id: Union[str, None] = None + repository_id: Union[str, None] = None + workspace_id: Union[str, None] = None + domain_id: Union[str, None] = None + created_at: Union[datetime, None] = None + + +class RoleBindingsResponse(BaseModel): + results: List[RoleBindingResponse] + total_count: int diff --git a/src/spaceone/identity/model/role_request.py b/src/spaceone/identity/model/role_request.py new file mode 100644 index 00000000..777b9b88 --- /dev/null +++ b/src/spaceone/identity/model/role_request.py @@ -0,0 +1,55 @@ +from typing import Union, List, Literal +from pydantic import BaseModel + +__all__ = [ + "RoleCreateRequest", + "RoleUpdateRequest", + "RoleDeleteRequest", + "RoleGetRequest", + "RoleSearchQueryRequest", + "RoleStatQueryRequest", +] + +RoleType = Literal["DOMAIN", "PROJECT", "USER"] + + +class RoleCreateRequest(BaseModel): + name: str + role_type: RoleType + policy_id: str + permissions: List[str] + tags: Union[dict, None] = None + domain_id: str + + +class RoleUpdateRequest(BaseModel): + role_id: str + name: Union[str, None] = None + policy_id: str + permissions: Union[List[str], None] + tags: Union[dict, None] + domain_id: str + + +class RoleDeleteRequest(BaseModel): + role_id: str + domain_id: str + + +class RoleGetRequest(BaseModel): + role_id: str + domain_id: str + + +class RoleSearchQueryRequest(BaseModel): + query: Union[dict, None] = None + role_id: Union[str, None] = None + name: Union[str, None] = None + role_type: Union[RoleType, None] = None + policy_id: Union[str, None] = None + domain_id: str + + +class RoleStatQueryRequest(BaseModel): + query: dict + domain_id: str diff --git a/src/spaceone/identity/model/role_response.py b/src/spaceone/identity/model/role_response.py new file mode 100644 index 00000000..eeeeb6ce --- /dev/null +++ b/src/spaceone/identity/model/role_response.py @@ -0,0 +1,23 @@ +from datetime import datetime +from typing import Union, List +from pydantic import BaseModel + +from spaceone.identity.model.role_request import RoleType + +__all__ = ["RoleResponse", "RolesResponse"] + + +class RoleResponse(BaseModel): + role_id: Union[str, None] = None + name: Union[str, None] = None + role_type: Union[RoleType, None] = None + policy_id: Union[str, None] = None + permissions: Union[list, None] = None + tags: Union[dict, None] = None + domain_id: Union[str, None] = None + created_at: Union[datetime, None] = None + + +class RolesResponse(BaseModel): + results: List[RoleResponse] + total_count: int diff --git a/src/spaceone/identity/model/service_account_request.py b/src/spaceone/identity/model/service_account_request.py new file mode 100644 index 00000000..b0a444e0 --- /dev/null +++ b/src/spaceone/identity/model/service_account_request.py @@ -0,0 +1,78 @@ +from datetime import datetime +from typing import Union, Literal +from pydantic import BaseModel + +__all__ = [ + "ServiceAccountCreateRequest", + "ServiceAccountUpdateRequest", + "ServiceAccountChangeTrustedServiceAccountRequest", + "ServiceAccountChangeProjectRequest", + "ServiceAccountDeleteRequest", + "ServiceAccountGetRequest", + "ServiceAccountSearchQueryRequest", + "ServiceAccountStatQueryRequest", +] + + +class ServiceAccountCreateRequest(BaseModel): + name: str + data: dict + provider: str + trusted_service_account_id: Union[str, None] = None + tags: Union[dict, None] = None + project_id: str + workspace_id: str + domain_id: str + + +class ServiceAccountUpdateRequest(BaseModel): + service_account_id: str + name: Union[str, None] = None + data: Union[dict, None] = None + tags: Union[dict, None] = None + workspace_id: str + domain_id: str + + +class ServiceAccountChangeTrustedServiceAccountRequest(BaseModel): + service_account_id: str + trusted_service_account_id: str + workspace_id: str + domain_id: str + + +class ServiceAccountChangeProjectRequest(BaseModel): + service_account_id: str + project_id: str + workspace_id: str + domain_id: str + + +class ServiceAccountDeleteRequest(BaseModel): + service_account_id: str + workspace_id: str + domain_id: str + + +class ServiceAccountGetRequest(BaseModel): + service_account_id: str + workspace_id: Union[str, None] = None + domain_id: str + + +class ServiceAccountSearchQueryRequest(BaseModel): + query: Union[dict, None] = None + service_account_id: Union[str, None] = None + name: Union[str, None] = None + provider: Union[str, None] = None + has_secret: Union[bool, None] = None + trusted_service_account_id: Union[str, None] = None + project_id: Union[str, None] = None + workspace_id: Union[str, None] = None + domain_id: str + + +class ServiceAccountStatQueryRequest(BaseModel): + query: dict + workspace_id: Union[str, None] = None + domain_id: str diff --git a/src/spaceone/identity/model/service_account_response.py b/src/spaceone/identity/model/service_account_response.py new file mode 100644 index 00000000..933311c4 --- /dev/null +++ b/src/spaceone/identity/model/service_account_response.py @@ -0,0 +1,25 @@ +from datetime import datetime +from typing import Union, Literal, List +from pydantic import BaseModel + +__all__ = ["ServiceAccountResponse", "ServiceAccountsResponse"] + +Scope = Literal["DOMAIN", "WORKSPACE"] + + +class ServiceAccountResponse(BaseModel): + service_account_id: Union[str, None] = None + name: Union[str, None] = None + data: Union[dict, None] = None + provider: Union[str, None] = None + tags: Union[dict, None] = None + trusted_service_account_id: Union[str, None] = None + project_id: Union[str, None] = None + workspace_id: Union[str, None] = None + domain_id: Union[str, None] = None + created_at: Union[datetime, None] = None + + +class ServiceAccountsResponse(BaseModel): + results: List[ServiceAccountResponse] + total_count: int diff --git a/src/spaceone/identity/model/token_request.py b/src/spaceone/identity/model/token_request.py new file mode 100644 index 00000000..e5fd9544 --- /dev/null +++ b/src/spaceone/identity/model/token_request.py @@ -0,0 +1,15 @@ +from typing import Union, Literal +from pydantic import BaseModel + +__all__: ["TokenIssueRequest"] + +AuthType = Literal["LOCAL", "EXTERNAL"] + + +class TokenIssueRequest(BaseModel): + credentials: dict + auth_type: AuthType + timeout: Union[int, None] = None + refresh_count: Union[int, None] = None + verify_count: Union[int, None] = None + domain_id: str diff --git a/src/spaceone/identity/model/token_response.py b/src/spaceone/identity/model/token_response.py new file mode 100644 index 00000000..4e66b67f --- /dev/null +++ b/src/spaceone/identity/model/token_response.py @@ -0,0 +1,8 @@ +from pydantic import BaseModel + +__all__ = ["TokenResponse"] + + +class TokenResponse(BaseModel): + access_token: str + refresh_token: str diff --git a/src/spaceone/identity/model/trusted_service_account_request.py b/src/spaceone/identity/model/trusted_service_account_request.py new file mode 100644 index 00000000..6263d3b0 --- /dev/null +++ b/src/spaceone/identity/model/trusted_service_account_request.py @@ -0,0 +1,60 @@ +from typing import Union, Literal +from pydantic import BaseModel + +__all__ = [ + "TrustedServiceAccountCreateRequest", + "TrustedServiceAccountUpdateRequest", + "TrustedServiceAccountDeleteRequest", + "TrustedServiceAccountGetRequest", + "TrustedServiceAccountSearchQueryRequest", + "TrustedServiceAccountStatQueryRequest", +] + +Scope = Literal["DOMAIN", "WORKSPACE"] + + +class TrustedServiceAccountCreateRequest(BaseModel): + name: str + data: dict + provider: str + tags: Union[dict, None] = None + scope: Scope + workspace_id: Union[str, None] = None + domain_id: str + + +class TrustedServiceAccountUpdateRequest(BaseModel): + trusted_service_account_id: str + name: Union[str, None] = None + data: Union[dict, None] = None + tags: Union[dict, None] = None + workspace_id: Union[str, None] = None + domain_id: str + + +class TrustedServiceAccountDeleteRequest(BaseModel): + trusted_service_account_id: str + workspace_id: Union[str, None] = None + domain_id: str + + +class TrustedServiceAccountGetRequest(BaseModel): + trusted_service_account_id: str + workspace_id: Union[str, None] = None + domain_id: str + + +class TrustedServiceAccountSearchQueryRequest(BaseModel): + query: Union[dict, None] = None + trusted_service_account_id: Union[str, None] = None + name: Union[str, None] = None + provider: Union[str, None] = None + scope: Union[Scope, None] = None + workspace_id: Union[str, None] = None + domain_id: str + + +class TrustedServiceAccountStatQueryRequest(BaseModel): + query: dict + workspace_id: Union[str, None] = None + domain_id: str diff --git a/src/spaceone/identity/model/trusted_service_account_response.py b/src/spaceone/identity/model/trusted_service_account_response.py new file mode 100644 index 00000000..7014e66e --- /dev/null +++ b/src/spaceone/identity/model/trusted_service_account_response.py @@ -0,0 +1,24 @@ +from datetime import datetime +from typing import Union, Literal, List +from pydantic import BaseModel + +__all__ = ["TrustedServiceAccountResponse", "TrustedServiceAccountsResponse"] + +Scope = Literal["DOMAIN", "WORKSPACE"] + + +class TrustedServiceAccountResponse(BaseModel): + trusted_service_account_id: Union[str, None] = None + name: Union[str, None] = None + data: Union[dict, None] = None + provider: Union[str, None] = None + tags: Union[dict, None] = None + scope: Union[Scope, None] = None + workspace_id: Union[str, None] = None + domain_id: Union[str, None] = None + created_at: Union[datetime, None] = None + + +class TrustedServiceAccountsResponse(BaseModel): + results: List[TrustedServiceAccountResponse] + total_count: int diff --git a/src/spaceone/identity/model/user_group_request.py b/src/spaceone/identity/model/user_group_request.py new file mode 100644 index 00000000..0e4d3dad --- /dev/null +++ b/src/spaceone/identity/model/user_group_request.py @@ -0,0 +1,69 @@ +from typing import Union, List +from pydantic import BaseModel + +__all__ = [ + "UserGroupCreateRequest", + "UserGroupUpdateRequest", + "UserGroupDeleteRequest", + "UserGroupAddUsersRequest", + "UserGroupRemoveUsersRequest", + "UserGroupGetRequest", + "UserGroupSearchQueryRequest", + "UserGroupStatQueryRequest", +] + + +class UserGroupCreateRequest(BaseModel): + name: str + tags: Union[dict, None] = None + workspace_id: str + domain_id: str + + +class UserGroupUpdateRequest(BaseModel): + user_group_id: str + name: Union[str, None] = None + tags: Union[dict, None] = None + workspace_id: str + domain_id: str + + +class UserGroupDeleteRequest(BaseModel): + user_group_id: str + workspace_id: str + domain_id: str + + +class UserGroupAddUsersRequest(BaseModel): + user_group_id: str + users: List[str] + workspace_id: str + domain_id: str + + +class UserGroupRemoveUsersRequest(BaseModel): + user_group_id: str + users: List[str] + workspace_id: str + domain_id: str + + +class UserGroupGetRequest(BaseModel): + user_group_id: str + workspace_id: str + domain_id: str + + +class UserGroupSearchQueryRequest(BaseModel): + query: Union[dict, None] = None + user_group_id: Union[str, None] = None + name: Union[str, None] = None + user_id: Union[str, None] = None + workspace_id: Union[str, None] = None + domain_id: str + + +class UserGroupStatQueryRequest(BaseModel): + query: dict + workspace_id: Union[str, None] = None + domain_id: str diff --git a/src/spaceone/identity/model/user_group_response.py b/src/spaceone/identity/model/user_group_response.py new file mode 100644 index 00000000..921c80a6 --- /dev/null +++ b/src/spaceone/identity/model/user_group_response.py @@ -0,0 +1,19 @@ +from datetime import datetime +from typing import Union, List +from pydantic import BaseModel + +__all__ = ["UserGroupResponse", "UserGroupsResponse"] + + +class UserGroupResponse(BaseModel): + user_group_id: Union[str, None] = None + name: Union[str, None] = None + users: Union[List[str], None] = None + tags: Union[dict, None] = None + domain_id: Union[str, None] = None + created_at: Union[datetime, None] = None + + +class UserGroupsResponse(BaseModel): + results: List[UserGroupResponse] + total_count: int diff --git a/src/spaceone/identity/model/user_request.py b/src/spaceone/identity/model/user_request.py new file mode 100644 index 00000000..9ff6cdf3 --- /dev/null +++ b/src/spaceone/identity/model/user_request.py @@ -0,0 +1,139 @@ +from typing import Union, Literal, List +from pydantic import BaseModel + +__all__ = [ + "UserSearchQueryRequest", + "UserCreateRequest", + "UserUpdateRequest", + "UserVerifyEmailRequest", + "UserStatQueryRequest", + "UserConfirmEmailRequest", + "UserResetPasswordRequest", + "UserSetRequiredActionsRequest", + "UserEnableMFARequest", + "UserDisableMFARequest", + "UserConfirmMFARequest", + "UserDeleteRequest", + "UserEnableRequest", + "UserDisableRequest", + "UserGetRequest", + "UserType", + "Backend", + "State", + "RoleType", +] + +State = Literal["ENABLED", "DISABLED", "PENDING"] +Backend = Literal["LOCAL", "EXTERNAL"] +RoleType = Literal[ + "SYSTEM_ADMIN", "DOMAIN_ADMIN", "WORKSPACE_OWNER", "WORKSPACE_MEMBER", "NO_RULE" +] +UserType = Literal["USER", "API_USER"] + + +class UserCreateRequest(BaseModel): + user_id: str + password: Union[str, None] = None + name: Union[str, None] = None + email: Union[str, None] = None + user_type: Union[UserType, None] = None + backend: Backend + language: Union[str, None] = None + timezone: Union[str, None] = None + tags: Union[dict, None] = None + reset_password: Union[bool, None] = None + role_binding: Union[dict, None] = None + domain_id: str + + +class UserUpdateRequest(BaseModel): + user_id: str + password: Union[str, None] = None + name: Union[str, None] = None + email: Union[str, None] = None + language: Union[str, None] = None + timezone: Union[str, None] = None + tags: Union[dict, None] = None + reset_password: Union[bool, None] = None + domain_id: str + + +class UserVerifyEmailRequest(BaseModel): + user_id: str + email: Union[str, None] = None + force: Union[bool, None] = None + domain_id: str + + +class UserConfirmEmailRequest(BaseModel): + user_id: str + verify_code: str + domain_id: str + + +class UserResetPasswordRequest(BaseModel): + user_id: str + domain_id: str + + +class UserSetRequiredActionsRequest(BaseModel): + user_id: str + actions: List[str] + domain_id: str + + +class UserEnableMFARequest(BaseModel): + user_id: str + mfa_type: str + options: dict + domain_id: str + + +class UserDisableMFARequest(BaseModel): + user_id: str + force: Union[bool, None] = None + domain_id: str + + +class UserConfirmMFARequest(BaseModel): + user_id: str + verify_code: str + domain_id: str + + +class UserDeleteRequest(BaseModel): + user_id: str + domain_id: str + + +class UserEnableRequest(BaseModel): + user_id: str + domain_id: str + + +class UserDisableRequest(BaseModel): + user_id: str + domain_id: str + + +class UserGetRequest(BaseModel): + user_id: str + domain_id: str + + +class UserSearchQueryRequest(BaseModel): + query: Union[dict, None] = None + user_id: Union[str, None] = None + name: Union[str, None] = None + state: Union[str, None] = None + email: Union[str, None] = None + user_type: Union[str, None] = None + backend: Union[str, None] = None + workspace_id: Union[str, None] = None + domain_id: str + + +class UserStatQueryRequest(BaseModel): + query: dict + workspace_id: Union[str, None] = None + domain_id: str diff --git a/src/spaceone/identity/model/user_response.py b/src/spaceone/identity/model/user_response.py new file mode 100644 index 00000000..884037bd --- /dev/null +++ b/src/spaceone/identity/model/user_response.py @@ -0,0 +1,30 @@ +from datetime import datetime +from typing import Union, List +from pydantic import BaseModel + +from spaceone.identity.model.user_request import State, UserType, Backend, RoleType + +__all__ = ["UserResponse", "UsersResponse"] + + +class UserResponse(BaseModel): + user_id: Union[str, None] = None + name: Union[str, None] = None + state: Union[State, None] = None + email_verified: Union[bool, None] = None + user_type: Union[UserType, None] = None + backend: Union[Backend, None] = None + role_type: Union[RoleType, None] = None + mfa: Union[dict, None] = None + required_actions: Union[List[str], None] = None + language: Union[str, None] = None + timezone: Union[str, None] = None + tags: Union[dict, None] = None + domain_id: str + created_at: Union[datetime, None] = None + last_accessed_at: Union[datetime, None] = None + + +class UsersResponse(BaseModel): + results: List[UserResponse] + total_count: int diff --git a/src/spaceone/identity/model/workspace_request.py b/src/spaceone/identity/model/workspace_request.py new file mode 100644 index 00000000..148b19fb --- /dev/null +++ b/src/spaceone/identity/model/workspace_request.py @@ -0,0 +1,59 @@ +from typing import Union +from enum import Enum +from pydantic import BaseModel, Field + +__all__ = [ + "WorkspaceCreateRequest", + "WorkspaceUpdateRequest", + "WorkspaceDeleteRequest", + "WorkspaceEnableRequest", + "WorkspaceDisableRequest", + "WorkspaceGetRequest", + "WorkspaceSearchQueryRequest", + "WorkspaceStatQueryRequest", +] + + +class WorkspaceCreateRequest(BaseModel): + name: str + tags: dict + domain_id: str + + +class WorkspaceUpdateRequest(BaseModel): + workspace_id: str + name: Union[str, None] = None + tags: Union[dict, None] = {} + domain_id: str + + +class WorkspaceDeleteRequest(BaseModel): + workspace_id: str + domain_id: str + + +class WorkspaceEnableRequest(BaseModel): + workspace_id: str + domain_id: str + + +class WorkspaceDisableRequest(BaseModel): + workspace_id: str + domain_id: str + + +class WorkspaceGetRequest(BaseModel): + workspace_id: str + domain_id: str + + +class WorkspaceSearchQueryRequest(BaseModel): + query: Union[dict, None] = None + name: Union[str, None] = None + workspace_id: Union[str, None] = None + domain_id: str + + +class WorkspaceStatQueryRequest(BaseModel): + query: dict + domain_id: str diff --git a/src/spaceone/identity/model/workspace_response.py b/src/spaceone/identity/model/workspace_response.py new file mode 100644 index 00000000..678d5205 --- /dev/null +++ b/src/spaceone/identity/model/workspace_response.py @@ -0,0 +1,22 @@ +from datetime import datetime +from typing import Union, List, Literal +from enum import Enum +from pydantic import BaseModel + +__all__ = ["WorkspaceResponse", "WorkspacesResponse"] + +State = Literal["ENABLED", "DISABLED"] + + +class WorkspaceResponse(BaseModel): + workspace_id: Union[str, None] = None + name: Union[str, None] = None + state: Union[State, None] = None + tags: Union[dict, None] = {} + domain_id: Union[str, None] = None + created_at: Union[datetime, None] = None + + +class WorkspacesResponse(BaseModel): + results: List[WorkspaceResponse] + total_count: int diff --git a/src/spaceone/identity/service/__init__.py b/src/spaceone/identity/service/__init__.py index b1a00645..e69de29b 100644 --- a/src/spaceone/identity/service/__init__.py +++ b/src/spaceone/identity/service/__init__.py @@ -1,14 +0,0 @@ -from spaceone.identity.service.authorization_service import AuthorizationService -from spaceone.identity.service.user_service import UserService -from spaceone.identity.service.role_service import RoleService -from spaceone.identity.service.role_binding_service import RoleBindingService -from spaceone.identity.service.api_key_service import APIKeyService -from spaceone.identity.service.domain_service import DomainService -from spaceone.identity.service.domain_owner_service import DomainOwnerService -from spaceone.identity.service.provider_service import ProviderService -from spaceone.identity.service.service_account_service import ServiceAccountService -from spaceone.identity.service.policy_service import PolicyService -from spaceone.identity.service.token_service import TokenService -from spaceone.identity.service.project_group_service import ProjectGroupService -from spaceone.identity.service.project_service import ProjectService -from spaceone.identity.service.endpoint_service import EndpointService diff --git a/src/spaceone/identity/service/api_key_service.py b/src/spaceone/identity/service/api_key_service.py index e8aec64c..f04197c9 100644 --- a/src/spaceone/identity/service/api_key_service.py +++ b/src/spaceone/identity/service/api_key_service.py @@ -1,149 +1,49 @@ -from spaceone.core.service import * -from spaceone.identity.manager import APIKeyManager, UserManager +import logging +from typing import Union +from spaceone.core.service import BaseService, transaction, convert_model +from spaceone.identity.model.api_key_request import * +from spaceone.identity.model.api_key_response import * +_LOGGER = logging.getLogger(__name__) -@authentication_handler(exclude=['get']) -@authorization_handler(exclude=['get']) -@mutation_handler -@event_handler -class APIKeyService(BaseService): - - def __init__(self, metadata): - super().__init__(metadata) - self.api_key_mgr: APIKeyManager = self.locator.get_manager('APIKeyManager') - - @transaction(append_meta={'authorization.scope': 'USER'}) - @check_required(['user_id', 'domain_id']) - def create(self, params): - """ Create api key - - Args: - params (dict): { - 'user_id': 'str', - 'domain_id': 'str' - } - - Returns: - api_key_vo (object) - """ - - user_id = params['user_id'] - domain_id = params['domain_id'] - - # Check user is exists. - user_mgr: UserManager = self.locator.get_manager('UserManager') - user_vo = user_mgr.get_user(user_id=user_id, domain_id=domain_id) - - return self.api_key_mgr.create_api_key(user_vo, domain_id) - - @transaction(append_meta={'authorization.scope': 'USER'}) - @check_required(['api_key_id', 'domain_id']) - def enable(self, params): - """ Enable api key - - Args: - params (dict): { - 'api_key_id': 'str', - 'domain_id': 'str' - } - - Returns: - api_key_vo (object) - """ - - return self.api_key_mgr.enable_api_key(params['api_key_id'], params['domain_id']) - - @transaction(append_meta={'authorization.scope': 'USER'}) - @check_required(['api_key_id', 'domain_id']) - def disable(self, params): - """ Disable api key - - Args: - params (dict): { - 'api_key_id': 'str', - 'domain_id': 'str' - } - - Returns: - api_key_vo (object) - """ - - return self.api_key_mgr.disable_api_key(params['api_key_id'], params['domain_id']) - @transaction(append_meta={'authorization.scope': 'USER'}) - @check_required(['api_key_id', 'domain_id']) - def delete(self, params): - """ Delete api key - - Args: - params (dict): { - 'api_key_id': 'str', - 'domain_id': 'str' - } - - Returns: - None - """ - - self.api_key_mgr.delete_api_key(params['api_key_id'], params['domain_id']) - - @transaction(append_meta={'authorization.scope': 'USER'}) - @check_required(['api_key_id', 'domain_id']) - def get(self, params): - """ Get api key - - Args: - params (dict): { - 'api_key_id': 'str', - 'domain_id': 'str', - 'only': 'list' - } - - Returns: - api_key_vo (object) - """ - - return self.api_key_mgr.get_api_key(params['api_key_id'], params['domain_id'], params.get('only')) - - @transaction(append_meta={'authorization.scope': 'USER'}) - @check_required(['domain_id']) - @append_query_filter(['api_key_id', 'state', 'user_id', 'domain_id']) - @append_keyword_filter(['api_key_id', 'user_id']) - def list(self, params): - """ List api keys - - Args: - params (dict): { - 'api_key_id': 'str', - 'state': 'str', - 'user_id': 'str', - 'domain_id': 'str', - 'query': 'dict (spaceone.api.core.v1.Query)' - } - - Returns: - results (list): 'list of api_key_vo' - total_count (int) - """ - - return self.api_key_mgr.list_api_keys(params.get('query', {})) - - @transaction(append_meta={'authorization.scope': 'USER'}) - @check_required(['query', 'domain_id']) - @append_query_filter(['domain_id']) - @append_keyword_filter(['api_key_id', 'user_id']) - def stat(self, params): - """ - Args: - params (dict): { - 'domain_id': 'str', - 'query': 'dict (spaceone.api.core.v1.StatisticsQuery)' - } - - Returns: - values (list): 'list of statistics data' - total_count (int) - """ - - query = params.get('query', {}) - return self.api_key_mgr.stat_api_keys(query) +class APIKeyService(BaseService): + @transaction + @convert_model + def create(self, params: APIKeyCreateRequest) -> Union[APIKeyResponse, dict]: + return {} + + @transaction + @convert_model + def update(self, params: APIKeyUpdateRequest) -> Union[APIKeyResponse, dict]: + return {} + + @transaction + @convert_model + def enable(self, params: APIKeyEnableRequest) -> Union[APIKeyResponse, dict]: + return {} + + @transaction + @convert_model + def disable(self, params: APIKeyDisableRequest) -> Union[APIKeyResponse, dict]: + return {} + + @transaction + @convert_model + def delete(self, params: APIKeyDeleteRequest) -> None: + pass + + @transaction + @convert_model + def get(self, params: APIKeyGetRequest) -> Union[APIKeyResponse, dict]: + return {} + + @transaction + @convert_model + def list(self, params: APIKeySearchQueryRequest) -> Union[APIKeysResponse, dict]: + return {} + + @transaction + @convert_model + def stat(self, params: APIKeyStatQueryRequest) -> dict: + return {} diff --git a/src/spaceone/identity/service/authorization_service.py b/src/spaceone/identity/service/authorization_service.py index c6602450..d02dde42 100644 --- a/src/spaceone/identity/service/authorization_service.py +++ b/src/spaceone/identity/service/authorization_service.py @@ -1,247 +1,16 @@ import logging - -from spaceone.core import cache -from spaceone.core.service import * -from spaceone.core.error import * -from spaceone.identity.manager.authorization_manager import AuthorizationManager -from spaceone.identity.manager.domain_manager import DomainManager -from spaceone.identity.manager.user_manager import UserManager -from spaceone.identity.manager.project_manager import ProjectManager -from spaceone.identity.manager.project_group_manager import ProjectGroupManager -from spaceone.identity.manager.role_binding_manager import RoleBindingManager -from spaceone.identity.manager.role_manager import RoleManager -from spaceone.identity.manager.policy_manager import PolicyManager -from spaceone.identity.conf.permissions_conf import DEFAULT_PERMISSIONS +from typing import Union +from spaceone.core.service import BaseService, transaction, convert_model +from spaceone.identity.model.authorization_request import * +from spaceone.identity.model.authorization_response import * _LOGGER = logging.getLogger(__name__) -@authentication_handler -@event_handler class AuthorizationService(BaseService): - - def __init__(self, metadata): - super().__init__(metadata) - self.auth_mgr: AuthorizationManager = self.locator.get_manager('AuthorizationManager') - self.project_mgr: ProjectManager = self.locator.get_manager('ProjectManager') - self.project_group_mgr: ProjectGroupManager = self.locator.get_manager('ProjectGroupManager') - - @transaction(append_meta={'authorization.scope': 'SYSTEM'}) - @check_required(['service', 'resource', 'verb', 'scope']) - def verify(self, params): - """ Verify authority - - Args: - params (dict): { - 'service': 'str', - 'resource': 'str', - 'verb': 'str', - 'scope': 'str', - 'domain_id': 'str', - 'project_id': 'str', - 'project_group_id': 'str', - 'user_id': 'str', - 'require_project_id': 'bool', - 'require_project_group_id': 'bool', - 'require_user_id': 'bool', - 'require_domain_id': 'bool', - } - - Returns: - domain_owner_vo (object) - """ - - user_id = self.transaction.get_meta('user_id') - domain_id = self.transaction.get_meta('domain_id') - service = params['service'] - resource = params['resource'] - verb = params['verb'] - scope = params['scope'] - request_domain_id = params.get('domain_id') - request_project_id = params.get('project_id') - request_project_group_id = params.get('project_group_id') - request_user_id = params.get('user_id') - require_project_id = params.get('require_project_id', False) - require_project_group_id = params.get('require_project_group_id', False) - require_user_id = params.get('require_user_id', False) - require_domain_id = params.get('require_domain_id', False) - - self._check_user_state(user_id, domain_id) - self._check_domain_state(domain_id) - - project_path = self.project_mgr.get_project_path(request_project_id, request_project_group_id, domain_id) - role_type, request_roles, projects, project_groups = \ - self._get_user_roles_and_projects_from_role_bindings(user_id, domain_id, scope, project_path) - - projects, project_groups = self._get_all_projects_and_project_groups(projects, project_groups, domain_id) - - user_permissions = self._get_permissions(role_type, request_roles, domain_id) - - self.auth_mgr.check_permissions(user_id, domain_id, user_permissions, service, resource, verb, request_roles) - self.auth_mgr.check_scope_by_role_type(user_id, domain_id, scope, role_type, projects, project_groups, - request_domain_id, request_project_id, request_project_group_id, - request_user_id, require_project_id, require_project_group_id, - require_user_id, require_domain_id) - - return { - 'role_type': role_type, - 'projects': projects, - 'project_groups': project_groups - } - - @cache.cacheable(key='user-state:{domain_id}:{user_id}', expire=3600) - def _check_user_state(self, user_id, domain_id): - user_mgr: UserManager = self.locator.get_manager('UserManager') - try: - user_vo = user_mgr.get_user(user_id, domain_id) - except Exception as e: - _LOGGER.error(f'[_check_user_state] Get User Error: {e}') - raise ERROR_PERMISSION_DENIED() - - if user_vo.state == 'DISABLED': - _LOGGER.error(f'[_check_user_state] User has been disabled. (user_id={user_id}, domain_id={domain_id})') - raise ERROR_PERMISSION_DENIED() - - @cache.cacheable(key='domain-state:{domain_id}', expire=3600) - def _check_domain_state(self, domain_id): - domain_mgr: DomainManager = self.locator.get_manager('DomainManager') - try: - domain_vo = domain_mgr.get_domain(domain_id) - except Exception as e: - _LOGGER.error(f'[_check_user_state] Get Domain Error: {e}') - raise ERROR_PERMISSION_DENIED() - - if domain_vo.state == 'DISABLED': - _LOGGER.error(f'[_check_domain_state] Domain has been disabled. (domain_id={domain_id})') - raise ERROR_PERMISSION_DENIED() - - @cache.cacheable(key='role-bindings:{domain_id}:{user_id}:{scope}:{project_path}', expire=3600) - def _get_user_roles_and_projects_from_role_bindings(self, user_id, domain_id, scope, project_path): - role_binding_mgr: RoleBindingManager = self.locator.get_manager('RoleBindingManager') - - role_binding_vos = role_binding_mgr.get_user_role_bindings(user_id, domain_id) - - role_type = None - request_roles = [] - projects = [] - project_groups = [] - for role_binding_vo in role_binding_vos: - rb_role_type = role_binding_vo.role.role_type - rb_role_id = role_binding_vo.role_id - if role_type is None: - role_type = rb_role_type - else: - if rb_role_type != 'PROJECT': - role_type = rb_role_type - - if rb_role_type == 'PROJECT': - if role_binding_vo.project_id: - project_id = role_binding_vo.project_id - projects.append(project_id) - - if scope == 'PROJECT' and isinstance(project_path, list): - if project_id in project_path: - request_roles.append(rb_role_id) - else: - request_roles.append(rb_role_id) - - elif role_binding_vo.project_group_id: - project_group_id = role_binding_vo.project_group_id - project_groups.append(project_group_id) - - if scope == 'PROJECT' and isinstance(project_path, list): - if project_group_id in project_path: - request_roles.append(rb_role_id) - else: - request_roles.append(rb_role_id) - else: - request_roles.append(rb_role_id) - - return role_type or 'USER', list(set(request_roles)), list(set(projects)), list(set(project_groups)) - - def _get_request_roles_by_scope(self, user_role_type, user_roles, scope, project_path): - if user_role_type == 'SYSTEM': - return user_roles['SYSTEM'] - elif user_role_type == 'DOMAIN': - if scope == 'PROJECT': - for project_or_project_group_id in project_path: - if project_or_project_group_id in user_roles['PROJECT']: - return user_roles['PROJECT'][project_or_project_group_id] - - return user_roles['DOMAIN'] - - elif user_role_type == 'PROJECT': - if scope == 'PROJECT': - if len(project_path) > 0: - for project_or_project_group_id in project_path: - if project_or_project_group_id in user_roles['PROJECT']: - return user_roles['PROJECT'][project_or_project_group_id] - - raise ERROR_PERMISSION_DENIED() - - project_roles = [] - for project_or_project_group_id, roles in user_roles['PROJECT'].items(): - project_roles += roles - - return project_roles - else: - return [] - - def _get_all_projects_and_project_groups(self, projects, project_groups, domain_id): - for project_group_id in project_groups[:]: - child_projects, child_project_groups = self._get_children_in_project_group(project_group_id, domain_id) - projects += child_projects - project_groups += child_project_groups - - return projects, project_groups - - @cache.cacheable(key='project-group-children:{domain_id}:{project_group_id}', expire=3600) - def _get_children_in_project_group(self, project_group_id, domain_id): - project_group_vo = self.project_group_mgr.get_project_group(project_group_id, domain_id) - related_project_groups = self._get_related_project_group(project_group_vo, [project_group_vo]) - - project_vos = self.project_mgr.filter_projects(project_group=related_project_groups) - project_group_vos = self.project_group_mgr.filter_project_groups(parent_project_group=related_project_groups) - - projects = [project_vo.project_id for project_vo in project_vos] - project_groups = [project_group_vo.project_group_id for project_group_vo in project_group_vos] - - return projects, project_groups - - def _get_related_project_group(self, project_group_vo, related_project_groups): - project_group_vos = self.project_group_mgr.filter_project_groups(parent_project_group=project_group_vo) - - if project_group_vos.count() > 0: - related_project_groups += project_group_vos - for pg_vo in project_group_vos: - related_project_groups = self._get_related_project_group(pg_vo, related_project_groups) - - return related_project_groups - - @cache.cacheable(key='role-permissions:{domain_id}:{role_id}', expire=3600) - def _get_role_permissions(self, role_id, domain_id): - role_mgr: RoleManager = self.locator.get_manager('RoleManager') - policy_mgr: PolicyManager = self.locator.get_manager('PolicyManager') - role_vo = role_mgr.get_role(role_id, domain_id) - permissions = [] - - for role_policy in role_vo.policies: - if role_policy.policy_type == 'MANAGED': - policy_vo = policy_mgr.get_managed_policy(role_policy.policy.policy_id, domain_id) - permissions += policy_vo.permissions - - elif role_policy.policy_type == 'CUSTOM': - permissions += role_policy.policy.permissions - - return permissions - - def _get_permissions(self, user_role_type, roles, domain_id): - if user_role_type == 'USER': - return DEFAULT_PERMISSIONS - else: - permissions = [] - - for role_id in roles: - permissions += self._get_role_permissions(role_id, domain_id) - - return permissions + @transaction + @convert_model + def verify( + self, params: AuthorizationVerifyRequest + ) -> Union[AuthorizationResponse, dict]: + return {} diff --git a/src/spaceone/identity/service/base_service.py b/src/spaceone/identity/service/base_service.py new file mode 100644 index 00000000..e22fc844 --- /dev/null +++ b/src/spaceone/identity/service/base_service.py @@ -0,0 +1,39 @@ +import logging +from typing import Union +from spaceone.core.service import BaseService, transaction, convert_model +from spaceone.identity.model.domain_request import * +from spaceone.identity.model.domain_response import * + +_LOGGER = logging.getLogger(__name__) + + +class BaseService(BaseService): + @transaction + @convert_model + def create(self, params: DomainCreateRequest) -> Union[DomainResponse, dict]: + return {} + + @transaction + @convert_model + def update(self, params: DomainUpdateRequest) -> Union[DomainResponse, dict]: + return {} + + @transaction + @convert_model + def delete(self, params: DomainRequest) -> None: + pass + + @transaction + @convert_model + def get(self, params: DomainRequest) -> Union[DomainResponse, dict]: + return {} + + @transaction + @convert_model + def list(self, params: DomainSearchQueryRequest) -> Union[DomainsResponse, dict]: + return {} + + @transaction + @convert_model + def stat(self, params: DomainStatQuery) -> dict: + return {} diff --git a/src/spaceone/identity/service/domain_service.py b/src/spaceone/identity/service/domain_service.py index 96743bde..fbd53e0b 100644 --- a/src/spaceone/identity/service/domain_service.py +++ b/src/spaceone/identity/service/domain_service.py @@ -1,267 +1,63 @@ import logging - -from spaceone.core.service import * -from spaceone.core import utils -from spaceone.identity.error import * -from spaceone.identity.manager import DomainManager -from spaceone.identity.manager.domain_secret_manager import DomainSecretManager -from spaceone.identity.model import Domain +from typing import Union +from spaceone.core.service import BaseService, transaction, convert_model +from spaceone.identity.model.domain_request import * +from spaceone.identity.model.domain_response import * _LOGGER = logging.getLogger(__name__) -@authentication_handler(exclude=['create', 'list', 'get_public_key']) -@authorization_handler(exclude=['create', 'list', 'get_public_key']) -@mutation_handler(exclude=['create', 'list', 'get_public_key']) -@event_handler class DomainService(BaseService): - - def __init__(self, metadata): - super().__init__(metadata) - self.domain_mgr: DomainManager = self.locator.get_manager('DomainManager') - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['name']) - def create(self, params): - """ Create domain - - Args: - params (dict): { - 'name': 'str', - 'config': 'dict', - 'plugin_info': 'dict', - 'tags': 'dict' - } - - Returns: - domain_vo (object) - """ - - # Create Domain - domain_vo: Domain = self.domain_mgr.create_domain(params) - - # Create domain secret - domain_secret_mgr: DomainSecretManager = self._get_domain_secret_manager() - domain_secret_mgr.create_domain_secret(domain_vo.domain_id) - - return domain_vo - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['domain_id']) - def update(self, params): - """ Update domain - - Args: - params (dict): { - 'domain_id': 'str', - 'config': 'dict', - 'tags': 'dict' - } - - Returns: - domain_vo (object) - """ - - return self.domain_mgr.update_domain(params) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['domain_id']) - def change_auth_plugin(self, params): - """ Change domain auth plugin - - Args: - params (dict): { - 'domain_id': 'str', - 'plugin_info': 'dict', - 'release_auth_plugin': 'bool' - } - - Returns: - domain_vo (object) - """ - - domain_id = params['domain_id'] - plugin_info = params.get('plugin_info') - release_auth_plugin = params.get('release_auth_plugin', False) - - if release_auth_plugin: - # release auth plugin - _LOGGER.debug(f'[change_auth_plugin] release auth plugin') - return self.domain_mgr.release_auth_plugin(domain_id) - else: - if plugin_info is None: - raise ERROR_REQUIRED_PARAMETER(key='plugin_info') - - _LOGGER.debug(f'[change_auth_plugin] update plugin_info: {plugin_info}') - return self.domain_mgr.change_auth_plugin(domain_id, plugin_info) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['domain_id']) - def update_plugin(self, params): - """ Update Plugin - Args: - params (dict): { - 'domain_id': 'str', - 'version': 'str', - 'options': 'dict', - 'upgrade_mode': 'str', - } - - Returns: - domain_vo (object) - """ - - domain_id = params['domain_id'] - version = params.get('version') - options = params.get('options') - upgrade_mode = params.get('upgrade_mode') - - return self.domain_mgr.update_domain_plugin(domain_id, version, options, upgrade_mode) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['domain_id']) - def verify_plugin(self, params): - """ Update Plugin - Args: - params (dict): { - 'domain_id': 'str', - } - - Returns: - domain_vo (object) - """ - domain_id = params['domain_id'] - return self.domain_mgr.verify_auth_plugin(domain_id) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['domain_id']) - def delete(self, params): - """ Delete domain - - Args: - params (dict): { - 'domain_id': 'str' - } - - Returns: - None - """ - - self.domain_mgr.delete_domain(params['domain_id']) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['domain_id']) - def enable(self, params): - """ Enable domain - - Args: - params (dict): { - 'domain_id': 'str' - } - - Returns: - domain_vo (object) - """ - - return self.domain_mgr.enable_domain(params['domain_id']) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['domain_id']) - def disable(self, params): - """ Disable domain - - Args: - params (dict): { - 'domain_id': 'str' - } - - Returns: - domain_vo (object) - """ - - return self.domain_mgr.disable_domain(params['domain_id']) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['domain_id']) - def get(self, params): - """ Disable domain - - Args: - params (dict): { - 'domain_id': 'str', - 'only': 'list' - } - - Returns: - domain_vo (object) - """ - - return self.domain_mgr.get_domain(params['domain_id'], params.get('only')) - - @transaction(append_meta={'auth.scope': 'SYSTEM'}) - @check_required(['domain_id']) - def get_public_key(self, params): - """ Get domain's public key for authentication - - Args: - params (dict): { - 'domain_id': 'str' - } - - Returns: - result (dict): { - 'pub_jwk': 'str', - 'domain_id': 'str' - } - """ - - domain_id = params['domain_id'] - domain_secret_mgr: DomainSecretManager = self._get_domain_secret_manager() - pub_jwk = domain_secret_mgr.get_domain_public_key(domain_id=domain_id) - - return { - 'pub_jwk': pub_jwk, - 'domain_id': domain_id - } - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @append_query_filter(['domain_id', 'name']) - @append_keyword_filter(['domain_id', 'name']) - def list(self, params): - """ List api keys - - Args: - params (dict): { - 'domain_id': 'str', - 'name': 'str', - 'query': 'dict (spaceone.api.core.v1.Query)' - } - - Returns: - results (list): 'list of domain_vo' - total_count (int) - """ - - query = params.get('query', {}) - return self.domain_mgr.list_domains(query) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['query']) - @append_keyword_filter(['domain_id', 'name']) - def stat(self, params): - """ - Args: - params (dict): { - 'query': 'dict (spaceone.api.core.v1.StatisticsQuery)' - } - - Returns: - values (list): 'list of statistics data' - total_count (int) - """ - - query = params.get('query', {}) - return self.domain_mgr.stat_domains(query) - - def _get_domain_secret_manager(self): - return self.locator.get_manager('DomainSecretManager') + @transaction + @convert_model + def create(self, params: DomainCreateRequest) -> Union[DomainResponse, dict]: + return {} + + @transaction + @convert_model + def update(self, params: DomainUpdateRequest) -> Union[DomainResponse, dict]: + return {} + + @transaction + @convert_model + def delete(self, params: DomainRequest) -> None: + pass + + @transaction + @convert_model + def enable(self, params: DomainRequest) -> Union[DomainResponse, dict]: + return {} + + @transaction + @convert_model + def disable(self, params: DomainRequest) -> Union[DomainResponse, dict]: + return {} + + @transaction + @convert_model + def get(self, params: DomainRequest) -> Union[DomainResponse, dict]: + return {} + + @transaction + @convert_model + def get_metadata( + self, params: DomainGetMetadataRequest + ) -> Union[DomainMetadataResponse, dict]: + return {} + + @transaction + @convert_model + def get_public_key( + self, params: DomainRequest + ) -> Union[DomainSecretResponse, dict]: + return {} + + @transaction + @convert_model + def list(self, params: DomainSearchQueryRequest) -> Union[DomainsResponse, dict]: + return {} + + @transaction + @convert_model + def stat(self, params: DomainStatQuery) -> dict: + return {} diff --git a/src/spaceone/identity/service/endpoint_service.py b/src/spaceone/identity/service/endpoint_service.py index 5d6e7056..b9c9d661 100644 --- a/src/spaceone/identity/service/endpoint_service.py +++ b/src/spaceone/identity/service/endpoint_service.py @@ -1,31 +1,16 @@ -from spaceone.core.service import * -from spaceone.identity.manager.endpoint_manager import EndpointManager +import logging +from typing import Union +from spaceone.core.service import BaseService, transaction, convert_model +from spaceone.identity.model.endpoint_request import * +from spaceone.identity.model.endpoint_response import * -@authentication_handler -@authorization_handler -@mutation_handler -@event_handler -class EndpointService(BaseService): +_LOGGER = logging.getLogger(__name__) - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.endpoint_mgr: EndpointManager = self.locator.get_manager('EndpointManager') - @transaction(append_meta={'authorization.scope': 'PUBLIC'}) - @append_query_filter(['service']) - @append_keyword_filter(['service']) - def list(self, params): - """ - Args: - params (dict): { - 'query': 'dict (spaceone.api.core.v1.Query)', - 'service': 'str', - 'endpoint_type': 'str' - } +class EndpointService(BaseService): + @transaction + @convert_model + def list(self, params: EndpointSearchQueryRequest) -> Union[EndpointsResponse, dict]: + return {} - Returns: - results (list): list of endpoint_vo - total_count (int) - """ - return self.endpoint_mgr.list_endpoints(params.get('query', {}), params.get('endpoint_type', 'public')) diff --git a/src/spaceone/identity/service/policy_service.py b/src/spaceone/identity/service/policy_service.py index 1626ef65..dee9d383 100644 --- a/src/spaceone/identity/service/policy_service.py +++ b/src/spaceone/identity/service/policy_service.py @@ -1,142 +1,39 @@ -from spaceone.core.service import * -from spaceone.core import utils -from spaceone.identity.manager import PolicyManager +import logging +from typing import Union +from spaceone.core.service import BaseService, transaction, convert_model +from spaceone.identity.model.policy_request import * +from spaceone.identity.model.policy_response import * +_LOGGER = logging.getLogger(__name__) -@authentication_handler -@authorization_handler -@mutation_handler -@event_handler -class PolicyService(BaseService): - - def __init__(self, metadata): - super().__init__(metadata) - self.policy_mgr: PolicyManager = self.locator.get_manager('PolicyManager') - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['name', 'permissions', 'domain_id']) - def create(self, params): - """ Create policy - - Args: - params (dict): { - 'name': 'str', - 'permissions': 'list', - 'tags': 'dict', - 'domain_id': 'str' - } - - Returns: - policy_vo (object) - """ - - return self.policy_mgr.create_policy(params) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['policy_id', 'domain_id']) - def update(self, params): - """ Update policy - - Args: - params (dict): { - 'policy_id': 'str', - 'name': 'str', - 'permissions': 'list', - 'tags': 'dict', - 'domain_id': 'str' - } - - Returns: - policy_vo (object) - """ - - return self.policy_mgr.update_policy(params) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['policy_id', 'domain_id']) - def delete(self, params): - """ Delete policy - - Args: - params (dict): { - 'policy_id': 'str', - 'domain_id': 'str' - } - - Returns: - None - """ - - self.policy_mgr.delete_policy(params['policy_id'], params['domain_id']) - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['policy_id', 'domain_id']) - def get(self, params): - """ Get policy - - Args: - params (dict): { - 'policy_id': 'str', - 'domain_id': 'str', - 'only': 'list' - } - - Returns: - domain_vo (object) - """ - - return self.policy_mgr.get_policy(params['policy_id'], params['domain_id'], params.get('only')) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['domain_id']) - @append_query_filter(['policy_id', 'name', 'domain_id']) - @append_keyword_filter(['policy_id', 'name']) - def list(self, params): - """ List polices - - Args: - params (dict): { - 'policy_id': 'str', - 'name': 'str', - 'domain_id': 'str', - 'query': 'dict (spaceone.api.core.v1.Query)' - } - - Returns: - results (list): 'list of policy_vo' - total_count (int) - """ - - query = self._append_policy_type_filter(params.get('query', {})) - return self.policy_mgr.list_policies(query) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['query', 'domain_id']) - @append_query_filter(['domain_id']) - @append_keyword_filter(['policy_id', 'name']) - def stat(self, params): - """ - Args: - params (dict): { - 'domain_id': 'str', - 'query': 'dict (spaceone.api.core.v1.StatisticsQuery)' - } - - Returns: - values (list): 'list of statistics data' - total_count (int) - """ - - query = self._append_policy_type_filter(params.get('query', {})) - return self.policy_mgr.stat_policies(query) - - @staticmethod - def _append_policy_type_filter(query): - query['filter'] = query.get('filter', []) - query['filter'].append({ - 'k': 'policy_type', - 'v': 'CUSTOM', - 'o': 'eq' - }) - - return query +class PolicyService(BaseService): + @transaction + @convert_model + def create(self, params: PolicyCreateRequest) -> Union[PolicyResponse, dict]: + return {} + + @transaction + @convert_model + def update(self, params: PolicyUpdateRequest) -> Union[PolicyResponse, dict]: + return {} + + @transaction + @convert_model + def delete(self, params: PolicyDeleteRequest) -> None: + pass + + @transaction + @convert_model + def get(self, params: PolicyGetRequest) -> Union[PolicyResponse, dict]: + return {} + + @transaction + @convert_model + def list(self, params: PolicySearchQueryRequest) -> Union[PoliciesResponse, dict]: + return {} + + @transaction + @convert_model + def stat(self, params: PolicyStatQueryRequest) -> dict: + return {} diff --git a/src/spaceone/identity/service/project_group_service.py b/src/spaceone/identity/service/project_group_service.py index cd3c5375..a3ec6d7f 100644 --- a/src/spaceone/identity/service/project_group_service.py +++ b/src/spaceone/identity/service/project_group_service.py @@ -1,531 +1,52 @@ import logging -from spaceone.core.service import * -from spaceone.core import utils -from spaceone.identity.error.error_project import * -from spaceone.identity.manager.domain_manager import DomainManager -from spaceone.identity.manager.user_manager import UserManager -from spaceone.identity.model.project_group_model import ProjectGroup -from spaceone.identity.manager.project_manager import ProjectManager -from spaceone.identity.manager.project_group_manager import ProjectGroupManager -from spaceone.identity.manager.role_manager import RoleManager -from spaceone.identity.manager.role_binding_manager import RoleBindingManager +from typing import Union +from spaceone.core.service import BaseService, transaction, convert_model +from spaceone.identity.model.project_group_request import * +from spaceone.identity.model.project_group_response import * _LOGGER = logging.getLogger(__name__) -@authentication_handler -@authorization_handler -@mutation_handler -@event_handler class ProjectGroupService(BaseService): - - def __init__(self, metadata): - super().__init__(metadata) - self.project_group_mgr: ProjectGroupManager = self.locator.get_manager('ProjectGroupManager') - - @transaction(append_meta={ - 'authorization.scope': 'PROJECT', - 'authorization.project_group_id': 'parent_project_group_id', - 'authorization.require_project_group_id': True - }) - @check_required(['name', 'domain_id']) - def create(self, params): - """ Create project group - - Args: - params (dict): { - 'name': 'str', - 'parent_project_group_id': 'str', - 'tags': 'dict', - 'domain_id': 'str' - } - - Returns: - project_group_vo (object) - """ - - params['created_by'] = self.transaction.get_meta('user_id') - - if 'parent_project_group_id' in params: - params['parent_project_group'] = self._get_parent_project_group(params['parent_project_group_id'], - params['domain_id']) - else: - params['parent_project_group'] = None - - return self.project_group_mgr.create_project_group(params) - - @transaction(append_meta={ - 'authorization.scope': 'PROJECT', - 'authorization.project_group_id': 'parent_project_group_id' - }) - @check_required(['project_group_id', 'domain_id']) - def update(self, params): - """ Update project group - - Args: - params (dict): { - 'project_group_id': 'str', - 'name': 'str', - 'parent_project_group_id': 'str', - 'release_parent_project_group': 'bool', - 'tags': 'dict', - 'domain_id': 'str' - } - - Returns: - project_group_vo (object) - """ - - domain_id = params['domain_id'] - release_parent_project_group = params.get('release_parent_project_group', False) - - project_group_vo = self.project_group_mgr.get_project_group(params['project_group_id'], domain_id) - - if release_parent_project_group: - params['parent_project_group'] = None - params['parent_project_group_id'] = None - else: - if 'parent_project_group_id' in params: - params['parent_project_group'] = self._get_parent_project_group( - params['parent_project_group_id'], params['domain_id'], params['project_group_id']) - - return self.project_group_mgr.update_project_group_by_vo(params, project_group_vo) - - @transaction(append_meta={'authorization.scope': 'PROJECT'}) - @check_required(['project_group_id', 'domain_id']) - def delete(self, params): - """ Delete project group - - Args: - params (dict): { - 'project_group_id': 'str', - 'domain_id': 'str' - } - - Returns: - None - """ - - project_group_vo = self.project_group_mgr.get_project_group(params['project_group_id'], params['domain_id']) - self.project_group_mgr.delete_project_group_by_vo(project_group_vo) - - @transaction(append_meta={'authorization.scope': 'PROJECT'}) - @check_required(['project_group_id', 'domain_id']) - @change_only_key({'parent_project_group_info': 'parent_project_group'}) - def get(self, params): - """ Get project group - - Args: - params (dict): { - 'project_group_id': 'str', - 'domain_id': 'str', - 'only': 'list' - } - - Returns: - project_group_vo - """ - - return self.project_group_mgr.get_project_group(params['project_group_id'], params['domain_id'], - params.get('only')) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['domain_id']) - @change_only_key({'parent_project_group_info': 'parent_project_group'}, key_path='query.only') - @append_query_filter(['project_group_id', 'name', 'parent_project_group_id', 'domain_id']) - @append_keyword_filter(['project_group_id', 'name']) - def list(self, params): - """ List project groups - - Args: - params (dict): { - 'project_group_id': 'str', - 'name': 'str', - 'parent_project_group_id': 'str', - 'author_within': 'bool', - 'domain_id': 'str', - 'query': 'dict (spaceone.api.core.v1.Query)' - } - - Returns: - results (list): 'list of project_group_vo' - total_count (int) - """ - role_type = self.transaction.get_meta('authorization.role_type') - user_projects = self.transaction.get_meta('authorization.projects') - user_project_groups = self.transaction.get_meta('authorization.project_groups') - author_within = params.get('author_within', False) - query = params.get('query', {}) - domain_id = params['domain_id'] - - if 'only' in query and 'parent_project_group' in query['only']: - query['only'].append('parent_project_group_id') - - # For Access Control - if role_type == 'PROJECT': - if author_within: - query['filter'].append({ - 'k': 'user_project_groups', - 'v': user_project_groups, - 'o': 'in' - }) - else: - self._append_user_project_group_filter(query, user_projects, user_project_groups, domain_id) - - project_group_vos, total_count = self.project_group_mgr.list_project_groups(query) - - return project_group_vos, total_count, self._get_parent_project_groups_info(project_group_vos) - - @transaction(append_meta={'authorization.scope': 'PROJECT'}) - @check_required(['query', 'domain_id']) - @append_query_filter(['domain_id', 'user_project_groups']) - @append_keyword_filter(['project_id', 'name']) - def stat(self, params): - """ - Args: - params (dict): { - 'domain_id': 'str', - 'query': 'dict (spaceone.api.core.v1.StatisticsQuery)', - 'user_project_groups': 'list', # from meta - } - - Returns: - values (list): 'list of statistics data' - total_count (int) - """ - - query = params.get('query', {}) - return self.project_group_mgr.stat_project_groups(query) - - @transaction(append_meta={'authorization.scope': 'PROJECT'}) - @check_required(['project_group_id', 'user_id', 'role_id', 'domain_id']) - def add_member(self, params): - """ Add project group member - - Args: - params (dict): { - 'project_group_id': 'str', - 'user_id': 'str', - 'is_external_user': 'bool', - 'role_id': 'str', - 'labels': 'list', - 'tags': 'dict', - 'domain_id': 'str' - } - - Returns: - role_binding_vo (object) - """ - - is_external_user = params.get('is_external_user', False) - user_id = params['user_id'] - domain_id = params['domain_id'] - - if is_external_user: - self._create_external_user(user_id, domain_id) - - params['resource_type'] = 'identity.User' - params['resource_id'] = params['user_id'] - del params['user_id'] - - role_mgr: RoleManager = self.locator.get_manager('RoleManager') - role_binding_mgr: RoleBindingManager = self.locator.get_manager('RoleBindingManager') - - role_vo = role_mgr.get_role(params['role_id'], params['domain_id']) - if role_vo.role_type != 'PROJECT': - raise ERROR_ONLY_PROJECT_ROLE_TYPE_ALLOWED() - - return role_binding_mgr.create_role_binding(params) - - @transaction(append_meta={'authorization.scope': 'PROJECT'}) - @check_required(['project_group_id', 'user_id', 'domain_id']) - def modify_member(self, params): - """ Modify project group member - - Args: - params (dict): { - 'project_group_id': 'str', - 'user_id': 'str', - 'labels': 'list', - 'tags': 'list', - 'domain_id': 'str' - } - - Returns: - role_binding_vo (object) - """ - - project_group_id = params['project_group_id'] - user_id = params['user_id'] - domain_id = params['domain_id'] - - project_group_vo = self.project_group_mgr.get_project_group(project_group_id, domain_id) - - role_binding_mgr: RoleBindingManager = self.locator.get_manager('RoleBindingManager') - role_binding_vos = role_binding_mgr.get_project_role_binding('identity.User', user_id, domain_id, - project_group_vo=project_group_vo) - - if role_binding_vos.count() == 0: - raise ERROR_NOT_FOUND(key='user_id', value=user_id) - - return role_binding_mgr.update_role_binding_by_vo(params, role_binding_vos[0]) - - @transaction(append_meta={'authorization.scope': 'PROJECT'}) - @check_required(['project_group_id', 'domain_id', 'user_id']) - def remove_member(self, params): - """ Remove project group member - - Args: - params (dict): { - 'project_group_id': 'str', - 'user_id': 'str', - 'domain_id': 'str' - } - - Returns: - None - """ - - project_group_id = params['project_group_id'] - user_id = params['user_id'] - domain_id = params['domain_id'] - - project_group_vo = self.project_group_mgr.get_project_group(project_group_id, domain_id) - - role_binding_mgr: RoleBindingManager = self.locator.get_manager('RoleBindingManager') - role_binding_vos = role_binding_mgr.get_project_role_binding('identity.User', user_id, domain_id, - project_group_vo=project_group_vo) - - if role_binding_vos.count() == 0: - raise ERROR_NOT_FOUND(key='user_id', value=user_id) - - for role_binding_vo in role_binding_vos: - role_binding_mgr.delete_role_binding_by_vo(role_binding_vo) - - @transaction(append_meta={'authorization.scope': 'PROJECT'}) - @check_required(['project_group_id', 'domain_id']) - @change_only_key({'project_group_info': 'project_group', 'project_info': 'project', 'role_info': 'role'}, - key_path='query.only') - @append_query_filter(['user_id', 'role_id']) - @append_keyword_filter(['resource_id']) - def list_members(self, params): - """ List users in project group - - Args: - params (dict): { - 'project_group_id': 'str', - 'user_id': 'str', - 'role_id': 'str', - 'domain_id': 'str', - 'include_parent_member': 'bool', - 'query': 'dict (spaceone.api.core.v1.Query)' - } - - Returns: - results (list): 'list of role_binding_vo' - total_count (int) - """ - - project_group_id = params['project_group_id'] - domain_id = params['domain_id'] - include_parent_member = params.get('include_parent_member', False) - - role_binding_mgr: RoleBindingManager = self.locator.get_manager('RoleBindingManager') - - query = params.get('query', {}) - query['filter'] = query.get('filter', []) - - if include_parent_member: - project_group_vo = self.project_group_mgr.get_project_group(project_group_id, domain_id) - parent_project_group_vos = self._get_parents(project_group_vo.parent_project_group, []) - query['filter'].append({ - 'k': 'project_group', - 'v': parent_project_group_vos + [project_group_vo], - 'o': 'in' - }) - query['filter'].append({ - 'k': 'role_type', - 'v': 'PROJECT', - 'o': 'eq' - }) - else: - query['filter'].append({ - 'k': 'project_group_id', - 'v': project_group_id, - 'o': 'eq' - }) - - return role_binding_mgr.list_role_bindings(query) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['project_group_id', 'domain_id']) - @change_only_key({'project_group_info': 'project_group'}, key_path='query.only') - @append_keyword_filter(['project_id', 'name']) - def list_projects(self, params): - """ List projects in project group - - Args: - params (dict): { - 'project_group_id': 'str', - 'recursive': 'bool', - 'domain_id': 'str', - 'query': 'dict (spaceone.api.core.v1.Query)' - } - - Returns: - results (list): 'list of project_vo' - total_count (int) - """ - - role_type = self.transaction.get_meta('authorization.role_type') - user_projects = self.transaction.get_meta('authorization.projects') - project_group_id = params['project_group_id'] - domain_id = params['domain_id'] - recursive = params.get('recursive', False) - query = params.get('query', {}) - - if 'only' in query and 'project_group' in query['only']: - query['only'].append('project_group_id') - - project_mgr: ProjectManager = self.locator.get_manager('ProjectManager') - - if 'filter' not in query: - query['filter'] = [] - - related_project_groups = [] - project_group_vo = self.project_group_mgr.get_project_group(project_group_id, domain_id) - related_project_groups.append(project_group_vo) - - if recursive: - related_project_groups = self._get_related_project_group(project_group_vo, related_project_groups) - - query['filter'].append({ - 'k': 'project_group', - 'v': related_project_groups, - 'o': 'in' - }) - - if role_type == 'PROJECT': - query['filter'].append({ - 'k': 'user_projects', - 'v': user_projects, - 'o': 'in' - }) - - project_vos, total_count = project_mgr.list_projects(query) - - return project_vos, total_count, self._get_project_groups_info(project_vos) - - def _get_related_project_group(self, project_group_vo, related_project_groups): - query = { - 'filter': [{ - 'k': 'parent_project_group', - 'v': project_group_vo, - 'o': 'eq' - }] - } - - project_group_vos, total_count = self.project_group_mgr.list_project_groups(query) - - if total_count > 0: - related_project_groups += project_group_vos - for pg_vo in project_group_vos: - related_project_groups = self._get_related_project_group(pg_vo, related_project_groups) - - return related_project_groups - - def _get_parent_project_group(self, parent_project_group_id, domain_id, project_group_id=None): - query_filter = [{ - 'k': 'project_group_id', - 'v': parent_project_group_id, - 'o': 'eq' - }, { - 'k': 'domain_id', - 'v': domain_id, - 'o': 'eq' - }] - - if project_group_id: - query_filter.append({ - 'k': 'project_group_id', - 'v': project_group_id, - 'o': 'not' - }) - - parent_project_group_vos, total_count = self.project_group_mgr.list_project_groups({'filter': query_filter}) - - if total_count == 0: - raise ERROR_NOT_FOUND(key='parent_project_group_id', value=parent_project_group_id) - - return parent_project_group_vos[0] - - def _append_user_project_group_filter(self, query, projects, project_groups, domain_id): - self.project_mgr: ProjectManager = self.locator.get_manager('ProjectManager') - query['filter'] = query.get('filter', []) - if projects or project_groups: - all_user_project_path = project_groups[:] - for project_id in projects: - if project_id is not None: - all_user_project_path += self.project_mgr.get_project_path(project_id, None, domain_id) or [] - - for project_group_id in project_groups: - if project_group_id is not None: - all_user_project_path += self.project_mgr.get_project_path(None, project_group_id, domain_id) or [] - - query['filter'].append({ - 'k': 'user_project_groups', - 'v': list(set(all_user_project_path)), - 'o': 'in' - }) - - return query - - def _get_parents(self, project_group_vo: ProjectGroup, parent_project_group_vos): - parent_project_group_vos.append(project_group_vo) - - if project_group_vo.parent_project_group: - return self._get_parents(project_group_vo.parent_project_group, parent_project_group_vos) - else: - return parent_project_group_vos - - def _create_external_user(self, user_id, domain_id): - user_mgr: UserManager = self.locator.get_manager('UserManager') - domain_mgr: DomainManager = self.locator.get_manager('DomainManager') - - user_vos = user_mgr.filter_users(user_id=user_id, domain_id=domain_id) - - if user_vos.count() == 0: - domain_vo = domain_mgr.get_domain(domain_id) - user_mgr.create_user({ - 'user_id': user_id, - 'user_type': 'USER', - 'backend': 'EXTERNAL', - 'domain_id': domain_id - }, domain_vo) - - def _get_project_groups_info(self, project_vos): - project_groups_info = {} - project_group_ids = [] - for project_vo in project_vos: - if project_vo.project_group_id: - project_group_ids.append(project_vo.project_group_id) - - pg_vos = self.project_group_mgr.filter_project_groups(project_group_id=project_group_ids) - for pg_vo in pg_vos: - project_groups_info[pg_vo.project_group_id] = pg_vo - - return project_groups_info - - def _get_parent_project_groups_info(self, project_group_vos): - parent_project_groups_info = {} - parent_project_group_ids = [] - for project_group_vo in project_group_vos: - if project_group_vo.parent_project_group_id: - parent_project_group_ids.append(project_group_vo.parent_project_group_id) - - if len(parent_project_group_ids) > 0: - parent_pg_vos = self.project_group_mgr.filter_project_groups(project_group_id=parent_project_group_ids) - for parent_pg_vo in parent_pg_vos: - parent_project_groups_info[parent_pg_vo.project_group_id] = parent_pg_vo - - return parent_project_groups_info + @transaction + @convert_model + def create( + self, params: ProjectGroupCreateRequest + ) -> Union[ProjectGroupResponse, dict]: + return {} + + @transaction + @convert_model + def update( + self, params: ProjectGroupUpdateRequest + ) -> Union[ProjectGroupResponse, dict]: + return {} + + @transaction + @convert_model + def change_parent_group( + self, params: ProjectChangeParentGroupRequest + ) -> Union[ProjectGroupResponse, dict]: + return {} + + @transaction + @convert_model + def delete(self, params: ProjectGroupDeleteRequest) -> None: + pass + + @transaction + @convert_model + def get(self, params: ProjectGroupGetRequest) -> Union[ProjectGroupResponse, dict]: + return {} + + @transaction + @convert_model + def list( + self, params: ProjectGroupSearchQueryRequest + ) -> Union[ProjectGroupsResponse, dict]: + return {} + + @transaction + @convert_model + def stat(self, params: ProjectGroupStatQueryRequest) -> dict: + return {} diff --git a/src/spaceone/identity/service/project_service.py b/src/spaceone/identity/service/project_service.py index 70367e63..5a0c4a1d 100644 --- a/src/spaceone/identity/service/project_service.py +++ b/src/spaceone/identity/service/project_service.py @@ -1,380 +1,79 @@ import logging -from spaceone.core.service import * -from spaceone.core import utils -from spaceone.identity.error.error_project import * -from spaceone.identity.model.project_group_model import ProjectGroup -from spaceone.identity.manager.domain_manager import DomainManager -from spaceone.identity.manager.user_manager import UserManager -from spaceone.identity.manager.project_manager import ProjectManager -from spaceone.identity.manager.project_group_manager import ProjectGroupManager -from spaceone.identity.manager.role_manager import RoleManager -from spaceone.identity.manager.role_binding_manager import RoleBindingManager +from typing import Union +from spaceone.core.service import BaseService, transaction, convert_model +from spaceone.identity.model.project_request import * +from spaceone.identity.model.project_response import * _LOGGER = logging.getLogger(__name__) -@authentication_handler -@authorization_handler -@mutation_handler -@event_handler class ProjectService(BaseService): - - def __init__(self, metadata): - super().__init__(metadata) - self.project_mgr: ProjectManager = self.locator.get_manager('ProjectManager') - - @transaction(append_meta={'authorization.scope': 'PROJECT'}) - @check_required(['name', 'project_group_id', 'domain_id']) - def create(self, params): - """ Create project - - Args: - params (dict): { - 'name': 'str', - 'project_group_id': 'str', - 'tags': 'dict', - 'domain_id': 'str' - } - - Returns: - project_vo (object) - """ - - params['created_by'] = self.transaction.get_meta('user_id') - - if 'project_group_id' in params: - project_group_mgr: ProjectGroupManager = self.locator.get_manager('ProjectGroupManager') - params['project_group'] = project_group_mgr.get_project_group(params['project_group_id'], - params['domain_id']) - - return self.project_mgr.create_project(params) - - @transaction(append_meta={'authorization.scope': 'PROJECT'}) - @check_required(['project_id', 'domain_id']) - def update(self, params): - """ Update project - - Args: - params (dict): { - 'project_id': 'str', - 'name': 'str', - 'project_group_id': 'str', - 'tags': 'dict', - 'domain_id': 'str' - } - - Returns: - project_vo (object) - """ - - domain_id = params['domain_id'] - - project_vo = self.project_mgr.get_project(params['project_id'], domain_id) - - if 'project_group_id' in params: - project_group_mgr: ProjectGroupManager = self.locator.get_manager('ProjectGroupManager') - params['project_group'] = project_group_mgr.get_project_group(params['project_group_id'], domain_id) - - return self.project_mgr.update_project_by_vo(params, project_vo) - - @transaction(append_meta={'authorization.scope': 'PROJECT'}) - @check_required(['project_id', 'domain_id']) - def delete(self, params): - """ Delete project - - Args: - params (dict): { - 'project_id': 'str', - 'domain_id': 'str' - } - - Returns: - None - """ - - project_vo = self.project_mgr.get_project(params['project_id'], params['domain_id']) - self.project_mgr.delete_project_by_vo(project_vo) - - @transaction(append_meta={'authorization.scope': 'PROJECT'}) - @check_required(['project_id', 'domain_id']) - @change_only_key({'project_group_info': 'project_group'}) - def get(self, params): - """ Get project - - Args: - params (dict): { - 'project_id': 'str', - 'domain_id': 'str', - 'only': 'list' - } - - Returns: - project_vo (object) - """ - - return self.project_mgr.get_project(params['project_id'], params['domain_id'], params.get('only')) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['domain_id']) - @change_only_key({'project_group_info': 'project_group'}, key_path='query.only') - @append_query_filter(['project_id', 'name', 'project_group_id', 'domain_id', 'user_projects']) - @append_keyword_filter(['project_id', 'name']) - def list(self, params): - """ List projects - - Args: - params (dict): { - 'project_id': 'str', - 'name': 'str', - 'project_group_id': 'str', - 'domain_id': 'str', - 'query': 'dict (spaceone.api.core.v1.Query)', - 'user_projects': 'list', # from meta - } - - Returns: - results (list): 'list of project_vo' - total_count (int) - """ - - role_type = self.transaction.get_meta('authorization.role_type') - user_projects = self.transaction.get_meta('authorization.projects') - query = params.get('query', {}) - - if 'only' in query and 'project_group' in query['only']: - query['only'].append('project_group_id') - - if role_type == 'PROJECT': - query['filter'].append({ - 'k': 'user_projects', - 'v': user_projects, - 'o': 'in' - }) - - project_vos, total_count = self.project_mgr.list_projects(params.get('query', {})) - - return project_vos, total_count, self._get_project_groups_info(project_vos) - - @transaction(append_meta={'authorization.scope': 'PROJECT'}) - @check_required(['query', 'domain_id']) - @append_query_filter(['domain_id', 'user_projects']) - @append_keyword_filter(['project_id', 'name']) - def stat(self, params): - """ - Args: - params (dict): { - 'domain_id': 'str', - 'query': 'dict (spaceone.api.core.v1.StatisticsQuery)', - 'user_projects': 'list', # from meta - } - - Returns: - values (list): 'list of statistics data' - total_count (int) - """ - - query = params.get('query', {}) - return self.project_mgr.stat_projects(query) - - @transaction(append_meta={'authorization.scope': 'PROJECT'}) - @check_required(['project_id', 'user_id', 'role_id', 'domain_id']) - def add_member(self, params): - """ Add project member - - Args: - params (dict): { - 'project_id': 'str', - 'user_id': 'str', - 'is_external_user': 'bool', - 'role_id': 'str', - 'labels': 'list', - 'tags': 'dict', - 'domain_id': 'str' - } - - Returns: - role_binding_vo (object) - """ - - is_external_user = params.get('is_external_user', False) - user_id = params['user_id'] - domain_id = params['domain_id'] - - if is_external_user: - self._create_external_user(user_id, domain_id) - - params['resource_type'] = 'identity.User' - params['resource_id'] = user_id - del params['user_id'] - - role_mgr: RoleManager = self.locator.get_manager('RoleManager') - role_binding_mgr: RoleBindingManager = self.locator.get_manager('RoleBindingManager') - - role_vo = role_mgr.get_role(params['role_id'], params['domain_id']) - if role_vo.role_type != 'PROJECT': - raise ERROR_ONLY_PROJECT_ROLE_TYPE_ALLOWED() - - return role_binding_mgr.create_role_binding(params) - - @transaction(append_meta={'authorization.scope': 'PROJECT'}) - @check_required(['project_id', 'user_id', 'domain_id']) - def modify_member(self, params): - """ Modify project member - - Args: - params (dict): { - 'project_id': 'str', - 'user_id': 'str', - 'labels': 'list', - 'tags': 'dict', - 'domain_id': 'str' - } - - Returns: - role_binding_vo (object) - """ - - project_id = params['project_id'] - user_id = params['user_id'] - domain_id = params['domain_id'] - - project_vo = self.project_mgr.get_project(project_id, domain_id) - - role_binding_mgr: RoleBindingManager = self.locator.get_manager('RoleBindingManager') - role_binding_vos = role_binding_mgr.get_project_role_binding('identity.User', user_id, domain_id, - project_vo=project_vo) - - if role_binding_vos.count() == 0: - raise ERROR_NOT_FOUND(key='user_id', value=user_id) - - return role_binding_mgr.update_role_binding_by_vo(params, role_binding_vos[0]) - - @transaction(append_meta={'authorization.scope': 'PROJECT'}) - @check_required(['project_id', 'user_id', 'domain_id']) - def remove_member(self, params): - """ Remove project member - - Args: - params (dict): { - 'project_id': 'str', - 'user_id': 'str', - 'domain_id': 'str' - } - - Returns: - None - """ - - project_id = params['project_id'] - user_id = params['user_id'] - domain_id = params['domain_id'] - - project_vo = self.project_mgr.get_project(project_id, domain_id) - - role_binding_mgr: RoleBindingManager = self.locator.get_manager('RoleBindingManager') - role_binding_vos = role_binding_mgr.get_project_role_binding('identity.User', user_id, domain_id, - project_vo=project_vo) - - if role_binding_vos.count() == 0: - raise ERROR_NOT_FOUND(key='user_id', value=user_id) - - for role_binding_vo in role_binding_vos: - role_binding_mgr.delete_role_binding_by_vo(role_binding_vo) - - @transaction(append_meta={'authorization.scope': 'PROJECT'}) - @check_required(['project_id', 'domain_id']) - @change_only_key({'project_group_info': 'project_group', 'project_info': 'project', 'role_info': 'role'}, - key_path='query.only') - @append_query_filter(['user_id', 'role_id']) - @append_keyword_filter(['user_id', 'name', 'email']) - def list_members(self, params): - """ List project members - - Args: - params (dict): { - 'project_id': 'str', - 'user_id': 'str', - 'role_id': 'str', - 'domain_id': 'str', - 'include_parent_member': 'bool', - 'query': 'dict (spaceone.api.core.v1.Query)' - } - - Returns: - results (list): 'list of role_binding_vo' - total_count (int) - """ - - project_id = params['project_id'] - domain_id = params['domain_id'] - include_parent_member = params.get('include_parent_member', False) - - role_binding_mgr: RoleBindingManager = self.locator.get_manager('RoleBindingManager') - - query = params.get('query', {}) - query['filter'] = query.get('filter', []) - - if include_parent_member: - project_vo = self.project_mgr.get_project(project_id, domain_id) - project_group_vos = self._get_parents(project_vo.project_group, []) - query['filter'].append({ - 'k': 'project', - 'v': [project_vo, None], - 'o': 'in' - }) - query['filter'].append({ - 'k': 'project_group', - 'v': project_group_vos + [None], - 'o': 'in' - }) - query['filter'].append({ - 'k': 'role_type', - 'v': 'PROJECT', - 'o': 'eq' - }) - else: - query['filter'].append({ - 'k': 'project_id', - 'v': project_id, - 'o': 'eq' - }) - - return role_binding_mgr.list_role_bindings(query) - - def _get_parents(self, project_group_vo: ProjectGroup, parent_project_group_vos): - parent_project_group_vos.append(project_group_vo) - - if project_group_vo.parent_project_group: - return self._get_parents(project_group_vo.parent_project_group, parent_project_group_vos) - else: - return parent_project_group_vos - - def _create_external_user(self, user_id, domain_id): - user_mgr: UserManager = self.locator.get_manager('UserManager') - domain_mgr: DomainManager = self.locator.get_manager('DomainManager') - - user_vos = user_mgr.filter_users(user_id=user_id, domain_id=domain_id) - - if user_vos.count() == 0: - domain_vo = domain_mgr.get_domain(domain_id) - user_mgr.create_user({ - 'user_id': user_id, - 'user_type': 'USER', - 'backend': 'EXTERNAL', - 'domain_id': domain_id - }, domain_vo) - - def _get_project_groups_info(self, project_vos): - project_group_mgr: ProjectGroupManager = self.locator.get_manager('ProjectGroupManager') - - project_groups_info = {} - project_group_ids = [] - for project_vo in project_vos: - if project_vo.project_group_id: - project_group_ids.append(project_vo.project_group_id) - - pg_vos = project_group_mgr.filter_project_groups(project_group_id=project_group_ids) - for pg_vo in pg_vos: - project_groups_info[pg_vo.project_group_id] = pg_vo - - return project_groups_info + @transaction + @convert_model + def create(self, params: ProjectCreateRequest) -> Union[ProjectResponse, dict]: + return {} + + @transaction + @convert_model + def update(self, params: ProjectUpdateRequest) -> Union[ProjectResponse, dict]: + return {} + + @transaction + @convert_model + def update_project_type( + self, params: ProjectUpdateProjectTypeRequest + ) -> Union[ProjectResponse, dict]: + return {} + + @transaction + @convert_model + def change_project_group( + self, params: ProjectChangeProjectGroupRequest + ) -> Union[ProjectResponse, dict]: + return {} + + @transaction + @convert_model + def delete(self, params: ProjectDeleteRequest) -> None: + pass + + @transaction + @convert_model + def add_users(self, params: ProjectAddUsersRequest) -> Union[ProjectResponse, dict]: + return {} + + @transaction + @convert_model + def remove_users( + self, params: ProjectRemoveUsersRequest + ) -> Union[ProjectResponse, dict]: + return {} + + @transaction + @convert_model + def add_user_groups( + self, params: ProjectAddUserGroupsRequest + ) -> Union[ProjectResponse, dict]: + return {} + + @transaction + @convert_model + def remove_user_groups( + self, params: ProjectRemoveUserGroupsRequest + ) -> Union[ProjectResponse, dict]: + return {} + + @transaction + @convert_model + def get(self, params: ProjectGetRequest) -> Union[ProjectResponse, dict]: + return {} + + @transaction + @convert_model + def list(self, params: ProjectSearchQueryRequest) -> Union[ProjectsResponse, dict]: + return {} + + @transaction + @convert_model + def stat(self, params: ProjectStatQueryRequest) -> dict: + return {} diff --git a/src/spaceone/identity/service/provider.py b/src/spaceone/identity/service/provider.py new file mode 100644 index 00000000..f20ab4e2 --- /dev/null +++ b/src/spaceone/identity/service/provider.py @@ -0,0 +1,41 @@ +import logging +from typing import Union +from spaceone.core.service import BaseService, transaction, convert_model +from spaceone.identity.model.provider_request import * +from spaceone.identity.model.provider_response import * + +_LOGGER = logging.getLogger(__name__) + + +class Provider(BaseService): + @transaction + @convert_model + def create(self, params: ProviderCreateRequest) -> Union[ProviderResponse, dict]: + return {} + + @transaction + @convert_model + def update(self, params: ProviderUpdateRequest) -> Union[ProviderResponse, dict]: + return {} + + @transaction + @convert_model + def delete(self, params: ProviderDeleteRequest) -> None: + pass + + @transaction + @convert_model + def get(self, params: ProviderGetRequest) -> Union[ProviderResponse, dict]: + return {} + + @transaction + @convert_model + def list( + self, params: ProviderSearchQueryRequest + ) -> Union[ProvidersResponse, dict]: + return {} + + @transaction + @convert_model + def stat(self, params: ProviderStatQueryRequest) -> dict: + return {} diff --git a/src/spaceone/identity/service/role_binding_service.py b/src/spaceone/identity/service/role_binding_service.py index 14665d13..4d53bd27 100644 --- a/src/spaceone/identity/service/role_binding_service.py +++ b/src/spaceone/identity/service/role_binding_service.py @@ -1,144 +1,45 @@ -from spaceone.core.service import * -from spaceone.core import utils -from spaceone.identity.manager import RoleBindingManager +import logging +from typing import Union +from spaceone.core.service import BaseService, transaction, convert_model +from spaceone.identity.model.role_binding_request import * +from spaceone.identity.model.role_binding_response import * +_LOGGER = logging.getLogger(__name__) -@authentication_handler -@authorization_handler -@mutation_handler -@event_handler -class RoleBindingService(BaseService): - - def __init__(self, metadata): - super().__init__(metadata) - self.role_binding_mgr: RoleBindingManager = self.locator.get_manager('RoleBindingManager') - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['resource_type', 'resource_id', 'role_id', 'domain_id']) - def create(self, params): - """ Create role binding - - Args: - params (dict): { - 'resource_type': 'str', - 'resource_id': 'str', - 'role_id': 'str', - 'project_id': 'str', - 'project_group_id': 'str', - 'labels': 'list', - 'tags': 'dict', - 'domain_id': 'str' - } - - Returns: - role_binding_vo (object) - """ - - return self.role_binding_mgr.create_role_binding(params) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['role_binding_id', 'domain_id']) - def update(self, params): - """ Update role binding - - Args: - params (dict): { - 'role_binding_id': 'str', - 'labels': 'list', - 'tags': 'dict', - 'domain_id': 'str' - } - - Returns: - role_binding_vo (object) - """ - - return self.role_binding_mgr.update_role_binding(params) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['role_binding_id', 'domain_id']) - def delete(self, params): - """ Delete role binding - - Args: - params (dict): { - 'role_binding_id': 'str', - 'domain_id': 'str' - } - - Returns: - None - """ - self.role_binding_mgr.delete_role_binding(params['role_binding_id'], params['domain_id']) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['role_binding_id', 'domain_id']) - @change_only_key({'project_group_info': 'project_group', 'project_info': 'project', 'role_info': 'role'}) - def get(self, params): - """ Get role binding - - Args: - params (dict): { - 'role_binding_id': 'str', - 'domain_id': 'str', - 'only': 'list' - } - - Returns: - role_binding_vo (object) - """ - - return self.role_binding_mgr.get_role_binding(params['role_binding_id'], params['domain_id'], - params.get('only')) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['domain_id']) - @change_only_key({'project_group_info': 'project_group', 'project_info': 'project', 'role_info': 'role'}, - key_path='query.only') - @append_query_filter(['role_binding_id', 'resource_type', 'resource_id', 'role_id', 'role_type', - 'project_id', 'project_group_id', 'domain_id']) - @append_keyword_filter(['role_binding_id', 'resource_id', 'name']) - def list(self, params): - """ List role bindings - - Args: - params (dict): { - 'role_binding_id': 'str', - 'resource_type': 'str', - 'resource_id': 'str', - 'role_id': 'str', - 'role_type': 'str', - 'project_id': 'str', - 'project_group_id': 'str', - 'domain_id': 'str', - 'query': 'dict (spaceone.api.core.v1.Query)' - } - - Returns: - results (list): 'list of role_binding_vo' - total_count (int) - """ - query = params.get('query', {}) - - return self.role_binding_mgr.list_role_bindings(params.get('query', {})) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['query', 'domain_id']) - @append_query_filter(['domain_id']) - @append_keyword_filter(['role_binding_id', 'resource_type', 'resource_id']) - def stat(self, params): - """ - Args: - params (dict): { - 'domain_id': 'str', - 'query': 'dict (spaceone.api.core.v1.StatisticsQuery)' - } - - Returns: - values (list): 'list of statistics data' - total_count (int) - """ - - query = params.get('query', {}) - return self.role_binding_mgr.stat_role_bindings(query) +class RoleBindingService(BaseService): + @transaction + @convert_model + def create( + self, params: RoleBindingCreateRequest + ) -> Union[RoleBindingResponse, dict]: + return {} + + @transaction + @convert_model + def update_role( + self, params: RoleBindingUpdateRoleRequest + ) -> Union[RoleBindingResponse, dict]: + return {} + + @transaction + @convert_model + def delete(self, params: RoleBindingDeleteRequest) -> None: + pass + + @transaction + @convert_model + def get(self, params: RoleBindingGetRequest) -> Union[RoleBindingResponse, dict]: + return {} + + @transaction + @convert_model + def list( + self, params: RoleBindingSearchQueryRequest + ) -> Union[RoleBindingsResponse, dict]: + return {} + + @transaction + @convert_model + def stat(self, params: RoleBindingStatQueryRequest) -> dict: + return {} diff --git a/src/spaceone/identity/service/role_service.py b/src/spaceone/identity/service/role_service.py index 0626bb5f..2c1dd08e 100644 --- a/src/spaceone/identity/service/role_service.py +++ b/src/spaceone/identity/service/role_service.py @@ -1,171 +1,39 @@ -from spaceone.core.service import * -from spaceone.core.error import * -from spaceone.core import config -from spaceone.identity.manager import RoleManager, PolicyManager, DomainManager +import logging +from typing import Union +from spaceone.core.service import BaseService, transaction, convert_model +from spaceone.identity.model.role_request import * +from spaceone.identity.model.role_response import * +_LOGGER = logging.getLogger(__name__) -@authentication_handler -@authorization_handler -@mutation_handler -@event_handler -class RoleService(BaseService): - - def __init__(self, metadata): - super().__init__(metadata) - self.role_mgr: RoleManager = self.locator.get_manager('RoleManager') - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['name', 'role_type', 'policies', 'domain_id']) - def create(self, params): - """ Create role - - Args: - params (dict): { - 'name': 'str', - 'role_type': 'str', - 'policies': 'list', - 'page_permissions': 'list', - 'tags': 'dict', - 'domain_id': 'str' - } - - Returns: - role_vo (object) - """ - - # The system role type is allowed only in the root domain. - if params['role_type'] == 'SYSTEM': - self._check_system_role_type(params['domain_id']) - - params['policies'] = self._check_policy_info(params['policies'], params['domain_id']) - - return self.role_mgr.create_role(params) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['role_id', 'domain_id']) - def update(self, params): - """ Update role - - Args: - params (dict): { - 'role_id': 'str', - 'name': 'str', - 'policies': 'list', - 'page_permissions': 'list', - 'tags': 'dict', - 'release_page_permissions': 'bool', - 'domain_id': 'str' - } - - Returns: - role_vo (object) - """ - - release_page_permissions = params.get('release_page_permissions', False) - - if release_page_permissions: - params['page_permissions'] = [] - - if 'policies' in params: - params['policies'] = self._check_policy_info(params['policies'], params['domain_id']) - - return self.role_mgr.update_role(params) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['role_id', 'domain_id']) - def delete(self, params): - """ Delete role - - Args: - params (dict): { - 'role_id': 'str', - 'domain_id': 'str' - } - - Returns: - None - """ - self.role_mgr.delete_role(params['role_id'], params['domain_id']) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['role_id', 'domain_id']) - def get(self, params): - """ Get role - - Args: - params (dict): { - 'role_id': 'str', - 'domain_id': 'str', - 'only': 'list' - } - - Returns: - role_vo (object) - """ - - return self.role_mgr.get_role(params['role_id'], params['domain_id'], params.get('only')) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['domain_id']) - @append_query_filter(['role_id', 'name', 'role_type', 'policy_id', 'domain_id']) - @append_keyword_filter(['role_id', 'name']) - def list(self, params): - """ List roles - - Args: - params (dict): { - 'role_id': 'str', - 'name': 'str', - 'role_type': 'str', - 'domain_id': 'str', - 'query': 'dict (spaceone.api.core.v1.Query)' - } - - Returns: - results (list): 'list of role_vo' - total_count (int) - """ - - return self.role_mgr.list_roles(params.get('query', {})) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['query', 'domain_id']) - @append_query_filter(['domain_id']) - @append_keyword_filter(['role_id', 'name']) - def stat(self, params): - """ - Args: - params (dict): { - 'domain_id': 'str', - 'query': 'dict (spaceone.api.core.v1.StatisticsQuery)' - } - - Returns: - values (list): 'list of statistics data' - total_count (int) - """ - - query = params.get('query', {}) - return self.role_mgr.stat_roles(query) - - def _check_policy_info(self, policies, domain_id): - policy_mgr: PolicyManager = self.locator.get_manager('PolicyManager') - - change_policies = [] - for policy in policies: - if policy['policy_type'] == 'MANAGED': - policy['policy'] = policy_mgr.get_managed_policy(policy['policy_id'], domain_id) - elif policy['policy_type'] == 'CUSTOM': - policy['policy'] = policy_mgr.get_policy(policy['policy_id'], domain_id) - change_policies.append(policy) - - return change_policies - - def _check_system_role_type(self, domain_id): - root_domain_name = config.get_global('ROOT_DOMAIN_NAME', 'root') - domain_mgr: DomainManager = self.locator.get_manager('DomainManager') - domain_vo = domain_mgr.get_domain(domain_id) - - if root_domain_name != domain_vo.name: - raise ERROR_PERMISSION_DENIED() +class RoleService(BaseService): + @transaction + @convert_model + def create(self, params: RoleCreateRequest) -> Union[RoleResponse, dict]: + return {} + + @transaction + @convert_model + def update(self, params: RoleUpdateRequest) -> Union[RoleResponse, dict]: + return {} + + @transaction + @convert_model + def delete(self, params: RoleDeleteRequest) -> None: + pass + + @transaction + @convert_model + def get(self, params: RoleGetRequest) -> Union[RoleResponse, dict]: + return {} + + @transaction + @convert_model + def list(self, params: RoleSearchQueryRequest) -> Union[RolesResponse, dict]: + return {} + + @transaction + @convert_model + def stat(self, params: RoleStatQueryRequest) -> dict: + return {} diff --git a/src/spaceone/identity/service/service_account_service.py b/src/spaceone/identity/service/service_account_service.py index ebf964ed..fb6d7406 100644 --- a/src/spaceone/identity/service/service_account_service.py +++ b/src/spaceone/identity/service/service_account_service.py @@ -1,281 +1,61 @@ -from jsonschema import validate +import logging +from typing import Union +from spaceone.core.service import BaseService, transaction, convert_model +from spaceone.identity.model.service_account_request import * +from spaceone.identity.model.service_account_response import * -from spaceone.core.service import * -from spaceone.identity.manager.service_account_manager import ServiceAccountManager -from spaceone.identity.manager.project_manager import ProjectManager -from spaceone.identity.manager.provider_manager import ProviderManager -from spaceone.identity.model.service_account_model import ServiceAccount -from spaceone.identity.error.error_service_account import * +_LOGGER = logging.getLogger(__name__) -@authentication_handler -@authorization_handler -@mutation_handler -@event_handler class ServiceAccountService(BaseService): - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.service_account_mgr: ServiceAccountManager = self.locator.get_manager('ServiceAccountManager') - - @transaction(append_meta={ - 'authorization.scope': 'DOMAIN_OR_PROJECT', - 'authorization.require_project_id': True - }) - @check_required(['name', 'data', 'provider', 'service_account_type', 'domain_id']) - def create(self, params): - """ - Args: - params (dict): { - 'name': 'str', - 'service_account_type': 'str', - 'data': 'dict', - 'provider': 'str', - 'trusted_service_account_id': 'str', - 'project_id': 'str', - 'tags': 'dict', - 'domain_id': 'str' - } - - Returns: - service_account_vo (object) - """ - domain_id = params['domain_id'] - service_account_type = params['service_account_type'] - - if 'project_id' in params and params['service_account_type'] == 'TRUSTED': - raise ERROR_INVALID_PARAMETER(key='project_id', - reason='Trusted service account cannot configure a project.') - - if service_account_type == 'TRUSTED': - params.update({ - 'trusted_service_account_id': None, - 'scope': 'DOMAIN' - }) - elif service_account_type == 'GENERAL': - params['scope'] = 'PROJECT' - - if trusted_service_account_id := params.get('trusted_service_account_id'): - self._validation_trusted_service_account_check(trusted_service_account_id, domain_id) - else: - raise ERROR_INVALID_PARAMETER(key='service_account_type', reason=f'{service_account_type}') - - self._check_data(params['data'], params['provider'], domain_id) - - if 'project_id' in params: - params['project'] = self._get_project(params['project_id'], params['domain_id']) - - return self.service_account_mgr.create_service_account(params) - - @transaction(append_meta={'authorization.scope': 'DOMAIN_OR_PROJECT'}) - @check_required(['service_account_id', 'domain_id']) - def update(self, params): - """ - Args: - params (dict): { - 'service_account_id': 'str', - 'name': 'str', - 'data': 'dict', - 'project_id': 'str', - 'tags': 'dict', - 'release_service_account': 'bool', - 'domain_id': 'str' - } - - Returns: - service_account_vo (object) - """ - - service_account_id = params['service_account_id'] - domain_id = params['domain_id'] - project_id = params.get('project_id') - - service_account_vo: ServiceAccount = self.service_account_mgr.get_service_account(service_account_id, domain_id) - - if 'data' in params: - self._check_data(params['data'], service_account_vo.provider, domain_id) - - if project_id: - if service_account_vo.service_account_type == 'TRUSTED': - raise ERROR_INVALID_PARAMETER(key='project_id', - reason='Trusted service account cannot configure a project.') - - params['project'] = self._get_project(params['project_id'], params['domain_id']) - - if 'trusted_service_account_id' in params: - self._validation_trusted_service_account_check(params['trusted_service_account_id'], domain_id) - - if params.get('release_trusted_service_account') is True: - params['trusted_service_account_id'] = None - - service_account_vo = self.service_account_mgr.update_service_account_by_vo(params, service_account_vo) - - if project_id: - self.service_account_mgr.update_secret_project(service_account_id, project_id, domain_id) - - return service_account_vo - - @transaction(append_meta={'authorization.scope': 'DOMAIN_OR_PROJECT'}) - @check_required(['service_account_id', 'domain_id']) - def delete(self, params): - """ - Args: - params (dict): { - 'service_account_id': 'str', - 'domain_id': 'str' - } - - Returns: - None - """ - - service_account_id = params['service_account_id'] - domain_id = params['domain_id'] - service_account_vo: ServiceAccount = self.service_account_mgr.get_service_account(service_account_id, domain_id) - - # self.service_account_mgr.check_service_account_secrets(service_account_id, domain_id, - # service_account_vo.service_account_type) - self.service_account_mgr.delete_service_account_secrets(service_account_id, domain_id, - service_account_vo.service_account_type) - - service_account_vo.delete() - - @transaction(append_meta={'authorization.scope': 'DOMAIN_OR_PROJECT'}) - @check_required(['service_account_id', 'domain_id']) - @change_only_key({'project_info': 'project'}) - def get(self, params): - """ - Args: - params (dict): { - 'service_account_id': 'str', - 'domain_id': 'str', - 'only': 'list' - } - - Returns: - service_account_vo (object) - """ - - return self.service_account_mgr.get_service_account(params['service_account_id'], params['domain_id'], - params.get('only')) - - @transaction(append_meta={'authorization.scope': 'DOMAIN_OR_PROJECT'}) - @check_required(['domain_id']) - @change_only_key({'project_info': 'project'}, key_path='query.only') - @append_query_filter(['service_account_id', 'service_account_type', 'trusted_service_account_id', - 'name', 'provider', 'scope', 'project_id', 'domain_id', 'user_projects']) - @append_keyword_filter(['service_account_id', 'name', 'provider']) - def list(self, params): - """ - Args: - params (dict): { - 'service_account_id': 'str', - 'service_account_type': 'str', - 'trusted_service_account_id': 'str', - 'name': 'str', - 'scope': 'str', - 'provider': 'str', - 'project_id': 'str', - 'has_secret': 'bool', - 'domain_id': 'str', - 'query': 'dict (spaceone.api.core.v1.Query)', - 'user_projects': 'list', // from meta - } - - Returns: - results (list): 'list of service_account_vo' - total_count (int) - """ - - query = params.get('query', {}) - - has_secret = params.get('has_secret', False) - if has_secret: - query = self._append_secret_filter(query, params['domain_id']) - - service_account_vos, total_count = self.service_account_mgr.list_service_accounts(query) - - return service_account_vos, total_count, self._get_project_info(service_account_vos) - - @transaction(append_meta={'authorization.scope': 'DOMAIN_OR_PROJECT'}) - @check_required(['query', 'domain_id']) - @append_query_filter(['project_id', 'domain_id', 'user_projects']) - @append_keyword_filter(['service_account_id', 'name', 'provider']) - def stat(self, params): - """ - Args: - params (dict): { - 'domain_id': 'str', - 'has_secret': 'bool', - 'query': 'dict (spaceone.api.core.v1.StatisticsQuery)', - 'user_projects': 'list', // from meta - } - - Returns: - values (list): 'list of statistics data' - total_count (int) - """ - - query = params.get('query', {}) - - has_secret = params.get('has_secret', False) - if has_secret: - query = self._append_secret_filter(query, params['domain_id']) - - return self.service_account_mgr.stat_service_accounts(query) - - def _check_data(self, data, provider, domain_id): - provider_mgr: ProviderManager = self.locator.get_manager('ProviderManager') - provider_vo = provider_mgr.get_provider(provider, domain_id) - schema = provider_vo.template.get('service_account', {}).get('schema') - - if schema: - try: - validate(instance=data, schema=schema) - except Exception as e: - raise ERROR_INVALID_PARAMETER(key='data', reason=e.message) - else: - if data != {}: - raise ERROR_INVALID_PARAMETER(key='data', reason='data format is invalid.') - - def _validation_trusted_service_account_check(self, trusted_service_account_id, domain_id): - query = { - 'filter': [ - {'k': 'service_account_id', 'v': trusted_service_account_id, 'o': 'eq'}, - {'k': 'service_account_type', 'v': 'TRUSTED', 'o': 'eq'}, - {'k': 'domain_id', 'v': domain_id, 'o': 'eq'}, - ] - } - results, total_count = self.service_account_mgr.list_service_accounts(query) - if total_count == 0: - raise ERROR_NOT_FOUND_TRUSTED_SERVICE_ACCOUNT_ID(trusted_service_account_id=trusted_service_account_id) - - def _get_project(self, project_id, domain_id): - project_mgr: ProjectManager = self.locator.get_manager('ProjectManager') - return project_mgr.get_project(project_id, domain_id) - - def _get_project_info(self, service_account_vos): - project_mgr: ProjectManager = self.locator.get_manager('ProjectManager') - - projects_info = {} - project_ids = [] - for service_account_vo in service_account_vos: - if service_account_vo.project_id: - project_ids.append(service_account_vo.project_id) - - project_vos = project_mgr.filter_projects(project_id=project_ids) - for project_vo in project_vos: - projects_info[project_vo.project_id] = project_vo - - return projects_info - - def _append_secret_filter(self, query, domain_id): - service_account_ids = self.service_account_mgr.get_all_service_account_ids_using_secret(domain_id) - query['filter'] = query.get('filter', []) - query['filter'].append({ - 'k': 'service_account_id', - 'v': service_account_ids, - 'o': 'in' - }) - - return query + @transaction + @convert_model + def create( + self, params: ServiceAccountCreateRequest + ) -> Union[ServiceAccountResponse, dict]: + return {} + + @transaction + @convert_model + def update( + self, params: ServiceAccountUpdateRequest + ) -> Union[ServiceAccountResponse, dict]: + return {} + + @transaction + @convert_model + def change_trusted_service_account( + self, params: ServiceAccountChangeTrustedServiceAccountRequest + ) -> Union[ServiceAccountResponse, dict]: + return {} + + @transaction + @convert_model + def change_project( + self, params: ServiceAccountChangeProjectRequest + ) -> Union[ServiceAccountResponse, dict]: + return {} + + @transaction + @convert_model + def delete(self, params: ServiceAccountDeleteRequest) -> None: + pass + + @transaction + @convert_model + def get( + self, params: ServiceAccountGetRequest + ) -> Union[ServiceAccountResponse, dict]: + return {} + + @transaction + @convert_model + def list( + self, params: ServiceAccountSearchQueryRequest + ) -> Union[ServiceAccountsResponse, dict]: + return {} + + @transaction + @convert_model + def stat(self, params: ServiceAccountStatQueryRequest) -> dict: + return {} diff --git a/src/spaceone/identity/service/token_service.py b/src/spaceone/identity/service/token_service.py index 19b855cc..c48343c8 100644 --- a/src/spaceone/identity/service/token_service.py +++ b/src/spaceone/identity/service/token_service.py @@ -1,178 +1,21 @@ import logging +from typing import Union +from spaceone.core.service import BaseService, transaction, convert_model +from spaceone.identity.model.token_request import * +from spaceone.identity.model.token_response import * -from spaceone.core import cache -from spaceone.core.auth.jwt import JWTAuthenticator, JWTUtil -from spaceone.core.service import * -from spaceone.identity.error.error_authentication import * -from spaceone.identity.error.error_domain import * -from spaceone.identity.error.error_mfa import ERROR_MFA_REQUIRED -from spaceone.identity.manager import DomainManager, DomainSecretManager, UserManager -from spaceone.identity.manager.mfa_manager import MFAManager -from spaceone.identity.model import User, Domain _LOGGER = logging.getLogger(__name__) -@event_handler class TokenService(BaseService): - - def __init__(self, metadata): - super().__init__(metadata) - self.user_mgr: UserManager = self.locator.get_manager('UserManager') - self.domain_mgr: DomainManager = self.locator.get_manager('DomainManager') - self.domain_secret_mgr: DomainSecretManager = self.locator.get_manager('DomainSecretManager') - @transaction - @check_required(['credentials', 'domain_id']) - def issue(self, params): - """ Issue token - - Args: - params (dict): { - 'user_id': 'str', - 'credentials': 'dict' - 'user_type': 'str', - 'timeout': 'int', - 'refresh_count': 'int', - 'verify_code': 'str', - 'domain_id': 'str' - } - - Returns: - result (dict): { - 'access_token': 'str', - 'refresh_token': 'str' - } - """ - - user_id = params.get('user_id') - user_type = params.get('user_type', 'USER') - domain_id = params['domain_id'] - timeout = params.get('timeout') - refresh_count = params.get('refresh_count') - verify_code = params.get('verify_code') - - private_jwk = self.domain_secret_mgr.get_domain_private_key(domain_id=domain_id) - refresh_private_jwk = self.domain_secret_mgr.get_domain_refresh_private_key(domain_id=domain_id) - - token_manager = self._get_token_manager(user_id, user_type, domain_id) - token_manager.authenticate(user_id, domain_id, params['credentials']) - - user_vo = token_manager.user - user_mfa = user_vo.mfa.to_dict() if user_vo.mfa else {} - - if user_mfa.get('state', 'DISABLED') == 'ENABLED': - if verify_code: - token_manager.check_mfa_verify_code(user_id, domain_id, verify_code) - else: - mfa_email = user_mfa['options'].get('email') - mfa_manager = MFAManager.get_manager_by_mfa_type(user_mfa.get('mfa_type')) - mfa_manager.send_mfa_authentication_email(user_id, domain_id, mfa_email, user_vo.language) - raise ERROR_MFA_REQUIRED(user_id=user_id) - - token_info = token_manager.issue_token(private_jwk=private_jwk, refresh_private_jwk=refresh_private_jwk, - timeout=timeout, ttl=refresh_count) - - return token_info + @convert_model + def issue(self, params: TokenIssueRequest) -> Union[TokenResponse, dict]: + return {} @transaction - def refresh(self, params): - """ Refresh token - - Args: - params (dict): {} - - Returns: - result (dict): { - 'access_token': 'str', - 'refresh_token': 'str' - } - """ - - refresh_token = self.transaction.get_meta('token') - - if refresh_token is None: - raise ERROR_INVALID_REFRESH_TOKEN() - - domain_id = self._extract_domain_id(refresh_token) - - private_jwk = self.domain_secret_mgr.get_domain_private_key(domain_id=domain_id) - refresh_public_jwk = self.domain_secret_mgr.get_domain_refresh_public_key(domain_id=domain_id) - refresh_private_jwk = self.domain_secret_mgr.get_domain_refresh_private_key(domain_id=domain_id) - - token_info = self._verify_refresh_token(refresh_token, refresh_public_jwk) - token_mgr = self._get_token_manager(token_info['user_id'], token_info['user_type'], domain_id) - token_mgr.check_refreshable(token_info['key'], token_info['ttl']) - - return token_mgr.refresh_token(token_info['user_id'], domain_id, ttl=token_info['ttl']-1, - private_jwk=private_jwk, refresh_private_jwk=refresh_private_jwk) - - def _get_token_manager(self, user_id, user_type, domain_id): - self._check_domain_state(domain_id) - - # user_type = USER | DOMAIN_OWNER - if user_type == 'DOMAIN_OWNER': - return self.locator.get_manager('DomainOwnerTokenManager') - - if user_id: - user_backend = self._get_user_backend(user_id, domain_id) - - if user_backend == 'LOCAL': - return self.locator.get_manager('LocalTokenManager') - else: - return self.locator.get_manager('ExternalTokenManager') - else: - return self.locator.get_manager('ExternalTokenManager') - - @cache.cacheable(key='user-backend:{domain_id}:{user_id}', expire=600) - def _get_user_backend(self, user_id, domain_id): - try: - user_vo: User = self.user_mgr.get_user(user_id, domain_id) - except Exception as e: - _LOGGER.error(f'[_get_user_backend] Authentication failure: {getattr(e, "message", e)}') - raise ERROR_AUTHENTICATION_FAILURE(user_id=user_id) - - return user_vo.backend - - @cache.cacheable(key='domain-state:{domain_id}', expire=3600) - def _check_domain_state(self, domain_id): - domain_vo: Domain = self.domain_mgr.get_domain(domain_id) - - if domain_vo.state != 'ENABLED': - raise ERROR_DOMAIN_STATE(domain_id=domain_vo.domain_id) - - @staticmethod - def _extract_domain_id(token): - try: - decoded = JWTUtil.unverified_decode(token) - except Exception as e: - _LOGGER.error(f'[_extract_domain_id] {e}') - _LOGGER.error(token) - raise ERROR_AUTHENTICATE_FAILURE(message='Cannot decode token.') - - domain_id = decoded.get('did') - - if domain_id is None: - raise ERROR_AUTHENTICATE_FAILURE(message='Empty domain_id provided.') - - return domain_id - - @staticmethod - def _verify_refresh_token(token, public_jwk): - try: - decoded = JWTAuthenticator(public_jwk).validate(token) - except Exception as e: - _LOGGER.error(f'[_verify_refresh_token] {e}') - raise ERROR_AUTHENTICATE_FAILURE(message='Token validation failed.') - - if decoded.get('cat') != 'REFRESH_TOKEN': - raise ERROR_INVALID_REFRESH_TOKEN() + @convert_model + def refresh(self, params: dict) -> Union[TokenResponse, dict]: + return {} - return { - 'user_id': decoded['aud'], - 'user_type': decoded['user_type'], - 'key': decoded['key'], - 'ttl': decoded['ttl'], - 'iat': decoded['iat'], - 'exp': decoded['exp'] - } diff --git a/src/spaceone/identity/service/trusted_service_account_service.py b/src/spaceone/identity/service/trusted_service_account_service.py new file mode 100644 index 00000000..e36c2b39 --- /dev/null +++ b/src/spaceone/identity/service/trusted_service_account_service.py @@ -0,0 +1,47 @@ +import logging +from typing import Union +from spaceone.core.service import BaseService, transaction, convert_model +from spaceone.identity.model.trusted_service_account_request import * +from spaceone.identity.model.trusted_service_account_response import * + +_LOGGER = logging.getLogger(__name__) + + +class TrustedServiceAccountService(BaseService): + @transaction + @convert_model + def create( + self, params: TrustedServiceAccountCreateRequest + ) -> Union[TrustedServiceAccountResponse, dict]: + return {} + + @transaction + @convert_model + def update( + self, params: TrustedServiceAccountUpdateRequest + ) -> Union[TrustedServiceAccountResponse, dict]: + return {} + + @transaction + @convert_model + def delete(self, params: TrustedServiceAccountDeleteRequest) -> None: + pass + + @transaction + @convert_model + def get( + self, params: TrustedServiceAccountGetRequest + ) -> Union[TrustedServiceAccountResponse, dict]: + return {} + + @transaction + @convert_model + def list( + self, params: TrustedServiceAccountSearchQueryRequest + ) -> Union[TrustedServiceAccountsResponse, dict]: + return {} + + @transaction + @convert_model + def stat(self, params: TrustedServiceAccountStatQueryRequest) -> dict: + return {} diff --git a/src/spaceone/identity/service/user_group_service.py b/src/spaceone/identity/service/user_group_service.py new file mode 100644 index 00000000..1b7813cb --- /dev/null +++ b/src/spaceone/identity/service/user_group_service.py @@ -0,0 +1,55 @@ +import logging +from typing import Union +from spaceone.core.service import BaseService, transaction, convert_model +from spaceone.identity.model.user_group_request import * +from spaceone.identity.model.user_group_response import * + +_LOGGER = logging.getLogger(__name__) + + +class UserGroupService(BaseService): + @transaction + @convert_model + def create(self, params: UserGroupCreateRequest) -> Union[UserGroupResponse, dict]: + return {} + + @transaction + @convert_model + def update(self, params: UserGroupUpdateRequest) -> Union[UserGroupResponse, dict]: + return {} + + @transaction + @convert_model + def delete(self, params: UserGroupDeleteRequest) -> None: + pass + + @transaction + @convert_model + def add_users( + self, params: UserGroupAddUsersRequest + ) -> Union[UserGroupResponse, dict]: + return {} + + @transaction + @convert_model + def remove_users( + self, params: UserGroupRemoveUsersRequest + ) -> Union[UserGroupResponse, dict]: + return {} + + @transaction + @convert_model + def get(self, params: UserGroupGetRequest) -> Union[UserGroupResponse, dict]: + return {} + + @transaction + @convert_model + def list( + self, params: UserGroupSearchQueryRequest + ) -> Union[UserGroupsResponse, dict]: + return {} + + @transaction + @convert_model + def stat(self, params: UserGroupStatQueryRequest) -> dict: + return {} diff --git a/src/spaceone/identity/service/user_service.py b/src/spaceone/identity/service/user_service.py index 8c3ab63c..645f5df5 100644 --- a/src/spaceone/identity/service/user_service.py +++ b/src/spaceone/identity/service/user_service.py @@ -1,630 +1,88 @@ -import pytz -import random -import string -import re -from spaceone.core.service import * -from spaceone.core import config, utils -from spaceone.identity.error.error_user import * -from spaceone.identity.error.error_mfa import * -from spaceone.identity.model import Domain, User -from spaceone.identity.manager import UserManager, DomainManager, DomainSecretManager, LocalTokenManager, EmailManager -from spaceone.identity.manager.mfa_manager import MFAManager +import logging +from typing import Union +from spaceone.core.service import BaseService, transaction, convert_model +from spaceone.identity.model.user_request import * +from spaceone.identity.model.user_response import * +_LOGGER = logging.getLogger(__name__) -@authentication_handler(exclude=['reset_password']) -@authorization_handler(exclude=['reset_password']) -@mutation_handler(exclude=['reset_password']) -@event_handler -class UserService(BaseService): - - def __init__(self, metadata): - super().__init__(metadata) - self.user_mgr: UserManager = self.locator.get_manager('UserManager') - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['user_id', 'domain_id']) - def create(self, params): - """ Create user - - Args: - params (dict): { - 'user_id': 'str', - 'password': 'str', - 'name': 'str', - 'email': 'str', - 'reset_password': 'bool', - 'user_type': 'str', - 'backend': 'str', - 'language': 'str', - 'timezone': 'str', - 'tags': 'dict', - 'domain_id': 'str' - } - - Returns: - user_vo (object) - """ - - params['user_type'] = params.get('user_type', 'USER') - params['backend'] = params.get('backend', 'LOCAL') - domain_id = params['domain_id'] - user_id = params['user_id'] - email = params.get('email') - reset_password = params.get('reset_password', False) - - domain_mgr: DomainManager = self.locator.get_manager('DomainManager') - domain_vo: Domain = domain_mgr.get_domain(domain_id) - - default_language = self._get_default_config(domain_vo, 'LANGUAGE') - default_timezone = self._get_default_config(domain_vo, 'TIMEZONE') - - self._check_user_type_and_backend(params['user_type'], params['backend'], domain_vo) - - if 'language' not in params: - params['language'] = default_language - - if 'timezone' not in params: - params['timezone'] = default_timezone - - if 'timezone' in params: - self._check_timezone(params['timezone']) - - if reset_password: - self._check_reset_password_eligibility(user_id, params['backend'], email) - - email_manager: EmailManager = self.locator.get_manager('EmailManager') - language = params['language'] - params['required_actions'] = ['UPDATE_PASSWORD'] - params['password'] = self._generate_temporary_password() - - reset_password_type = config.get_global('RESET_PASSWORD_TYPE') - if reset_password_type == 'ACCESS_TOKEN': - token = self._issue_temporary_token(user_id, domain_id) - reset_password_link = self._get_console_sso_url(domain_id, token['access_token']) - - user_vo = self.user_mgr.create_user(params, domain_vo) - email_manager.send_reset_password_email_when_user_added(user_id, email, reset_password_link, language) - else: - temp_password = params['password'] - console_link = self._get_console_url(domain_id) - - user_vo = self.user_mgr.create_user(params, domain_vo) - email_manager.send_temporary_password_email_when_user_added(user_id, email, console_link, temp_password, - language) - else: - user_vo = self.user_mgr.create_user(params, domain_vo) - - return user_vo - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['user_id', 'domain_id']) - def update(self, params): - """ Update user - - Args: - params (dict): { - 'user_id': 'str', - 'password': 'str', - 'name': 'str', - 'email': 'str', - 'reset_password' : 'bool', - 'language': 'str', - 'timezone': 'str', - 'tags': 'dict', - 'domain_id': 'str' - } - - Returns: - user_vo (object) - """ - - if 'timezone' in params: - self._check_timezone(params['timezone']) - - user_vo = self.user_mgr.get_user(params['user_id'], params['domain_id']) - - if params.get('reset_password'): - domain_id = params['domain_id'] - user_id = user_vo.user_id - backend = user_vo.backend - email = params.get('email', user_vo.email) - email_verified = user_vo.email_verified - - language = user_vo.language - - self._check_reset_password_eligibility(user_id, backend, email) - - if email_verified is False: - raise ERROR_VERIFICATION_UNAVAILABLE(user_id=user_id) - - reset_password_type = config.get_global('RESET_PASSWORD_TYPE') - email_manager: EmailManager = self.locator.get_manager('EmailManager') - temp_password = self._generate_temporary_password() - params['password'] = temp_password - - user_vo = self.user_mgr.update_user_by_vo(params, user_vo) - user_vo = self.user_mgr.update_user_by_vo({'required_actions': ['UPDATE_PASSWORD']}, user_vo) - - if reset_password_type == 'ACCESS_TOKEN': - token = self._issue_temporary_token(user_id, domain_id) - reset_password_link = self._get_console_sso_url(domain_id, token['access_token']) - - email_manager.send_reset_password_email(user_id, email, reset_password_link, language) - elif reset_password_type == 'PASSWORD': - console_link = self._get_console_url(domain_id) - - email_manager.send_temporary_password_email(user_id, email, console_link, temp_password, language) - else: - user_vo = self.user_mgr.update_user_by_vo(params, user_vo) - - return user_vo - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['user_id', 'domain_id']) - def verify_email(self, params): - """ Verify email - - Args: - params (dict): { - 'user_id': 'str', - 'email': 'str', - 'force': 'bool', - 'domain_id': 'str' - } - - - Returns: - UserInfo - """ - user_id = params['user_id'] - domain_id = params['domain_id'] - - user_vo = self.user_mgr.get_user(user_id, domain_id) - email = params.get('email', user_vo.email) - force = params.get('force', False) - - if force: - params['email_verified'] = True - user_vo = self.user_mgr.update_user(params) - else: - params['email_verified'] = False - user_vo = self.user_mgr.update_user(params) - - token_manager: LocalTokenManager = self.locator.get_manager('LocalTokenManager') - verify_code = token_manager.create_verify_code(user_id, domain_id) - - email_manager: EmailManager = self.locator.get_manager('EmailManager') - email_manager.send_verification_email(user_id, email, verify_code, user_vo.language) - - return user_vo - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['user_id', 'verify_code', 'domain_id']) - def confirm_email(self, params): - """ Confirm email - - Args: - params (dict): { - 'user_id': 'str', - 'verify_code': 'str', - 'domain_id': 'str' - } - - - Returns: - None - """ - - user_id = params['user_id'] - domain_id = params['domain_id'] - verify_code = params['verify_code'] - - token_manager: LocalTokenManager = self.locator.get_manager('LocalTokenManager') - - if token_manager.check_verify_code(user_id, domain_id, verify_code): - params['email_verified'] = True - return self.user_mgr.update_user(params) - else: - raise ERROR_INVALID_VERIFY_CODE(verify_code=verify_code) +class UserService(BaseService): @transaction - @check_required(['user_id', 'domain_id']) - def reset_password(self, params): - """ Reset password - - Args: - params (dict): { - 'user_id': 'str', - 'domain_id': 'str' - } - - Returns: - None - """ - - user_vo: User = self.user_mgr.get_user(params['user_id'], params['domain_id']) - user_id = params['user_id'] - domain_id = params['domain_id'] - backend = user_vo.backend - email = user_vo.email - language = user_vo.language - - self._check_reset_password_eligibility(user_id, backend, email) - - if user_vo.email_verified is False: - raise ERROR_VERIFICATION_UNAVAILABLE(user_id=user_id) - - reset_password_type = config.get_global('RESET_PASSWORD_TYPE', 'ACCESS_TOKEN') - email_manager: EmailManager = self.locator.get_manager('EmailManager') - if reset_password_type == 'ACCESS_TOKEN': - token = self._issue_temporary_token(user_id, domain_id) - reset_password_link = self._get_console_sso_url(domain_id, token['access_token']) - email_manager.send_reset_password_email(user_id, email, reset_password_link, language) - - elif reset_password_type == 'PASSWORD': - temp_password = self._generate_temporary_password() - self.user_mgr.update_user_by_vo({'password': temp_password}, user_vo) - self.user_mgr.update_user_by_vo({'required_actions': ['UPDATE_PASSWORD']}, user_vo) - console_link = self._get_console_url(domain_id) - email_manager.send_temporary_password_email(user_id, email, console_link, temp_password, language) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['user_id', 'actions', 'domain_id']) - def set_required_actions(self, params): - """ Update user - - Args: - params (dict): { - 'user_id': 'str', - 'actions': 'list', - 'domain_id': 'str' - } - - Returns: - user_vo (object) - """ - - new_actions = params['actions'] - - user_vo: User = self.user_mgr.get_user(params['user_id'], params['domain_id']) - - if 'UPDATE_PASSWORD' in new_actions: - if user_vo.backend == 'EXTERNAL' or user_vo.user_type == 'API_USER': - raise ERROR_NOT_ALLOWED_ACTIONS(action='UPDATE_PASSWORD') - - required_actions = list(set(user_vo.required_actions + new_actions)) - - return self.user_mgr.update_user_by_vo({'required_actions': required_actions}, user_vo) - - @transaction(append_meta={'authorization.scope': 'USER'}) - @check_required(['user_id', 'mfa_type', 'options', 'domain_id']) - def enable_mfa(self, params): - """ Enable MFA + @convert_model + def create(self, params: UserCreateRequest) -> Union[UserResponse, dict]: + return {} - Args: - params (dict): { - 'user_id': 'str', - 'mfa_type': 'dict', - 'options': 'dict' - 'domain_id': 'str' - } - - Returns: - user_vo (object) - """ - - user_id = params['user_id'] - mfa_type = params['mfa_type'] - options = params['options'] - domain_id = params['domain_id'] - - user_vo = self.user_mgr.get_user(user_id, domain_id) - user_mfa = user_vo.mfa.to_dict() if user_vo.mfa else {} - - if not options: - raise ERROR_REQUIRED_PARAMETER(key='options') - - if user_mfa.get('state', 'DISABLED') == 'ENABLED': - raise ERROR_MFA_ALREADY_ENABLED(user_id=user_id) - - mfa_manager = MFAManager.get_manager_by_mfa_type(mfa_type) - - if mfa_type == 'EMAIL': - user_mfa['mfa_type'] = mfa_type - user_mfa['options'] = options - user_mfa['state'] = user_mfa.get('state', 'DISABLED') - mfa_manager.enable_mfa(user_id, domain_id, user_mfa, user_vo.language) - user_vo = self.user_mgr.update_user_by_vo({'mfa': user_mfa}, user_vo) - else: - raise ERROR_NOT_SUPPORTED_MFA_TYPE(support_mfa_types=['EMAIL']) - - return user_vo - - @transaction(append_meta={'authorization.scope': 'USER'}) - @check_required(['user_id', 'domain_id']) - def disable_mfa(self, params): - """ Disable MFA - Args: - params (dict): { - 'user_id': 'str', - 'force': 'bool', - 'domain_id': 'str' - return Empty - """ - user_id = params['user_id'] - domain_id = params['domain_id'] - force = params.get('force', False) - - user_vo = self.user_mgr.get_user(user_id, domain_id) - user_mfa = user_vo.mfa.to_dict() if user_vo.mfa else {} - mfa_type = user_mfa.get('mfa_type') - - if user_mfa.get('state', 'DISABLED') == 'DISABLED' or mfa_type is None: - raise ERROR_MFA_ALREADY_DISABLED(user_id=user_id) - - mfa_manager = MFAManager.get_manager_by_mfa_type(mfa_type) - - if force: - user_mfa = {'state': 'DISABLED'} - self.user_mgr.update_user_by_vo({'mfa': user_mfa}, user_vo) - elif mfa_type == 'EMAIL': - mfa_manager.disable_mfa(user_id, domain_id, user_mfa, user_vo.language) - - @transaction(append_meta={'authorization.scope': 'USER'}) - @check_required(['user_id', 'verify_code', 'domain_id']) - def confirm_mfa(self, params): - """ Confirm MFA - Args: - params (dict): { - 'user_id': 'str', - 'verify_code': 'str', - 'domain_id': 'str' - return Empty - """ - - user_id = params['user_id'] - domain_id = params['domain_id'] - verify_code = params['verify_code'] - - user_vo = self.user_mgr.get_user(user_id, domain_id) - mfa_type = user_vo.mfa.mfa_type - - if not mfa_type: - raise ERROR_MFA_NOT_ENABLED(user_id=user_id) - - mfa_manager = MFAManager.get_manager_by_mfa_type(mfa_type) - - if mfa_type == 'EMAIL': - if mfa_manager.confirm_mfa(user_id, domain_id, verify_code): - user_mfa = user_vo.mfa.to_dict() if user_vo.mfa else {} - if user_mfa.get('state', 'DISABLED') == 'ENABLED': - user_mfa = {'state': 'DISABLED'} - elif user_mfa.get('state', 'DISABLED') == 'DISABLED': - user_mfa['state'] = 'ENABLED' - user_vo = self.user_mgr.update_user_by_vo({'mfa': user_mfa}, user_vo) - else: - raise ERROR_INVALID_VERIFY_CODE(verify_code=verify_code) - - return user_vo - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['user_id', 'domain_id']) - def enable(self, params): - """ Enable user - - Args: - params (dict): { - 'user_id': 'str', - 'domain_id': 'str' - } - - Returns: - user_vo (object) - """ - - return self.user_mgr.enable_user(params['user_id'], params['domain_id']) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['user_id', 'domain_id']) - def disable(self, params): - """ Disable user - - Args: - params (dict): { - 'user_id': 'str', - 'domain_id': 'str' - } - - Returns: - user_vo (object) - """ - - return self.user_mgr.disable_user(params['user_id'], params['domain_id']) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['user_id', 'domain_id']) - def delete(self, params): - """ Delete user - - Args: - params (dict): { - 'user_id': 'str', - 'domain_id': 'str' - } - - Returns: - None - """ - - return self.user_mgr.delete_user(params['user_id'], params['domain_id']) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['search', 'domain_id']) - def find(self, params): - """ Disable user - - Args: - params (dict): { - 'search' (one of): { - 'user_id': 'str', - 'keyword': 'str' - }, - 'domain_id': 'str' - } - - Returns: - results(list) : 'list of { - 'user_id': 'str', - 'name': 'str', - 'email': 'str', - 'tags': 'list' - }' - """ - - if not any(k in params['search'] for k in ['keyword', 'user_id']): - raise ERROR_REQUIRED_PARAMETER(key='search.keyword | search.user_id') - - domain_mgr: DomainManager = self.locator.get_manager('DomainManager') - domain_vo: Domain = domain_mgr.get_domain(params['domain_id']) - - # Check External Authentication from Domain - if not domain_vo.plugin_info: - raise ERROR_NOT_ALLOWED_EXTERNAL_AUTHENTICATION() - - return self.user_mgr.find_user(params['search'], domain_vo) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['user_id', 'domain_id']) - def get(self, params): - """ Get user - - Args: - params (dict): { - 'user_id': 'str', - 'domain_id': 'str', - 'only': 'list' - } - - Returns: - user_vo (object) - """ - - return self.user_mgr.get_user(params['user_id'], params['domain_id'], params.get('only')) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['domain_id']) - @append_query_filter(['user_id', 'name', 'state', 'email', 'user_type', 'backend', 'role_id', 'domain_id']) - @append_keyword_filter(['user_id', 'name', 'email']) - def list(self, params): - """ List users - - Args: - params (dict): { - 'user_id': 'str', - 'name': 'str', - 'state': 'str', - 'email': 'str', - 'user_type': 'str', - 'backend': 'str', - 'domain_id': 'str', - 'query': 'dict (spaceone.api.core.v1.Query)' - } - - Returns: - results (list): 'list of user_vo' - total_count (int) - """ - - query: dict = params.get('query', {}) - return self.user_mgr.list_users(query) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['query', 'domain_id']) - @append_query_filter(['domain_id']) - @append_keyword_filter(['user_id', 'name', 'email']) - def stat(self, params): - """ - Args: - params (dict): { - 'domain_id': 'str', - 'query': 'dict (spaceone.api.core.v1.StatisticsQuery)' - } - - Returns: - values (list): 'list of statistics data' - total_count (int) - """ - - query = params.get('query', {}) - return self.user_mgr.stat_users(query) - - def _get_domain_name(self, domain_id): - domain_mgr: DomainManager = self.locator.get_manager('DomainManager') - domain_vo: Domain = domain_mgr.get_domain(domain_id) - return domain_vo.name - - def _issue_temporary_token(self, user_id, domain_id): - identity_conf = config.get_global('identity') or {} - timeout = identity_conf.get('temporary_token_timeout', 86400) + @transaction + @convert_model + def update(self, params: UserUpdateRequest) -> Union[UserResponse, dict]: + return {} - domain_secret_mgr: DomainSecretManager = self.locator.get_manager('DomainSecretManager') - private_jwk = domain_secret_mgr.get_domain_private_key(domain_id=domain_id) + @transaction + @convert_model + def verify_email(self, params: UserVerifyEmailRequest) -> None: + pass - local_token_manager: LocalTokenManager = self.locator.get_manager('LocalTokenManager') - return local_token_manager.issue_temporary_token(user_id, domain_id, private_jwk=private_jwk, timeout=timeout) + @transaction + @convert_model + def confirm_email( + self, params: UserConfirmEmailRequest + ) -> Union[UserResponse, dict]: + return {} - def _get_console_sso_url(self, domain_id, token): - domain_name = self._get_domain_name(domain_id) + @transaction + @convert_model + def reset_password(self, params: UserResetPasswordRequest) -> None: + pass - console_domain = config.get_global('EMAIL_CONSOLE_DOMAIN') - console_domain = console_domain.format(domain_name=domain_name) + @transaction + @convert_model + def set_required_actions( + self, params: UserSetRequiredActionsRequest + ) -> Union[UserResponse, dict]: + return {} - return f'{console_domain}?sso_access_token={token}' + @transaction + @convert_model + def enable_mfa(self, params: UserEnableMFARequest) -> Union[UserResponse, dict]: + return {} - def _get_console_url(self, domain_id): - domain_name = self._get_domain_name(domain_id) + @transaction + @convert_model + def disable_mfa(self, params: UserDisableMFARequest) -> Union[UserResponse, dict]: + return {} - console_domain = config.get_global('EMAIL_CONSOLE_DOMAIN') - return console_domain.format(domain_name=domain_name) + @transaction + @convert_model + def confirm_mfa(self, params: UserConfirmMFARequest) -> Union[UserResponse, dict]: + return {} - @staticmethod - def _get_default_config(vo, item): - DEFAULT = { - 'TIMEZONE': 'UTC', - 'LANGUAGE': 'en' - } - dict_domain = vo.to_dict() - config = dict_domain.get('config', {}) - value = config.get(item, DEFAULT.get(item, None)) - return value + @transaction + @convert_model + def delete(self, params: UserDeleteRequest) -> None: + pass - @staticmethod - def _check_timezone(timezone): - if timezone not in pytz.all_timezones: - raise ERROR_INVALID_PARAMETER(key='timezone', reason='Timezone is invalid.') + @transaction + @convert_model + def enable(self, params: UserEnableRequest) -> Union[UserResponse, dict]: + return {} - @staticmethod - def _check_user_type_and_backend(user_type, backend, domain_vo): - # Check User Type and Backend - if user_type == 'API_USER': - if backend == 'EXTERNAL': - raise ERROR_EXTERNAL_USER_NOT_ALLOWED_API_USER() + @transaction + @convert_model + def disable(self, params: UserDisableRequest) -> Union[UserResponse, dict]: + return {} - # Check External Authentication from Domain - if backend == 'EXTERNAL': - if not domain_vo.plugin_info: - raise ERROR_NOT_ALLOWED_EXTERNAL_AUTHENTICATION() + @transaction + @convert_model + def get(self, params: UserGetRequest) -> Union[UserResponse, dict]: + return {} - @staticmethod - def _check_reset_password_eligibility(user_id, backend, email): - if backend == 'EXTERNAL': - raise ERROR_UNABLE_TO_RESET_PASSWORD_IN_EXTERNAL_AUTH(user_id=user_id) - elif email is None: - raise ERROR_UNABLE_TO_RESET_PASSWORD_WITHOUT_EMAIL(user_id=user_id) + @transaction + @convert_model + def list(self, params: UserSearchQueryRequest) -> Union[UsersResponse, dict]: + return {} - @staticmethod - def _generate_temporary_password(): - while True: - random_password = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(12)) - if re.search('[a-z]', random_password) and re.search('[A-Z]', random_password) and re.search('[0-9]', random_password): - return random_password + @transaction + @convert_model + def stat(self, params: UserStatQueryRequest) -> dict: + return {} diff --git a/src/spaceone/identity/service/workspace_service.py b/src/spaceone/identity/service/workspace_service.py new file mode 100644 index 00000000..ffff6e1a --- /dev/null +++ b/src/spaceone/identity/service/workspace_service.py @@ -0,0 +1,53 @@ +import logging +from typing import Generator, Union +from spaceone.core.service import BaseService, transaction, convert_model +from spaceone.identity.model.workspace_request import * +from spaceone.identity.model.workspace_response import * + +_LOGGER = logging.getLogger(__name__) + + +class WorkspaceService(BaseService): + @transaction + @convert_model + def create(self, params: WorkspaceCreateRequest) -> Union[WorkspaceResponse, dict]: + return {} + + @transaction + @convert_model + def update(self, params: WorkspaceUpdateRequest) -> Union[WorkspaceResponse, dict]: + return {} + + @transaction + @convert_model + def delete(self, params: WorkspaceDeleteRequest) -> None: + pass + + @transaction + @convert_model + def enable(self, params: WorkspaceEnableRequest) -> Union[WorkspaceResponse, dict]: + return {} + + @transaction + @convert_model + def disable( + self, params: WorkspaceDisableRequest + ) -> Union[WorkspaceResponse, dict]: + return {} + + @transaction + @convert_model + def get(self, params: WorkspaceGetRequest) -> Union[WorkspaceResponse, dict]: + return {} + + @transaction + @convert_model + def list( + self, params: WorkspaceSearchQueryRequest + ) -> Union[WorkspacesResponse, dict]: + return {} + + @transaction + @convert_model + def stat(self, params: WorkspaceStatQueryRequest) -> dict: + return {} From 243de50ff50d91b2b226a77faeb1ce9f69013149 Mon Sep 17 00:00:00 2001 From: ImMin5 Date: Tue, 14 Nov 2023 11:19:51 +0900 Subject: [PATCH 02/45] feat: add external_auth interface and model (#73) Signed-off-by: ImMin5 --- .../identity/interface/grpc/external_auth.py | 26 +++++++++++++++ .../identity/model/external_auth_request.py | 33 +++++++++++++++++++ .../identity/model/external_auth_response.py | 24 ++++++++++++++ .../identity/service/external_auth_service.py | 26 +++++++++++++++ 4 files changed, 109 insertions(+) create mode 100644 src/spaceone/identity/interface/grpc/external_auth.py create mode 100644 src/spaceone/identity/model/external_auth_request.py create mode 100644 src/spaceone/identity/model/external_auth_response.py create mode 100644 src/spaceone/identity/service/external_auth_service.py diff --git a/src/spaceone/identity/interface/grpc/external_auth.py b/src/spaceone/identity/interface/grpc/external_auth.py new file mode 100644 index 00000000..24298b52 --- /dev/null +++ b/src/spaceone/identity/interface/grpc/external_auth.py @@ -0,0 +1,26 @@ +from spaceone.core.pygrpc import BaseAPI +from spaceone.api.identity.v2 import external_auth_pb2, external_auth_pb2_grpc +from spaceone.identity.service.external_auth_service import ExternalAuthService + + +class ExternalAuth(BaseAPI, external_auth_pb2_grpc.ExternalAuthServicer): + pb2 = external_auth_pb2 + pb2_grpc = external_auth_pb2_grpc + + def set(self, request, context): + params, metadata = self.parse_request(request, context) + external_auth_svc = ExternalAuthService(metadata) + response: dict = external_auth_svc.set(params) + return self.dict_to_message(response) + + def unset(self, request, context): + params, metadata = self.parse_request(request, context) + external_auth_svc = ExternalAuthService(metadata) + response: dict = external_auth_svc.unset(params) + return self.dict_to_message(response) + + def get(self, request, context): + params, metadata = self.parse_request(request, context) + external_auth_svc = ExternalAuthService(metadata) + response: dict = external_auth_svc.get(params) + return self.dict_to_message(response) diff --git a/src/spaceone/identity/model/external_auth_request.py b/src/spaceone/identity/model/external_auth_request.py new file mode 100644 index 00000000..d4b15388 --- /dev/null +++ b/src/spaceone/identity/model/external_auth_request.py @@ -0,0 +1,33 @@ +from typing import Union, Literal +from pydantic import BaseModel, Field + +__all__ = [ + "ExternalAuthSetRequest", + "ExternalAuthUnsetRequest", + "ExternalAuthGetRequest", +] + +UpgradeMode = Literal["AUTO", "MANUAL"] + + +class Plugin(BaseModel): + plugin_id: str + version: Union[str, None] = None + upgrade_mode: Union[str, None] = None + options: Union[dict, None] = None + secret_data: Union[dict, None] = None + schema_name: Union[str, None] = Field(None, alias="schema") + metadata: dict + + +class ExternalAuthSetRequest(BaseModel): + domain_id: str + external_auth_id: Plugin + + +class ExternalAuthUnsetRequest(BaseModel): + domain_id: str + + +class ExternalAuthGetRequest(BaseModel): + domain_id: str diff --git a/src/spaceone/identity/model/external_auth_response.py b/src/spaceone/identity/model/external_auth_response.py new file mode 100644 index 00000000..1cce778a --- /dev/null +++ b/src/spaceone/identity/model/external_auth_response.py @@ -0,0 +1,24 @@ +from datetime import datetime +from typing import Union, Literal +from pydantic import BaseModel, Field + +__all__ = ["ExternalAuthResponse"] + +State = Literal["ENABLED", "DISABLED"] + + +class Plugin(BaseModel): + plugin_id: str + version: Union[str, None] = None + upgrade_mode: Union[str, None] = None + options: Union[dict, None] = None + schema_name: Union[str, None] = Field(None, alias="schema") + metadata: dict + secret_id: Union[dict, None] = None + + +class ExternalAuthResponse(BaseModel): + domain_id: Union[str, None] = None + state: Union[State, None] = None + plugin_info: Union[Plugin, None] = None + updated_at: Union[datetime, None] = None diff --git a/src/spaceone/identity/service/external_auth_service.py b/src/spaceone/identity/service/external_auth_service.py new file mode 100644 index 00000000..ccfe1726 --- /dev/null +++ b/src/spaceone/identity/service/external_auth_service.py @@ -0,0 +1,26 @@ +import logging +from typing import Union +from spaceone.core.service import BaseService, transaction, convert_model +from spaceone.identity.model.external_auth_request import * +from spaceone.identity.model.external_auth_response import * + +_LOGGER = logging.getLogger(__name__) + + +class ExternalAuthService(BaseService): + @transaction + @convert_model + def set(self, params: ExternalAuthSetRequest) -> Union[ExternalAuthResponse, dict]: + return {} + + @transaction + @convert_model + def unset( + self, params: ExternalAuthUnsetRequest + ) -> Union[ExternalAuthResponse, dict]: + return {} + + @transaction + @convert_model + def get(self, params: ExternalAuthGetRequest) -> Union[ExternalAuthResponse, dict]: + return {} From 8ee34a804b6671e680c736c823f23c0ef23262f4 Mon Sep 17 00:00:00 2001 From: ImMin5 Date: Sun, 19 Nov 2023 15:49:49 +0900 Subject: [PATCH 03/45] feat: modify User and Domain model spec (#73) Signed-off-by: ImMin5 --- .../identity/manager/domain_manager.py | 240 ++---------------- src/spaceone/identity/manager/user_manager.py | 210 +++------------ .../identity/model/domain_db_model.py | 55 ++++ .../identity/model/domain_response.py | 13 +- src/spaceone/identity/model/user_db_model.py | 57 +++++ src/spaceone/identity/model/user_request.py | 16 +- src/spaceone/identity/model/user_response.py | 10 +- .../identity/service/domain_service.py | 47 +++- 8 files changed, 234 insertions(+), 414 deletions(-) create mode 100644 src/spaceone/identity/model/domain_db_model.py create mode 100644 src/spaceone/identity/model/user_db_model.py diff --git a/src/spaceone/identity/manager/domain_manager.py b/src/spaceone/identity/manager/domain_manager.py index 3bc4f131..b7985194 100644 --- a/src/spaceone/identity/manager/domain_manager.py +++ b/src/spaceone/identity/manager/domain_manager.py @@ -2,270 +2,82 @@ from spaceone.core import cache from spaceone.core.manager import BaseManager -from spaceone.core.connector.space_connector import SpaceConnector -from spaceone.identity.error.error_domain import * -from spaceone.identity.connector import AuthPluginConnector -from spaceone.identity.model.domain_model import Domain +from spaceone.identity.model.domain_db_model import Domain _LOGGER = logging.getLogger(__name__) class DomainManager(BaseManager): - def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.domain_model: Domain = self.locator.get_model('Domain') + self.domain_model = Domain() def create_domain(self, params): def _rollback(vo): - _LOGGER.info(f'[create_domain._rollback] Delete domain : {vo.name} ({vo.domain_id})') + _LOGGER.info( + f"[create_domain._rollback] Delete domain : {vo.name} ({vo.domain_id})" + ) vo.delete() - domain_vo: Domain = self.domain_model.create(params) + domain_vo = self.domain_model.create(params) self.transaction.add_rollback(_rollback, domain_vo) - if 'plugin_info' in params: - domain_id = domain_vo.domain_id - plugin_info = params['plugin_info'] - options = plugin_info.get('options', {}) - endpoint, updated_version = self.get_auth_plugin_endpoint(domain_id, plugin_info) - - if updated_version: - params['plugin_info']['version'] = updated_version - - response = self.init_auth_plugin(endpoint, options) - params['plugin_info']['metadata'] = response['metadata'] - - secret_data = plugin_info.get('secret_data') - - if secret_data: - schema = plugin_info.get('schema') - secret_id = self._create_secret(domain_id, secret_data, schema) - - if secret_id: - params['plugin_info']['secret_id'] = secret_id - del params['plugin_info']['secret_data'] - - return domain_vo.update({ - 'plugin_info': params['plugin_info'] - }) - else: - return domain_vo + return domain_vo def update_domain(self, params): def _rollback(old_data): - _LOGGER.info(f'[update_domain._rollback] Revert Data : {old_data["name"]} ({old_data["domain_id"]})') + _LOGGER.info( + f'[update_domain._rollback] Revert Data : {old_data["name"]} ({old_data["domain_id"]})' + ) domain_vo.update(old_data) - domain_vo: Domain = self.get_domain(params['domain_id']) + domain_vo: Domain = self.get_domain(params["domain_id"]) self.transaction.add_rollback(_rollback, domain_vo.to_dict()) return domain_vo.update(params) - def change_auth_plugin(self, domain_id, plugin_info): - """" - Case 1. No plugin_info -> New plugin_info - Case 2. plugin_info(1) -> New plugin_info - """ - domain_vo: Domain = self.get_domain(domain_id) - secret_id = None - - try: - secret_id = domain_vo.plugin_info.secret_id - except Exception as e: - # No plugin_info or plugin_info without secret_id - pass - - options = plugin_info.get('options', {}) - endpoint, updated_version = self.get_auth_plugin_endpoint(domain_id, plugin_info) - - if updated_version: - plugin_info['version'] = updated_version - - response = self.init_auth_plugin(endpoint, options) - plugin_info['metadata'] = response['metadata'] - - secret_data = plugin_info.get('secret_data') - - if secret_data: - schema = plugin_info.get('schema') - - if secret_id: - self._update_secret_data(secret_id, domain_id, secret_data, schema) - else: - secret_id = self._create_secret(domain_id, secret_data, schema) - - if secret_id: - plugin_info['secret_id'] = secret_id - del plugin_info['secret_data'] - - updated_domain_vo = domain_vo.update({'plugin_info': plugin_info}) - - return updated_domain_vo - - def release_auth_plugin(self, domain_id): - """ release plugin_info - """ - domain_vo: Domain = self.get_domain(domain_id) - - # clean plugin_info - # secret_id, if exist - if domain_vo.plugin_info.secret_id: - self._delete_secret(domain_vo.plugin_info.secret_id, domain_id) - - return domain_vo.update({'plugin_info': None}) - - def update_domain_plugin(self, domain_id, version=None, options=None, upgrade_mode=None): - """ Update plugin of domain - If options exists, it should be complete content. - """ - domain_vo: Domain = self.get_domain(domain_id) - - if domain_vo.plugin_info is None: - raise ERROR_PLUGIN_IS_NOT_SET() - - plugin_info = domain_vo.plugin_info.to_dict() - - if version: - plugin_info['version'] = version - - if options: - plugin_info['options'] = options - - if upgrade_mode: - plugin_info['upgrade_mode'] = upgrade_mode - - endpoint, updated_version = self.get_auth_plugin_endpoint(domain_id, plugin_info) - - if updated_version: - plugin_info['version'] = updated_version - - response = self.init_auth_plugin(endpoint, plugin_info.get('options', {})) - plugin_info['metadata'] = response['metadata'] - - return domain_vo.update({'plugin_info': plugin_info}) - - def verify_auth_plugin(self, domain_id): - domain_vo: Domain = self.get_domain(domain_id) - endpoint = self.get_auth_plugin_endpoint_by_vo(domain_vo) - plugin_info = domain_vo.plugin_info.to_dict() - - auth_conn: AuthPluginConnector = self.locator.get_connector('AuthPluginConnector') - auth_conn.initialize(endpoint) - - return auth_conn.verify(plugin_info['options']) - def delete_domain(self, domain_id): domain_vo: Domain = self.get_domain(domain_id) domain_vo.delete() - cache.delete_pattern(f'domain-state:{domain_id}') + cache.delete_pattern(f"domain-state:{domain_id}") def enable_domain(self, domain_id): def _rollback(old_data): - _LOGGER.info(f'[enable_domain._rollback] Revert Data : {old_data["name"]} ({old_data["domain_id"]})') + _LOGGER.info( + f'[enable_domain._rollback] Revert Data : {old_data["name"]} ({old_data["domain_id"]})' + ) domain_vo.update(old_data) domain_vo: Domain = self.get_domain(domain_id) - if domain_vo.state != 'ENABLED': + if domain_vo.state != "ENABLED": self.transaction.add_rollback(_rollback, domain_vo.to_dict()) - domain_vo.update({'state': 'ENABLED'}) + domain_vo.update({"state": "ENABLED"}) - cache.delete_pattern(f'domain-state:{domain_id}') + cache.delete_pattern(f"domain-state:{domain_id}") return domain_vo def disable_domain(self, domain_id): def _rollback(old_data): - _LOGGER.info(f'[disable_domain._rollback] Revert Data : {old_data["name"]} ({old_data["domain_id"]})') + _LOGGER.info( + f'[disable_domain._rollback] Revert Data : {old_data["name"]} ({old_data["domain_id"]})' + ) domain_vo.update(old_data) domain_vo: Domain = self.get_domain(domain_id) - if domain_vo.state != 'DISABLED': + if domain_vo.state != "DISABLED": self.transaction.add_rollback(_rollback, domain_vo.to_dict()) - domain_vo.update({'state': 'DISABLED'}) + domain_vo.update({"state": "DISABLED"}) - cache.delete_pattern(f'domain-state:{domain_id}') + cache.delete_pattern(f"domain-state:{domain_id}") return domain_vo - def get_domain(self, domain_id, only=None): - return self.domain_model.get(domain_id=domain_id, only=only) + def get_domain(self, domain_id): + return self.domain_model.get(domain_id=domain_id) def list_domains(self, query): return self.domain_model.query(**query) - - def stat_domains(self, query): - return self.domain_model.stat(**query) - - def get_auth_plugin_endpoint_by_vo(self, domain_vo: Domain): - plugin_info = domain_vo.plugin_info.to_dict() - endpoint, updated_version = self.get_auth_plugin_endpoint(domain_vo.domain_id, plugin_info) - - if updated_version: - _LOGGER.debug(f'[get_auth_plugin_endpoint_by_vo] upgrade plugin version: {plugin_info["version"]} -> {updated_version}') - self.upgrade_auth_plugin_version(domain_vo, endpoint, updated_version) - - return endpoint - - def get_auth_plugin_endpoint(self, domain_id, plugin_info): - plugin_connector: SpaceConnector = self.locator.get_connector('SpaceConnector', service='plugin') - response = plugin_connector.dispatch( - 'Plugin.get_plugin_endpoint', - { - 'plugin_id': plugin_info['plugin_id'], - 'version': plugin_info.get('version'), - 'upgrade_mode': plugin_info.get('upgrade_mode', 'AUTO'), - 'domain_id': domain_id - } - ) - - return response['endpoint'], response.get('updated_version') - - def upgrade_auth_plugin_version(self, domain_vo: Domain, endpoint, updated_version): - plugin_info = domain_vo.plugin_info.to_dict() - response = self.init_auth_plugin(endpoint, plugin_info.get('options', {})) - plugin_info['version'] = updated_version - plugin_info['metadata'] = response['metadata'] - domain_vo.update({'plugin_info': plugin_info}) - - def init_auth_plugin(self, endpoint, options): - auth_conn: AuthPluginConnector = self.locator.get_connector('AuthPluginConnector') - auth_conn.initialize(endpoint) - - return auth_conn.init(options) - - def _create_secret(self, domain_id, secret_data, schema): - secret_connector: SpaceConnector = self.locator.get_connector('SpaceConnector', service='secret') - params = { - 'name': f'{domain_id}-auth-plugin-credentials', - 'data': secret_data, - 'secret_type': 'CREDENTIALS', - 'schema': schema, - 'domain_id': domain_id - } - - resp = secret_connector.dispatch('Secret.create', params) - _LOGGER.debug(f'[_create_secret] {resp}') - return resp.get('secret_id') - - def _delete_secret(self, secret_id, domain_id): - secret_connector: SpaceConnector = self.locator.get_connector('SpaceConnector', service='secret') - params = { - 'secret_id': secret_id, - 'domain_id': domain_id - } - secret_connector.dispatch('Secret.delete', params) - - def _update_secret_data(self, secret_id, domain_id, secret_data, schema): - secret_connector: SpaceConnector = self.locator.get_connector('SpaceConnector', service='secret') - params = { - 'secret_id': secret_id, - 'data': secret_data, - 'schema': schema, - 'domain_id': domain_id, - } - - secret_connector.dispatch('Secret.update_data', params) diff --git a/src/spaceone/identity/manager/user_manager.py b/src/spaceone/identity/manager/user_manager.py index 7152a381..f9c1025c 100644 --- a/src/spaceone/identity/manager/user_manager.py +++ b/src/spaceone/identity/manager/user_manager.py @@ -1,89 +1,62 @@ -import re import logging +import re -from spaceone.core import cache from spaceone.core.manager import BaseManager -from spaceone.core.connector.space_connector import SpaceConnector -from spaceone.identity.connector import AuthPluginConnector + from spaceone.identity.lib.cipher import PasswordCipher -from spaceone.identity.model import Domain -from spaceone.identity.model.user_model import User from spaceone.identity.error.error_user import * -from spaceone.identity.manager.domain_manager import DomainManager +from spaceone.identity.model.domain_db_model import Domain +from spaceone.identity.model.user_db_model import User _LOGGER = logging.getLogger(__name__) class UserManager(BaseManager): - def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.user_model: User = self.locator.get_model('User') + self.user_model = User() def create_user(self, params, domain_vo: Domain, is_first_login_user=False): def _rollback(user_vo): - _LOGGER.info(f'[create_user._rollback] Delete user : {user_vo.name} ({user_vo.user_id})') + _LOGGER.info( + f"[create_user._rollback] Delete user : {user_vo.name} ({user_vo.user_id})" + ) user_vo.delete() - params['state'] = params.get('state', 'ENABLED') + params["state"] = params.get("state", "ENABLED") # If user create external authentication, call find action. - if params['backend'] == 'EXTERNAL': + if params["auth_type"] == "EXTERNAL": if not is_first_login_user: - found_users, count = self.find_user( - { - 'user_id': params['user_id'] - }, - domain_vo - ) - - if count == 1: - found_user = found_users[0] - _LOGGER.debug(f'[create_user] found user: {found_user}') - - if found_user.get('state') in ['ENABLED', 'DISABLED']: - params['state'] = found_user['state'] - else: - params['state'] = 'PENDING' - - if 'name' not in params: - params['name'] = found_user.get('name') - - if 'email' not in params: - params['email'] = found_user.get('email') - - elif count > 1: - raise ERROR_TOO_MANY_USERS_IN_EXTERNAL_AUTH(user_id=params['user_id']) - else: - raise ERROR_NOT_FOUND_USER_IN_EXTERNAL_AUTH(user_id=params['user_id']) + pass else: - if params['user_type'] == 'API_USER': - params['password'] = None + if params["user_type"] == "API_USER": + params["password"] = None else: - self._check_user_id_format(params['user_id']) + self._check_user_id_format(params["user_id"]) - password = params.get('password') + password = params.get("password") if password: self._check_password_format(password) else: - raise ERROR_REQUIRED_PARAMETER(key='password') + raise ERROR_REQUIRED_PARAMETER(key="password") hashed_pw = PasswordCipher().hashpw(password) - params['password'] = hashed_pw + params["password"] = hashed_pw - user_name = params.get('name') - user_email = params.get('email') + user_name = params.get("name") + user_email = params.get("email") if user_name: - params['name'] = user_name.strip() + params["name"] = user_name.strip() else: - params['name'] = '' + params["name"] = "" if user_email: - params['email'] = user_email.strip() + params["email"] = user_email.strip() else: - params['email'] = '' + params["email"] = "" user_vo = self.user_model.create(params) @@ -91,142 +64,23 @@ def _rollback(user_vo): return user_vo - def update_user(self, params): - user_vo: User = self.get_user(params['user_id'], params['domain_id']) - return self.update_user_by_vo(params, user_vo) - - def update_user_by_vo(self, params, user_vo): - def _rollback(old_data): - _LOGGER.info(f'[update_user._rollback] Revert Data : {old_data["name"], ({old_data["user_id"]})}') - user_vo.update(old_data) - - required_actions = list(user_vo.required_actions) - is_change_required_actions = False - - if new_password := params.get('password'): - if PasswordCipher().checkpw(new_password, user_vo.password): - raise ERROR_PASSWORD_NOT_CHANGED(user_id=user_vo.user_id) - - self._check_password_format(params['password']) - hashed_pw = PasswordCipher().hashpw(params['password']) - params['password'] = hashed_pw - - if 'UPDATE_PASSWORD' in required_actions: - required_actions.remove('UPDATE_PASSWORD') - is_change_required_actions = True - - if is_change_required_actions: - params['required_actions'] = required_actions - - self.transaction.add_rollback(_rollback, user_vo.to_dict()) - - return user_vo.update(params) - - def delete_user(self, user_id, domain_id): - user_vo = self.get_user(user_id, domain_id) - user_vo.delete() - - cache.delete_pattern(f'user-state:{domain_id}:{user_id}') - cache.delete_pattern(f'role-bindings:{domain_id}:{user_id}*') - cache.delete_pattern(f'user-permissions:{domain_id}:{user_id}*') - cache.delete_pattern(f'user-scopes:{domain_id}:{user_id}*') - - def enable_user(self, user_id, domain_id): - def _rollback(old_data): - _LOGGER.info(f'[enable_user._rollback] Revert Data : {old_data}') - user_vo.update(old_data) - - user_vo: User = self.get_user(user_id, domain_id) - - if user_vo.state != 'ENABLED': - self.transaction.add_rollback(_rollback, user_vo.to_dict()) - user_vo.update({'state': 'ENABLED'}) - - cache.delete_pattern(f'user-state:{domain_id}:{user_id}') - - return user_vo - - def disable_user(self, user_id, domain_id): - def _rollback(old_data): - _LOGGER.info(f'[disable_user._rollback] Revert Data : {old_data}') - user_vo.update(old_data) - - user_vo: User = self.get_user(user_id, domain_id) - - if user_vo.state != 'DISABLED': - self.transaction.add_rollback(_rollback, user_vo.to_dict()) - user_vo.update({'state': 'DISABLED'}) - - cache.delete_pattern(f'user-state:{domain_id}:{user_id}') - - return user_vo - - def get_user(self, user_id, domain_id, only=None): - return self.user_model.get(user_id=user_id, domain_id=domain_id, only=only) - - def filter_users(self, **conditions): - return self.user_model.filter(**conditions) - - def list_users(self, query): - return self.user_model.query(**query) - - def stat_users(self, query): - return self.user_model.stat(**query) - - def find_user(self, search, domain_vo: Domain): - keyword = search.get('keyword') - user_id = search.get('user_id') - - domain_mgr: DomainManager = self.locator.get_manager('DomainManager') - - endpoint = domain_mgr.get_auth_plugin_endpoint_by_vo(domain_vo) - - response = self._call_find(keyword, user_id, domain_vo, endpoint) - results = response.get('results', []) - total_count = response.get('total_count', 0) - - return results, total_count - @staticmethod def _check_user_id_format(user_id): rule = r"[^@]+@[^@]+\.[^@]+" if not re.match(rule, user_id): - raise ERROR_INCORRECT_USER_ID_FORMAT(rule='Email format required.') + raise ERROR_INCORRECT_USER_ID_FORMAT(rule="Email format required.") @staticmethod def _check_password_format(password): if len(password) < 8: - raise ERROR_INCORRECT_PASSWORD_FORMAT(rule='At least 9 characters long.') + raise ERROR_INCORRECT_PASSWORD_FORMAT(rule="At least 9 characters long.") elif not re.search("[a-z]", password): - raise ERROR_INCORRECT_PASSWORD_FORMAT(rule='Contains at least one lowercase character') + raise ERROR_INCORRECT_PASSWORD_FORMAT( + rule="Contains at least one lowercase character" + ) elif not re.search("[A-Z]", password): - raise ERROR_INCORRECT_PASSWORD_FORMAT(rule='Contains at least one uppercase character') + raise ERROR_INCORRECT_PASSWORD_FORMAT( + rule="Contains at least one uppercase character" + ) elif not re.search("[0-9]", password): - raise ERROR_INCORRECT_PASSWORD_FORMAT(rule='Contains at least one number') - - def _call_find(self, keyword, user_id, domain_vo, endpoint): - options = domain_vo.plugin_info.options - - auth_plugin_conn: AuthPluginConnector = self.locator.get_connector('AuthPluginConnector') - auth_plugin_conn.initialize(endpoint) - - secret_data, schema = self._get_auth_plugin_secret(domain_vo.to_dict()) - return auth_plugin_conn.call_find(keyword, user_id, options, secret_data, schema) - - def _get_auth_plugin_secret(self, domain_data): - """ - Return: (secret_data, schema) - Default: ({}, None) - """ - domain_id = domain_data['domain_id'] - plugin_info = domain_data.get('plugin_info', {}) - secret_id = plugin_info.get('secret_id') - - if secret_id is None: - return {}, None - - secret_connector: SpaceConnector = self.locator.get_connector('SpaceConnector', service='secret') - secret = secret_connector.dispatch('Secret.get', {'secret_id': secret_id, 'domain_id': domain_id}) - secret_data = secret_connector.dispatch('Secret.get_data', {'secret_id': secret_id, 'domain_id': domain_id}) - - return secret_data.get('data', {}), secret_data.get('schema') + raise ERROR_INCORRECT_PASSWORD_FORMAT(rule="Contains at least one number") diff --git a/src/spaceone/identity/model/domain_db_model.py b/src/spaceone/identity/model/domain_db_model.py new file mode 100644 index 00000000..45c77b31 --- /dev/null +++ b/src/spaceone/identity/model/domain_db_model.py @@ -0,0 +1,55 @@ +from mongoengine import * +from datetime import datetime +from spaceone.core.error import * +from spaceone.core.model.mongo_model import MongoModel + + +class Domain(MongoModel): + domain_id = StringField(max_length=40, generate_id="domain", unique=True) + name = StringField(max_length=255) + state = StringField(max_length=20, default="ENABLED") + tags = DictField() + created_at = DateTimeField(auto_now_add=True) + deleted_at = DateTimeField(default=None, null=True) + + meta = { + "updatable_fields": [ + "name", + "state", + "tags", + "deleted_at", + ], + "minimal_fields": [ + "domain_id", + "name", + "state", + ], + "ordering": ["name"], + "indexes": [ + # 'domain_id', + "state", + ], + } + + @queryset_manager + def objects(doc_cls, queryset): + return queryset.filter(state__ne="DELETED") + + @classmethod + def create(cls, data): + domain_vos = cls.filter(name=data["name"]) + if domain_vos.count() > 0: + raise ERROR_NOT_UNIQUE(key="name", value=data["name"]) + + return super().create(data) + + def update(self, data): + if "name" in data: + domain_vos = self.filter(name=data["name"], domain_id__ne=self.domain_id) + if domain_vos.count() > 0: + raise ERROR_NOT_UNIQUE(key="name", value=data["name"]) + + return super().update(data) + + def delete(self): + self.update({"state": "DELETED", "deleted_at": datetime.utcnow()}) diff --git a/src/spaceone/identity/model/domain_response.py b/src/spaceone/identity/model/domain_response.py index 453ba4dd..9ce5aac7 100644 --- a/src/spaceone/identity/model/domain_response.py +++ b/src/spaceone/identity/model/domain_response.py @@ -1,6 +1,7 @@ -from datetime import datetime from typing import List, Union, Literal -from pydantic import BaseModel +from pydantic import BaseModel, validator, Extra + +from spaceone.core.utils import datetime_to_iso8601 from spaceone.identity.model.domain_request import State @@ -14,12 +15,16 @@ ExternalAuthState = Literal["ENABLED", "DISABLED"] -class DomainResponse(BaseModel): +class DomainResponse(BaseModel, extra=Extra.ignore): domain_id: Union[str, None] = None name: Union[str, None] = None state: Union[State, None] = None tags: Union[dict, None] = {} - created_at: Union[datetime, None] = None + created_at: Union[str, None] = None + + _convert_datetime = validator("created_at", pre=True, allow_reuse=True)( + datetime_to_iso8601 + ) class DomainMetadataResponse(BaseModel): diff --git a/src/spaceone/identity/model/user_db_model.py b/src/spaceone/identity/model/user_db_model.py new file mode 100644 index 00000000..47f6ce89 --- /dev/null +++ b/src/spaceone/identity/model/user_db_model.py @@ -0,0 +1,57 @@ +from mongoengine import * +from spaceone.core.model.mongo_model import MongoModel + + +class MFA(EmbeddedDocument): + state = StringField( + max_length=20, choices=("ENABLED", "DISABLED"), default="DISABLED" + ) + mfa_type = StringField(max_length=20) + options = DictField() + + def to_dict(self): + return dict(self.to_mongo()) + + +class User(MongoModel): + user_id = StringField(max_length=40, unique_with="domain_id", required=True) + password = BinaryField(default=None) + name = StringField(max_length=128, default="") + state = StringField(max_length=20, choices=("ENABLED", "DISABLED", "PENDING")) + email = StringField(max_length=255, default="") + email_verified = BooleanField(default=False) + user_type = StringField(max_length=20, choices=("USER", "API_USER")) + auth_type = StringField(max_length=20, choices=("LOCAL", "EXTERNAL")) + mfa = EmbeddedDocumentField(MFA) + required_actions = ListField(StringField(choices=("UPDATE_PASSWORD",)), default=[]) + language = StringField(max_length=7, default="en") + timezone = StringField(max_length=50, default="UTC") + tags = DictField() + domain_id = StringField(max_length=40) + last_accessed_at = DateTimeField(default=None, null=True) + created_at = DateTimeField(auto_now_add=True) + + meta = { + "updatable_fields": [ + "password", + "name", + "state", + "email", + "email_verified", + "mfa", + "required_actions", + "language", + "timezone", + "tags", + "last_accessed_at", + ], + "minimal_fields": ["user_id", "name", "state", "user_type"], + "ordering": ["name"], + "indexes": [ + "state", + "user_type", + "auth_type", + "last_accessed_at", + # ('user_id', 'domain_id'), + ], + } diff --git a/src/spaceone/identity/model/user_request.py b/src/spaceone/identity/model/user_request.py index 9ff6cdf3..d5240c21 100644 --- a/src/spaceone/identity/model/user_request.py +++ b/src/spaceone/identity/model/user_request.py @@ -18,16 +18,12 @@ "UserDisableRequest", "UserGetRequest", "UserType", - "Backend", + "AuthType", "State", - "RoleType", ] State = Literal["ENABLED", "DISABLED", "PENDING"] -Backend = Literal["LOCAL", "EXTERNAL"] -RoleType = Literal[ - "SYSTEM_ADMIN", "DOMAIN_ADMIN", "WORKSPACE_OWNER", "WORKSPACE_MEMBER", "NO_RULE" -] +AuthType = Literal["LOCAL", "EXTERNAL"] UserType = Literal["USER", "API_USER"] @@ -37,7 +33,7 @@ class UserCreateRequest(BaseModel): name: Union[str, None] = None email: Union[str, None] = None user_type: Union[UserType, None] = None - backend: Backend + auth_type: AuthType language: Union[str, None] = None timezone: Union[str, None] = None tags: Union[dict, None] = None @@ -125,10 +121,10 @@ class UserSearchQueryRequest(BaseModel): query: Union[dict, None] = None user_id: Union[str, None] = None name: Union[str, None] = None - state: Union[str, None] = None + state: Union[State, None] = None email: Union[str, None] = None - user_type: Union[str, None] = None - backend: Union[str, None] = None + user_type: Union[UserType, None] = None + AuthType: Union[AuthType, None] = None workspace_id: Union[str, None] = None domain_id: str diff --git a/src/spaceone/identity/model/user_response.py b/src/spaceone/identity/model/user_response.py index 884037bd..9d31ec66 100644 --- a/src/spaceone/identity/model/user_response.py +++ b/src/spaceone/identity/model/user_response.py @@ -1,11 +1,15 @@ from datetime import datetime -from typing import Union, List +from typing import Union, List, Literal from pydantic import BaseModel -from spaceone.identity.model.user_request import State, UserType, Backend, RoleType +from spaceone.identity.model.user_request import State, UserType, AuthType __all__ = ["UserResponse", "UsersResponse"] +RoleType = Literal[ + "SYSTEM_ADMIN", "DOMAIN_ADMIN", "WORKSPACE_OWNER", "WORKSPACE_MEMBER", "NO_RULE" +] + class UserResponse(BaseModel): user_id: Union[str, None] = None @@ -13,7 +17,7 @@ class UserResponse(BaseModel): state: Union[State, None] = None email_verified: Union[bool, None] = None user_type: Union[UserType, None] = None - backend: Union[Backend, None] = None + auth_type: Union[AuthType, None] = None role_type: Union[RoleType, None] = None mfa: Union[dict, None] = None required_actions: Union[List[str], None] = None diff --git a/src/spaceone/identity/service/domain_service.py b/src/spaceone/identity/service/domain_service.py index fbd53e0b..8f674b33 100644 --- a/src/spaceone/identity/service/domain_service.py +++ b/src/spaceone/identity/service/domain_service.py @@ -1,6 +1,17 @@ import logging from typing import Union -from spaceone.core.service import BaseService, transaction, convert_model +from spaceone.core.service import ( + BaseService, + transaction, + convert_model, + append_query_filter, + append_keyword_filter, +) + +from spaceone.identity.manager.domain_manager import DomainManager +from spaceone.identity.manager.user_manager import UserManager + +# from spaceone.identity.manager.role_manager import RoleManager from spaceone.identity.model.domain_request import * from spaceone.identity.model.domain_response import * @@ -8,20 +19,41 @@ class DomainService(BaseService): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.domain_mgr = DomainManager() + self.user_mgr = UserManager() + # self.role_manager = RoleManager() + @transaction @convert_model def create(self, params: DomainCreateRequest) -> Union[DomainResponse, dict]: - return {} + """ + Args: + :param params: + :return: + """ + + domain_vo = self.domain_mgr.create_domain(params.dict()) + admin = params.admin + admin["auth_type"] = "LOCAL" + admin["user_type"] = "USER" + admin["domain_id"] = domain_vo.domain_id + + # create admin user with policy and role + self.user_mgr.create_user(admin, domain_vo) + return DomainResponse(**domain_vo.to_dict()) @transaction @convert_model def update(self, params: DomainUpdateRequest) -> Union[DomainResponse, dict]: - return {} + domain_vo = self.domain_mgr.update_domain(params.dict()) + return DomainResponse(**domain_vo.to_dict()) @transaction @convert_model def delete(self, params: DomainRequest) -> None: - pass + self.domain_mgr.delete_domain(params.dict().get("domain_id")) @transaction @convert_model @@ -36,7 +68,8 @@ def disable(self, params: DomainRequest) -> Union[DomainResponse, dict]: @transaction @convert_model def get(self, params: DomainRequest) -> Union[DomainResponse, dict]: - return {} + domain_vo = self.domain_mgr.get_domain(params.dict().get("domain_id")) + return DomainResponse(**domain_vo.to_dict()) @transaction @convert_model @@ -53,8 +86,12 @@ def get_public_key( return {} @transaction + @append_query_filter(["domain_id", "name"]) + @append_keyword_filter(["domain_id", "name"]) @convert_model def list(self, params: DomainSearchQueryRequest) -> Union[DomainsResponse, dict]: + query = params.dict().get("query", {}) + domain_vos = self.domain_mgr.list_domains(query) return {} @transaction From 83f713c69382691b8f59ede856ff0e9bc4b7ccf5 Mon Sep 17 00:00:00 2001 From: Jongmin Kim Date: Sun, 19 Nov 2023 16:11:38 +0900 Subject: [PATCH 04/45] feat: add provider order --- src/spaceone/identity/info/policy_info.py | 30 ------------------- .../interface/grpc/trusted_service_account.py | 8 ++--- src/spaceone/identity/manager/__init__.py | 19 ------------ 3 files changed, 4 insertions(+), 53 deletions(-) diff --git a/src/spaceone/identity/info/policy_info.py b/src/spaceone/identity/info/policy_info.py index 073225a9..e69de29b 100644 --- a/src/spaceone/identity/info/policy_info.py +++ b/src/spaceone/identity/info/policy_info.py @@ -1,30 +0,0 @@ -import functools -from spaceone.api.identity.v1 import policy_pb2 -from spaceone.core.pygrpc.message_type import * -from spaceone.core import utils -from spaceone.identity.model.policy_model import Policy - -__all__ = ['PolicyInfo', 'PoliciesInfo'] - - -def PolicyInfo(policy_vo: Policy, minimal=False): - info = { - 'policy_id': policy_vo.policy_id, - 'name': policy_vo.name - } - - if not minimal: - info.update({ - 'permissions': change_list_value_type(policy_vo.permissions), - 'tags': change_struct_type(policy_vo.tags), - 'domain_id': policy_vo.domain_id, - 'created_at': utils.datetime_to_iso8601(policy_vo.created_at) - }) - - return policy_pb2.PolicyInfo(**info) - - -def PoliciesInfo(policy_vos, total_count, **kwargs): - results = list(map(functools.partial(PolicyInfo, **kwargs), policy_vos)) - - return policy_pb2.PoliciesInfo(results=results, total_count=total_count) diff --git a/src/spaceone/identity/interface/grpc/trusted_service_account.py b/src/spaceone/identity/interface/grpc/trusted_service_account.py index 979dc8db..575b1ff2 100644 --- a/src/spaceone/identity/interface/grpc/trusted_service_account.py +++ b/src/spaceone/identity/interface/grpc/trusted_service_account.py @@ -1,15 +1,15 @@ from spaceone.core.pygrpc import BaseAPI -from spaceone.api.identity.v2 import trusted_account_pb2, trusted_account_pb2_grpc +from spaceone.api.identity.v2 import trusted_service_account_pb2, trusted_service_account_pb2_grpc from spaceone.identity.service.trusted_service_account_service import ( TrustedServiceAccountService, ) class TrustedServiceAccount( - BaseAPI, trusted_account_pb2_grpc.TrustedServiceAccountServicer + BaseAPI, trusted_service_account_pb2_grpc.TrustedServiceAccountServicer ): - pb2 = trusted_account_pb2 - pb2_grpc = trusted_account_pb2_grpc + pb2 = trusted_service_account_pb2 + pb2_grpc = trusted_service_account_pb2_grpc def create(self, request, context): params, metadata = self.parse_request(request, context) diff --git a/src/spaceone/identity/manager/__init__.py b/src/spaceone/identity/manager/__init__.py index 3fbfcac2..e69de29b 100644 --- a/src/spaceone/identity/manager/__init__.py +++ b/src/spaceone/identity/manager/__init__.py @@ -1,19 +0,0 @@ -from spaceone.identity.manager.api_key_manager import APIKeyManager -from spaceone.identity.manager.authorization_manager import AuthorizationManager -from spaceone.identity.manager.domain_manager import DomainManager -from spaceone.identity.manager.domain_secret_manager import DomainSecretManager -from spaceone.identity.manager.provider_manager import ProviderManager -from spaceone.identity.manager.service_account_manager import ServiceAccountManager -from spaceone.identity.manager.policy_manager import PolicyManager -from spaceone.identity.manager.project_group_manager import ProjectGroupManager -from spaceone.identity.manager.project_manager import ProjectManager -from spaceone.identity.manager.user_manager import UserManager -from spaceone.identity.manager.role_manager import RoleManager -from spaceone.identity.manager.role_binding_manager import RoleBindingManager -from spaceone.identity.manager.domain_owner_manager import DomainOwnerManager -from spaceone.identity.manager.token_manager.local_token_manager import LocalTokenManager -from spaceone.identity.manager.token_manager.external_token_manager import ExternalTokenManager -from spaceone.identity.manager.token_manager.domain_owner_token_manager import DomainOwnerTokenManager -from spaceone.identity.manager.endpoint_manager import EndpointManager -from spaceone.identity.manager.email_manager import EmailManager -from spaceone.identity.manager.mfa_manager.email_mfa_manger import EmailMFAManager From f941f4b1f4b80b9fd07b439ba5929f27081ba0fb Mon Sep 17 00:00:00 2001 From: Jongmin Kim Date: Sun, 19 Nov 2023 16:26:16 +0900 Subject: [PATCH 05/45] feat: implement Endpoint APIs --- .../identity/manager/endpoint_manager.py | 14 ++++---- .../identity/model/endpoint_request.py | 1 - .../identity/service/endpoint_service.py | 34 +++++++++++++++++-- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/spaceone/identity/manager/endpoint_manager.py b/src/spaceone/identity/manager/endpoint_manager.py index 06aaa889..60894e22 100644 --- a/src/spaceone/identity/manager/endpoint_manager.py +++ b/src/spaceone/identity/manager/endpoint_manager.py @@ -5,15 +5,13 @@ _LOGGER = logging.getLogger(__name__) -ENDPOINT_MAP = { - 'public': 'ENDPOINTS', - 'internal': 'INTERNAL_ENDPOINTS' -} - class EndpointManager(BaseManager): - def list_endpoints(self, query={}, endpoint_type='public'): - endpoint_map = ENDPOINT_MAP.get(endpoint_type, 'ENDPOINTS') - endpoints = config.get_global(endpoint_map, []) + def list_endpoints(self, service=None): + endpoints = config.get_global('ENDPOINTS', []) + + if service: + endpoints = [endpoint for endpoint in endpoints if endpoint.get('service') == service] + return endpoints, len(endpoints) diff --git a/src/spaceone/identity/model/endpoint_request.py b/src/spaceone/identity/model/endpoint_request.py index 1a2a773c..c8af3e5f 100644 --- a/src/spaceone/identity/model/endpoint_request.py +++ b/src/spaceone/identity/model/endpoint_request.py @@ -7,4 +7,3 @@ class EndpointSearchQueryRequest(BaseModel): query: Union[dict, None] = None service: Union[str, None] = None - endpoint_type: Union[str, None] = None diff --git a/src/spaceone/identity/service/endpoint_service.py b/src/spaceone/identity/service/endpoint_service.py index b9c9d661..d8f6a94c 100644 --- a/src/spaceone/identity/service/endpoint_service.py +++ b/src/spaceone/identity/service/endpoint_service.py @@ -1,8 +1,9 @@ import logging from typing import Union -from spaceone.core.service import BaseService, transaction, convert_model +from spaceone.core.service import BaseService, transaction, convert_model, append_query_filter, append_keyword_filter from spaceone.identity.model.endpoint_request import * from spaceone.identity.model.endpoint_response import * +from spaceone.identity.manager.endpoint_manager import EndpointManager _LOGGER = logging.getLogger(__name__) @@ -10,7 +11,34 @@ class EndpointService(BaseService): @transaction + # @append_query_filter(['service']) + # @append_keyword_filter(['service']) @convert_model - def list(self, params: EndpointSearchQueryRequest) -> Union[EndpointsResponse, dict]: - return {} + def list( + self, params: EndpointSearchQueryRequest + ) -> Union[EndpointsResponse, dict]: + """ list endpoints of service + + Args: + params (EndpointSearchQueryRequest): { + 'query': 'dict (spaceone.api.core.v1.Query)', + 'service': 'str' + } + + Returns: + EndpointsResponse: { + 'results': 'list', + 'total_count': 'int' + } + """ + + service = params.service + + endpoint_mgr: EndpointManager = EndpointManager() + endpoints_data, total_count = endpoint_mgr.list_endpoints(service) + + return EndpointsResponse( + results=endpoints_data, + total_count=total_count + ) From 204338d2d52c79ef836ebe658b6eb758c0b73800 Mon Sep 17 00:00:00 2001 From: Jongmin Kim Date: Sun, 19 Nov 2023 16:44:00 +0900 Subject: [PATCH 06/45] refactor: fix lint --- src/spaceone/identity/service/endpoint_service.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/spaceone/identity/service/endpoint_service.py b/src/spaceone/identity/service/endpoint_service.py index d8f6a94c..c81c4ac5 100644 --- a/src/spaceone/identity/service/endpoint_service.py +++ b/src/spaceone/identity/service/endpoint_service.py @@ -14,9 +14,7 @@ class EndpointService(BaseService): # @append_query_filter(['service']) # @append_keyword_filter(['service']) @convert_model - def list( - self, params: EndpointSearchQueryRequest - ) -> Union[EndpointsResponse, dict]: + def list(self, params: EndpointSearchQueryRequest) -> Union[EndpointsResponse, dict]: """ list endpoints of service Args: @@ -32,10 +30,8 @@ def list( } """ - service = params.service - endpoint_mgr: EndpointManager = EndpointManager() - endpoints_data, total_count = endpoint_mgr.list_endpoints(service) + endpoints_data, total_count = endpoint_mgr.list_endpoints(params.service) return EndpointsResponse( results=endpoints_data, From 457d2ee73eb352362899399230da4294e389db1c Mon Sep 17 00:00:00 2001 From: Jongmin Kim Date: Sun, 19 Nov 2023 17:59:23 +0900 Subject: [PATCH 07/45] refactor: add type hint --- src/spaceone/identity/manager/endpoint_manager.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/spaceone/identity/manager/endpoint_manager.py b/src/spaceone/identity/manager/endpoint_manager.py index 60894e22..5ba0aae8 100644 --- a/src/spaceone/identity/manager/endpoint_manager.py +++ b/src/spaceone/identity/manager/endpoint_manager.py @@ -1,4 +1,5 @@ import logging +from typing import Tuple from spaceone.core import config from spaceone.core.manager import BaseManager @@ -8,10 +9,10 @@ class EndpointManager(BaseManager): - def list_endpoints(self, service=None): - endpoints = config.get_global('ENDPOINTS', []) + def list_endpoints(self, service: str = None) -> Tuple[list, int]: + endpoints: list = config.get_global('ENDPOINTS', []) if service: - endpoints = [endpoint for endpoint in endpoints if endpoint.get('service') == service] + endpoints: list = [endpoint for endpoint in endpoints if endpoint.get('service') == service] return endpoints, len(endpoints) From bd3152be6bc51fed57ec15deea8ef573e7d2f911 Mon Sep 17 00:00:00 2001 From: Jongmin Kim Date: Sun, 19 Nov 2023 17:59:34 +0900 Subject: [PATCH 08/45] refactor: add authorization scope --- src/spaceone/identity/service/endpoint_service.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/spaceone/identity/service/endpoint_service.py b/src/spaceone/identity/service/endpoint_service.py index c81c4ac5..6e5d798e 100644 --- a/src/spaceone/identity/service/endpoint_service.py +++ b/src/spaceone/identity/service/endpoint_service.py @@ -10,7 +10,7 @@ class EndpointService(BaseService): - @transaction + @transaction(append_meta={'authorization.scope': 'PUBLIC'}) # @append_query_filter(['service']) # @append_keyword_filter(['service']) @convert_model @@ -24,17 +24,14 @@ def list(self, params: EndpointSearchQueryRequest) -> Union[EndpointsResponse, d } Returns: - EndpointsResponse: { - 'results': 'list', - 'total_count': 'int' - } + EndpointsResponse """ endpoint_mgr: EndpointManager = EndpointManager() - endpoints_data, total_count = endpoint_mgr.list_endpoints(params.service) + endpoints_info, total_count = endpoint_mgr.list_endpoints(params.service) return EndpointsResponse( - results=endpoints_data, + results=endpoints_info, total_count=total_count ) From d00146028e0b7ec2834001f32b37b3e59e3d42d6 Mon Sep 17 00:00:00 2001 From: Jongmin Kim Date: Sun, 19 Nov 2023 17:59:57 +0900 Subject: [PATCH 09/45] refactor: remove unused proto conf --- src/spaceone/identity/conf/proto_conf.py | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 src/spaceone/identity/conf/proto_conf.py diff --git a/src/spaceone/identity/conf/proto_conf.py b/src/spaceone/identity/conf/proto_conf.py deleted file mode 100644 index bf18f704..00000000 --- a/src/spaceone/identity/conf/proto_conf.py +++ /dev/null @@ -1,16 +0,0 @@ -PROTO = { - 'spaceone.identity.api.v1.domain': ['Domain'], - 'spaceone.identity.api.v1.domain_owner': ['DomainOwner'], - 'spaceone.identity.api.v1.endpoint': ['Endpoint'], - 'spaceone.identity.api.v1.provider': ['Provider'], - 'spaceone.identity.api.v1.service_account': ['ServiceAccount'], - 'spaceone.identity.api.v1.project_group': ['ProjectGroup'], - 'spaceone.identity.api.v1.project': ['Project'], - 'spaceone.identity.api.v1.policy': ['Policy'], - 'spaceone.identity.api.v1.role': ['Role'], - 'spaceone.identity.api.v1.role_binding': ['RoleBinding'], - 'spaceone.identity.api.v1.user': ['User'], - 'spaceone.identity.api.v1.api_key': ['APIKey'], - 'spaceone.identity.api.v1.token': ['Token'], - 'spaceone.identity.api.v1.authorization': ['Authorization'] -} From 827919fbe094a99ee86686e1dca3f65f3b01fa07 Mon Sep 17 00:00:00 2001 From: Jongmin Kim Date: Sun, 19 Nov 2023 18:00:12 +0900 Subject: [PATCH 10/45] feat: add provider APIs --- .../identity/interface/grpc/provider.py | 26 ++- .../identity/manager/provider_manager.py | 27 +-- .../{provider_model.py => provider_db.py} | 10 +- .../identity/model/provider_request.py | 6 +- .../identity/model/provider_response.py | 8 +- src/spaceone/identity/service/provider.py | 41 ----- .../identity/service/provider_service.py | 157 ++++++++++-------- 7 files changed, 126 insertions(+), 149 deletions(-) rename src/spaceone/identity/model/{provider_model.py => provider_db.py} (76%) delete mode 100644 src/spaceone/identity/service/provider.py diff --git a/src/spaceone/identity/interface/grpc/provider.py b/src/spaceone/identity/interface/grpc/provider.py index 62e99195..12cb3401 100644 --- a/src/spaceone/identity/interface/grpc/provider.py +++ b/src/spaceone/identity/interface/grpc/provider.py @@ -1,8 +1,6 @@ from spaceone.core.pygrpc import BaseAPI from spaceone.api.identity.v2 import provider_pb2, provider_pb2_grpc -from spaceone.identity.service.service_account_service import ( - ServiceAccountService, -) +from spaceone.identity.service.provider_service import ProviderService class Provider(BaseAPI, provider_pb2_grpc.ProviderServicer): @@ -11,36 +9,36 @@ class Provider(BaseAPI, provider_pb2_grpc.ProviderServicer): def create(self, request, context): params, metadata = self.parse_request(request, context) - service_account_svc = ServiceAccountService(metadata) - response: dict = service_account_svc.create(params) + provider_svc = ProviderService(metadata) + response: dict = provider_svc.create(params) return self.dict_to_message(response) def update(self, request, context): params, metadata = self.parse_request(request, context) - service_account_svc = ServiceAccountService(metadata) - response: dict = service_account_svc.update(params) + provider_svc = ProviderService(metadata) + response: dict = provider_svc.update(params) return self.dict_to_message(response) def delete(self, request, context): params, metadata = self.parse_request(request, context) - service_account_svc = ServiceAccountService(metadata) + service_account_svc = ProviderService(metadata) service_account_svc.delete(params) return self.empty() def get(self, request, context): params, metadata = self.parse_request(request, context) - service_account_svc = ServiceAccountService(metadata) - response: dict = service_account_svc.get(params) + provider_svc = ProviderService(metadata) + response: dict = provider_svc.get(params) return self.dict_to_message(response) def list(self, request, context): params, metadata = self.parse_request(request, context) - service_account_svc = ServiceAccountService(metadata) - response: dict = service_account_svc.list(params) + provider_svc = ProviderService(metadata) + response: dict = provider_svc.list(params) return self.dict_to_message(response) def stat(self, request, context): params, metadata = self.parse_request(request, context) - service_account_svc = ServiceAccountService(metadata) - response: dict = service_account_svc.stat(params) + provider_svc = ProviderService(metadata) + response: dict = provider_svc.stat(params) return self.dict_to_message(response) diff --git a/src/spaceone/identity/manager/provider_manager.py b/src/spaceone/identity/manager/provider_manager.py index 72e8259e..0e723e90 100644 --- a/src/spaceone/identity/manager/provider_manager.py +++ b/src/spaceone/identity/manager/provider_manager.py @@ -1,8 +1,9 @@ import logging +from typing import Tuple, List from spaceone.core.manager import BaseManager from spaceone.identity.conf.provider_conf import DEFAULT_PROVIDERS -from spaceone.identity.model.provider_model import Provider +from spaceone.identity.model.provider_db import Provider _LOGGER = logging.getLogger(__name__) @@ -11,10 +12,10 @@ class ProviderManager(BaseManager): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.provider_model: Provider = self.locator.get_model('Provider') + self.provider_model = Provider - def create_provider(self, params): - def _rollback(provider_vo): + def create_provider(self, params: dict) -> Provider: + def _rollback(provider_vo: Provider): _LOGGER.info(f'[create_provider._rollback] Create provider : {provider_vo.provider}') provider_vo.delete() @@ -23,7 +24,7 @@ def _rollback(provider_vo): return provider_vo - def update_provider(self, params): + def update_provider(self, params: dict) -> Provider: def _rollback(old_data): _LOGGER.info(f'[update_provider._rollback] Revert Data : {old_data["provider"]}') provider_vo.update(old_data) @@ -33,23 +34,23 @@ def _rollback(old_data): return provider_vo.update(params) - def delete_provider(self, provider, domain_id): - provider_vo: Provider = self.get_provider(provider, domain_id) + def delete_provider(self, provider: str, domain_id: str) -> None: + provider_vo = self.get_provider(provider, domain_id) provider_vo.delete() - def get_provider(self, provider, domain_id, only=None): - return self.provider_model.get(provider=provider, domain_id=domain_id, only=only) + def get_provider(self, provider: str, domain_id: str) -> Provider: + return self.provider_model.get(provider=provider, domain_id=domain_id) - def filter_providers(self, **conditions): + def filter_providers(self, **conditions) -> Tuple[Provider, ...]: return self.provider_model.filter(**conditions) - def list_providers(self, query={}): + def list_providers(self, query: dict) -> Tuple[list, int]: return self.provider_model.query(**query) - def stat_providers(self, query): + def stat_providers(self, query: dict) -> dict: return self.provider_model.stat(**query) - def create_default_providers(self, installed_providers, domain_id): + def create_default_providers(self, installed_providers: List[str], domain_id: str) -> None: for provider in DEFAULT_PROVIDERS: if provider['provider'] not in installed_providers: _LOGGER.debug(f'Create default provider: {provider["name"]}') diff --git a/src/spaceone/identity/model/provider_model.py b/src/spaceone/identity/model/provider_db.py similarity index 76% rename from src/spaceone/identity/model/provider_model.py rename to src/spaceone/identity/model/provider_db.py index 834b96fb..e4d1076b 100644 --- a/src/spaceone/identity/model/provider_model.py +++ b/src/spaceone/identity/model/provider_db.py @@ -6,11 +6,11 @@ class Provider(MongoModel): provider = StringField(max_length=40, unique_with='domain_id') name = StringField(max_length=255) order = IntField(min_value=1, default=10) - template = DictField() - metadata = DictField() - capability = DictField() - tags = DictField() - domain_id = StringField(max_length=255) + template = DictField(default=None) + metadata = DictField(default=None) + capability = DictField(default=None) + tags = DictField(default=None) + domain_id = StringField(max_length=40) created_at = DateTimeField(auto_now_add=True) meta = { diff --git a/src/spaceone/identity/model/provider_request.py b/src/spaceone/identity/model/provider_request.py index 6192c009..2df07e49 100644 --- a/src/spaceone/identity/model/provider_request.py +++ b/src/spaceone/identity/model/provider_request.py @@ -23,7 +23,7 @@ class ProviderCreateRequest(BaseModel): class ProviderUpdateRequest(BaseModel): - provider_id: str + provider: str name: Union[str, None] = None order: Union[int, None] = None template: Union[dict, None] = None @@ -34,12 +34,12 @@ class ProviderUpdateRequest(BaseModel): class ProviderDeleteRequest(BaseModel): - provider_id: str + provider: str domain_id: str class ProviderGetRequest(BaseModel): - provider_id: str + provider: str domain_id: str diff --git a/src/spaceone/identity/model/provider_response.py b/src/spaceone/identity/model/provider_response.py index 3237c6ab..b3855087 100644 --- a/src/spaceone/identity/model/provider_response.py +++ b/src/spaceone/identity/model/provider_response.py @@ -1,6 +1,7 @@ from datetime import datetime from typing import Union, List from pydantic import BaseModel +from spaceone.core import utils __all__ = ["ProviderResponse", "ProvidersResponse"] @@ -13,9 +14,14 @@ class ProviderResponse(BaseModel): metadata: Union[dict, None] = None capability: Union[dict, None] = None tags: Union[dict, None] = None - domain_id: str + domain_id: Union[str, None] = None created_at: Union[datetime, None] = None + def dict(self, *args, **kwargs): + data = super().dict(*args, **kwargs) + data['created_at'] = utils.datetime_to_iso8601(data['created_at']) + return data + class ProvidersResponse(BaseModel): results: List[ProviderResponse] = [] diff --git a/src/spaceone/identity/service/provider.py b/src/spaceone/identity/service/provider.py deleted file mode 100644 index f20ab4e2..00000000 --- a/src/spaceone/identity/service/provider.py +++ /dev/null @@ -1,41 +0,0 @@ -import logging -from typing import Union -from spaceone.core.service import BaseService, transaction, convert_model -from spaceone.identity.model.provider_request import * -from spaceone.identity.model.provider_response import * - -_LOGGER = logging.getLogger(__name__) - - -class Provider(BaseService): - @transaction - @convert_model - def create(self, params: ProviderCreateRequest) -> Union[ProviderResponse, dict]: - return {} - - @transaction - @convert_model - def update(self, params: ProviderUpdateRequest) -> Union[ProviderResponse, dict]: - return {} - - @transaction - @convert_model - def delete(self, params: ProviderDeleteRequest) -> None: - pass - - @transaction - @convert_model - def get(self, params: ProviderGetRequest) -> Union[ProviderResponse, dict]: - return {} - - @transaction - @convert_model - def list( - self, params: ProviderSearchQueryRequest - ) -> Union[ProvidersResponse, dict]: - return {} - - @transaction - @convert_model - def stat(self, params: ProviderStatQueryRequest) -> dict: - return {} diff --git a/src/spaceone/identity/service/provider_service.py b/src/spaceone/identity/service/provider_service.py index b635e1c7..959710e3 100644 --- a/src/spaceone/identity/service/provider_service.py +++ b/src/spaceone/identity/service/provider_service.py @@ -1,147 +1,160 @@ +import logging +from typing import Union from spaceone.core import cache -from spaceone.core.service import * -from spaceone.core import utils +from spaceone.core.service import BaseService, transaction, convert_model, append_query_filter, append_keyword_filter +from spaceone.identity.model.provider_request import * +from spaceone.identity.model.provider_response import * from spaceone.identity.manager.provider_manager import ProviderManager +_LOGGER = logging.getLogger(__name__) + -@authentication_handler -@authorization_handler -@mutation_handler -@event_handler class ProviderService(BaseService): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.provider_mgr: ProviderManager = self.locator.get_manager('ProviderManager') + self.provider_mgr = ProviderManager() @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['provider', 'name', 'domain_id']) - def create(self, params): - """ + @convert_model + def create(self, params: ProviderCreateRequest) -> Union[ProviderResponse, dict]: + """ create provider + Args: - params (dict): { - 'provider': 'str', - 'name': 'str', + params (ProviderCreateRequest): { + 'provider': 'str', # required + 'name': 'str', # required 'order': 'int', 'template': 'dict', 'metadata': 'dict', 'capability': 'dict', 'tags': 'dict', - 'domain_id': 'str' + 'domain_id': 'str' # required } Returns: - provider_vo (object) + ProviderResponse """ + # TODO: validate a template data # TODO: validate a capability data - return self.provider_mgr.create_provider(params) + provider_vo = self.provider_mgr.create_provider(params.dict()) + return ProviderResponse(**provider_vo.to_dict()) @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['provider', 'domain_id']) - def update(self, params): - """ + @convert_model + def update(self, params: ProviderUpdateRequest) -> Union[ProviderResponse, dict]: + """ update provider + Args: - params (dict): { - 'provider': 'str', + params (ProviderUpdateRequest): { + 'provider': 'str', # required 'name': 'str', 'order': 'int', 'template': 'dict', 'metadata': 'dict', 'capability': 'dict', - 'tags': 'list', - 'domain_id': 'str' + 'tags': 'dict', + 'domain_id': 'str' # required } Returns: - provider_vo (object) + ProviderResponse """ + # TODO: validate a template data # TODO: validate a capability data - return self.provider_mgr.update_provider(params) + provider_vo = self.provider_mgr.update_provider(params.dict()) + return ProviderResponse(**provider_vo.to_dict()) @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['provider', 'domain_id']) - def delete(self, params): - """ + @convert_model + def delete(self, params: ProviderDeleteRequest) -> None: + """ delete provider + Args: - params (dict): { - 'provider': 'str', - 'domain_id': 'str' + params (ProviderDeleteRequest): { + 'provider': 'str', # required + 'domain_id': 'str' # required } Returns: None """ - self.provider_mgr.delete_provider(params['provider']) - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['provider', 'domain_id']) - def get(self, params): - """ + self.provider_mgr.delete_provider(params.provider, params.domain_id) + + @transaction(append_meta={'authorization.scope': 'DOMAIN_READ'}) + @convert_model + def get(self, params: ProviderGetRequest) -> Union[ProviderResponse, dict]: + """ delete provider + Args: - params (dict): { - 'provider': 'str', - 'only': 'list', - 'domain_id': 'str' + params (ProviderGetRequest): { + 'provider': 'str', # required + 'domain_id': 'str' # required } Returns: - provider_vo (object) + ProviderResponse """ - domain_id = params['domain_id'] - - self._create_default_provider(domain_id) - return self.provider_mgr.get_provider(params['provider'], domain_id, params.get('only')) + provider_vo = self.provider_mgr.get_provider(params.provider, params.domain_id) + return ProviderResponse(**provider_vo.to_dict()) - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['domain_id']) + @transaction(append_meta={'authorization.scope': 'DOMAIN_READ'}) @append_query_filter(['provider', 'name', 'domain_id']) @append_keyword_filter(['provider', 'name']) - def list(self, params): - """ + @convert_model + def list(self, params: ProviderSearchQueryRequest) -> Union[ProvidersResponse, dict]: + """ list providers + Args: - params (dict): { - 'query': 'dict (spaceone.api.core.v1.Query)', - 'provider': 'str', - 'name': 'str', - 'domain_id': 'str' - } + params (ProviderSearchQueryRequest): { + 'query': 'dict (spaceone.api.core.v1.Query)', + 'provider': 'str', + 'name': 'str', + 'domain_id': 'str' # required + } Returns: - results (list): 'list of provider_vo' - total_count (int) + ProvidersResponse """ - domain_id = params['domain_id'] + query = params.query or {} - self._create_default_provider(domain_id) - return self.provider_mgr.list_providers(params.get('query', {})) + self._create_default_provider(params.domain_id) + + providers_vos, total_count = self.provider_mgr.list_providers(query) + + providers_info = [provider_vo.to_dict() for provider_vo in providers_vos] + return ProvidersResponse(results=providers_info, total_count=total_count) + + @transaction(append_meta={'authorization.scope': 'DOMAIN_READ'}) + @convert_model + def stat(self, params: ProviderStatQueryRequest) -> dict: + """ stat providers - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['query', 'domain_id']) - @append_query_filter(['domain_id']) - @append_keyword_filter(['provider', 'name']) - def stat(self, params): - """ Args: - params (dict): { + params (ProviderStatQueryRequest): { 'query': 'dict (spaceone.api.core.v1.StatisticsQuery)', - 'domain_id': 'str' + 'domain_id': 'str' # required } Returns: - values (list): 'list of statistics data' - total_count (int) + dict: { + 'results': 'list', + 'total_count': 'int' + } + """ - query = params.get('query', {}) + query = params.query or {} return self.provider_mgr.stat_providers(query) - @cache.cacheable(key='provider:{domain_id}:default:init', expire=300) + @cache.cacheable(key='identity:provider:{domain_id}:default:init', expire=300) def _create_default_provider(self, domain_id): provider_vos = self.provider_mgr.filter_providers(domain_id=domain_id) installed_providers = [provider_vo.provider for provider_vo in provider_vos] From 05326afa875debb1e325b5756a2a5372ac16c103 Mon Sep 17 00:00:00 2001 From: Jongmin Kim Date: Sun, 19 Nov 2023 18:49:56 +0900 Subject: [PATCH 11/45] chore: change doc string --- src/spaceone/identity/service/provider_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spaceone/identity/service/provider_service.py b/src/spaceone/identity/service/provider_service.py index 959710e3..41a537a9 100644 --- a/src/spaceone/identity/service/provider_service.py +++ b/src/spaceone/identity/service/provider_service.py @@ -139,7 +139,7 @@ def stat(self, params: ProviderStatQueryRequest) -> dict: Args: params (ProviderStatQueryRequest): { - 'query': 'dict (spaceone.api.core.v1.StatisticsQuery)', + 'query': 'dict (spaceone.api.core.v1.StatisticsQuery)', # required 'domain_id': 'str' # required } From 8ec4803b098b72c03b83d4c23a1ca03dacb5e20b Mon Sep 17 00:00:00 2001 From: ImMin5 Date: Sun, 19 Nov 2023 20:41:45 +0900 Subject: [PATCH 12/45] feat: remove identity v1 files (#73) Signed-off-by: ImMin5 --- src/setup.py | 40 ++- src/spaceone/__init__.py | 2 +- .../identity/conf/permissions_conf.py | 6 - src/spaceone/identity/conf/proto_conf.py | 16 - src/spaceone/identity/conf/provider_conf.py | 234 ------------- src/spaceone/identity/connector/__init__.py | 2 - .../connector/auth_plugin_connector.py | 97 ------ .../identity/connector/smtp_connector.py | 51 --- src/spaceone/identity/error/custom.py | 2 +- .../identity/error/error_authentication.py | 16 +- src/spaceone/identity/error/error_domain.py | 10 +- src/spaceone/identity/error/error_mfa.py | 10 +- src/spaceone/identity/error/error_project.py | 8 +- src/spaceone/identity/error/error_role.py | 22 +- src/spaceone/identity/error/error_smtp.py | 3 +- src/spaceone/identity/error/error_user.py | 28 +- src/spaceone/identity/info/__init__.py | 16 - src/spaceone/identity/info/api_key_info.py | 29 -- .../identity/info/authorization_info.py | 7 - src/spaceone/identity/info/common_info.py | 12 - src/spaceone/identity/info/domain_info.py | 60 ---- .../identity/info/domain_owner_info.py | 26 -- src/spaceone/identity/info/endpoint_info.py | 26 -- src/spaceone/identity/info/find_user_info.py | 28 -- src/spaceone/identity/info/policy_info.py | 0 .../identity/info/project_group_info.py | 108 ------ src/spaceone/identity/info/project_info.py | 71 ---- src/spaceone/identity/info/provider_info.py | 33 -- .../identity/info/role_binding_info.py | 43 --- src/spaceone/identity/info/role_info.py | 67 ---- .../identity/info/service_account_info.py | 47 --- src/spaceone/identity/info/token_info.py | 12 - src/spaceone/identity/info/user_info.py | 51 --- .../identity/interface/grpc/domain_owner.py | 37 -- src/spaceone/identity/lib/cipher.py | 2 +- src/spaceone/identity/lib/key_generator.py | 35 +- .../identity/manager/api_key_manager.py | 81 ----- .../identity/manager/authorization_manager.py | 126 ------- .../identity/manager/domain_owner_manager.py | 69 ---- .../identity/manager/domain_secret_manager.py | 83 ----- .../identity/manager/email_manager.py | 102 ------ .../identity/manager/endpoint_manager.py | 17 - .../identity/manager/mfa_manager/__init__.py | 74 ---- .../manager/mfa_manager/email_mfa_manger.py | 82 ----- .../identity/manager/project_group_manager.py | 89 ----- .../identity/manager/project_manager.py | 122 ------- .../identity/manager/provider_manager.py | 57 ---- .../identity/manager/role_binding_manager.py | 173 ---------- src/spaceone/identity/manager/role_manager.py | 56 --- .../manager/service_account_manager.py | 146 -------- .../manager/token_manager/__init__.py | 181 ---------- .../domain_owner_token_manager.py | 60 ---- .../token_manager/external_token_manager.py | 120 ------- .../token_manager/local_token_manager.py | 98 ------ src/spaceone/identity/model/api_key_model.py | 32 -- src/spaceone/identity/model/domain_model.py | 75 ---- .../identity/model/domain_owner_model.py | 48 --- .../identity/model/domain_secret_model.py | 28 -- src/spaceone/identity/model/policy_model.py | 33 -- .../identity/model/project_group_model.py | 39 --- src/spaceone/identity/model/project_model.py | 41 --- src/spaceone/identity/model/provider_model.py | 31 -- .../identity/model/role_binding_model.py | 66 ---- src/spaceone/identity/model/role_model.py | 56 --- .../identity/model/service_account_model.py | 53 --- src/spaceone/identity/model/user_model.py | 60 ---- src/spaceone/identity/service/base_service.py | 39 --- .../identity/service/domain_owner_service.py | 103 ------ .../identity/service/provider_service.py | 150 -------- .../template/authentication_code_en.html | 256 -------------- .../template/authentication_code_jp.html | 255 -------------- .../template/authentication_code_ko.html | 254 -------------- .../reset_pwd_link_when_pw_forgotten_en.html | 316 ----------------- .../reset_pwd_link_when_pw_forgotten_ja.html | 310 ----------------- .../reset_pwd_link_when_pw_forgotten_ko.html | 320 ----------------- .../reset_pwd_link_when_user_added_en.html | 305 ----------------- .../reset_pwd_link_when_user_added_ja.html | 305 ----------------- .../reset_pwd_link_when_user_added_ko.html | 305 ----------------- .../temp_pwd_when_pw_forgotten_en.html | 319 ----------------- .../temp_pwd_when_pw_forgotten_ja.html | 318 ----------------- .../temp_pwd_when_pw_forgotten_ko.html | 322 ------------------ .../template/temp_pwd_when_user_added_en.html | 313 ----------------- .../template/temp_pwd_when_user_added_ja.html | 315 ----------------- .../template/temp_pwd_when_user_added_ko.html | 316 ----------------- .../template/verification_MFA_code_en.html | 256 -------------- .../template/verification_MFA_code_jp.html | 255 -------------- .../template/verification_MFA_code_ko.html | 254 -------------- .../template/verification_code_en.html | 256 -------------- .../template/verification_code_ja.html | 254 -------------- .../template/verification_code_ko.html | 254 -------------- 90 files changed, 95 insertions(+), 9810 deletions(-) delete mode 100644 src/spaceone/identity/conf/permissions_conf.py delete mode 100644 src/spaceone/identity/conf/proto_conf.py delete mode 100644 src/spaceone/identity/conf/provider_conf.py delete mode 100644 src/spaceone/identity/connector/__init__.py delete mode 100644 src/spaceone/identity/connector/auth_plugin_connector.py delete mode 100644 src/spaceone/identity/connector/smtp_connector.py delete mode 100644 src/spaceone/identity/info/__init__.py delete mode 100644 src/spaceone/identity/info/api_key_info.py delete mode 100644 src/spaceone/identity/info/authorization_info.py delete mode 100644 src/spaceone/identity/info/common_info.py delete mode 100644 src/spaceone/identity/info/domain_info.py delete mode 100644 src/spaceone/identity/info/domain_owner_info.py delete mode 100644 src/spaceone/identity/info/endpoint_info.py delete mode 100644 src/spaceone/identity/info/find_user_info.py delete mode 100644 src/spaceone/identity/info/policy_info.py delete mode 100644 src/spaceone/identity/info/project_group_info.py delete mode 100644 src/spaceone/identity/info/project_info.py delete mode 100644 src/spaceone/identity/info/provider_info.py delete mode 100644 src/spaceone/identity/info/role_binding_info.py delete mode 100644 src/spaceone/identity/info/role_info.py delete mode 100644 src/spaceone/identity/info/service_account_info.py delete mode 100644 src/spaceone/identity/info/token_info.py delete mode 100644 src/spaceone/identity/info/user_info.py delete mode 100644 src/spaceone/identity/interface/grpc/domain_owner.py delete mode 100644 src/spaceone/identity/manager/api_key_manager.py delete mode 100644 src/spaceone/identity/manager/authorization_manager.py delete mode 100644 src/spaceone/identity/manager/domain_owner_manager.py delete mode 100644 src/spaceone/identity/manager/domain_secret_manager.py delete mode 100644 src/spaceone/identity/manager/email_manager.py delete mode 100644 src/spaceone/identity/manager/endpoint_manager.py delete mode 100644 src/spaceone/identity/manager/mfa_manager/__init__.py delete mode 100644 src/spaceone/identity/manager/mfa_manager/email_mfa_manger.py delete mode 100644 src/spaceone/identity/manager/project_group_manager.py delete mode 100644 src/spaceone/identity/manager/project_manager.py delete mode 100644 src/spaceone/identity/manager/provider_manager.py delete mode 100644 src/spaceone/identity/manager/role_binding_manager.py delete mode 100644 src/spaceone/identity/manager/role_manager.py delete mode 100644 src/spaceone/identity/manager/service_account_manager.py delete mode 100644 src/spaceone/identity/manager/token_manager/__init__.py delete mode 100644 src/spaceone/identity/manager/token_manager/domain_owner_token_manager.py delete mode 100644 src/spaceone/identity/manager/token_manager/external_token_manager.py delete mode 100644 src/spaceone/identity/manager/token_manager/local_token_manager.py delete mode 100644 src/spaceone/identity/model/api_key_model.py delete mode 100644 src/spaceone/identity/model/domain_model.py delete mode 100644 src/spaceone/identity/model/domain_owner_model.py delete mode 100644 src/spaceone/identity/model/domain_secret_model.py delete mode 100644 src/spaceone/identity/model/policy_model.py delete mode 100644 src/spaceone/identity/model/project_group_model.py delete mode 100644 src/spaceone/identity/model/project_model.py delete mode 100644 src/spaceone/identity/model/provider_model.py delete mode 100644 src/spaceone/identity/model/role_binding_model.py delete mode 100644 src/spaceone/identity/model/role_model.py delete mode 100644 src/spaceone/identity/model/service_account_model.py delete mode 100644 src/spaceone/identity/model/user_model.py delete mode 100644 src/spaceone/identity/service/base_service.py delete mode 100644 src/spaceone/identity/service/domain_owner_service.py delete mode 100644 src/spaceone/identity/service/provider_service.py delete mode 100644 src/spaceone/identity/template/authentication_code_en.html delete mode 100644 src/spaceone/identity/template/authentication_code_jp.html delete mode 100644 src/spaceone/identity/template/authentication_code_ko.html delete mode 100644 src/spaceone/identity/template/reset_pwd_link_when_pw_forgotten_en.html delete mode 100644 src/spaceone/identity/template/reset_pwd_link_when_pw_forgotten_ja.html delete mode 100644 src/spaceone/identity/template/reset_pwd_link_when_pw_forgotten_ko.html delete mode 100644 src/spaceone/identity/template/reset_pwd_link_when_user_added_en.html delete mode 100644 src/spaceone/identity/template/reset_pwd_link_when_user_added_ja.html delete mode 100644 src/spaceone/identity/template/reset_pwd_link_when_user_added_ko.html delete mode 100644 src/spaceone/identity/template/temp_pwd_when_pw_forgotten_en.html delete mode 100644 src/spaceone/identity/template/temp_pwd_when_pw_forgotten_ja.html delete mode 100644 src/spaceone/identity/template/temp_pwd_when_pw_forgotten_ko.html delete mode 100644 src/spaceone/identity/template/temp_pwd_when_user_added_en.html delete mode 100644 src/spaceone/identity/template/temp_pwd_when_user_added_ja.html delete mode 100644 src/spaceone/identity/template/temp_pwd_when_user_added_ko.html delete mode 100644 src/spaceone/identity/template/verification_MFA_code_en.html delete mode 100644 src/spaceone/identity/template/verification_MFA_code_jp.html delete mode 100644 src/spaceone/identity/template/verification_MFA_code_ko.html delete mode 100644 src/spaceone/identity/template/verification_code_en.html delete mode 100644 src/spaceone/identity/template/verification_code_ja.html delete mode 100644 src/spaceone/identity/template/verification_code_ko.html diff --git a/src/setup.py b/src/setup.py index 919acc27..af1bb527 100644 --- a/src/setup.py +++ b/src/setup.py @@ -17,30 +17,28 @@ from setuptools import setup, find_packages setup( - name='spaceone-identity', - version=os.environ.get('PACKAGE_VERSION'), - description='SpaceONE identity service', - long_description='', - url='https://www.spaceone.dev/', - author='MEGAZONE SpaceONE Team', - author_email='admin@spaceone.dev', - license='Apache License 2.0', + name="spaceone-identity", + version=os.environ.get("PACKAGE_VERSION"), + description="SpaceONE identity service", + long_description="", + url="https://www.spaceone.dev/", + author="MEGAZONE SpaceONE Team", + author_email="admin@spaceone.dev", + license="Apache License 2.0", packages=find_packages(), install_requires=[ - 'spaceone-core', - 'spaceone-api', - 'mongoengine', - 'langcodes', - 'bcrypt', + "spaceone-core", + "spaceone-api", + "mongoengine", + "langcodes", + "bcrypt", # 'bcrypt==3.1.6', - 'redis', - 'jinja2', - 'fakeredis', - 'mongomock', - 'pytz' + "redis", + "jinja2", + "fakeredis", + "mongomock", + "pytz", ], - package_data={ - 'spaceone': ['identity/template/*.html'] - }, + package_data={"spaceone": ["identity/template/*.html"]}, zip_safe=False, ) diff --git a/src/spaceone/__init__.py b/src/spaceone/__init__.py index 69e3be50..8db66d3d 100644 --- a/src/spaceone/__init__.py +++ b/src/spaceone/__init__.py @@ -1 +1 @@ -__path__ = __import__('pkgutil').extend_path(__path__, __name__) +__path__ = __import__("pkgutil").extend_path(__path__, __name__) diff --git a/src/spaceone/identity/conf/permissions_conf.py b/src/spaceone/identity/conf/permissions_conf.py deleted file mode 100644 index f9a3b3b1..00000000 --- a/src/spaceone/identity/conf/permissions_conf.py +++ /dev/null @@ -1,6 +0,0 @@ -DEFAULT_PERMISSIONS = [ - 'identity.User.get', - 'identity.User.update', - 'identity.User.verify_email', - 'identity.User.confirm_email' -] diff --git a/src/spaceone/identity/conf/proto_conf.py b/src/spaceone/identity/conf/proto_conf.py deleted file mode 100644 index bf18f704..00000000 --- a/src/spaceone/identity/conf/proto_conf.py +++ /dev/null @@ -1,16 +0,0 @@ -PROTO = { - 'spaceone.identity.api.v1.domain': ['Domain'], - 'spaceone.identity.api.v1.domain_owner': ['DomainOwner'], - 'spaceone.identity.api.v1.endpoint': ['Endpoint'], - 'spaceone.identity.api.v1.provider': ['Provider'], - 'spaceone.identity.api.v1.service_account': ['ServiceAccount'], - 'spaceone.identity.api.v1.project_group': ['ProjectGroup'], - 'spaceone.identity.api.v1.project': ['Project'], - 'spaceone.identity.api.v1.policy': ['Policy'], - 'spaceone.identity.api.v1.role': ['Role'], - 'spaceone.identity.api.v1.role_binding': ['RoleBinding'], - 'spaceone.identity.api.v1.user': ['User'], - 'spaceone.identity.api.v1.api_key': ['APIKey'], - 'spaceone.identity.api.v1.token': ['Token'], - 'spaceone.identity.api.v1.authorization': ['Authorization'] -} diff --git a/src/spaceone/identity/conf/provider_conf.py b/src/spaceone/identity/conf/provider_conf.py deleted file mode 100644 index 5f681ef8..00000000 --- a/src/spaceone/identity/conf/provider_conf.py +++ /dev/null @@ -1,234 +0,0 @@ -DEFAULT_PROVIDERS = [{ - "provider": "aws", - "name": "AWS", - "order": 1, - "template": { - "service_account": { - "schema": { - "type": "object", - "properties": { - "account_id": { - "title": "Account ID", - "type": "string", - "minLength": 4 - } - }, - "required": ["account_id"] - } - } - }, - "metadata": { - "view": { - "layouts": { - "help:service_account:create": { - "name": "Creation Help", - "type": "markdown", - "options": { - "markdown": { - "en": ( - "# Help for AWS Users\n" - "## Find Your AWS Account ID\n" - "Get your AWS Account ID.\n" - "[AWS Account ID](https://docs.aws.amazon.com/IAM/latest/UserGuide/console_account-alias.html)\n" - "## Get Your Assume role\n" - "Granting permissions to create temporary security credentials.\n" - "[AWS Assume Role](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_permissions-to-switch.html)\n" - "## Issue AWS Access Key \n" - "Get your AWS Access Key & AWS Secret Key\n" - "[AWS Access Key & AWS Secret Key](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html#Using_CreateAccessKey)\n" - ), - "ko": ( - "# AWS 이용자 가이드\n" - "## AWS 어카운트 아이디(Account ID) 찾기\n" - "사용자의 AWS 어카운트 아이디 AWS 콘솔(Console)에서 확인하기\n" - "[AWS Account ID](https://docs.aws.amazon.com/ko_kr/IAM/latest/UserGuide/console_account-alias.html)\n" - "## Assume role 획득하기\n" - "임시 보안 자격증명을 만들 수있는 권한을 부여하기.\n" - "[AWS Assume Role](https://docs.aws.amazon.com/ko_kr/IAM/latest/UserGuide/id_roles_use_permissions-to-switch.html)\n" - "## AWS Access Key 발급하기\n" - "AWS Access Key & AWS Secret Key 발급하기\n" - "[AWS Access Key & AWS Secret Key](https://docs.aws.amazon.com/ko_kr/IAM/latest/UserGuide/id_credentials_access-keys.html#Using_CreateAccessKey)\n" - ), - - } - } - } - } - } - }, - "capability": { - "general_service_account_schema": [ - "aws_assume_role" - ], - "trusted_service_account_schema": [ - "aws_access_key" - ], - "support_trusted_service_account": True, - "supported_schema": [ - "aws_access_key" - ] - }, - "tags": { - 'color': '#FF9900', - 'icon': 'https://spaceone-custom-assets.s3.ap-northeast-2.amazonaws.com/console-assets/icons/aws.svg', - 'external_link_template': 'https://<%- data.account_id %>.signin.aws.amazon.com/console' - } -}, { - "provider": "google_cloud", - "version": "v1", - "name": "Google Cloud", - "order": 2, - "template": { - "service_account": { - "schema": { - "type": "object", - "properties": { - "project_id": { - "title": "Project ID", - "type": "string", - "minLength": 4 - } - }, - "required": ["project_id"] - } - } - }, - "metadata": { - "view": { - "layouts": { - "help:service_account:create": { - "name": "Creation Help", - "type": "markdown", - "options": { - "markdown": { - "en": ( - "# Getting started with Google Cloud\n" - "## Identifying Your Project\n" - "Get your Project infos (Project Name, Project ID and Project number)\n" - "[Project Info](https://cloud.google.com/resource-manager/docs/creating-managing-projects#identifying_projects)\n" - "## Get Your Service Account Key(JSON)\n" - "Generate Your JSON Service Account Key.\n" - "[Service Account Key](https://cloud.google.com/docs/authentication/getting-started)\n" - ), - "ko": ( - "# Google Cloud 시작 가이드\n" - "## Project 정보 확인하기\n" - "프로젝트 명, 프로젝트 아이디, 프로젝트 번호 등의 프로젝트 정보 확인하기\n" - "[Project Info](https://cloud.google.com/resource-manager/docs/creating-managing-projects?hl=ko#identifying_projects)\n" - "## 서비스 어카운트 키(JSON) 받기\n" - "JSON 포멧의 서비스 어카운트 키를 생성하기.\n" - "[Service Account Key](https://cloud.google.com/docs/authentication/getting-started?hl=ko)\n" - ), - - } - } - } - } - } - }, - "capability": { - "general_service_account_schema": [ - "google_cloud_project_id" - ], - "trusted_service_account_schema": [ - "google_oauth2_credentials" - ], - "support_trusted_service_account": True, - "supported_schema": ["google_oauth2_credentials"] - }, - "tags": { - 'color': '#4285F4', - 'icon': 'https://spaceone-custom-assets.s3.ap-northeast-2.amazonaws.com/console-assets/icons/google_cloud.svg', - 'external_link_template': 'https://console.cloud.google.com/home/dashboard?project=<%- data.project_id %>', - 'label': 'Google' - } -}, { - "provider": "azure", - "name": "Azure", - "order": 3, - "template": { - "service_account": { - "schema": { - "type": "object", - "properties": { - "tenant_id": { - "title": "Tenant ID", - "type": "string", - "minLength": 4 - }, - "subscription_id": { - "title": "Subscription ID", - "type": "string", - "minLength": 4 - } - }, - "required": ["tenant_id", "subscription_id"] - } - } - }, - "metadata": { - "view": { - "layouts": { - "help:service_account:create": { - "name": "Creation Help", - "type": "markdown", - "options": { - "markdown": { - "en": ( - "# Help for Azure Users\n" - "## Find Your Azure Subscription ID\n" - "Azure Subscription ID via CLI.\n" - "[Azure Subscription CLI](https://docs.microsoft.com/en-us/cli/azure/ext/account/account/subscription?view=azure-cli-latest)\n" - "Azure Subscription ID via PowerShell.\n" - "[Azure Subscription PowerShell](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-authenticate-service-principal-powershell)\n" - "Create Azure Subscription via Portal.\n" - "[Azure Subscription Portal](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal)\n" - "## Find Your Azure Tenant ID\n" - "Azure Tenant ID via CLI.\n" - "[Azure Tenant CLI](https://docs.microsoft.com/en-us/cli/azure/ext/account/account/tenant?view=azure-cli-latest)\n" - "Azure Tenant ID via PowerShell.\n" - "[Azure Tenant PowerShell](https://docs.microsoft.com/en-us/powershell/module/az.accounts/get-aztenant?view=azps-5.0.0)\n" - "## Get Your Client Secret and ID\n" - "Check Client Secret via Portal.\n" - "[Azure Client Secret Portal](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal)\n" - ), - "ko": ( - "# Azure 이용자 가이드\n" - "## Azure 구독 아이디(Subscription ID) 찾기\n" - "CLI에서 사용자의 구독 아이디 확인하기.\n" - "[Azure Subscription CLI](https://docs.microsoft.com/en-us/cli/azure/ext/account/account/subscription?view=azure-cli-latest)\n" - "PowerShell에서 사용자의 구독 아이디 확인하기.\n" - "[Azure Subscription PowerShell](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-authenticate-service-principal-powershell)\n" - "포털에서 사용자의 구독 아이디 확인하기.\n" - "[Azure Subscription Portal](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal)\n" - "## Azure 테넌트 아이디(Tenant ID) 찾기\n" - "CLI에서 사용자의 테넌트 아이디 확인하기.\n" - "[Azure Tenant CLI](https://docs.microsoft.com/en-us/cli/azure/ext/account/account/tenant?view=azure-cli-latest)\n" - "PowerShell에서 사용자의 테넌트 아이디 확인하기.\n" - "[Azure Tenant PowerShell](https://docs.microsoft.com/en-us/powershell/module/az.accounts/get-aztenant?view=azps-5.0.0)\n" - "## 사용자의 클라이언트 시크릿 정보(Client Secret&ID) 가져오기\n" - "포털에서 사용자의 클라이언트 시크릿 정보 확인하기.\n" - "[Azure Client Secret Portal](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal)\n" - ), - - } - } - } - } - } - }, - "capability": { - "general_service_account_schema": [ - "azure_subscription_id" - ], - "trusted_service_account_schema": [ - "azure_credentials", - ], - "support_trusted_service_account": True, - "supported_schema": ["azure_client_secret"] - }, - "tags": { - 'color': '#00BCF2', - 'icon': 'https://spaceone-custom-assets.s3.ap-northeast-2.amazonaws.com/console-assets/icons/azure.svg' - } -}] \ No newline at end of file diff --git a/src/spaceone/identity/connector/__init__.py b/src/spaceone/identity/connector/__init__.py deleted file mode 100644 index eb9fb505..00000000 --- a/src/spaceone/identity/connector/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from spaceone.identity.connector.auth_plugin_connector import AuthPluginConnector -from spaceone.identity.connector.smtp_connector import SMTPConnector diff --git a/src/spaceone/identity/connector/auth_plugin_connector.py b/src/spaceone/identity/connector/auth_plugin_connector.py deleted file mode 100644 index 6bfaedc6..00000000 --- a/src/spaceone/identity/connector/auth_plugin_connector.py +++ /dev/null @@ -1,97 +0,0 @@ -import logging - -from google.protobuf.json_format import MessageToDict -from spaceone.core import pygrpc -from spaceone.core.connector import BaseConnector -from spaceone.core.utils import parse_grpc_endpoint - -from spaceone.identity.error.error_authentication import * - -_LOGGER = logging.getLogger(__name__) - - -class AuthPluginConnector(BaseConnector): - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.client = None - - def initialize(self, endpoint): - static_endpoint = self.config.get('endpoint') - - if static_endpoint: - endpoint = static_endpoint - - _LOGGER.info(f'[initialize] endpoint: {endpoint}') - - e = parse_grpc_endpoint(endpoint) - self.client = pygrpc.client(endpoint=e['endpoint'], ssl_enabled=e['ssl_enabled']) - - def call_login(self, credentials, options, secret_data, schema=None): - params = { - 'options': options, - 'secret_data': secret_data, - 'schema': schema, - 'user_credentials': credentials - } - - try: - response = self.client.Auth.login(params, metadata=self.transaction.get_connection_meta()) - except ERROR_BASE as e: - _LOGGER.error(f'[call_login] Auth.login failed. (reason={e.message})') - raise ERROR_INVALID_CREDENTIALS() - except Exception as e: - _LOGGER.error(f'[call_login] Auth.login failed. (reason={str(e)})') - raise ERROR_INVALID_CREDENTIALS() - - return MessageToDict(response, preserving_proto_field_name=True) - - def init(self, options): - params = { - 'options': options - } - - try: - response = self.client.Auth.init(params, metadata=self.transaction.get_connection_meta()) - return MessageToDict(response, preserving_proto_field_name=True) - except ERROR_BASE as e: - raise ERROR_AUTHENTICATION_FAILURE_PLUGIN(message=e.message) - except Exception as e: - raise ERROR_AUTHENTICATION_FAILURE_PLUGIN(messsage=str(e)) - - def verify(self, options, secret_data=None, schema=None): - params = { - 'options': options, - 'secret_data': secret_data, - 'schema': schema - } - try: - # TODO: meta (plugin has no meta) - response = self.client.Auth.verify(params, metadata=self.transaction.get_connection_meta()) - return MessageToDict(response, preserving_proto_field_name=True) - except ERROR_BASE as e: - raise ERROR_AUTHENTICATION_FAILURE_PLUGIN(message=e.message) - except Exception as e: - raise ERROR_AUTHENTICATION_FAILURE_PLUGIN(messsage=str(e)) - - def call_find(self, keyword, user_id, options, secret_data={}, schema=None): - params = { - 'options': options, - 'secret_data': secret_data, - 'schema': schema, - 'keyword': keyword, - 'user_id': user_id - } - _LOGGER.info(f'[call_find] params: {params}') - - try: - response = self.client.Auth.find(params, metadata=self.transaction.get_connection_meta()) - - users_info = MessageToDict(response, preserving_proto_field_name=True) - _LOGGER.debug(f'[call_find] MessageToDict(user_info): {users_info}') - return users_info - - except ERROR_BASE as e: - raise ERROR_AUTHENTICATION_FAILURE_PLUGIN(message=e.message) - except Exception as e: - raise ERROR_AUTHENTICATION_FAILURE_PLUGIN(messsage=str(e)) diff --git a/src/spaceone/identity/connector/smtp_connector.py b/src/spaceone/identity/connector/smtp_connector.py deleted file mode 100644 index b7d5bb72..00000000 --- a/src/spaceone/identity/connector/smtp_connector.py +++ /dev/null @@ -1,51 +0,0 @@ -import logging -import smtplib -from email.mime.multipart import MIMEMultipart -from email.mime.text import MIMEText - -from spaceone.identity.error.error_smtp import * - -from spaceone.core.connector import BaseConnector - - -__all__ = ['SMTPConnector'] - -_LOGGER = logging.getLogger(__name__) - - -class SMTPConnector(BaseConnector): - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.smtp = None - host = self.config.get('host') - port = self.config.get('port') - user = self.config.get('user') - password = self.config.get('password') - self.from_email = self.config.get('from_email') - self.set_smtp(host, port, user, password) - - def set_smtp(self, host, port, user, password): - try: - self.smtp = smtplib.SMTP(host, port) - self.smtp.connect(host, port) - self.smtp.ehlo() - self.smtp.starttls() - self.smtp.login(user, password) - except Exception as e: - _LOGGER.error(f'[set_smtp] set smtp failed : Please check smtp config {e}') - raise ERROR_SMTP_CONNECTION_FAILED() - - def send_email(self, to_emails, subject, contents): - multipart_msg = MIMEMultipart("alternative") - - multipart_msg["Subject"] = subject - multipart_msg["From"] = self.from_email - multipart_msg["To"] = to_emails - - multipart_msg.attach(MIMEText(contents, 'html')) - - self.smtp.sendmail(self.from_email, to_emails.split(','), multipart_msg.as_string()) - - def quit_smtp(self): - self.smtp.quit() diff --git a/src/spaceone/identity/error/custom.py b/src/spaceone/identity/error/custom.py index 4a7a2cd2..f09ef448 100644 --- a/src/spaceone/identity/error/custom.py +++ b/src/spaceone/identity/error/custom.py @@ -2,4 +2,4 @@ class ERROR_GENERATE_KEY_FAILURE(ERROR_BASE): - _message = 'Error on generate key.' + _message = "Error on generate key." diff --git a/src/spaceone/identity/error/error_authentication.py b/src/spaceone/identity/error/error_authentication.py index 9745df04..9971641d 100644 --- a/src/spaceone/identity/error/error_authentication.py +++ b/src/spaceone/identity/error/error_authentication.py @@ -6,32 +6,32 @@ class ERROR_AUTHENTICATION_FAILURE(ERROR_AUTHENTICATE_FAILURE): class ERROR_NOT_AUTHENTICATED(ERROR_AUTHENTICATE_FAILURE): - _message = 'A user is not authenticated.' + _message = "A user is not authenticated." class ERROR_NOT_ALLOWED_ISSUE_TOKEN_API_USER(ERROR_AUTHENTICATE_FAILURE): - _message = 'API_USER are not allowed to issue tokens. (user_id = {user_id})' + _message = "API_USER are not allowed to issue tokens. (user_id = {user_id})" class ERROR_INVALID_CREDENTIALS(ERROR_AUTHENTICATE_FAILURE): - _message = 'Invalid credentials provided.' + _message = "Invalid credentials provided." class ERROR_AUTHENTICATION_FAILURE_PLUGIN(ERROR_INTERNAL_API): - _message = 'External plugin authentication exception. (reason = {message})' + _message = "External plugin authentication exception. (reason = {message})" class ERROR_INVALID_REFRESH_TOKEN(ERROR_AUTHENTICATE_FAILURE): - _message = 'Refresh token is invalid or expired.' + _message = "Refresh token is invalid or expired." class ERROR_REFRESH_COUNT(ERROR_AUTHENTICATE_FAILURE): - _message = 'The refresh count is exhausted.' + _message = "The refresh count is exhausted." class ERROR_NOT_FOUND_PRIVATE_KEY(ERROR_AUTHENTICATE_FAILURE): - _message = 'Private key not found. (purpose = {purpose})' + _message = "Private key not found. (purpose = {purpose})" class ERROR_MAX_REFRESH_COUNT(ERROR_AUTHENTICATE_FAILURE): - _message = 'The maximum refresh count has been exceeded. (max_refresh_count = {max_refresh_count})' + _message = "The maximum refresh count has been exceeded. (max_refresh_count = {max_refresh_count})" diff --git a/src/spaceone/identity/error/error_domain.py b/src/spaceone/identity/error/error_domain.py index dbc17873..fe11f7f2 100644 --- a/src/spaceone/identity/error/error_domain.py +++ b/src/spaceone/identity/error/error_domain.py @@ -2,23 +2,23 @@ class ERROR_DOMAIN_STATE(ERROR_BASE): - _message = 'Domain is disabled (domain_id = {domain_id})' + _message = "Domain is disabled (domain_id = {domain_id})" class ERROR_DOMAIN_AUTH_PLUGIN(ERROR_BASE): - _message = 'Failed to get endpoint of version = {version}, domain_id = {domain_id}' + _message = "Failed to get endpoint of version = {version}, domain_id = {domain_id}" class ERROR_EXTERNAL_USER_NOT_ALLOWED_API_USER(ERROR_INVALID_ARGUMENT): - _message = 'External user cannot be created with the API_USER type.' + _message = "External user cannot be created with the API_USER type." class ERROR_NOT_ALLOWED_ROLE_TYPE(ERROR_INVALID_ARGUMENT): - _message = 'Duplicate assignment of system roles and domain or project roles is not allowed.' + _message = "Duplicate assignment of system roles and domain or project roles is not allowed." class ERROR_NOT_ALLOWED_EXTERNAL_AUTHENTICATION(ERROR_INVALID_ARGUMENT): - _message = 'This domain does not allow external authentication.' + _message = "This domain does not allow external authentication." class ERROR_PLUGIN_IS_NOT_SET(ERROR_BASE): diff --git a/src/spaceone/identity/error/error_mfa.py b/src/spaceone/identity/error/error_mfa.py index 119a66a2..c365f4ca 100644 --- a/src/spaceone/identity/error/error_mfa.py +++ b/src/spaceone/identity/error/error_mfa.py @@ -2,20 +2,20 @@ class ERROR_NOT_SUPPORTED_MFA_TYPE(ERROR_INVALID_ARGUMENT): - _message = 'Not supported MFA type. (support_mfa_types = {support_mfa_types})' + _message = "Not supported MFA type. (support_mfa_types = {support_mfa_types})" class ERROR_MFA_ALREADY_ENABLED(ERROR_UNKNOWN): - _message = 'MFA already enabled. (user_id = {user_id})' + _message = "MFA already enabled. (user_id = {user_id})" class ERROR_MFA_ALREADY_DISABLED(ERROR_UNKNOWN): - _message = 'MFA already disabled. (user_id = {user_id})' + _message = "MFA already disabled. (user_id = {user_id})" class ERROR_MFA_REQUIRED(ERROR_UNKNOWN): - _message = 'MFA is required. (user_id = {user_id})' + _message = "MFA is required. (user_id = {user_id})" class ERROR_MFA_NOT_ENABLED(ERROR_UNKNOWN): - _message = 'MFA is not enabled. (user_id = {user_id})' + _message = "MFA is not enabled. (user_id = {user_id})" diff --git a/src/spaceone/identity/error/error_project.py b/src/spaceone/identity/error/error_project.py index 3f470366..9b6537e1 100644 --- a/src/spaceone/identity/error/error_project.py +++ b/src/spaceone/identity/error/error_project.py @@ -2,11 +2,13 @@ class ERROR_EXIST_CHILD_PROJECT_GROUP(ERROR_BASE): - _message = 'Child project group is exist. project_group_id = {project_group_id}' + _message = "Child project group is exist. project_group_id = {project_group_id}" class ERROR_ALREADY_EXIST_USER_IN_PROJECT_GROUP(ERROR_BASE): - _message = 'A user "{user_id}" is already exist in project group({project_group_id}).' + _message = ( + 'A user "{user_id}" is already exist in project group({project_group_id}).' + ) class ERROR_ALREADY_EXIST_USER_IN_PROJECT(ERROR_BASE): @@ -22,4 +24,4 @@ class ERROR_NOT_FOUND_USER_IN_PROJECT(ERROR_BASE): class ERROR_ONLY_PROJECT_ROLE_TYPE_ALLOWED(ERROR_INVALID_ARGUMENT): - _message = 'Only project role type can be allowed.' + _message = "Only project role type can be allowed." diff --git a/src/spaceone/identity/error/error_role.py b/src/spaceone/identity/error/error_role.py index e2331f1f..ecd15c87 100644 --- a/src/spaceone/identity/error/error_role.py +++ b/src/spaceone/identity/error/error_role.py @@ -2,32 +2,38 @@ class ERROR_NOT_ALLOWED_ROLE_TYPE(ERROR_INVALID_ARGUMENT): - _message = 'Duplicate assignment of system role and domain or project role is not allowed.' + _message = ( + "Duplicate assignment of system role and domain or project role is not allowed." + ) class ERROR_REQUIRED_PROJECT_OR_PROJECT_GROUP(ERROR_INVALID_ARGUMENT): - _message = 'Project role require project_id or project_group_id.' + _message = "Project role require project_id or project_group_id." class ERROR_NOT_ALLOWED_PROJECT_ID(ERROR_INVALID_ARGUMENT): - _message = 'Domain or system role dose not need project_id.' + _message = "Domain or system role dose not need project_id." class ERROR_NOT_ALLOWED_PROJECT_GROUP_ID(ERROR_INVALID_ARGUMENT): - _message = 'Domain or system role dose not need project_group_id.' + _message = "Domain or system role dose not need project_group_id." class ERROR_DUPLICATE_ROLE_BOUND(ERROR_INVALID_ARGUMENT): - _message = 'Duplicate role bound. (role_id = {role_id}, resource_id = {resource_id})' + _message = ( + "Duplicate role bound. (role_id = {role_id}, resource_id = {resource_id})" + ) class ERROR_DUPLICATE_RESOURCE_IN_PROJECT(ERROR_INVALID_ARGUMENT): - _message = 'There are duplicate resource in the project. (project_id = {project_id}, resource_id = {resource_id})' + _message = "There are duplicate resource in the project. (project_id = {project_id}, resource_id = {resource_id})" class ERROR_DUPLICATE_RESOURCE_IN_PROJECT_GROUP(ERROR_INVALID_ARGUMENT): - _message = 'There are duplicate resource in the project_group. (project_group_id = {project_group_id}, resource_id = {resource_id})' + _message = "There are duplicate resource in the project_group. (project_group_id = {project_group_id}, resource_id = {resource_id})" class ERROR_POLICY_IS_IN_USE(ERROR_INVALID_ARGUMENT): - _message = 'The policy is in use by role. (policy_id = {policy_id}, role_id = {role_id})' + _message = ( + "The policy is in use by role. (policy_id = {policy_id}, role_id = {role_id})" + ) diff --git a/src/spaceone/identity/error/error_smtp.py b/src/spaceone/identity/error/error_smtp.py index cff39675..28ae5b6d 100644 --- a/src/spaceone/identity/error/error_smtp.py +++ b/src/spaceone/identity/error/error_smtp.py @@ -1,4 +1,5 @@ from spaceone.core.error import * + class ERROR_SMTP_CONNECTION_FAILED(ERROR_UNKNOWN): - _message = 'SMTP server connection failed. Please contact the administrator.' \ No newline at end of file + _message = "SMTP server connection failed. Please contact the administrator." diff --git a/src/spaceone/identity/error/error_user.py b/src/spaceone/identity/error/error_user.py index 001e806b..4f58f3d7 100644 --- a/src/spaceone/identity/error/error_user.py +++ b/src/spaceone/identity/error/error_user.py @@ -6,52 +6,54 @@ class ERROR_USER_STATUS_CHECK_FAILURE(ERROR_BASE): class ERROR_EXTERNAL_USER_NOT_ALLOWED_API_USER(ERROR_INVALID_ARGUMENT): - _message = 'External user cannot be created with the API_USER type.' + _message = "External user cannot be created with the API_USER type." class ERROR_NOT_ALLOWED_EXTERNAL_AUTHENTICATION(ERROR_INVALID_ARGUMENT): - _message = 'This domain does not allow external authentication.' + _message = "This domain does not allow external authentication." class ERROR_TOO_MANY_USERS_IN_EXTERNAL_AUTH(ERROR_INVALID_ARGUMENT): - _message = 'There are two or more users in the external authentication system. (user_id = {user_id})' + _message = "There are two or more users in the external authentication system. (user_id = {user_id})" class ERROR_NOT_FOUND_USER_IN_EXTERNAL_AUTH(ERROR_INVALID_ARGUMENT): - _message = 'The user could not be found in the external authentication system. (user_id = {user_id})' + _message = "The user could not be found in the external authentication system. (user_id = {user_id})" class ERROR_INCORRECT_PASSWORD_FORMAT(ERROR_INVALID_ARGUMENT): - _message = 'The password format is incorrect. (rule = {rule})' + _message = "The password format is incorrect. (rule = {rule})" class ERROR_INCORRECT_USER_ID_FORMAT(ERROR_INVALID_ARGUMENT): - _message = 'The user id format is incorrect. (rule = {rule})' + _message = "The user id format is incorrect. (rule = {rule})" class ERROR_NOT_ALLOWED_ACTIONS(ERROR_INVALID_ARGUMENT): - _message = 'External or API user are supported for actions. (action = {action})' + _message = "External or API user are supported for actions. (action = {action})" class ERROR_VERIFICATION_UNAVAILABLE(ERROR_INVALID_ARGUMENT): - _message = 'Email verification incomplete. (user_id = {user_id})' + _message = "Email verification incomplete. (user_id = {user_id})" class ERROR_INVALID_VERIFY_CODE(ERROR_VERIFICATION_UNAVAILABLE): - _message = 'Invalid verify code. (verify_code = {verify_code})' + _message = "Invalid verify code. (verify_code = {verify_code})" class ERROR_UNABLE_TO_RESET_PASSWORD(ERROR_INVALID_ARGUMENT): - _message = 'Unable to reset password. (user_id = {user_id})' + _message = "Unable to reset password. (user_id = {user_id})" class ERROR_PASSWORD_NOT_CHANGED(ERROR_UNABLE_TO_RESET_PASSWORD): - _message = 'The password cannot be the same as the old password. (user_id = {user_id})' + _message = ( + "The password cannot be the same as the old password. (user_id = {user_id})" + ) class ERROR_UNABLE_TO_RESET_PASSWORD_IN_EXTERNAL_AUTH(ERROR_UNABLE_TO_RESET_PASSWORD): - _message = 'Unable to reset password in external authentication system. (user_id = {user_id})' + _message = "Unable to reset password in external authentication system. (user_id = {user_id})" class ERROR_UNABLE_TO_RESET_PASSWORD_WITHOUT_EMAIL(ERROR_UNABLE_TO_RESET_PASSWORD): - _message = 'Unable to reset password without email. (user_id = {user_id})' + _message = "Unable to reset password without email. (user_id = {user_id})" diff --git a/src/spaceone/identity/info/__init__.py b/src/spaceone/identity/info/__init__.py deleted file mode 100644 index 2c6e4847..00000000 --- a/src/spaceone/identity/info/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -from spaceone.identity.info.api_key_info import * -from spaceone.identity.info.authorization_info import * -from spaceone.identity.info.common_info import * -from spaceone.identity.info.domain_info import * -from spaceone.identity.info.policy_info import * -from spaceone.identity.info.project_group_info import * -from spaceone.identity.info.project_info import * -from spaceone.identity.info.role_info import * -from spaceone.identity.info.role_binding_info import * -from spaceone.identity.info.token_info import * -from spaceone.identity.info.user_info import * -from spaceone.identity.info.domain_owner_info import * -from spaceone.identity.info.find_user_info import * -from spaceone.identity.info.provider_info import * -from spaceone.identity.info.service_account_info import * -from spaceone.identity.info.endpoint_info import * diff --git a/src/spaceone/identity/info/api_key_info.py b/src/spaceone/identity/info/api_key_info.py deleted file mode 100644 index 20af9075..00000000 --- a/src/spaceone/identity/info/api_key_info.py +++ /dev/null @@ -1,29 +0,0 @@ -import functools -from spaceone.api.identity.v1 import api_key_pb2 -from spaceone.core import utils -from spaceone.identity.model.api_key_model import APIKey - -__all__ = ['APIKeyInfo', 'APIKeysInfo'] - - -def APIKeyInfo(api_key_vo: APIKey, minimal=False, api_key=None, **kwargs): - info = { - 'api_key': api_key, - 'api_key_id': api_key_vo.api_key_id, - 'state': api_key_vo.state, - 'user_id': api_key_vo.user_id - } - - if not minimal: - info.update({ - 'domain_id': api_key_vo.domain_id, - 'last_accessed_at': utils.datetime_to_iso8601(api_key_vo.last_accessed_at), - 'created_at': utils.datetime_to_iso8601(api_key_vo.created_at) - }) - - return api_key_pb2.APIKeyInfo(**info) - - -def APIKeysInfo(api_key_vos, total_count, **kwargs): - results = list(map(functools.partial(APIKeyInfo, **kwargs), api_key_vos)) - return api_key_pb2.APIKeysInfo(results=results, total_count=total_count) diff --git a/src/spaceone/identity/info/authorization_info.py b/src/spaceone/identity/info/authorization_info.py deleted file mode 100644 index d384c461..00000000 --- a/src/spaceone/identity/info/authorization_info.py +++ /dev/null @@ -1,7 +0,0 @@ -from spaceone.api.core.v1 import handler_pb2 - -__all__ = ['AuthorizationResponse'] - - -def AuthorizationResponse(auth_data): - return handler_pb2.AuthorizationResponse(**auth_data) diff --git a/src/spaceone/identity/info/common_info.py b/src/spaceone/identity/info/common_info.py deleted file mode 100644 index 89bf2a32..00000000 --- a/src/spaceone/identity/info/common_info.py +++ /dev/null @@ -1,12 +0,0 @@ -from google.protobuf.empty_pb2 import Empty -from spaceone.core.pygrpc.message_type import * - -__all__ = ['EmptyInfo', 'StatisticsInfo'] - - -def EmptyInfo(): - return Empty() - - -def StatisticsInfo(result): - return change_struct_type(result) diff --git a/src/spaceone/identity/info/domain_info.py b/src/spaceone/identity/info/domain_info.py deleted file mode 100644 index f556be24..00000000 --- a/src/spaceone/identity/info/domain_info.py +++ /dev/null @@ -1,60 +0,0 @@ -import functools -import json - -from spaceone.api.core.v1 import handler_pb2 -from spaceone.api.identity.v1 import domain_pb2 - -from spaceone.core.pygrpc.message_type import * -from spaceone.core import utils -from spaceone.identity.model.domain_model import Domain - -__all__ = ['DomainInfo', 'DomainsInfo', 'DomainPublicKeyInfo'] - - -def DomainInfo(domain_vo: Domain, minimal=False): - info = { - 'domain_id': domain_vo.domain_id, - 'name': domain_vo.name, - 'state': domain_vo.state - } - - if not minimal: - info.update({ - 'plugin_info': PluginInfo(domain_vo.plugin_info), - 'config': change_struct_type(domain_vo.config), - 'created_at': utils.datetime_to_iso8601(domain_vo.created_at), - 'deleted_at': utils.datetime_to_iso8601(domain_vo.deleted_at), - 'tags': change_struct_type(domain_vo.tags) - }) - - return domain_pb2.DomainInfo(**info) - - -def DomainsInfo(domain_vos, total_count, **kwargs): - results = list(map(functools.partial(DomainInfo, **kwargs), domain_vos)) - - return domain_pb2.DomainsInfo(results=results, total_count=total_count) - - -def PluginInfo(plugin_info): - if plugin_info: - info = { - 'plugin_id': plugin_info.plugin_id, - 'version': plugin_info.version, - 'options': change_struct_type(plugin_info.options), - 'metadata': change_struct_type(plugin_info.metadata), - 'secret_id': plugin_info.secret_id, - 'schema': plugin_info.schema, - 'upgrade_mode': plugin_info.upgrade_mode - } - return domain_pb2.PluginInfo(**info) - - return None - - -def DomainPublicKeyInfo(public_key, domain_id): - info = { - 'public_key': json.dumps(public_key).__str__(), - 'domain_id': domain_id - } - return handler_pb2.AuthenticationResponse(**info) diff --git a/src/spaceone/identity/info/domain_owner_info.py b/src/spaceone/identity/info/domain_owner_info.py deleted file mode 100644 index e5d2882c..00000000 --- a/src/spaceone/identity/info/domain_owner_info.py +++ /dev/null @@ -1,26 +0,0 @@ -from spaceone.api.identity.v1 import domain_owner_pb2 - -from spaceone.core import utils -from spaceone.identity.model import DomainOwner - -__all__ = ['DomainOwnerInfo'] - - -def DomainOwnerInfo(owner_vo: DomainOwner, minimal=False): - info = { - 'owner_id': owner_vo.owner_id, - 'name': owner_vo.name - } - - if not minimal: - info.update({ - 'email': owner_vo.email, - 'language': owner_vo.language, - 'timezone': owner_vo.timezone, - 'domain_id': owner_vo.domain_id, - 'last_accessed_at': utils.datetime_to_iso8601(owner_vo.last_accessed_at), - 'created_at': utils.datetime_to_iso8601(owner_vo.created_at) - - }) - - return domain_owner_pb2.DomainOwnerInfo(**info) diff --git a/src/spaceone/identity/info/endpoint_info.py b/src/spaceone/identity/info/endpoint_info.py deleted file mode 100644 index f065fbda..00000000 --- a/src/spaceone/identity/info/endpoint_info.py +++ /dev/null @@ -1,26 +0,0 @@ -import functools -from spaceone.api.identity.v1 import endpoint_pb2 - -__all__ = ['EndpointInfo', 'EndpointsInfo'] - - -def EndpointInfo(endpoint_vo: dict, minimal=False): - info = { - 'service': endpoint_vo['service'], - 'name': endpoint_vo['name'], - 'endpoint': endpoint_vo['endpoint'], - } - - if not minimal: - info.update({ - 'state': endpoint_vo.get('state'), - 'version': endpoint_vo.get('version'), - }) - - return endpoint_pb2.EndpointInfo(**info) - - -def EndpointsInfo(endpoint_vos, total_count, **kwargs): - results = list(map(functools.partial(EndpointInfo, **kwargs), endpoint_vos)) - - return endpoint_pb2.EndpointsInfo(results=results, total_count=total_count) diff --git a/src/spaceone/identity/info/find_user_info.py b/src/spaceone/identity/info/find_user_info.py deleted file mode 100644 index 0f3dfca6..00000000 --- a/src/spaceone/identity/info/find_user_info.py +++ /dev/null @@ -1,28 +0,0 @@ -import functools -from spaceone.core.pygrpc.message_type import * -from spaceone.api.identity.v1 import user_pb2 - -__all__ = ['FindUserInfo', 'FindUsersInfo'] - - -def FindUserInfo(user_data: dict, minimal=False): - info = { - 'user_id': user_data.get('user_id'), - 'name': user_data.get('name'), - 'email': user_data.get('email') - } - if not minimal: - tags = {} - for tag in user_data.get('tags', []): - tags[tag['key']] = tag['value'] - - info.update({ - 'tags': change_struct_type(tags), - }) - - return user_pb2.FindUserInfo(**info) - - -def FindUsersInfo(find_users_vos, total_count, **kwargs): - results = list(map(functools.partial(FindUserInfo, **kwargs), find_users_vos)) - return user_pb2.FindUsersInfo(results=results, total_count=total_count) diff --git a/src/spaceone/identity/info/policy_info.py b/src/spaceone/identity/info/policy_info.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/spaceone/identity/info/project_group_info.py b/src/spaceone/identity/info/project_group_info.py deleted file mode 100644 index 45355ba1..00000000 --- a/src/spaceone/identity/info/project_group_info.py +++ /dev/null @@ -1,108 +0,0 @@ -import functools -from spaceone.api.identity.v1 import project_group_pb2 -from spaceone.core.pygrpc.message_type import * -from spaceone.core import utils -from spaceone.identity.model.project_model import Project -from spaceone.identity.model.project_group_model import ProjectGroup -from spaceone.identity.info.role_info import RoleInfo - -__all__ = ['ProjectGroupInfo', 'ProjectGroupsInfo', 'ProjectGroupRoleBindingInfo', 'ProjectGroupRoleBindingsInfo', - 'ProjectGroupProjectsInfo'] - - -def ProjectGroupInfo(project_group_vo: ProjectGroup, minimal=False, parent_project_groups_info=None): - info = { - 'project_group_id': project_group_vo.project_group_id, - 'name': project_group_vo.name - } - - if not minimal: - if parent_project_groups_info: - parent_project_group_id = project_group_vo.parent_project_group_id - if parent_project_group_id and parent_project_group_id in parent_project_groups_info: - parent_project_group_info = ProjectGroupInfo(parent_project_groups_info[parent_project_group_id], - minimal=True) - - info.update({ - 'parent_project_group_info': parent_project_group_info - }) - else: - if project_group_vo.parent_project_group: - info.update({ - 'parent_project_group_info': ProjectGroupInfo(project_group_vo.parent_project_group, minimal=True) - }) - - info.update({ - 'tags': change_struct_type(project_group_vo.tags), - 'domain_id': project_group_vo.domain_id, - 'created_by': project_group_vo.created_by, - 'created_at': utils.datetime_to_iso8601(project_group_vo.created_at) - }) - - return project_group_pb2.ProjectGroupInfo(**info) - - -def ProjectGroupsInfo(project_group_vos, total_count, **kwargs): - results = list(map(functools.partial(ProjectGroupInfo, **kwargs), project_group_vos)) - return project_group_pb2.ProjectGroupsInfo(results=results, total_count=total_count) - - -def ProjectGroupRoleBindingInfo(role_binding_vo, minimal=False): - info = { - 'role_binding_id': role_binding_vo.role_binding_id, - 'resource_type': role_binding_vo.resource_type, - 'resource_id': role_binding_vo.resource_id, - 'role_info': RoleInfo(role_binding_vo.role, minimal=True) if role_binding_vo.role else None - } - - if not minimal: - info.update({ - 'project_group_info': ProjectGroupInfo(role_binding_vo.project_group, - minimal=True) if role_binding_vo.project_group else None, - 'labels': change_list_value_type(role_binding_vo.labels), - 'tags': change_struct_type(role_binding_vo.tags), - 'domain_id': role_binding_vo.domain_id, - 'created_at': utils.datetime_to_iso8601(role_binding_vo.created_at) - }) - - return project_group_pb2.ProjectGroupRoleBindingInfo(**info) - - -def ProjectGroupRoleBindingsInfo(role_binding_vos, total_count, **kwargs): - results = list(map(functools.partial(ProjectGroupRoleBindingInfo, **kwargs), role_binding_vos)) - - return project_group_pb2.ProjectGroupRoleBindingsInfo(results=results, total_count=total_count) - - -def ProjectGroupProjectInfo(project_vo: Project, minimal=False, project_groups_info=None, **kwargs): - info = { - 'project_id': project_vo.project_id, - 'name': project_vo.name - } - - if not minimal: - if project_groups_info: - if project_vo.project_group_id and project_vo.project_group_id in project_groups_info: - project_group_info = ProjectGroupInfo(project_groups_info[project_vo.project_group_id], minimal=True) - info.update({ - 'project_group_info': project_group_info - }) - else: - if project_vo.project_group: - info.update({ - 'project_group_info': ProjectGroupInfo(project_vo.project_group, minimal=True) - }) - - info.update({ - 'tags': change_struct_type(project_vo.tags), - 'domain_id': project_vo.domain_id, - 'created_by': project_vo.created_by, - 'created_at': utils.datetime_to_iso8601(project_vo.created_at) - }) - - return project_group_pb2.ProjectGroupProjectInfo(**info) - - -def ProjectGroupProjectsInfo(project_vos, total_count, **kwargs): - results = list(map(functools.partial(ProjectGroupProjectInfo, **kwargs), project_vos)) - return project_group_pb2.ProjectGroupProjectsInfo(results=results, total_count=total_count) diff --git a/src/spaceone/identity/info/project_info.py b/src/spaceone/identity/info/project_info.py deleted file mode 100644 index b68815ad..00000000 --- a/src/spaceone/identity/info/project_info.py +++ /dev/null @@ -1,71 +0,0 @@ -import functools -from spaceone.api.identity.v1 import project_pb2 -from spaceone.core.pygrpc.message_type import * -from spaceone.core import utils -from spaceone.identity.model.project_model import Project -from spaceone.identity.info.project_group_info import ProjectGroupInfo -from spaceone.identity.info.role_info import RoleInfo - -__all__ = ['ProjectInfo', 'ProjectsInfo', 'ProjectRoleBindingInfo', 'ProjectRoleBindingsInfo'] - - -def ProjectInfo(project_vo: Project, minimal=False, project_groups_info=None): - info = { - 'project_id': project_vo.project_id, - 'name': project_vo.name - - } - - if not minimal: - if project_groups_info: - if project_vo.project_group_id and project_vo.project_group_id in project_groups_info: - project_group_info = ProjectGroupInfo(project_groups_info[project_vo.project_group_id], minimal=True) - info.update({ - 'project_group_info': project_group_info - }) - else: - if project_vo.project_group: - info.update({ - 'project_group_info': ProjectGroupInfo(project_vo.project_group, minimal=True) - }) - - info.update({ - 'tags': change_struct_type(project_vo.tags), - 'domain_id': project_vo.domain_id, - 'created_by': project_vo.created_by, - 'created_at': utils.datetime_to_iso8601(project_vo.created_at) - }) - - return project_pb2.ProjectInfo(**info) - - -def ProjectsInfo(project_vos, total_count, **kwargs): - results = list(map(functools.partial(ProjectInfo, **kwargs), project_vos)) - return project_pb2.ProjectsInfo(results=results, total_count=total_count) - - -def ProjectRoleBindingInfo(role_binding_vo, minimal=False): - info = { - 'role_binding_id': role_binding_vo.role_binding_id, - 'resource_type': role_binding_vo.resource_type, - 'resource_id': role_binding_vo.resource_id, - 'role_info': RoleInfo(role_binding_vo.role, minimal=True) if role_binding_vo.role else None - } - - if not minimal: - info.update({ - 'project_info': ProjectInfo(role_binding_vo.project, minimal=True) if role_binding_vo.project else None, - 'project_group_info': ProjectGroupInfo(role_binding_vo.project_group, minimal=True) if role_binding_vo.project_group else None, - 'labels': change_list_value_type(role_binding_vo.labels), - 'tags': change_struct_type(role_binding_vo.tags), - 'domain_id': role_binding_vo.domain_id, - 'created_at': utils.datetime_to_iso8601(role_binding_vo.created_at) - }) - - return project_pb2.ProjectRoleBindingInfo(**info) - - -def ProjectRoleBindingsInfo(role_binding_vos, total_count, **kwargs): - results = list(map(functools.partial(ProjectRoleBindingInfo, **kwargs), role_binding_vos)) - - return project_pb2.ProjectRoleBindingsInfo(results=results, total_count=total_count) diff --git a/src/spaceone/identity/info/provider_info.py b/src/spaceone/identity/info/provider_info.py deleted file mode 100644 index 8731dc99..00000000 --- a/src/spaceone/identity/info/provider_info.py +++ /dev/null @@ -1,33 +0,0 @@ -import functools -from spaceone.api.identity.v1 import provider_pb2 -from spaceone.core.pygrpc.message_type import * -from spaceone.core import utils -from spaceone.identity.model.provider_model import Provider - -__all__ = ['ProviderInfo', 'ProvidersInfo'] - - -def ProviderInfo(provider_vo: Provider, minimal=False): - info = { - 'provider': provider_vo.provider, - 'name': provider_vo.name, - 'order': provider_vo.order - } - - if not minimal: - info.update({ - 'template': change_struct_type(provider_vo.template), - 'metadata': change_struct_type(provider_vo.metadata), - 'capability': change_struct_type(provider_vo.capability), - 'tags': change_struct_type(provider_vo.tags), - 'domain_id': provider_vo.domain_id, - 'created_at': utils.datetime_to_iso8601(provider_vo.created_at) - }) - - return provider_pb2.ProviderInfo(**info) - - -def ProvidersInfo(provider_vos, total_count, **kwargs): - results = list(map(functools.partial(ProviderInfo, **kwargs), provider_vos)) - - return provider_pb2.ProvidersInfo(results=results, total_count=total_count) diff --git a/src/spaceone/identity/info/role_binding_info.py b/src/spaceone/identity/info/role_binding_info.py deleted file mode 100644 index 5246528d..00000000 --- a/src/spaceone/identity/info/role_binding_info.py +++ /dev/null @@ -1,43 +0,0 @@ -import functools -from spaceone.api.identity.v1 import role_binding_pb2 -from spaceone.core.pygrpc.message_type import * -from spaceone.core import utils -from spaceone.identity.model.role_binding_model import RoleBinding -from spaceone.identity.info.role_info import RoleInfo -from spaceone.identity.info.project_info import ProjectInfo -from spaceone.identity.info.project_group_info import ProjectGroupInfo - -__all__ = ['RoleBindingInfo', 'RoleBindingsInfo'] - - -def RoleBindingInfo(role_binding_vo: RoleBinding, minimal=False): - info = { - 'role_binding_id': role_binding_vo.role_binding_id, - 'resource_type': role_binding_vo.resource_type, - 'resource_id': role_binding_vo.resource_id, - 'role_info': RoleInfo(role_binding_vo.role, minimal=True) if role_binding_vo.role else None - } - - if not minimal: - info.update({ - 'project_info': ProjectInfo(role_binding_vo.project, minimal=True) if role_binding_vo.project else None, - 'project_group_info': ProjectGroupInfo(role_binding_vo.project_group, minimal=True) if role_binding_vo.project_group else None, - 'labels': change_list_value_type(role_binding_vo.labels), - 'tags': change_struct_type(role_binding_vo.tags), - 'domain_id': role_binding_vo.domain_id, - 'created_at': utils.datetime_to_iso8601(role_binding_vo.created_at) - }) - - if not role_binding_vo.project_id and role_binding_vo.project: - role_binding_vo.update({'project_id': role_binding_vo.project.project_id}) - - if not role_binding_vo.project_group_id and role_binding_vo.project_group: - role_binding_vo.update({'project_group_id': role_binding_vo.project_group.project_group_id}) - - return role_binding_pb2.RoleBindingInfo(**info) - - -def RoleBindingsInfo(role_binding_vos, total_count, **kwargs): - results = list(map(functools.partial(RoleBindingInfo, **kwargs), role_binding_vos)) - - return role_binding_pb2.RoleBindingsInfo(results=results, total_count=total_count) diff --git a/src/spaceone/identity/info/role_info.py b/src/spaceone/identity/info/role_info.py deleted file mode 100644 index 8f1b885f..00000000 --- a/src/spaceone/identity/info/role_info.py +++ /dev/null @@ -1,67 +0,0 @@ -import functools -from spaceone.api.identity.v1 import role_pb2 -from spaceone.core.pygrpc.message_type import * -from spaceone.core import utils -from spaceone.identity.model.role_model import Role, RolePolicy, PagePermission - -__all__ = ['RoleInfo', 'RolesInfo'] - - -def RolePolicyInfo(role_policy_vo: RolePolicy): - role_policy_info = { - 'policy_type': role_policy_vo.policy_type, - 'policy_id': role_policy_vo.policy_id - } - - return role_policy_info - - -def PagePermissionInfo(page_perm_vo: PagePermission): - page_perm_info = { - 'page': page_perm_vo.page, - 'permission': page_perm_vo.permission - } - - return page_perm_info - - -def RoleInfo(role_vo: Role, minimal=False): - # Temporary Code for Migration - policies = [] - is_changed = False - for policy_vo in role_vo.policies: - if not policy_vo.policy_id: - policy_vo.policy_id = policy_vo.policy.policy_id - is_changed = True - - policies.append({ - 'policy_type': policy_vo.policy_type, - 'policy': policy_vo.policy, - 'policy_id': policy_vo.policy_id - }) - - if is_changed: - role_vo = role_vo.update({'policies': policies}) - - info = { - 'role_id': role_vo.role_id, - 'name': role_vo.name, - 'role_type': role_vo.role_type, - 'page_permissions': list(map(lambda page_perm_vo: PagePermissionInfo(page_perm_vo), role_vo.page_permissions)) - } - - if not minimal: - info.update({ - 'policies': list(map(lambda policy: RolePolicyInfo(policy), role_vo.policies)), - 'tags': change_struct_type(role_vo.tags), - 'domain_id': role_vo.domain_id, - 'created_at': utils.datetime_to_iso8601(role_vo.created_at) - }) - - return role_pb2.RoleInfo(**info) - - -def RolesInfo(role_vos, total_count, **kwargs): - results = list(map(functools.partial(RoleInfo, **kwargs), role_vos)) - - return role_pb2.RolesInfo(results=results, total_count=total_count) diff --git a/src/spaceone/identity/info/service_account_info.py b/src/spaceone/identity/info/service_account_info.py deleted file mode 100644 index a71e37cb..00000000 --- a/src/spaceone/identity/info/service_account_info.py +++ /dev/null @@ -1,47 +0,0 @@ -import functools -from spaceone.api.identity.v1 import service_account_pb2 -from spaceone.core.pygrpc.message_type import * -from spaceone.core import utils -from spaceone.identity.model.service_account_model import ServiceAccount -from spaceone.identity.info.project_info import ProjectInfo - -__all__ = ['ServiceAccountInfo', 'ServiceAccountsInfo'] - - -def ServiceAccountInfo(service_account_vo: ServiceAccount, minimal=False, projects_info=None): - info = { - 'service_account_id': service_account_vo.service_account_id, - 'name': service_account_vo.name, - 'provider': service_account_vo.provider, - 'service_account_type': service_account_vo.service_account_type - } - - if not minimal: - info.update({ - 'trusted_service_account_id': service_account_vo.trusted_service_account_id, - 'scope': service_account_vo.scope, - 'data': change_struct_type(service_account_vo.data), - 'tags': change_struct_type(service_account_vo.tags), - 'domain_id': service_account_vo.domain_id, - 'created_at': utils.datetime_to_iso8601(service_account_vo.created_at) - }) - - if projects_info: - if service_account_vo.project_id and service_account_vo.project_id in projects_info: - project_info = ProjectInfo(projects_info[service_account_vo.project_id], minimal=True) - info.update({ - 'project_info': project_info - }) - else: - if service_account_vo.project: - info.update({ - 'project_info': ProjectInfo(service_account_vo.project, minimal=True) - }) - - return service_account_pb2.ServiceAccountInfo(**info) - - -def ServiceAccountsInfo(service_account_vos, total_count, **kwargs): - results = list(map(functools.partial(ServiceAccountInfo, **kwargs), service_account_vos)) - - return service_account_pb2.ServiceAccountsInfo(results=results, total_count=total_count) diff --git a/src/spaceone/identity/info/token_info.py b/src/spaceone/identity/info/token_info.py deleted file mode 100644 index 53046cd0..00000000 --- a/src/spaceone/identity/info/token_info.py +++ /dev/null @@ -1,12 +0,0 @@ -from spaceone.api.identity.v1 import token_pb2 - -__all__ = ['TokenInfo'] - - -def TokenInfo(token_data): - info = { - 'access_token': token_data['access_token'], - 'refresh_token': token_data['refresh_token'] - } - - return token_pb2.TokenInfo(**info) \ No newline at end of file diff --git a/src/spaceone/identity/info/user_info.py b/src/spaceone/identity/info/user_info.py deleted file mode 100644 index edf7f2c6..00000000 --- a/src/spaceone/identity/info/user_info.py +++ /dev/null @@ -1,51 +0,0 @@ -import functools -from spaceone.api.identity.v1 import user_pb2 -from spaceone.core.pygrpc.message_type import * -from spaceone.core import utils -from spaceone.identity.model.user_model import User - -__all__ = ['UserInfo', 'UsersInfo'] - - -def MFAInfo(mfa_vo): - if mfa_vo is None: - return None - - mfa_info = { - 'mfa_type': mfa_vo.mfa_type, - 'state': mfa_vo.state, - 'options': change_struct_type(mfa_vo.options), - } - - return mfa_info - - -def UserInfo(user_vo: User, minimal=False): - info = { - 'user_id': user_vo.user_id, - 'name': user_vo.name, - 'state': user_vo.state, - 'user_type': user_vo.user_type - } - - if not minimal: - info.update({ - 'email': user_vo.email, - 'email_verified': user_vo.email_verified, - 'backend': user_vo.backend, - 'mfa': MFAInfo(user_vo.mfa), - 'required_actions': user_vo.required_actions, - 'language': user_vo.language, - 'timezone': user_vo.timezone, - 'tags': change_struct_type(user_vo.tags), - 'domain_id': user_vo.domain_id, - 'last_accessed_at': utils.datetime_to_iso8601(user_vo.last_accessed_at), - 'created_at': utils.datetime_to_iso8601(user_vo.created_at) - }) - - return user_pb2.UserInfo(**info) - - -def UsersInfo(user_vos, total_count, **kwargs): - results = list(map(functools.partial(UserInfo, **kwargs), user_vos)) - return user_pb2.UsersInfo(results=results, total_count=total_count) diff --git a/src/spaceone/identity/interface/grpc/domain_owner.py b/src/spaceone/identity/interface/grpc/domain_owner.py deleted file mode 100644 index 5d69b239..00000000 --- a/src/spaceone/identity/interface/grpc/domain_owner.py +++ /dev/null @@ -1,37 +0,0 @@ -from spaceone.api.identity.v1 import domain_owner_pb2, domain_owner_pb2_grpc -from spaceone.core.pygrpc import BaseAPI - - -class DomainOwner(BaseAPI, domain_owner_pb2_grpc.DomainOwnerServicer): - - pb2 = domain_owner_pb2 - pb2_grpc = domain_owner_pb2_grpc - - def create(self, request, context): - params, metadata = self.parse_request(request, context) - - with self.locator.get_service('DomainOwnerService', metadata) as domain_owner_svc: - data = domain_owner_svc.create(params) - return self.locator.get_info('DomainOwnerInfo', data) - - def update(self, request, context): - params, metadata = self.parse_request(request, context) - - with self.locator.get_service('DomainOwnerService', metadata) as domain_owner_svc: - data = domain_owner_svc.update(params) - return self.locator.get_info('DomainOwnerInfo', data) - - def delete(self, request, context): - params, metadata = self.parse_request(request, context) - - with self.locator.get_service('DomainOwnerService', metadata) as domain_owner_svc: - # TODO: block delete when users, projects, inventories are existing. - data = domain_owner_svc.delete(params) - return self.locator.get_info('EmptyInfo') - - def get(self, request, context): - params, metadata = self.parse_request(request, context) - - with self.locator.get_service('DomainOwnerService', metadata) as domain_owner_svc: - data = domain_owner_svc.get(params) - return self.locator.get_info('DomainOwnerInfo', data) diff --git a/src/spaceone/identity/lib/cipher.py b/src/spaceone/identity/lib/cipher.py index f547290b..9d9d96d9 100644 --- a/src/spaceone/identity/lib/cipher.py +++ b/src/spaceone/identity/lib/cipher.py @@ -6,7 +6,7 @@ class PasswordCipher: @staticmethod def __encoder(password: str) -> str: - return str(password).encode('utf-8') + return str(password).encode("utf-8") def hashpw(self, password: str) -> bytes: return bcrypt.hashpw(self.__encoder(password), bcrypt.gensalt()) diff --git a/src/spaceone/identity/lib/key_generator.py b/src/spaceone/identity/lib/key_generator.py index c4ebd2cd..0f33389e 100644 --- a/src/spaceone/identity/lib/key_generator.py +++ b/src/spaceone/identity/lib/key_generator.py @@ -10,7 +10,6 @@ class KeyGenerator: - def __init__(self, prv_jwk, domain_id, audience): self.prv_jwk = prv_jwk self.domain_id = domain_id @@ -24,25 +23,29 @@ def _do_parameter_check(self): if self.domain_id is None: raise ERROR_GENERATE_KEY_FAILURE() - def generate_api_key(self, api_key_id: str, user_type: str = 'USER') -> Tuple[str, Any]: + def generate_api_key( + self, api_key_id: str, user_type: str = "USER" + ) -> Tuple[str, Any]: payload = { - 'cat': 'API_KEY', - 'user_type': user_type, - 'did': self.domain_id, - 'aud': self.audience, - 'iat': int(time.time()), - 'api_key_id': api_key_id, - 'ver': '2020-12-07' + "cat": "API_KEY", + "user_type": user_type, + "did": self.domain_id, + "aud": self.audience, + "iat": int(time.time()), + "api_key_id": api_key_id, + "ver": "2020-12-07", } encoded = JWTUtil.encode(payload, self.prv_jwk) - _LOGGER.debug(f'[KeyGenerator] Generated payload. ( ' - f'cat: {payload.get("cat")}, ' - f'user_type: {payload.get("user_type")}, ' - f'did: {payload.get("did")}, ' - f'aud: {payload.get("aud")}, ' - f'api_key_id: {payload.get("api_key_id")}, ' - f'version: {payload.get("version")} )') + _LOGGER.debug( + f"[KeyGenerator] Generated payload. ( " + f'cat: {payload.get("cat")}, ' + f'user_type: {payload.get("user_type")}, ' + f'did: {payload.get("did")}, ' + f'aud: {payload.get("aud")}, ' + f'api_key_id: {payload.get("api_key_id")}, ' + f'version: {payload.get("version")} )' + ) return encoded diff --git a/src/spaceone/identity/manager/api_key_manager.py b/src/spaceone/identity/manager/api_key_manager.py deleted file mode 100644 index 3edab301..00000000 --- a/src/spaceone/identity/manager/api_key_manager.py +++ /dev/null @@ -1,81 +0,0 @@ -import logging - -from spaceone.core.cache import cacheable -from spaceone.core.manager import BaseManager -from spaceone.identity.lib.key_generator import KeyGenerator -from spaceone.identity.model.api_key_model import APIKey -from spaceone.identity.model.domain_secret_model import DomainSecret - -_LOGGER = logging.getLogger(__name__) - - -class APIKeyManager(BaseManager): - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.api_key_model: APIKey = self.locator.get_model('APIKey') - self.secret_model: DomainSecret = self.locator.get_model('DomainSecret') - - def create_api_key(self, user_vo, domain_id): - def _rollback(api_key_vo): - _LOGGER.info(f'[create_api_key._rollback] Delete api_key : {api_key_vo.api_key_id}') - api_key_vo.delete() - - params = { - 'user_id': user_vo.user_id, - 'user': user_vo, - 'domain_id': domain_id - } - api_key_vo: APIKey = self.api_key_model.create(params) - self.transaction.add_rollback(_rollback, api_key_vo) - - prv_jwk = self._query_domain_secret(domain_id) - - key_gen = KeyGenerator(prv_jwk=prv_jwk, - domain_id=domain_id, - audience=user_vo.user_id) - - api_key = key_gen.generate_api_key(api_key_vo.api_key_id, user_vo.user_type) - return api_key_vo, api_key - - def delete_api_key(self, api_key_id, domain_id): - api_key_vo = self.get_api_key(api_key_id, domain_id) - api_key_vo.delete() - - def enable_api_key(self, api_key_id, domain_id): - def _rollback(old_data): - _LOGGER.info(f'[enable_api_key._rollback] Revert Data: {old_data}') - api_key_vo.update(old_data) - - api_key_vo: APIKey = self.get_api_key(api_key_id, domain_id) - if api_key_vo.state != 'ENABLED': - self.transaction.add_rollback(_rollback, api_key_vo.to_dict()) - api_key_vo.update({'state': 'ENABLED'}) - - return api_key_vo - - def disable_api_key(self, api_key_id, domain_id): - def _rollback(old_data): - _LOGGER.info(f'[disable_api_key._rollback] Revert Data: {old_data}') - api_key_vo.update(old_data) - - api_key_vo: APIKey = self.get_api_key(api_key_id, domain_id) - if api_key_vo.state != 'DISABLED': - self.transaction.add_rollback(_rollback, api_key_vo.to_dict()) - api_key_vo.update({'state': 'DISABLED'}) - - return api_key_vo - - def get_api_key(self, api_key_id, domain_id, only=None): - return self.api_key_model.get(api_key_id=api_key_id, domain_id=domain_id, only=only) - - def list_api_keys(self, query): - return self.api_key_model.query(**query) - - def stat_api_keys(self, query): - return self.api_key_model.stat(**query) - - @cacheable(key='api-key:{domain_id}', expire=60) - def _query_domain_secret(self, domain_id: str) -> DomainSecret: - domain_secret = self.secret_model.get(domain_id=domain_id) - return domain_secret.prv_jwk diff --git a/src/spaceone/identity/manager/authorization_manager.py b/src/spaceone/identity/manager/authorization_manager.py deleted file mode 100644 index c4780882..00000000 --- a/src/spaceone/identity/manager/authorization_manager.py +++ /dev/null @@ -1,126 +0,0 @@ -import fnmatch -import functools -import logging - -from spaceone.core import cache -from spaceone.core.manager import BaseManager -from spaceone.core.error import * - -_LOGGER = logging.getLogger(__name__) - - -class AuthorizationManager(BaseManager): - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - @cache.cacheable(key='user-permissions:{domain_id}:{user_id}:{service}:{resource}:{verb}:{request_roles}', - expire=3600) - def check_permissions(self, user_id, domain_id, permissions, service, resource, verb, request_roles): - try: - next(filter(functools.partial( - self._match_permission, service, resource, verb), permissions)) - except Exception as e: - _LOGGER.debug(f'[verify] Not matched permissions. ' - f'(user_id={user_id}, api={service}.{resource}.{verb})') - raise ERROR_PERMISSION_DENIED() - - @cache.cacheable(key='user-scopes:{domain_id}:{user_id}:{scope}:{role_type}:{request_domain_id}:' - '{request_project_id}:{request_project_group_id}:{projects}:{project_groups}:' - '{request_user_id}:{require_project_id}:{require_project_group_id}:{require_user_id}:' - '{require_domain_id}', - expire=3600) - def check_scope_by_role_type(self, user_id, domain_id, scope, role_type, projects, project_groups, - request_domain_id, request_project_id, request_project_group_id, request_user_id, - require_project_id, require_project_group_id, require_user_id, require_domain_id): - - if scope == 'SYSTEM': - # Excluding system api checking process - # self._check_system_scope(user_id, domain_id, role_type) - pass - elif scope == 'PUBLIC': - # No Check Public Resource - pass - elif scope in ['DOMAIN', 'PUBLIC_OR_DOMAIN']: - self._check_domain_scope(user_id, domain_id, role_type, request_domain_id, require_domain_id) - elif scope in ['PROJECT', 'DOMAIN_OR_PROJECT']: - self._check_domain_scope(user_id, domain_id, role_type, request_domain_id, require_domain_id) - self._check_project_scope(user_id, domain_id, role_type, projects, project_groups, - request_project_id, request_project_group_id, - require_project_id, require_project_group_id) - elif scope == 'USER': - self._check_domain_scope(user_id, domain_id, role_type, request_domain_id, require_domain_id) - self._check_user_scope(user_id, domain_id, role_type, request_user_id, require_user_id) - - @staticmethod - def _check_system_scope(user_id, domain_id, role_type): - if role_type != 'SYSTEM': - _LOGGER.debug(f'[_check_system_scope] system api is not allowed.' - f' (user_id = {user_id}, user_domain_id = {domain_id})') - raise ERROR_PERMISSION_DENIED() - - @staticmethod - def _check_domain_scope(user_id, domain_id, role_type, request_domain_id, require_domain_id): - if role_type != 'SYSTEM': - if require_domain_id and request_domain_id is None: - _LOGGER.debug(f'[_check_domain_scope] domain_id is required.' - f' (user_id = {user_id}, user_domain_id = {domain_id},' - f' request_domain_id = {request_domain_id})') - raise ERROR_PERMISSION_DENIED() - - if request_domain_id and request_domain_id != domain_id: - _LOGGER.debug(f'[_check_domain_scope] domain_id is not allowed.' - f' (user_id = {user_id}, user_domain_id = {domain_id},' - f' request_domain_id = {request_domain_id})') - raise ERROR_PERMISSION_DENIED() - - @staticmethod - def _check_user_scope(user_id, domain_id, role_type, request_user_id, require_user_id): - if role_type in ['PROJECT', 'USER']: - if require_user_id and request_user_id is None: - _LOGGER.debug(f'[_check_user_scope] user_id is required.' - f' (user_id = {user_id}, user_domain_id = {domain_id},' - f' request_user_id = {request_user_id})') - raise ERROR_PERMISSION_DENIED() - - if request_user_id and request_user_id != user_id: - _LOGGER.debug(f'[_check_user_scope] user role_type can only access self resource.' - f' (user_id = {user_id}, user_domain_id = {domain_id},' - f' request_user_id = {request_user_id})') - raise ERROR_PERMISSION_DENIED() - - @staticmethod - def _check_project_scope(user_id, domain_id, role_type, projects, project_groups, - request_project_id, request_project_group_id, - require_project_id, require_project_group_id): - if role_type == 'PROJECT': - if require_project_id and request_project_id is None: - _LOGGER.debug(f'[_check_project_scope] project_id is required.' - f' (user_id = {user_id}, user_domain_id = {domain_id},' - f' request_project_id = {request_project_id})') - raise ERROR_PERMISSION_DENIED() - - if require_project_group_id and request_project_group_id is None: - _LOGGER.debug(f'[_check_project_scope] project_group_id is required.' - f' (user_id = {user_id}, user_domain_id = {domain_id},' - f' request_project_group_id = {request_project_group_id})') - raise ERROR_PERMISSION_DENIED() - - if request_project_id and request_project_id not in projects: - _LOGGER.debug(f'[_check_project_scope] project_id is not allowed.' - f' (user_id = {user_id}, user_domain_id = {domain_id},' - f' request_project_id = {request_project_id})') - raise ERROR_PERMISSION_DENIED() - - if request_project_group_id and request_project_group_id not in project_groups: - _LOGGER.debug(f'[_check_project_scope] project_group_id is not allowed.' - f' (user_id = {user_id}, user_domain_id = {domain_id},' - f' request_project_group_id = {request_project_group_id})') - raise ERROR_PERMISSION_DENIED() - - @staticmethod - def _match_permission(service, api_class, method, permission): - if fnmatch.fnmatch(f'{service}.{api_class}.{method}', permission): - return True - else: - return False diff --git a/src/spaceone/identity/manager/domain_owner_manager.py b/src/spaceone/identity/manager/domain_owner_manager.py deleted file mode 100644 index 066c6ff8..00000000 --- a/src/spaceone/identity/manager/domain_owner_manager.py +++ /dev/null @@ -1,69 +0,0 @@ -import re -import logging - -from spaceone.core.manager import BaseManager -from spaceone.identity.lib.cipher import PasswordCipher -from spaceone.identity.model.domain_owner_model import DomainOwner -from spaceone.identity.error.error_user import * - -_LOGGER = logging.getLogger(__name__) - - -class DomainOwnerManager(BaseManager): - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.domain_owner_model: DomainOwner = self.locator.get_model('DomainOwner') - - def create_owner(self, params): - def _rollback(vo): - _LOGGER.info(f'[create_owner._rollback] Delete domain owner : {vo["owner_id"]} ({vo["domain_id"]})') - vo.delete() - - if params.get('password'): - self._check_password_format(params['password']) - hashed_pw = PasswordCipher().hashpw(params['password']) - params['password'] = hashed_pw - - domain_owner: DomainOwner = self.domain_owner_model.create(params) - - self.transaction.add_rollback(_rollback, domain_owner) - - return domain_owner - - def update_owner(self, params): - def _rollback(old_vo): - _LOGGER.info(f'[update_owner._rollback] Revert domain owner : {old_vo["name"]} ({old_vo["domain_id"]})') - domain_owner.update(old_vo) - - if params.get('password'): - self._check_password_format(params['password']) - hashed_pw = PasswordCipher().hashpw(params['password']) - params['password'] = hashed_pw - - domain_owner: DomainOwner = self.domain_owner_model.get(owner_id=params['owner_id'], domain_id=params['domain_id']) - - self.transaction.add_rollback(_rollback, domain_owner.to_dict()) - - return domain_owner.update(params) - - def delete_owner(self, domain_id, owner_id): - domain_owner: DomainOwner = self.domain_owner_model.get(domain_id=domain_id, owner_id=owner_id) - domain_owner.delete() - - def get_owner(self, domain_id, owner_id=None, only=None): - if owner_id: - return self.domain_owner_model.get(domain_id=domain_id, owner_id=owner_id, only=only) - else: - return self.domain_owner_model.get(domain_id=domain_id, only=only) - - @staticmethod - def _check_password_format(password): - if len(password) < 8: - raise ERROR_INCORRECT_PASSWORD_FORMAT(rule='At least 9 characters long.') - elif not re.search("[a-z]", password): - raise ERROR_INCORRECT_PASSWORD_FORMAT(rule='Contains at least one lowercase character') - elif not re.search("[A-Z]", password): - raise ERROR_INCORRECT_PASSWORD_FORMAT(rule='Contains at least one uppercase character') - elif not re.search("[0-9]", password): - raise ERROR_INCORRECT_PASSWORD_FORMAT(rule='Contains at least one number') diff --git a/src/spaceone/identity/manager/domain_secret_manager.py b/src/spaceone/identity/manager/domain_secret_manager.py deleted file mode 100644 index 51ad4129..00000000 --- a/src/spaceone/identity/manager/domain_secret_manager.py +++ /dev/null @@ -1,83 +0,0 @@ -import logging -from spaceone.core.auth.jwt import JWTUtil -from spaceone.core.cache import cacheable -from spaceone.core.manager import * -from spaceone.core import utils -from spaceone.identity.model.domain_secret_model import DomainSecret -from spaceone.identity.model.domain_model import Domain - -_LOGGER = logging.getLogger(__name__) - - -class DomainSecretManager(BaseManager): - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.domain_secret_model: DomainSecret = self.locator.get_model('DomainSecret') - - def create_domain_secret(self, domain_id): - def _rollback(vo): - _LOGGER.info(f'[create_domain_secret._rollback] Delete domain-secret : {vo.domain_id} ({vo.secret[0:6]}...)') - vo.delete() - - # Generate Domain-secret - secret = self._generate_domain_secret(domain_id) - - # Generate Domain Key - secret['domain_key'] = utils.random_string(16) - - domain_secret_vo: DomainSecret = self.domain_secret_model.create(secret) - self.transaction.add_rollback(_rollback, domain_secret_vo) - - @cacheable(key='public-jwk:{domain_id}', expire=600) - def get_domain_public_key(self, domain_id): - domain_secret_vo: DomainSecret = self.domain_secret_model.get(domain_id=domain_id) - return domain_secret_vo.pub_jwk - - @cacheable(key='private-jwk:{domain_id}', expire=600) - def get_domain_private_key(self, domain_id): - domain_secret_vo: DomainSecret = self.domain_secret_model.get(domain_id=domain_id) - return domain_secret_vo.prv_jwk - - @cacheable(key='refresh-public-jwk:{domain_id}', expire=600) - def get_domain_refresh_public_key(self, domain_id): - domain_secret_vo: DomainSecret = self.domain_secret_model.get(domain_id=domain_id) - if not domain_secret_vo.refresh_pub_jwk: - domain_secret_vo: DomainSecret = self._create_refresh_key(domain_secret_vo, domain_id) - - return domain_secret_vo.refresh_pub_jwk - - @cacheable(key='refresh-private-jwk:{domain_id}', expire=600) - def get_domain_refresh_private_key(self, domain_id): - domain_secret_vo: DomainSecret = self.domain_secret_model.get(domain_id=domain_id) - if not domain_secret_vo.refresh_pub_jwk: - domain_secret_vo: DomainSecret = self._create_refresh_key(domain_secret_vo, domain_id) - - return domain_secret_vo.refresh_prv_jwk - - def _create_refresh_key(self, domain_secret_vo: DomainSecret, domain_id): - domain_model: Domain = self.locator.get_model('Domain') - domain_vo = domain_model.get(domain_id=domain_id) - refresh_private_jwk, refresh_public_jwk = JWTUtil.generate_jwk() - - return domain_secret_vo.update({ - 'refresh_pub_jwk': refresh_public_jwk, - 'refresh_prv_jwk': refresh_private_jwk, - 'domain': domain_vo, - }) - - def _generate_domain_secret(self, domain_id: str) -> dict: - domain_model: Domain = self.locator.get_model('Domain') - domain_vo = domain_model.get(domain_id=domain_id) - - private_jwk, public_jwk = JWTUtil.generate_jwk() - refresh_private_jwk, refresh_public_jwk = JWTUtil.generate_jwk() - data = { - 'pub_jwk': public_jwk, - 'prv_jwk': private_jwk, - 'refresh_pub_jwk': refresh_public_jwk, - 'refresh_prv_jwk': refresh_private_jwk, - 'domain_id': domain_id, - 'domain': domain_vo - } - return data diff --git a/src/spaceone/identity/manager/email_manager.py b/src/spaceone/identity/manager/email_manager.py deleted file mode 100644 index e206f98c..00000000 --- a/src/spaceone/identity/manager/email_manager.py +++ /dev/null @@ -1,102 +0,0 @@ -import logging -import os - -from jinja2 import Environment, FileSystemLoader, select_autoescape - -from spaceone.core import config, utils -from spaceone.core.manager import BaseManager -from spaceone.identity.connector.smtp_connector import SMTPConnector - -_LOGGER = logging.getLogger(__name__) - -TEMPLATE_PATH = os.path.join(os.path.dirname(__file__), f'../template') -JINJA_ENV = Environment( - loader=FileSystemLoader(searchpath=TEMPLATE_PATH), - autoescape=select_autoescape() -) - -LANGUAGE_MAPPER = { - 'default': { - 'reset_password': 'Reset your password', - 'temp_password': 'Your password has been changed', - 'verify_email': 'Verify your notification email', - }, - 'ko': { - 'reset_password': '비밀번호 재설정 안내', - 'temp_password': '임시 비밀번호 발급 안내', - 'verify_email': '알림전용 이메일 계정 인증 안내', - }, - 'en': { - 'reset_password': 'Reset your password', - 'temp_password': 'Your password has been changed', - 'verify_email': 'Verify your notification email', - }, - 'ja': { - 'reset_password': 'パスワードリセットのご案内', - 'temp_password': '仮パスワード発行のご案内', - 'verify_email': '通知メールアカウント認証のご案内', - } - -} - - -class EmailManager(BaseManager): - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.smtp_connector: SMTPConnector = self.locator.get_connector('SMTPConnector') - - def send_reset_password_email(self, user_id, email, reset_password_link, language): - service_name = self._get_service_name() - language_map_info = LANGUAGE_MAPPER.get(language, 'default') - - template = JINJA_ENV.get_template(f'reset_pwd_link_when_pw_forgotten_{language}.html') - email_contents = template.render(user_name=user_id, reset_password_link=reset_password_link, service_name=service_name) - subject = f'[{service_name}] {language_map_info["reset_password"]}' - - self.smtp_connector.send_email(email, subject, email_contents) - - def send_temporary_password_email(self, user_id, email, console_link, temp_password, language): - service_name = self._get_service_name() - language_map_info = LANGUAGE_MAPPER.get(language, 'default') - - template = JINJA_ENV.get_template(f'temp_pwd_when_pw_forgotten_{language}.html') - email_contents = template.render(user_name=user_id, temp_password=temp_password, service_name=service_name, login_link=console_link) - subject = f'[{service_name}] {language_map_info["temp_password"]}' - - self.smtp_connector.send_email(email, subject, email_contents) - - def send_reset_password_email_when_user_added(self, user_id, email, reset_password_link, language): - service_name = self._get_service_name() - language_map_info = LANGUAGE_MAPPER.get(language, 'default') - - template = JINJA_ENV.get_template(f'reset_pwd_link_when_user_added_{language}.html') - email_contents = template.render(user_name=user_id, reset_password_link=reset_password_link, service_name=service_name) - subject = f'[{service_name}] {language_map_info["reset_password"]}' - - self.smtp_connector.send_email(email, subject, email_contents) - - def send_temporary_password_email_when_user_added(self, user_id, email, console_link, temp_password, language): - service_name = self._get_service_name() - language_map_info = LANGUAGE_MAPPER.get(language, 'default') - - template = JINJA_ENV.get_template(f'temp_pwd_when_user_added_{language}.html') - email_contents = template.render(user_name=user_id, temp_password=temp_password, service_name=service_name, login_link=console_link) - subject = f'[{service_name}] {language_map_info["temp_password"]}' - - self.smtp_connector.send_email(email, subject, email_contents) - - def send_verification_email(self, user_id, email, verification_code, language): - service_name = self._get_service_name() - language_map_info = LANGUAGE_MAPPER.get(language, 'default') - - template = JINJA_ENV.get_template(f'verification_code_{language}.html') - email_contents = template.render(user_name=user_id, verification_code=verification_code, service_name=service_name) - subject = f'[{service_name}] {language_map_info["verify_email"]}' - - self.smtp_connector.send_email(email, subject, email_contents) - - @staticmethod - def _get_service_name(): - return config.get_global('EMAIL_SERVICE_NAME') - diff --git a/src/spaceone/identity/manager/endpoint_manager.py b/src/spaceone/identity/manager/endpoint_manager.py deleted file mode 100644 index 60894e22..00000000 --- a/src/spaceone/identity/manager/endpoint_manager.py +++ /dev/null @@ -1,17 +0,0 @@ -import logging - -from spaceone.core import config -from spaceone.core.manager import BaseManager - -_LOGGER = logging.getLogger(__name__) - - -class EndpointManager(BaseManager): - - def list_endpoints(self, service=None): - endpoints = config.get_global('ENDPOINTS', []) - - if service: - endpoints = [endpoint for endpoint in endpoints if endpoint.get('service') == service] - - return endpoints, len(endpoints) diff --git a/src/spaceone/identity/manager/mfa_manager/__init__.py b/src/spaceone/identity/manager/mfa_manager/__init__.py deleted file mode 100644 index ea29ed6f..00000000 --- a/src/spaceone/identity/manager/mfa_manager/__init__.py +++ /dev/null @@ -1,74 +0,0 @@ -import logging -import random -from abc import abstractmethod, ABC, ABCMeta - -from spaceone.core import config, utils, cache -from spaceone.core.manager import BaseManager - -from spaceone.identity.error.error_mfa import ERROR_NOT_SUPPORTED_MFA_TYPE - -__all__ = ['BaseMFAManager', 'MFAManager'] -_LOGGER = logging.getLogger(__name__) - - -class BaseMFAManager(BaseManager, ABC): - - @abstractmethod - def enable_mfa(self, **kwargs): - pass - - @abstractmethod - def disable_mfa(self, **kwargs): - pass - - def confirm_mfa(self, **kwargs): - pass - - def _load_conf(self): - identity_conf = config.get_global('IDENTITY') or {} - mfa_conf = identity_conf.get('token', {}) - self.CONST_MFA_VERIFICATION_CODE_TIMEOUT = mfa_conf.get('mfa_verify_code_timeout', 300) - - -class MFAManager(BaseMFAManager, metaclass=ABCMeta): - mfa_type: str - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._load_conf() - - def enable_mfa(self, **kwargs): - raise NotImplementedError('MFAManager.enable_mfa not implemented!') - - def disable_mfa(self, **kwargs): - raise NotImplementedError('MFAManager.disable_mfa not implemented!') - - def confirm_mfa(self, **kwargs): - raise NotImplementedError('MFAManager.confirm_mfa not implemented!') - - def create_mfa_verify_code(self, user_id, domain_id): - if cache.is_set(): - verify_code = self._generate_verify_code() - cache.delete(f'mfa-verify-code:{domain_id}:{user_id}') - cache.set(f'mfa-verify-code:{domain_id}:{user_id}', verify_code, expire=self.CONST_MFA_VERIFICATION_CODE_TIMEOUT) - return verify_code - - @classmethod - def get_manager_by_mfa_type(cls, mfa_type): - for subclass in cls.__subclasses__(): - if subclass.mfa_type == mfa_type: - return subclass() - raise ERROR_NOT_SUPPORTED_MFA_TYPE(support_mfa_types=['EMAIL']) - - @staticmethod - def check_mfa_verify_code(user_id, domain_id, verify_code): - if cache.is_set(): - cached_verify_code = cache.get(f'mfa-verify-code:{domain_id}:{user_id}') - if cached_verify_code == verify_code: - cache.delete(f'mfa-verify-code:{domain_id}:{user_id}') - return True - return False - - @staticmethod - def _generate_verify_code(): - return str(random.randint(100000, 999999)) diff --git a/src/spaceone/identity/manager/mfa_manager/email_mfa_manger.py b/src/spaceone/identity/manager/mfa_manager/email_mfa_manger.py deleted file mode 100644 index 5375fa0f..00000000 --- a/src/spaceone/identity/manager/mfa_manager/email_mfa_manger.py +++ /dev/null @@ -1,82 +0,0 @@ -import logging -import os - -from jinja2 import Environment, FileSystemLoader, select_autoescape - -from spaceone.core import config -from spaceone.identity.connector.smtp_connector import SMTPConnector -from spaceone.identity.manager.mfa_manager import MFAManager - -_LOGGER = logging.getLogger(__name__) - -TEMPLATE_PATH = os.path.join(os.path.dirname(__file__), f'../../template') -JINJA_ENV = Environment( - loader=FileSystemLoader(searchpath=TEMPLATE_PATH), - autoescape=select_autoescape() -) - - -LANGUAGE_MAPPER = { - 'default': { - 'verify_mfa_email': 'Verify your MFA email', - 'authentication_mfa_email': 'Your multi-factor authentication code.', - }, - 'ko': { - 'verify_mfa_email': 'MFA 이메일 계정 인증 안내', - 'authentication_mfa_email': 'MFA 인증 코드 발송', - }, - 'en': { - 'verify_mfa_email': 'Verify your MFA email', - 'authentication_mfa_email': 'Your multi-factor authentication code.', - }, - 'ja': { - 'verify_mfa_email': 'MFAメールアカウント認証のご案内', - 'authentication_mfa_email': 'MFA認証コード送信', - } - -} - - -class EmailMFAManager(MFAManager): - mfa_type = 'EMAIL' - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.smtp_connector: SMTPConnector = self.locator.get_connector('SMTPConnector') - - def enable_mfa(self, user_id, domain_id, user_mfa, language): - self.send_mfa_verify_email(user_id, domain_id, user_mfa['options'].get('email'), language) - - def disable_mfa(self, user_id, domain_id, user_mfa, language): - self.send_mfa_verify_email(user_id, domain_id, user_mfa['options'].get('email'), language) - - def confirm_mfa(self, user_id, domain_id, verify_code): - return self.check_mfa_verify_code(user_id, domain_id, verify_code) - - def send_mfa_verify_email(self, user_id, domain_id, email, language): - service_name = self._get_service_name() - language_map_info = LANGUAGE_MAPPER.get(language, 'default') - verify_code = self.create_mfa_verify_code(user_id, domain_id) - - template = JINJA_ENV.get_template(f'verification_MFA_code_{language}.html') - email_contents = template.render(user_name=user_id, verification_code=verify_code, - service_name=service_name) - subject = f'[{service_name}] {language_map_info["verify_mfa_email"]}' - - self.smtp_connector.send_email(email, subject, email_contents) - - def send_mfa_authentication_email(self, user_id, domain_id, email, language): - service_name = self._get_service_name() - language_map_info = LANGUAGE_MAPPER.get(language, 'default') - verify_code = self.create_mfa_verify_code(user_id, domain_id) - - template = JINJA_ENV.get_template(f'authentication_code_{language}.html') - email_contents = template.render(user_name=user_id, verification_code=verify_code, - service_name=service_name) - subject = f'[{service_name}] {language_map_info["authentication_mfa_email"]}' - - self.smtp_connector.send_email(email, subject, email_contents) - - @staticmethod - def _get_service_name(): - return config.get_global('EMAIL_SERVICE_NAME') diff --git a/src/spaceone/identity/manager/project_group_manager.py b/src/spaceone/identity/manager/project_group_manager.py deleted file mode 100644 index 6029b991..00000000 --- a/src/spaceone/identity/manager/project_group_manager.py +++ /dev/null @@ -1,89 +0,0 @@ -import logging -from spaceone.core import cache -from spaceone.core.manager import BaseManager -from spaceone.identity.model.project_group_model import ProjectGroup - -_LOGGER = logging.getLogger(__name__) - - -class ProjectGroupManager(BaseManager): - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.project_group_model: ProjectGroup = self.locator.get_model('ProjectGroup') - - def create_project_group(self, params): - def _rollback(project_group_vo): - _LOGGER.info( - f'[create_project_group._rollback] Delete project group : {project_group_vo.name} ({project_group_vo.project_group_id})') - project_group_vo.delete() - - project_group_vo: ProjectGroup = self.project_group_model.create(params) - self.transaction.add_rollback(_rollback, project_group_vo) - - if params.get('parent_project_group') is not None: - parent_project_group_id = params['parent_project_group_id'] - cache.delete_pattern(f'project-path:*{parent_project_group_id}*') - cache.delete_pattern(f'role-bindings:*{parent_project_group_id}*') - cache.delete_pattern(f'user-scopes:*{parent_project_group_id}*') - self._delete_parent_project_group_cache(params['parent_project_group']) - - return project_group_vo - - def update_project_group(self, params): - project_group_vo = self.get_project_group(params['project_group_id'], params['domain_id']) - return self.update_project_group_by_vo(params, project_group_vo) - - def update_project_group_by_vo(self, params, project_group_vo): - def _rollback(old_data): - _LOGGER.info(f'[update_project_group._rollback] Revert Data : {old_data["name"]} ({old_data["project_group_id"]})') - project_group_vo.update(old_data) - - self.transaction.add_rollback(_rollback, project_group_vo.to_dict()) - - if params.get('parent_project_group') is not None: - parent_project_group_id = params['parent_project_group_id'] - project_group_id = project_group_vo.project_group_id - - if parent_project_group_id is not None: - cache.delete_pattern(f'project-path:*{parent_project_group_id}*') - cache.delete_pattern(f'role-bindings:*{parent_project_group_id}*') - cache.delete_pattern(f'user-scopes:*{parent_project_group_id}*') - self._delete_parent_project_group_cache(params['parent_project_group']) - - cache.delete_pattern(f'project-path:*{project_group_id}*') - cache.delete_pattern(f'role-bindings:*{project_group_id}*') - cache.delete_pattern(f'user-scopes:*{project_group_id}*') - self._delete_parent_project_group_cache(project_group_vo) - - return project_group_vo.update(params) - - def delete_project_group(self, project_group_id, domain_id): - project_group_vo = self.get_project_group(project_group_id, domain_id) - self.delete_project_group_by_vo(project_group_vo) - - def delete_project_group_by_vo(self, project_group_vo): - project_group_vo.delete() - - cache.delete_pattern(f'project-path:*{project_group_vo.project_group_id}*') - cache.delete_pattern(f'role-bindings:*{project_group_vo.project_group_id}*') - cache.delete_pattern(f'role-bindings:*:') - cache.delete_pattern(f'user-scopes:*{project_group_vo.project_group_id}*') - self._delete_parent_project_group_cache(project_group_vo) - - def get_project_group(self, project_group_id, domain_id, only=None): - return self.project_group_model.get(project_group_id=project_group_id, domain_id=domain_id, only=only) - - def filter_project_groups(self, **conditions): - return self.project_group_model.filter(**conditions) - - def list_project_groups(self, query): - return self.project_group_model.query(**query) - - def stat_project_groups(self, query): - return self.project_group_model.stat(**query) - - def _delete_parent_project_group_cache(self, project_group_vo): - cache.delete_pattern(f'project-group-children:*{project_group_vo.project_group_id}') - if project_group_vo.parent_project_group: - self._delete_parent_project_group_cache(project_group_vo.parent_project_group) diff --git a/src/spaceone/identity/manager/project_manager.py b/src/spaceone/identity/manager/project_manager.py deleted file mode 100644 index d1ed5864..00000000 --- a/src/spaceone/identity/manager/project_manager.py +++ /dev/null @@ -1,122 +0,0 @@ -import logging -from spaceone.core import cache -from spaceone.core.manager import BaseManager -from spaceone.identity.manager import ProjectGroupManager -from spaceone.identity.model.project_model import Project - -_LOGGER = logging.getLogger(__name__) - - -class ProjectManager(BaseManager): - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.project_model: Project = self.locator.get_model('Project') - self.project_group_mgr: ProjectGroupManager = self.locator.get_manager('ProjectGroupManager') - - def create_project(self, params): - def _rollback(project_vo): - _LOGGER.info(f'[create_project._rollback] Delete project : {project_vo.name} ({project_vo.project_id})') - project_vo.delete() - - project_vo: Project = self.project_model.create(params) - self.transaction.add_rollback(_rollback, project_vo) - - project_group_id = params['project_group_id'] - cache.delete_pattern(f'project-path:*{project_group_id}*') - cache.delete_pattern(f'role-bindings:*{project_group_id}*') - cache.delete_pattern(f'user-scopes:*{project_group_id}*') - self._delete_parent_project_group_cache(params['project_group']) - - return project_vo - - def update_project(self, params): - project_vo = self.get_project(params['project_id'], params['domain_id']) - return self.update_project_by_vo(params, project_vo) - - def update_project_by_vo(self, params, project_vo): - def _rollback(old_data): - _LOGGER.info(f'[update_project._rollback] Revert Data : {old_data["name"]} ({old_data["project_id"]})') - project_vo.update(old_data) - - self.transaction.add_rollback(_rollback, project_vo.to_dict()) - - if 'project_group' in params: - new_project_group_id = params['project_group_id'] - old_project_group_id = project_vo.project_group.project_group_id - - cache.delete_pattern(f'project-path:*{new_project_group_id}*') - cache.delete_pattern(f'project-path:*{old_project_group_id}*') - cache.delete_pattern(f'role-bindings:*{new_project_group_id}*') - cache.delete_pattern(f'role-bindings:*{old_project_group_id}*') - cache.delete_pattern(f'user-scopes:*{new_project_group_id}*') - cache.delete_pattern(f'user-scopes:*{old_project_group_id}*') - self._delete_parent_project_group_cache(params['project_group']) - self._delete_parent_project_group_cache(project_vo.project_group) - - return project_vo.update(params) - - def delete_project(self, project_id, domain_id): - project_vo = self.get_project(project_id, domain_id) - self.delete_project_by_vo(project_vo) - - def delete_project_by_vo(self, project_vo): - project_group_id = project_vo.project_group_id - project_vo.delete() - - if project_group_id: - cache.delete_pattern(f'project-path:*{project_group_id}*') - cache.delete_pattern(f'project-group-children:*{project_group_id}') - cache.delete_pattern(f'role-bindings:*{project_group_id}*') - cache.delete_pattern(f'role-bindings:*:') - cache.delete_pattern(f'user-scopes:*{project_group_id}*') - self._delete_parent_project_group_cache(project_vo.project_group) - - def get_project(self, project_id, domain_id, only=None): - return self.project_model.get(project_id=project_id, domain_id=domain_id, only=only) - - def filter_projects(self, **conditions): - return self.project_model.filter(**conditions) - - def list_projects(self, query): - return self.project_model.query(**query) - - def stat_projects(self, query): - return self.project_model.stat(**query) - - @cache.cacheable(key='project-path:{domain_id}:{project_id}:{project_group_id}', expire=3600) - def get_project_path(self, project_id, project_group_id, domain_id): - project_path = None - if project_id: - try: - project_vo = self.get_project(project_id, domain_id) - project_path = [project_id] - project_path += self._get_parent_project_group_path(project_vo.project_group, []) - except Exception as e: - _LOGGER.debug(f'[get_project_path] Project could not be found. ' - f'(project_id={project_id}, reason={e})') - - elif project_group_id: - try: - project_group_vo = self.project_group_mgr.get_project_group(project_group_id, domain_id) - project_path = [project_group_id] - project_path += self._get_parent_project_group_path(project_group_vo.parent_project_group, []) - except Exception as e: - _LOGGER.debug(f'[_get_project_path] Project group could not be found. ' - f'(project_group_id={project_group_id}, reason={e})') - - return project_path - - def _get_parent_project_group_path(self, project_group_vo, project_path): - project_group_id = project_group_vo.project_group_id - project_path.append(project_group_id) - - if project_group_vo.parent_project_group: - project_path = self._get_parent_project_group_path(project_group_vo.parent_project_group, project_path) - - return project_path - - def _delete_parent_project_group_cache(self, project_group_vo): - cache.delete_pattern(f'project-group-children:*{project_group_vo.project_group_id}') - if project_group_vo.parent_project_group: - self._delete_parent_project_group_cache(project_group_vo.parent_project_group) diff --git a/src/spaceone/identity/manager/provider_manager.py b/src/spaceone/identity/manager/provider_manager.py deleted file mode 100644 index 72e8259e..00000000 --- a/src/spaceone/identity/manager/provider_manager.py +++ /dev/null @@ -1,57 +0,0 @@ -import logging - -from spaceone.core.manager import BaseManager -from spaceone.identity.conf.provider_conf import DEFAULT_PROVIDERS -from spaceone.identity.model.provider_model import Provider - -_LOGGER = logging.getLogger(__name__) - - -class ProviderManager(BaseManager): - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.provider_model: Provider = self.locator.get_model('Provider') - - def create_provider(self, params): - def _rollback(provider_vo): - _LOGGER.info(f'[create_provider._rollback] Create provider : {provider_vo.provider}') - provider_vo.delete() - - provider_vo: Provider = self.provider_model.create(params) - self.transaction.add_rollback(_rollback, provider_vo) - - return provider_vo - - def update_provider(self, params): - def _rollback(old_data): - _LOGGER.info(f'[update_provider._rollback] Revert Data : {old_data["provider"]}') - provider_vo.update(old_data) - - provider_vo: Provider = self.get_provider(params['provider'], params['domain_id']) - self.transaction.add_rollback(_rollback, provider_vo.to_dict()) - - return provider_vo.update(params) - - def delete_provider(self, provider, domain_id): - provider_vo: Provider = self.get_provider(provider, domain_id) - provider_vo.delete() - - def get_provider(self, provider, domain_id, only=None): - return self.provider_model.get(provider=provider, domain_id=domain_id, only=only) - - def filter_providers(self, **conditions): - return self.provider_model.filter(**conditions) - - def list_providers(self, query={}): - return self.provider_model.query(**query) - - def stat_providers(self, query): - return self.provider_model.stat(**query) - - def create_default_providers(self, installed_providers, domain_id): - for provider in DEFAULT_PROVIDERS: - if provider['provider'] not in installed_providers: - _LOGGER.debug(f'Create default provider: {provider["name"]}') - provider['domain_id'] = domain_id - self.create_provider(provider) diff --git a/src/spaceone/identity/manager/role_binding_manager.py b/src/spaceone/identity/manager/role_binding_manager.py deleted file mode 100644 index a6e6d248..00000000 --- a/src/spaceone/identity/manager/role_binding_manager.py +++ /dev/null @@ -1,173 +0,0 @@ -import logging -from spaceone.core import cache -from spaceone.core.manager import BaseManager -from spaceone.identity.error.error_role import * -from spaceone.identity.model.role_binding_model import * -from spaceone.identity.manager import RoleManager, ProjectManager, ProjectGroupManager, UserManager - -_LOGGER = logging.getLogger(__name__) -_SUPPORTED_RESOURCE_TYPES = ['identity.User'] - - -class RoleBindingManager(BaseManager): - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.role_binding_model: RoleBinding = self.locator.get_model('RoleBinding') - - def create_role_binding(self, params): - def _rollback(role_binding_vo): - _LOGGER.info(f'[create_role_binding._rollback] Delete role binding : {role_binding_vo.name} ({role_binding_vo.role_binding_id})') - role_binding_vo.delete() - - resource_type = params['resource_type'] - resource_id = params['resource_id'] - role_id = params['role_id'] - project_id = params.get('project_id') - project_group_id = params.get('project_group_id') - domain_id = params['domain_id'] - - role_mgr: RoleManager = self.locator.get_manager('RoleManager') - project_mgr: ProjectManager = self.locator.get_manager('ProjectManager') - project_group_mgr: ProjectGroupManager = self.locator.get_manager('ProjectGroupManager') - user_mgr: UserManager = self.locator.get_manager('UserManager') - - self._check_resource_type(resource_type) - if resource_type == 'identity.User': - params['user'] = user_mgr.get_user(resource_id, domain_id) - - role_vo = role_mgr.get_role(role_id, domain_id) - self._check_role_type(role_vo.role_type, resource_type, resource_id, domain_id) - params['role'] = role_vo - - if role_vo.role_type == 'PROJECT': - if project_id: - project_vo = project_mgr.get_project(project_id, domain_id) - self._check_duplicate_project_role(resource_type, resource_id, project_vo, project_id) - params['project'] = project_vo - - elif project_group_id: - project_group_vo = project_group_mgr.get_project_group(project_group_id, domain_id) - self._check_duplicate_project_group_role(resource_type, resource_id, project_group_vo, project_group_id) - params['project_group'] = project_group_vo - else: - raise ERROR_REQUIRED_PROJECT_OR_PROJECT_GROUP() - else: - self._check_duplicate_domain_and_system_role(resource_type, resource_id, role_vo, role_id) - if project_id: - raise ERROR_NOT_ALLOWED_PROJECT_ID() - elif project_group_id: - raise ERROR_NOT_ALLOWED_PROJECT_GROUP_ID() - - role_binding_vo: RoleBinding = self.role_binding_model.create(params) - self.transaction.add_rollback(_rollback, role_binding_vo) - - if role_vo.role_type in ['DOMAIN', 'SYSTEM']: - self._delete_old_domain_or_system_role_binding(resource_id, resource_type, role_vo.role_type, - role_binding_vo.role_binding_id, domain_id) - - cache.delete_pattern(f'role-bindings:{domain_id}:{resource_id}*') - cache.delete_pattern(f'user-permissions:{domain_id}:{resource_id}*') - cache.delete_pattern(f'user-scopes:{domain_id}:{resource_id}*') - - return role_binding_vo - - def update_role_binding(self, params): - role_binding_vo = self.get_role_binding(params['role_binding_id'], params['domain_id']) - return self.update_role_binding_by_vo(params, role_binding_vo) - - def update_role_binding_by_vo(self, params, role_binding_vo): - def _rollback(old_data): - _LOGGER.info(f'[update_role_binding._rollback] Revert Data : {old_data["role_binding_id"]}') - role_binding_vo.update(old_data) - - self.transaction.add_rollback(_rollback, role_binding_vo.to_dict()) - - return role_binding_vo.update(params) - - def delete_role_binding(self, role_binding_id, domain_id): - role_binding_vo = self.get_role_binding(role_binding_id, domain_id) - self.delete_role_binding_by_vo(role_binding_vo) - - def delete_role_binding_by_vo(self, role_binding_vo): - resource_id = role_binding_vo.resource_id - domain_id = role_binding_vo.domain_id - - _LOGGER.debug(f'[delete_role_binding_by_vo] resource_id = {resource_id}, domain_id = {domain_id}') - - role_binding_vo.delete() - - cache.delete_pattern(f'role-bindings:{domain_id}:{resource_id}*') - cache.delete_pattern(f'user-permissions:{domain_id}:{resource_id}*') - cache.delete_pattern(f'user-scopes:{domain_id}:{resource_id}*') - - def get_role_binding(self, role_binding_id, domain_id, only=None): - return self.role_binding_model.get(role_binding_id=role_binding_id, domain_id=domain_id, only=only) - - def get_project_role_binding(self, resource_type, resource_id, domain_id, project_vo=None, project_group_vo=None): - return self.role_binding_model.filter(resource_type=resource_type, resource_id=resource_id, domain_id=domain_id, - project=project_vo, project_group=project_group_vo) - - def get_user_role_bindings(self, user_id, domain_id): - return self.role_binding_model.filter(resource_type='identity.User', resource_id=user_id, domain_id=domain_id) - - def list_role_bindings(self, query): - return self.role_binding_model.query(**query) - - def stat_role_bindings(self, query): - return self.role_binding_model.stat(**query) - - def _delete_old_domain_or_system_role_binding(self, resource_id, resource_type, role_type, new_role_binding_id, - domain_id): - query = { - 'filter': [ - {'k': 'resource_id', 'v': resource_id, 'o': 'eq'}, - {'k': 'resource_type', 'v': resource_type, 'o': 'eq'}, - {'k': 'role.role_type', 'v': role_type, 'o': 'eq'}, - {'k': 'role_binding_id', 'v': new_role_binding_id, 'o': 'not'}, - {'k': 'domain_id', 'v': domain_id, 'o': 'eq'}, - ] - } - - _LOGGER.debug(f'[_delete_old_domain_or_system_role_binding] query = {query}') - - rb_vos, total_count = self.list_role_bindings(query) - rb_vos.delete() - - @staticmethod - def _check_resource_type(resource_type): - if resource_type not in _SUPPORTED_RESOURCE_TYPES: - raise ERROR_INVALID_PARAMETER( - key='resource_type', reason=f'resource_type is not supported. (support = {_SUPPORTED_RESOURCE_TYPES})') - - def _check_role_type(self, role_type, resource_type, resource_id, domain_id): - role_binding_vos = self.role_binding_model.filter(resource_type=resource_type, - resource_id=resource_id, domain_id=domain_id) - for role_binding_vo in role_binding_vos: - if role_type == 'SYSTEM': - if role_binding_vo.role.role_type in ['PROJECT', 'DOMAIN']: - raise ERROR_NOT_ALLOWED_ROLE_TYPE() - else: - if role_binding_vo.role.role_type == 'SYSTEM': - raise ERROR_NOT_ALLOWED_ROLE_TYPE() - - def _check_duplicate_domain_and_system_role(self, resource_type, resource_id, role_vo, role_id): - rb_vos = self.role_binding_model.filter(resource_type=resource_type, resource_id=resource_id, role=role_vo) - - if rb_vos.count() > 0: - raise ERROR_DUPLICATE_ROLE_BOUND(role_id=role_id, resource_id=resource_id) - - def _check_duplicate_project_role(self, resource_type, resource_id, project_vo, project_id): - project_rb_vos = self.role_binding_model.filter(resource_type=resource_type, resource_id=resource_id, - project=project_vo) - - if project_rb_vos.count() > 0: - raise ERROR_DUPLICATE_RESOURCE_IN_PROJECT(project_id=project_id, resource_id=resource_id) - - def _check_duplicate_project_group_role(self, resource_type, resource_id, project_group_vo, project_group_id): - pg_rb_vos = self.role_binding_model.filter(resource_type=resource_type, resource_id=resource_id, - project_group=project_group_vo) - - if pg_rb_vos.count() > 0: - raise ERROR_DUPLICATE_RESOURCE_IN_PROJECT_GROUP(project_group_id=project_group_id, - resource_id=resource_id) diff --git a/src/spaceone/identity/manager/role_manager.py b/src/spaceone/identity/manager/role_manager.py deleted file mode 100644 index 5923fe42..00000000 --- a/src/spaceone/identity/manager/role_manager.py +++ /dev/null @@ -1,56 +0,0 @@ -import logging -from spaceone.core import cache -from spaceone.core.manager import BaseManager -from spaceone.identity.model.policy_model import * -from spaceone.identity.model.role_model import * - -_LOGGER = logging.getLogger(__name__) - - -class RoleManager(BaseManager): - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.role_model: Role = self.locator.get_model('Role') - - def create_role(self, params): - def _rollback(role_vo): - _LOGGER.info(f'[create_role._rollback] Delete role : {role_vo.name} ({role_vo.role_id})') - role_vo.delete() - - role_vo = self.role_model.create(params) - self.transaction.add_rollback(_rollback, role_vo) - - return role_vo - - def update_role(self, params): - def _rollback(old_data): - _LOGGER.info(f'[update_role._rollback] Revert Data : {old_data["name"], ({old_data["role_id"]})}') - role_vo.update(old_data) - - role_vo: Role = self.get_role(params['role_id'], params['domain_id']) - self.transaction.add_rollback(_rollback, role_vo.to_dict()) - - cache.delete_pattern(f'role-permissions:*{params["role_id"]}') - cache.delete_pattern(f'user-permissions:*{params["role_id"]}*') - - return role_vo.update(params) - - def delete_role(self, role_id, domain_id): - role_vo = self.get_role(role_id, domain_id) - role_vo.delete() - - cache.delete_pattern(f'role-permissions:*{role_id}') - cache.delete_pattern(f'user-permissions:*{role_id}*') - - def get_role(self, role_id, domain_id, only=None): - return self.role_model.get(role_id=role_id, domain_id=domain_id, only=only) - - def filter_roles(self, **conditions): - return self.role_model.filter(**conditions) - - def list_roles(self, query): - return self.role_model.query(**query) - - def stat_roles(self, query): - return self.role_model.stat(**query) diff --git a/src/spaceone/identity/manager/service_account_manager.py b/src/spaceone/identity/manager/service_account_manager.py deleted file mode 100644 index f86374e5..00000000 --- a/src/spaceone/identity/manager/service_account_manager.py +++ /dev/null @@ -1,146 +0,0 @@ -import logging - -from spaceone.core.error import * -from spaceone.core.manager import BaseManager -from spaceone.core.connector.space_connector import SpaceConnector -from spaceone.identity.model.service_account_model import ServiceAccount - -_LOGGER = logging.getLogger(__name__) - - -class ServiceAccountManager(BaseManager): - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.service_account_model: ServiceAccount = self.locator.get_model('ServiceAccount') - - def create_service_account(self, params): - def _rollback(service_account_vo): - _LOGGER.info(f'[create_service_account._rollback] ' - f'Create service_account : {service_account_vo.name} ' - f'({service_account_vo.service_account_id})') - service_account_vo.delete() - - service_account_vo: ServiceAccount = self.service_account_model.create(params) - self.transaction.add_rollback(_rollback, service_account_vo) - - return service_account_vo - - def update_service_account(self, params): - service_account_vo: ServiceAccount = self.get_service_account(params['service_account_id'], - params['domain_id']) - - return self.update_service_account_by_vo(params, service_account_vo) - - def update_service_account_by_vo(self, params, service_account_vo): - def _rollback(old_data): - _LOGGER.info(f'[update_service_account._rollback] Revert Data : ' - f'{old_data["service_account_id"]}') - service_account_vo.update(old_data) - - self.transaction.add_rollback(_rollback, service_account_vo.to_dict()) - - return service_account_vo.update(params) - - def delete_service_account(self, service_account_id, domain_id): - service_account_vo: ServiceAccount = self.get_service_account(service_account_id, domain_id) - service_account_vo.delete() - - def get_service_account(self, service_account_id, domain_id, only=None): - return self.service_account_model.get(service_account_id=service_account_id, domain_id=domain_id, only=only) - - def list_service_accounts(self, query={}): - return self.service_account_model.query(**query) - - def stat_service_accounts(self, query): - return self.service_account_model.stat(**query) - - def update_secret_project(self, service_account_id, project_id, domain_id): - secret_connector: SpaceConnector = self.locator.get_connector('SpaceConnector', service='secret') - response = self._list_secrets(secret_connector, service_account_id, domain_id) - secrets = response.get('results', []) - - for secret_info in secrets: - secret_connector.dispatch('Secret.update', { - 'secret_id': secret_info['secret_id'], - 'project_id': project_id, - 'domain_id': domain_id - }) - - def release_secret_project(self, service_account_id, domain_id): - secret_connector: SpaceConnector = self.locator.get_connector('SpaceConnector', service='secret') - response = self._list_secrets(secret_connector, service_account_id, domain_id) - secrets = response.get('results', []) - - for secret_info in secrets: - secret_connector.dispatch('Secret.update', { - 'secret_id': secret_info['secret_id'], - 'release_project': True, - 'domain_id': domain_id - }) - - def delete_service_account_secrets(self, service_account_id, domain_id, service_account_type): - secret_connector: SpaceConnector = self.locator.get_connector('SpaceConnector', service='secret') - if service_account_type == 'TRUSTED': - response = self._list_trusted_secrets(secret_connector, service_account_id, domain_id) - for trusted_secret_info in response.get('results', []): - secret_connector.dispatch('TrustedSecret.delete', { - 'trusted_secret_id': trusted_secret_info['trusted_secret_id'], - 'domain_id': domain_id - }) - else: - response = self._list_secrets(secret_connector, service_account_id, domain_id) - for secret_info in response.get('results', []): - secret_connector.dispatch('Secret.delete', { - 'secret_id': secret_info['secret_id'], - 'domain_id': domain_id - }) - - def get_all_service_account_ids_using_secret(self, domain_id): - secret_connector: SpaceConnector = self.locator.get_connector('SpaceConnector', service='secret') - response = secret_connector.dispatch('Secret.stat', { - 'query': { - 'distinct': 'service_account_id', - 'filter': [ - { - 'k': 'service_account_id', - 'v': None, - 'o': 'not' - } - ] - }, - 'domain_id': domain_id - }) - - return response.get('results', []) - - def check_service_account_secrets(self, service_account_id, domain_id, service_account_type): - secret_connector: SpaceConnector = self.locator.get_connector('SpaceConnector', service='secret') - - if service_account_type == 'TRUSTED': - response = self._list_trusted_secrets(secret_connector, service_account_id, domain_id) - total_count = response.get('total_count', 0) - - if total_count > 0: - raise ERROR_EXIST_RESOURCE(parent='ServiceAccount', child='TrustedSecret') - else: - response = self._list_secrets(secret_connector, service_account_id, domain_id) - - total_count = response.get('total_count', 0) - - if total_count > 0: - raise ERROR_EXIST_RESOURCE(parent='ServiceAccount', child='Secret') - - @staticmethod - def _list_secrets(secret_connector, service_account_id, domain_id): - return secret_connector.dispatch('Secret.list', { - 'service_account_id': service_account_id, - 'domain_id': domain_id - }) - - @staticmethod - def _list_trusted_secrets(secret_connector, service_account_id, domain_id): - return secret_connector.dispatch('TrustedSecret.list', { - 'service_account_id': service_account_id, - 'domain_id': domain_id - }) \ No newline at end of file diff --git a/src/spaceone/identity/manager/token_manager/__init__.py b/src/spaceone/identity/manager/token_manager/__init__.py deleted file mode 100644 index 27081d4e..00000000 --- a/src/spaceone/identity/manager/token_manager/__init__.py +++ /dev/null @@ -1,181 +0,0 @@ -import logging -import time -import random -from abc import abstractmethod, ABC, ABCMeta - -from spaceone.core import config, utils, cache -from spaceone.core.manager import BaseManager -from spaceone.core.auth.jwt.jwt_util import JWTUtil - -from spaceone.identity.error.error_authentication import * - - -__all__ = ['TokenManager', 'JWTManager'] -_LOGGER = logging.getLogger(__name__) - - -class TokenManager(BaseManager, ABC): - - is_authenticated = False - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._load_conf() - - @abstractmethod - def issue_temporary_token(self, **kwargs): - pass - - @abstractmethod - def issue_token(self, **kwargs): - pass - - @abstractmethod - def refresh_token(self, user_id, domain_id, **kwargs): - pass - - @abstractmethod - def authenticate(self, user_id, domain_id, credentials): - pass - - @abstractmethod - def check_refreshable(self, key, ttl): - pass - - def _load_conf(self): - identity_conf = config.get_global('IDENTITY') or {} - token_conf = identity_conf.get('token', {}) - self.CONST_TOKEN_TIMEOUT = token_conf.get('token_timeout', 1800) - self.CONST_VERIFY_CODE_TIMEOUT = token_conf.get('verify_code_timeout', 3600) - self.CONST_REFRESH_TIMEOUT = token_conf.get('refresh_timeout', 3600) - self.CONST_REFRESH_TTL = token_conf.get('refresh_ttl', -1) - self.CONST_REFRESH_ONCE = token_conf.get('refresh_once', True) - - -class JWTManager(TokenManager, metaclass=ABCMeta): - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.old_refresh_key = None - - def issue_temporary_token(self, **kwargs): - raise NotImplementedError('TokenManager.issue_temporary_token not implemented!') - - def issue_token(self, **kwargs): - raise NotImplementedError('TokenManager.issue_token not implemented!') - - def refresh_token(self, user_id, domain_id, **kwargs): - raise NotImplementedError('TokenManager.refresh_token not implemented!') - - def authenticate(self, user_id, domain_id, credentials): - raise NotImplementedError('TokenManager.authenticate not implemented!') - - def check_refreshable(self, refresh_key, ttl): - if self.CONST_REFRESH_ONCE: - if cache.is_set() and cache.get(f'refresh-token:{refresh_key}') is None: - raise ERROR_INVALID_REFRESH_TOKEN() - - if ttl == 0: - raise ERROR_REFRESH_COUNT() - - self.is_authenticated = True - self.old_refresh_key = refresh_key - - def issue_access_token(self, user_type, user_id, domain_id, **kwargs): - private_jwk = self._get_private_jwk(kwargs) - permissions = kwargs.get('permissions') - timeout = kwargs.get('timeout') - if timeout is None: - timeout = self.CONST_TOKEN_TIMEOUT - - payload = { - 'cat': 'ACCESS_TOKEN', - 'user_type': user_type, - 'did': domain_id, - 'aud': user_id, - 'iat': int(time.time()), - 'exp': int(time.time() + timeout) - } - - if permissions: - payload['permissions'] = permissions - - encoded = JWTUtil.encode(payload, private_jwk) - return encoded - - def issue_refresh_token(self, user_type, user_id, domain_id, **kwargs): - refresh_private_jwk = self._get_refresh_private_jwk(kwargs) - ttl = kwargs.get('ttl') - timeout = kwargs.get('timeout') - - if ttl is None: - ttl = self.CONST_REFRESH_TTL - elif ttl > self.CONST_REFRESH_TTL: - raise ERROR_MAX_REFRESH_COUNT(max_refresh_count=self.CONST_REFRESH_TTL) - - if timeout is None: - timeout = self.CONST_REFRESH_TIMEOUT - - refresh_key = self._generate_refresh_key() - - payload = { - 'cat': 'REFRESH_TOKEN', - 'user_type': user_type, - 'did': domain_id, - 'aud': user_id, - 'iat': int(time.time()), - 'exp': int(time.time() + timeout), - 'key': refresh_key, - 'ttl': ttl - } - - encoded = JWTUtil.encode(payload, refresh_private_jwk) - - if self.CONST_REFRESH_ONCE: - self._set_refresh_token_cache(refresh_key) - - return encoded - - @staticmethod - def _generate_refresh_key(): - return utils.random_string(16) - - @staticmethod - def _get_private_jwk(kwargs): - if 'private_jwk' not in kwargs: - raise ERROR_NOT_FOUND_PRIVATE_KEY(purpose='Access Token') - - return kwargs['private_jwk'] - - @staticmethod - def _get_refresh_private_jwk(kwargs): - if 'refresh_private_jwk' not in kwargs: - raise ERROR_NOT_FOUND_PRIVATE_KEY(purpose='Refresh Token') - - return kwargs['refresh_private_jwk'] - - def _set_refresh_token_cache(self, new_refresh_key): - if cache.is_set(): - if self.old_refresh_key: - cache.delete(f'refresh-token:{self.old_refresh_key}') - - cache.set(f'refresh-token:{new_refresh_key}', '', expire=self.CONST_REFRESH_TIMEOUT) - - def create_verify_code(self, user_id, domain_id): - if cache.is_set(): - verify_code = self._generate_verify_code() - cache.delete(f'verify-code:{domain_id}:{user_id}') - cache.set(f'verify-code:{domain_id}:{user_id}', verify_code, expire=self.CONST_VERIFY_CODE_TIMEOUT) - return verify_code - - @staticmethod - def check_verify_code(user_id, domain_id, verify_code): - if cache.is_set(): - cached_verify_code = cache.get(f'verify-code:{domain_id}:{user_id}') - if cached_verify_code == verify_code: - return True - return False - - @staticmethod - def _generate_verify_code(): - return str(random.randint(100000, 999999)) diff --git a/src/spaceone/identity/manager/token_manager/domain_owner_token_manager.py b/src/spaceone/identity/manager/token_manager/domain_owner_token_manager.py deleted file mode 100644 index eb01f6a4..00000000 --- a/src/spaceone/identity/manager/token_manager/domain_owner_token_manager.py +++ /dev/null @@ -1,60 +0,0 @@ -import logging -from datetime import datetime - -from spaceone.identity.error.error_authentication import * -from spaceone.identity.lib.cipher import PasswordCipher -from spaceone.identity.manager.token_manager import JWTManager -from spaceone.identity.manager import DomainOwnerManager -from spaceone.identity.model import DomainOwner - -_LOGGER = logging.getLogger(__name__) - - -class DomainOwnerTokenManager(JWTManager): - user: DomainOwner = None - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.domain_owner_mgr: DomainOwnerManager = self.locator.get_manager('DomainOwnerManager') - - def authenticate(self, user_id, domain_id, credentials): - pw_to_check = self._parse_password(credentials) - - self.user = self.domain_owner_mgr.get_owner(owner_id=user_id, domain_id=domain_id) - - is_correct = PasswordCipher().checkpw(pw_to_check, self.user.password) - _LOGGER.debug(f'[authenticate] is_correct: {is_correct}, pw_to_check: {pw_to_check}, hashed_pw: {self.user.password}') - - if is_correct: - self.is_authenticated = True - else: - raise ERROR_AUTHENTICATION_FAILURE(user_id=self.user.owner_id) - - def issue_token(self, **kwargs): - if self.is_authenticated is False: - raise ERROR_NOT_AUTHENTICATED() - - # Issue token - access_token = self.issue_access_token('DOMAIN_OWNER', self.user.owner_id, self.user.domain_id, **kwargs) - refresh_token = self.issue_refresh_token('DOMAIN_OWNER', self.user.owner_id, self.user.domain_id, **kwargs) - - # Update user's last_accessed_at field - self.user.update({'last_accessed_at': datetime.utcnow()}) - - return { - 'access_token': access_token, - 'refresh_token': refresh_token - } - - def refresh_token(self, user_id, domain_id, **kwargs): - self.user = self.domain_owner_mgr.get_owner(owner_id=user_id, domain_id=domain_id) - return self.issue_token(**kwargs) - - @staticmethod - def _parse_password(credentials): - pw_to_check = credentials.get('password', None) - - if pw_to_check is None: - raise ERROR_INVALID_CREDENTIALS() - - return pw_to_check diff --git a/src/spaceone/identity/manager/token_manager/external_token_manager.py b/src/spaceone/identity/manager/token_manager/external_token_manager.py deleted file mode 100644 index bf58c5c2..00000000 --- a/src/spaceone/identity/manager/token_manager/external_token_manager.py +++ /dev/null @@ -1,120 +0,0 @@ -import logging -from datetime import datetime -from spaceone.identity.connector import AuthPluginConnector -from spaceone.identity.error.error_authentication import * -from spaceone.identity.error.error_user import ERROR_USER_STATUS_CHECK_FAILURE -from spaceone.identity.manager.user_manager import UserManager -from spaceone.identity.manager.token_manager import JWTManager -from spaceone.identity.manager.domain_manager import DomainManager -from spaceone.identity.model import Domain, User - -_LOGGER = logging.getLogger(__name__) - - -class ExternalTokenManager(JWTManager): - domain: Domain = None - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.domain_mgr: DomainManager = self.locator.get_manager('DomainManager') - self.user_mgr: UserManager = self.locator.get_manager('UserManager') - - def authenticate(self, user_id, domain_id, credentials): - _LOGGER.debug(f'[authenticate] domain_id: {domain_id}') - - # Add User ID for External Authentication - if user_id: - credentials['user_id'] = user_id - - self.domain: Domain = self.domain_mgr.get_domain(domain_id) - - self._check_domain_state() - - endpoint = self.domain_mgr.get_auth_plugin_endpoint_by_vo(self.domain) - auth_user_info = self._authenticate_with_plugin(endpoint, credentials) - - _LOGGER.info(f'[authenticate] Authentication success. (user_id={auth_user_info.get("user_id")})') - - auto_user_sync = self.domain.plugin_info.options.get('auto_user_sync', False) - - self._verify_user_from_plugin_user_info(auth_user_info, domain_id, auto_user_sync) - self._check_user_state() - - self.is_authenticated = True - - def issue_token(self, **kwargs): - if self.is_authenticated is False: - raise ERROR_NOT_AUTHENTICATED() - - if self.user.state == 'PENDING': - self.user: User = self.user.update({'state': 'ENABLED'}) - - # Issue token - access_token = self.issue_access_token('USER', self.user.user_id, self.user.domain_id, **kwargs) - refresh_token = self.issue_refresh_token('USER', self.user.user_id, self.user.domain_id, **kwargs) - - # Update user's last_accessed_at field - self.user.update({'last_accessed_at': datetime.utcnow()}) - - return { - 'access_token': access_token, - 'refresh_token': refresh_token - } - - def refresh_token(self, user_id, domain_id, **kwargs): - self.user: User = self.user_mgr.get_user(user_id, domain_id) - self._check_user_state() - - return self.issue_token(**kwargs) - - def _verify_user_from_plugin_user_info(self, auth_user_info, domain_id, auto_user_sync=False): - if 'user_id' not in auth_user_info: - _LOGGER.error(f'[_verify_user_from_plugin_user_info] does not return user_id from plugin user info.') - raise ERROR_AUTHENTICATION_FAILURE_PLUGIN(message='plugin response is invalid.') - - user_id = auth_user_info['user_id'] - state = auth_user_info.get('state', 'ENABLED') - - user_vos = self.user_mgr.filter_users(user_id=user_id, domain_id=domain_id) - - if user_vos.count() > 0: - self.user: User = user_vos[0] - else: - if auto_user_sync: - name = auth_user_info.get('name') - email = auth_user_info.get('email') - self.user: User = self._create_external_user(user_id, state, domain_id, name, email) - else: - raise ERROR_NOT_FOUND(key='user_id', value=user_id) - - def _authenticate_with_plugin(self, endpoint, credentials): - options = self.domain.plugin_info.options - - auth_plugin_conn: AuthPluginConnector = self.locator.get_connector('AuthPluginConnector') - auth_plugin_conn.initialize(endpoint) - - return auth_plugin_conn.call_login(credentials, options, {}) - - def _check_domain_state(self): - if not self.domain.plugin_info: - _LOGGER.error('[_get_token_manager] This domain does not allow external authentication.') - raise ERROR_AUTHENTICATION_FAILURE(user_id=self.user.user_id) - - def _check_user_state(self): - if self.user.state not in ['ENABLED', 'PENDING']: - raise ERROR_USER_STATUS_CHECK_FAILURE(user_id=self.user.user_id) - - if self.user.backend != 'EXTERNAL': - raise ERROR_NOT_FOUND(key='user_id', value=self.user.user_id) - - def _create_external_user(self, user_id, state, domain_id, name=None, email=None): - _LOGGER.error(f'[_create_external_user] create user on first login: {user_id}') - return self.user_mgr.create_user({ - 'user_id': user_id, - 'state': state, - 'name': name, - 'email': email, - 'user_type': 'USER', - 'backend': 'EXTERNAL', - 'domain_id': domain_id - }, self.domain, is_first_login_user=True) diff --git a/src/spaceone/identity/manager/token_manager/local_token_manager.py b/src/spaceone/identity/manager/token_manager/local_token_manager.py deleted file mode 100644 index 50e7cff4..00000000 --- a/src/spaceone/identity/manager/token_manager/local_token_manager.py +++ /dev/null @@ -1,98 +0,0 @@ -import logging -from datetime import datetime - -from spaceone.identity.error.error_authentication import * -from spaceone.identity.error.error_user import ERROR_USER_STATUS_CHECK_FAILURE -from spaceone.identity.lib.cipher import PasswordCipher -from spaceone.identity.manager.user_manager import UserManager -from spaceone.identity.manager.token_manager import JWTManager -from spaceone.identity.model import User - -_LOGGER = logging.getLogger(__name__) - - -class LocalTokenManager(JWTManager): - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.user_mgr: UserManager = self.locator.get_manager('UserManager') - - def authenticate(self, user_id, domain_id, credentials): - pw_to_check = self._parse_password(credentials) - - self.user = self.user_mgr.get_user(user_id, domain_id) - - self._check_user_state() - - # TODO: decrypt pw - is_correct = PasswordCipher().checkpw(pw_to_check, self.user.password) - _LOGGER.debug(f'[authenticate] is_correct: {is_correct}, pw_to_check: {pw_to_check}, hashed_pw: {self.user.password}') - - if is_correct: - self.is_authenticated = True - else: - raise ERROR_AUTHENTICATION_FAILURE(user_id=self.user.user_id) - - def issue_temporary_token(self, user_id, domain_id, **kwargs): - permissions = [ - 'identity.User.get', - 'identity.User.update' - ] - - # Issue token - access_token = self.issue_access_token('USER', user_id, domain_id, permissions=permissions, **kwargs) - - return {'access_token': access_token} - - def issue_token(self, **kwargs): - if self.is_authenticated is False: - raise ERROR_NOT_AUTHENTICATED() - - permissions = self._get_permissions_from_required_actions() - - # Issue token - access_token = self.issue_access_token('USER', self.user.user_id, self.user.domain_id, - permissions=permissions, **kwargs) - refresh_token = self.issue_refresh_token('USER', self.user.user_id, self.user.domain_id, **kwargs) - - # Update user's last_accessed_at field - user = self.user.update({'last_accessed_at': datetime.utcnow()}) - - return { - 'access_token': access_token, - 'refresh_token': refresh_token - } - - def refresh_token(self, user_id, domain_id, **kwargs): - self.user: User = self.user_mgr.get_user(user_id, domain_id) - self._check_user_state() - - return self.issue_token(**kwargs) - - def _get_permissions_from_required_actions(self): - if 'UPDATE_PASSWORD' in self.user.required_actions: - return [ - 'identity.User.get', - 'identity.User.update' - ] - - return None - - @staticmethod - def _parse_password(credentials): - pw_to_check = credentials.get('password', None) - - if pw_to_check is None: - raise ERROR_INVALID_CREDENTIALS() - - return pw_to_check - - def _check_user_state(self): - if self.user.user_type == 'API_USER': - raise ERROR_NOT_ALLOWED_ISSUE_TOKEN_API_USER(user_id=self.user.user_id) - - if self.user.state != 'ENABLED': - raise ERROR_USER_STATUS_CHECK_FAILURE(user_id=self.user.user_id) - - if self.user.backend != 'LOCAL': - raise ERROR_NOT_FOUND(key='user_id', value=self.user.user_id) diff --git a/src/spaceone/identity/model/api_key_model.py b/src/spaceone/identity/model/api_key_model.py deleted file mode 100644 index 14818eb1..00000000 --- a/src/spaceone/identity/model/api_key_model.py +++ /dev/null @@ -1,32 +0,0 @@ -from mongoengine import * -from spaceone.core.model.mongo_model import MongoModel -from spaceone.identity.model.user_model import User - - -class APIKey(MongoModel): - api_key_id = StringField(max_length=40, generate_id='api-key', unique=True) - state = StringField(max_length=20, default='ENABLED', choices=('ENABLED', 'DISABLED')) - user_id = StringField(max_length=40, required=True) - user = ReferenceField('User', null=True, default=None, reverse_delete_rule=CASCADE) - domain_id = StringField(max_length=40, required=True) - last_accessed_at = DateTimeField(default=None, null=True) - created_at = DateTimeField(auto_now_add=True) - - meta = { - 'updatable_fields': [ - 'state', - 'last_accessed_at' - ], - 'minimal_fields': [ - 'api_key_id', - 'state', - 'user_id' - ], - 'ordering': ['api_key_id'], - 'indexes': [ - 'state', - 'user_id', - 'domain_id', - 'last_accessed_at' - ] - } diff --git a/src/spaceone/identity/model/domain_model.py b/src/spaceone/identity/model/domain_model.py deleted file mode 100644 index a7df6750..00000000 --- a/src/spaceone/identity/model/domain_model.py +++ /dev/null @@ -1,75 +0,0 @@ -from mongoengine import * -from datetime import datetime -from spaceone.core.error import * -from spaceone.core.model.mongo_model import MongoModel - - -class PluginInfo(EmbeddedDocument): - plugin_id = StringField(max_length=40) - version = StringField(max_length=255) - options = DictField(default={}) - metadata = DictField(default={}) - secret_id = StringField(max_length=40, null=True, default=None) - schema = StringField(max_length=255, null=True, default=None) - upgrade_mode = StringField(max_length=20, default='AUTO', choices=('AUTO', 'MANUAL')) - - def to_dict(self): - return dict(self.to_mongo()) - - -class Domain(MongoModel): - domain_id = StringField(max_length=40, generate_id='domain', unique=True) - name = StringField(max_length=255) - state = StringField(max_length=20, default='ENABLED') - plugin_info = EmbeddedDocumentField(PluginInfo, default=None, null=True) - config = DictField() - tags = DictField() - created_at = DateTimeField(auto_now_add=True) - deleted_at = DateTimeField(default=None, null=True) - - meta = { - 'updatable_fields': [ - 'name', - 'state', - 'plugin_info', - 'config', - 'tags', - 'deleted_at' - ], - 'minimal_fields': [ - 'domain_id', - 'name', - 'state', - ], - 'ordering': ['name'], - 'indexes': [ - # 'domain_id', - 'state', - ] - } - - @queryset_manager - def objects(doc_cls, queryset): - return queryset.filter(state__ne='DELETED') - - @classmethod - def create(cls, data): - domain_vos = cls.filter(name=data['name']) - if domain_vos.count() > 0: - raise ERROR_NOT_UNIQUE(key='name', value=data['name']) - - return super().create(data) - - def update(self, data): - if 'name' in data: - domain_vos = self.filter(name=data['name'], domain_id__ne=self.domain_id) - if domain_vos.count() > 0: - raise ERROR_NOT_UNIQUE(key='name', value=data['name']) - - return super().update(data) - - def delete(self): - self.update({ - 'state': 'DELETED', - 'deleted_at': datetime.utcnow() - }) diff --git a/src/spaceone/identity/model/domain_owner_model.py b/src/spaceone/identity/model/domain_owner_model.py deleted file mode 100644 index 54462958..00000000 --- a/src/spaceone/identity/model/domain_owner_model.py +++ /dev/null @@ -1,48 +0,0 @@ -from mongoengine import * - -from spaceone.core.error import * -from spaceone.core.model.mongo_model import MongoModel - - -class DomainOwner(MongoModel): - owner_id = StringField(max_length=40, unique_with='domain_id', required=True) - password = BinaryField() - name = StringField(max_length=128) - email = StringField(max_length=255, default=None, null=True) - language = StringField(max_length=7, default='en') - timezone = StringField(max_length=50, default='UTC') - domain_id = StringField(max_length=40) - last_accessed_at = DateTimeField(default=None, null=True) - created_at = DateTimeField(auto_now_add=True) - - meta = { - 'updatable_fields': [ - 'password', - 'name', - 'email', - 'language', - 'timezone', - 'last_accessed_at' - ], - 'minimal_fields': [ - 'owner_id', - 'name' - ], - 'ordering': ['name'], - 'indexes': [ - 'owner_id', - 'email', - 'domain_id', - 'last_accessed_at' - ] - } - - @classmethod - def create(cls, data): - user_vos = cls.filter(domain_id=data['domain_id']) - if user_vos.count() > 0: - raise ERROR_NOT_UNIQUE(key='owner_id', value=data['owner_id']) - return super().create(data) - - def update(self, data): - return super().update(data) diff --git a/src/spaceone/identity/model/domain_secret_model.py b/src/spaceone/identity/model/domain_secret_model.py deleted file mode 100644 index 3ec808b6..00000000 --- a/src/spaceone/identity/model/domain_secret_model.py +++ /dev/null @@ -1,28 +0,0 @@ -from mongoengine import * - -from spaceone.core.model.mongo_model import MongoModel -from spaceone.identity.model.domain_model import Domain - - -class DomainSecret(MongoModel): - domain_key = StringField() - pub_jwk = DictField(required=True) - prv_jwk = DictField(required=True) - refresh_pub_jwk = DictField(required=True) - refresh_prv_jwk = DictField(required=True) - domain_id = StringField(max_length=40, unique=True) - domain = ReferenceField('Domain', reverse_delete_rule=CASCADE) - created_at = DateTimeField(auto_now_add=True) - - meta = { - 'updatable_fields': [ - 'refresh_pub_jwk', - 'refresh_prv_jwk', - 'domain' - ], - 'ordering': ['domain_id'], - 'indexes': [ - # 'domain_id', - 'domain' - ] - } diff --git a/src/spaceone/identity/model/policy_model.py b/src/spaceone/identity/model/policy_model.py deleted file mode 100644 index c5842656..00000000 --- a/src/spaceone/identity/model/policy_model.py +++ /dev/null @@ -1,33 +0,0 @@ -from mongoengine import * -from spaceone.core.error import * -from spaceone.core.model.mongo_model import MongoModel - - -class Policy(MongoModel): - policy_id = StringField(max_length=40, generate_id='policy', unique_with=['policy_type', 'domain_id']) - name = StringField(max_length=255) - policy_type = StringField(max_length=20, default='CUSTOM', choices=('CUSTOM', 'MANAGED')) - permissions = ListField(StringField()) - tags = DictField() - domain_id = StringField(max_length=40) - created_at = DateTimeField(auto_now_add=True) - updated_at = DateTimeField(default=None, null=True) - - meta = { - 'updatable_fields': [ - 'name', - 'permissions', - 'tags', - 'updated_at' - ], - 'minimal_fields': [ - 'policy_id', - 'name' - ], - 'ordering': ['name'], - 'indexes': [ - 'policy_id', - 'policy_type', - 'domain_id', - ] - } diff --git a/src/spaceone/identity/model/project_group_model.py b/src/spaceone/identity/model/project_group_model.py deleted file mode 100644 index c506cf70..00000000 --- a/src/spaceone/identity/model/project_group_model.py +++ /dev/null @@ -1,39 +0,0 @@ -from mongoengine import * -from spaceone.core.model.mongo_model import MongoModel - - -class ProjectGroup(MongoModel): - project_group_id = StringField(max_length=40, generate_id='pg', unique=True) - name = StringField(max_length=40) - parent_project_group = ReferenceField('self', null=True, default=None, reverse_delete_rule=DENY) - parent_project_group_id = StringField(max_length=40, null=True, default=None) - tags = DictField() - domain_id = StringField(max_length=255) - created_by = StringField(max_length=255, null=True) - created_at = DateTimeField(auto_now_add=True) - - meta = { - 'updatable_fields': [ - 'name', - 'parent_project_group', - 'parent_project_group_id', - 'tags' - ], - 'minimal_fields': [ - 'project_group_id', - 'name' - ], - 'change_query_keys': { - 'user_project_groups': 'project_group_id' - }, - 'reference_query_keys': { - 'parent_project_group': 'self' - }, - 'ordering': ['name'], - 'indexes': [ - # 'project_group_id', - 'parent_project_group', - 'parent_project_group_id', - 'domain_id', - ] - } diff --git a/src/spaceone/identity/model/project_model.py b/src/spaceone/identity/model/project_model.py deleted file mode 100644 index 3d4dc0cf..00000000 --- a/src/spaceone/identity/model/project_model.py +++ /dev/null @@ -1,41 +0,0 @@ -from mongoengine import * -from spaceone.core.model.mongo_model import MongoModel -from spaceone.identity.model.project_group_model import ProjectGroup - - -class Project(MongoModel): - project_id = StringField(max_length=40, generate_id='project', unique=True) - name = StringField(max_length=40) - project_group = ReferenceField('ProjectGroup', reverse_delete_rule=DENY) - project_group_id = StringField(max_length=40) - tags = DictField() - domain_id = StringField(max_length=255) - created_by = StringField(max_length=255, null=True) - created_at = DateTimeField(auto_now_add=True) - - meta = { - 'updatable_fields': [ - 'name', - 'project_group', - 'project_group_id', - 'tags' - ], - 'minimal_fields': [ - 'project_id', - 'name' - ], - 'change_query_keys': { - 'user_projects': 'project_id', - 'project_group_id': 'project_group.project_group_id' - }, - 'reference_query_keys': { - 'project_group': ProjectGroup - }, - 'ordering': ['name'], - 'indexes': [ - # 'project_id', - 'project_group', - 'project_group_id', - 'domain_id', - ] - } diff --git a/src/spaceone/identity/model/provider_model.py b/src/spaceone/identity/model/provider_model.py deleted file mode 100644 index 834b96fb..00000000 --- a/src/spaceone/identity/model/provider_model.py +++ /dev/null @@ -1,31 +0,0 @@ -from mongoengine import * -from spaceone.core.model.mongo_model import MongoModel - - -class Provider(MongoModel): - provider = StringField(max_length=40, unique_with='domain_id') - name = StringField(max_length=255) - order = IntField(min_value=1, default=10) - template = DictField() - metadata = DictField() - capability = DictField() - tags = DictField() - domain_id = StringField(max_length=255) - created_at = DateTimeField(auto_now_add=True) - - meta = { - 'updatable_fields': [ - 'name', - 'order', - 'template', - 'metadata', - 'capability', - 'tags' - ], - 'minimal_fields': [ - 'provider', - 'name', - 'order' - ], - 'ordering': ['order', 'name'] - } diff --git a/src/spaceone/identity/model/role_binding_model.py b/src/spaceone/identity/model/role_binding_model.py deleted file mode 100644 index b87ffaf5..00000000 --- a/src/spaceone/identity/model/role_binding_model.py +++ /dev/null @@ -1,66 +0,0 @@ -from mongoengine import * -from spaceone.core.error import * -from spaceone.core.model.mongo_model import MongoModel -from spaceone.identity.model.role_model import Role -from spaceone.identity.model.project_model import Project -from spaceone.identity.model.project_group_model import ProjectGroup -from spaceone.identity.model.user_model import User - - -class RoleBinding(MongoModel): - role_binding_id = StringField(max_length=40, generate_id='rb', unique=True) - resource_type = StringField(max_length=255) - resource_id = StringField(max_length=255) - role = ReferenceField('Role', reverse_delete_rule=DENY) - project = ReferenceField('Project', null=True, default=None, reverse_delete_rule=CASCADE) - project_group = ReferenceField('ProjectGroup', null=True, default=None, reverse_delete_rule=CASCADE) - user = ReferenceField('User', null=True, default=None, reverse_delete_rule=CASCADE) - role_id = StringField(max_length=40) - project_id = StringField(max_length=40, null=True, default=None) - project_group_id = StringField(max_length=40, null=True, default=None) - labels = ListField(StringField(max_length=255)) - tags = DictField() - domain_id = StringField(max_length=40) - created_at = DateTimeField(auto_now_add=True) - - meta = { - 'updatable_fields': [ - 'labels', - 'tags', - 'role_id', - 'project_id', - 'project_group_id' - ], - 'minimal_fields': [ - 'role_binding_id', - 'resource_type', - 'resource_id', - 'role' - ], - 'change_query_keys': { - 'user_id': 'user.user_id', - 'name': 'user.name', - 'email': 'user.email', - 'role_type': 'role.role_type', - 'project_group_id': 'project_group.project_group_id' - }, - 'reference_query_keys': { - 'user': User, - 'role': Role, - 'project': Project, - 'project_group': ProjectGroup - }, - 'ordering': ['resource_type', 'resource_id'], - 'indexes': [ - # 'role_binding_id', - 'resource_type', - 'resource_id', - 'role', - 'project', - 'project_group', - 'role_id', - 'project_id', - 'project_group_id', - 'domain_id', - ] - } diff --git a/src/spaceone/identity/model/role_model.py b/src/spaceone/identity/model/role_model.py deleted file mode 100644 index ad45ddb0..00000000 --- a/src/spaceone/identity/model/role_model.py +++ /dev/null @@ -1,56 +0,0 @@ -from mongoengine import * -from spaceone.core.error import * -from spaceone.core.model.mongo_model import MongoModel - - -class RolePolicy(EmbeddedDocument): - policy_type = StringField(max_length=20, choices=('MANAGED', 'CUSTOM')) - policy = ReferenceField('Policy') - policy_id = StringField(max_length=40) - - def to_dict(self): - return dict(self.to_mongo()) - - -class PagePermission(EmbeddedDocument): - page = StringField(max_length=255, required=True) - permission = StringField(max_length=20, choices=('VIEW', 'MANAGE')) - - def to_dict(self): - return dict(self.to_mongo()) - - -class Role(MongoModel): - role_id = StringField(max_length=40, generate_id='role', unique=True) - name = StringField(max_length=255, unique_with='domain_id') - role_type = StringField(max_length=20, choices=('SYSTEM', 'DOMAIN', 'PROJECT')) - tags = DictField(default={}) - policies = ListField(EmbeddedDocumentField(RolePolicy)) - page_permissions = ListField(EmbeddedDocumentField(PagePermission), default=[]) - domain_id = StringField(max_length=40) - created_at = DateTimeField(auto_now_add=True) - - meta = { - 'updatable_fields': [ - 'name', - 'policies', - 'page_permissions', - 'tags' - ], - 'minimal_fields': [ - 'role_id', - 'name', - 'role_type', - 'page_permissions' - ], - 'change_query_keys': { - 'policy_id': 'policies.policy_id' - }, - 'ordering': ['name'], - 'indexes': [ - # 'role_id', - 'role_type', - 'domain_id', - 'tags' - ] - } diff --git a/src/spaceone/identity/model/service_account_model.py b/src/spaceone/identity/model/service_account_model.py deleted file mode 100644 index 916e3749..00000000 --- a/src/spaceone/identity/model/service_account_model.py +++ /dev/null @@ -1,53 +0,0 @@ -from mongoengine import * -from spaceone.core.model.mongo_model import MongoModel -from spaceone.identity.model.project_model import Project - - -class ServiceAccount(MongoModel): - service_account_id = StringField(max_length=40, generate_id='sa', unique=True) - name = StringField(max_length=255, unique_with='domain_id') - data = DictField() - service_account_type = StringField(max_length=40, choices=('TRUSTED', 'GENERAL'), default='GENERAL') - provider = StringField(max_length=40) - project = ReferenceField('Project', null=True, default=None, reverse_delete_rule=DENY) - project_id = StringField(max_length=40, default=None, null=True) - trusted_service_account_id = StringField(max_length=40, null=True, default=None) - tags = DictField() - scope = StringField(max_length=40, choices=('DOMAIN', 'PROJECT'), default='PROJECT') - domain_id = StringField(max_length=255) - created_at = DateTimeField(auto_now_add=True) - - meta = { - 'updatable_fields': [ - 'name', - 'data', - 'project', - 'project_id', - 'trusted_service_account_id', - 'tags' - ], - 'minimal_fields': [ - 'service_account_id', - 'service_account_type', - 'name', - 'provider' - ], - 'change_query_keys': { - 'user_projects': 'project_id', - 'project_id': 'project_id' - }, - 'reference_query_keys': { - 'project': Project - }, - 'ordering': ['name'], - 'indexes': [ - # 'service_account_id', - 'name', - 'provider', - 'project', - 'project_id', - 'trusted_service_account_id', - 'scope', - 'domain_id' - ] - } diff --git a/src/spaceone/identity/model/user_model.py b/src/spaceone/identity/model/user_model.py deleted file mode 100644 index 0ecf5906..00000000 --- a/src/spaceone/identity/model/user_model.py +++ /dev/null @@ -1,60 +0,0 @@ -from mongoengine import * -from spaceone.core.model.mongo_model import MongoModel - - -class MFA(EmbeddedDocument): - state = StringField(max_length=20, choices=('ENABLED', 'DISABLED'), default='DISABLED') - mfa_type = StringField(max_length=20) - options = DictField() - - def to_dict(self): - return dict(self.to_mongo()) - - -class User(MongoModel): - user_id = StringField(max_length=40, unique_with='domain_id', required=True) - password = BinaryField(default=None) - name = StringField(max_length=128, default='') - state = StringField(max_length=20, choices=('ENABLED', 'DISABLED', 'PENDING')) - email = StringField(max_length=255, default='') - email_verified = BooleanField(default=False) - user_type = StringField(max_length=20, choices=('USER', 'API_USER')) - backend = StringField(max_length=20, choices=('LOCAL', 'EXTERNAL')) - mfa = EmbeddedDocumentField(MFA) - required_actions = ListField(StringField(choices=('UPDATE_PASSWORD',)), default=[]) - language = StringField(max_length=7, default='en') - timezone = StringField(max_length=50, default='UTC') - tags = DictField() - domain_id = StringField(max_length=40) - last_accessed_at = DateTimeField(default=None, null=True) - created_at = DateTimeField(auto_now_add=True) - - meta = { - 'updatable_fields': [ - 'password', - 'name', - 'state', - 'email', - 'email_verified', - 'mfa', - 'required_actions', - 'language', - 'timezone', - 'tags', - 'last_accessed_at' - ], - 'minimal_fields': [ - 'user_id', - 'name', - 'state', - 'user_type' - ], - 'ordering': ['name'], - 'indexes': [ - 'state', - 'user_type', - 'backend', - 'last_accessed_at', - # ('user_id', 'domain_id'), - ] - } diff --git a/src/spaceone/identity/service/base_service.py b/src/spaceone/identity/service/base_service.py deleted file mode 100644 index e22fc844..00000000 --- a/src/spaceone/identity/service/base_service.py +++ /dev/null @@ -1,39 +0,0 @@ -import logging -from typing import Union -from spaceone.core.service import BaseService, transaction, convert_model -from spaceone.identity.model.domain_request import * -from spaceone.identity.model.domain_response import * - -_LOGGER = logging.getLogger(__name__) - - -class BaseService(BaseService): - @transaction - @convert_model - def create(self, params: DomainCreateRequest) -> Union[DomainResponse, dict]: - return {} - - @transaction - @convert_model - def update(self, params: DomainUpdateRequest) -> Union[DomainResponse, dict]: - return {} - - @transaction - @convert_model - def delete(self, params: DomainRequest) -> None: - pass - - @transaction - @convert_model - def get(self, params: DomainRequest) -> Union[DomainResponse, dict]: - return {} - - @transaction - @convert_model - def list(self, params: DomainSearchQueryRequest) -> Union[DomainsResponse, dict]: - return {} - - @transaction - @convert_model - def stat(self, params: DomainStatQuery) -> dict: - return {} diff --git a/src/spaceone/identity/service/domain_owner_service.py b/src/spaceone/identity/service/domain_owner_service.py deleted file mode 100644 index 875ff5d2..00000000 --- a/src/spaceone/identity/service/domain_owner_service.py +++ /dev/null @@ -1,103 +0,0 @@ -import pytz -from spaceone.core.service import * -from spaceone.core.error import * -from spaceone.identity.manager import DomainOwnerManager - - -@authentication_handler(exclude=['create']) -@authorization_handler(exclude=['create']) -@mutation_handler(exclude=['create']) -@event_handler -class DomainOwnerService(BaseService): - - def __init__(self, metadata): - super().__init__(metadata) - self.domain_owner_mgr: DomainOwnerManager = self.locator.get_manager('DomainOwnerManager') - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['owner_id', 'password', 'domain_id']) - def create(self, params): - """ Create domain owner - - Args: - params (dict): { - 'owner_id': 'str', - 'password': 'str', - 'name': 'str', - 'email': 'str', - 'language': 'str', - 'timezone': 'str', - 'domain_id': 'str' - } - - Returns: - domain_owner_vo (object) - """ - - if 'timezone' in params: - self._check_timezone(params['timezone']) - - return self.domain_owner_mgr.create_owner(params) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['owner_id', 'domain_id']) - def update(self, params): - """ Update domain owner - - Args: - params (dict): { - 'owner_id': 'str', - 'password': 'str', - 'name': 'str', - 'email': 'str', - 'language': 'str', - 'timezone': 'str', - 'domain_id': 'str' - } - - Returns: - domain_owner_vo (object) - """ - - if 'timezone' in params: - self._check_timezone(params['timezone']) - - return self.domain_owner_mgr.update_owner(params) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['domain_id', 'owner_id']) - def delete(self, params): - """ Delete domain owner - - Args: - params (dict): { - 'owner_id': 'str', - 'domain_id': 'str' - } - - Returns: - None - """ - self.domain_owner_mgr.delete_owner(params['domain_id'], params['owner_id']) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['domain_id']) - def get(self, params): - """ Delete domain owner - - Args: - params (dict): { - 'owner_id': 'str', - 'domain_id': 'str', - 'only': 'list' - } - - Returns: - domain_owner_vo (object) - """ - return self.domain_owner_mgr.get_owner(params['domain_id'], params.get('owner_id'), params.get('only')) - - @staticmethod - def _check_timezone(timezone): - if timezone not in pytz.all_timezones: - raise ERROR_INVALID_PARAMETER(key='timezone', reason='Timezone is invalid.') diff --git a/src/spaceone/identity/service/provider_service.py b/src/spaceone/identity/service/provider_service.py deleted file mode 100644 index b635e1c7..00000000 --- a/src/spaceone/identity/service/provider_service.py +++ /dev/null @@ -1,150 +0,0 @@ -from spaceone.core import cache -from spaceone.core.service import * -from spaceone.core import utils -from spaceone.identity.manager.provider_manager import ProviderManager - - -@authentication_handler -@authorization_handler -@mutation_handler -@event_handler -class ProviderService(BaseService): - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.provider_mgr: ProviderManager = self.locator.get_manager('ProviderManager') - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['provider', 'name', 'domain_id']) - def create(self, params): - """ - Args: - params (dict): { - 'provider': 'str', - 'name': 'str', - 'order': 'int', - 'template': 'dict', - 'metadata': 'dict', - 'capability': 'dict', - 'tags': 'dict', - 'domain_id': 'str' - } - - Returns: - provider_vo (object) - """ - # TODO: validate a template data - # TODO: validate a capability data - - return self.provider_mgr.create_provider(params) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['provider', 'domain_id']) - def update(self, params): - """ - Args: - params (dict): { - 'provider': 'str', - 'name': 'str', - 'order': 'int', - 'template': 'dict', - 'metadata': 'dict', - 'capability': 'dict', - 'tags': 'list', - 'domain_id': 'str' - } - - Returns: - provider_vo (object) - """ - # TODO: validate a template data - # TODO: validate a capability data - - return self.provider_mgr.update_provider(params) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['provider', 'domain_id']) - def delete(self, params): - """ - Args: - params (dict): { - 'provider': 'str', - 'domain_id': 'str' - } - - Returns: - None - """ - self.provider_mgr.delete_provider(params['provider']) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['provider', 'domain_id']) - def get(self, params): - """ - Args: - params (dict): { - 'provider': 'str', - 'only': 'list', - 'domain_id': 'str' - } - - Returns: - provider_vo (object) - """ - - domain_id = params['domain_id'] - - self._create_default_provider(domain_id) - return self.provider_mgr.get_provider(params['provider'], domain_id, params.get('only')) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['domain_id']) - @append_query_filter(['provider', 'name', 'domain_id']) - @append_keyword_filter(['provider', 'name']) - def list(self, params): - """ - Args: - params (dict): { - 'query': 'dict (spaceone.api.core.v1.Query)', - 'provider': 'str', - 'name': 'str', - 'domain_id': 'str' - } - - Returns: - results (list): 'list of provider_vo' - total_count (int) - """ - - domain_id = params['domain_id'] - - self._create_default_provider(domain_id) - return self.provider_mgr.list_providers(params.get('query', {})) - - @transaction(append_meta={'authorization.scope': 'DOMAIN'}) - @check_required(['query', 'domain_id']) - @append_query_filter(['domain_id']) - @append_keyword_filter(['provider', 'name']) - def stat(self, params): - """ - Args: - params (dict): { - 'query': 'dict (spaceone.api.core.v1.StatisticsQuery)', - 'domain_id': 'str' - } - - Returns: - values (list): 'list of statistics data' - total_count (int) - """ - - query = params.get('query', {}) - return self.provider_mgr.stat_providers(query) - - @cache.cacheable(key='provider:{domain_id}:default:init', expire=300) - def _create_default_provider(self, domain_id): - provider_vos = self.provider_mgr.filter_providers(domain_id=domain_id) - installed_providers = [provider_vo.provider for provider_vo in provider_vos] - self.provider_mgr.create_default_providers(installed_providers, domain_id) - - return True diff --git a/src/spaceone/identity/template/authentication_code_en.html b/src/spaceone/identity/template/authentication_code_en.html deleted file mode 100644 index 166087f0..00000000 --- a/src/spaceone/identity/template/authentication_code_en.html +++ /dev/null @@ -1,256 +0,0 @@ - - - - - - - - - - - - - - -
-
- Use this to verify your MFA email address. -
- - - - - - -
- - - - - - - -
-

- -

-
-
- - diff --git a/src/spaceone/identity/template/authentication_code_jp.html b/src/spaceone/identity/template/authentication_code_jp.html deleted file mode 100644 index 294fe5ab..00000000 --- a/src/spaceone/identity/template/authentication_code_jp.html +++ /dev/null @@ -1,255 +0,0 @@ - - - - - - - - - - - - - - -
-
- この認証コードを使用してMFAメールアドレスを認証してください。 -
- - - - - - -
- - - - - - - -
-

- -

-
-
- - diff --git a/src/spaceone/identity/template/authentication_code_ko.html b/src/spaceone/identity/template/authentication_code_ko.html deleted file mode 100644 index b31cafc2..00000000 --- a/src/spaceone/identity/template/authentication_code_ko.html +++ /dev/null @@ -1,254 +0,0 @@ - - - - - - - - - - - - - - -
-
- 본 인증코드를 사용하여 다중 인증 이메일 주소를 인증하세요. -
- - - - - - -
- - - - - - - -
-

- -

-
-
- - diff --git a/src/spaceone/identity/template/reset_pwd_link_when_pw_forgotten_en.html b/src/spaceone/identity/template/reset_pwd_link_when_pw_forgotten_en.html deleted file mode 100644 index 4e0359a8..00000000 --- a/src/spaceone/identity/template/reset_pwd_link_when_pw_forgotten_en.html +++ /dev/null @@ -1,316 +0,0 @@ - - - - - - - - - - - - - - -
-
- We received a request to reset the password for your account. -
- - - - - - -
- - - - - - - -
-

- -

-
-
- - diff --git a/src/spaceone/identity/template/reset_pwd_link_when_pw_forgotten_ja.html b/src/spaceone/identity/template/reset_pwd_link_when_pw_forgotten_ja.html deleted file mode 100644 index b5c371a3..00000000 --- a/src/spaceone/identity/template/reset_pwd_link_when_pw_forgotten_ja.html +++ /dev/null @@ -1,310 +0,0 @@ - - - - - - - - - - - - - - -
-
- {{service_name}}コンソールアカウントのパスワード変更がリクエストされました。 -
- - - - - - -
- - - - - - - -
-

- -

-
-
- - diff --git a/src/spaceone/identity/template/reset_pwd_link_when_pw_forgotten_ko.html b/src/spaceone/identity/template/reset_pwd_link_when_pw_forgotten_ko.html deleted file mode 100644 index 1f0d8d1a..00000000 --- a/src/spaceone/identity/template/reset_pwd_link_when_pw_forgotten_ko.html +++ /dev/null @@ -1,320 +0,0 @@ - - - - - - - - - - - - - - -
-
- 비밀번호 재설정을 진행해주세요. -
- - - - - - -
- - - - - - - -
-

- -

-
-
- - diff --git a/src/spaceone/identity/template/reset_pwd_link_when_user_added_en.html b/src/spaceone/identity/template/reset_pwd_link_when_user_added_en.html deleted file mode 100644 index 0b64b552..00000000 --- a/src/spaceone/identity/template/reset_pwd_link_when_user_added_en.html +++ /dev/null @@ -1,305 +0,0 @@ - - - - - - - - - - - - - - -
-
- Reset your password to continue using {{service_name}} -
- - - - - - -
- - - - - - - -
-

- -

-
-
- - diff --git a/src/spaceone/identity/template/reset_pwd_link_when_user_added_ja.html b/src/spaceone/identity/template/reset_pwd_link_when_user_added_ja.html deleted file mode 100644 index c072d4e6..00000000 --- a/src/spaceone/identity/template/reset_pwd_link_when_user_added_ja.html +++ /dev/null @@ -1,305 +0,0 @@ - - - - - - - - - - - - - - -
-
- お客様のアカウントが、ドメイン管理者によって{{service_name}}に新しく追加または更新されたことを検知しました。 -
- - - - - - -
- - - - - - - -
-

- -

-
-
- - diff --git a/src/spaceone/identity/template/reset_pwd_link_when_user_added_ko.html b/src/spaceone/identity/template/reset_pwd_link_when_user_added_ko.html deleted file mode 100644 index 51e88bfc..00000000 --- a/src/spaceone/identity/template/reset_pwd_link_when_user_added_ko.html +++ /dev/null @@ -1,305 +0,0 @@ - - - - - - - - - - - - - - -
-
- 고객님의 계정이 도메인 관리자에 의해 {{service_name}}에 신규 추가 또는 업데이트 되었음이 확인되었습니다. -
- - - - - - -
- - - - - - - -
-

- -

-
-
- - diff --git a/src/spaceone/identity/template/temp_pwd_when_pw_forgotten_en.html b/src/spaceone/identity/template/temp_pwd_when_pw_forgotten_en.html deleted file mode 100644 index f46b9803..00000000 --- a/src/spaceone/identity/template/temp_pwd_when_pw_forgotten_en.html +++ /dev/null @@ -1,319 +0,0 @@ - - - - - - - - - - - - - - -
-
- We received a request to reset the password for your account. -
- - - - - - -
- - - - - - - -
-

- -

-
-
- - diff --git a/src/spaceone/identity/template/temp_pwd_when_pw_forgotten_ja.html b/src/spaceone/identity/template/temp_pwd_when_pw_forgotten_ja.html deleted file mode 100644 index 4205e502..00000000 --- a/src/spaceone/identity/template/temp_pwd_when_pw_forgotten_ja.html +++ /dev/null @@ -1,318 +0,0 @@ - - - - - - - - - - - - - - -
-
- 以下の仮パスワードをご案内いたします。ログイン後パスワードを変更してください。 -
- - - - - - -
- - - - - - - -
-

- -

-
-
- - diff --git a/src/spaceone/identity/template/temp_pwd_when_pw_forgotten_ko.html b/src/spaceone/identity/template/temp_pwd_when_pw_forgotten_ko.html deleted file mode 100644 index 56b7532e..00000000 --- a/src/spaceone/identity/template/temp_pwd_when_pw_forgotten_ko.html +++ /dev/null @@ -1,322 +0,0 @@ - - - - - - - - - - - - - - -
-
- 임시 비밀번호로 로그인하셔서 비밀번호를 변경하여 주시기 바랍니다. -
- - - - - - -
- - - - - - - -
-

- -

-
-
- - diff --git a/src/spaceone/identity/template/temp_pwd_when_user_added_en.html b/src/spaceone/identity/template/temp_pwd_when_user_added_en.html deleted file mode 100644 index a51cab89..00000000 --- a/src/spaceone/identity/template/temp_pwd_when_user_added_en.html +++ /dev/null @@ -1,313 +0,0 @@ - - - - - - - - - - - - - - -
-
- Please use the temporary password below to sign in to console and reset it. - -
- - - - - - -
- - - - - - - -
-

- -

-
-
- - diff --git a/src/spaceone/identity/template/temp_pwd_when_user_added_ja.html b/src/spaceone/identity/template/temp_pwd_when_user_added_ja.html deleted file mode 100644 index ce2b9b92..00000000 --- a/src/spaceone/identity/template/temp_pwd_when_user_added_ja.html +++ /dev/null @@ -1,315 +0,0 @@ - - - - - - - - - - - - - - -
-
- 以下の仮パスワードをご案内いたします。ログイン後パスワードを変更してください。 -
- - - - - - -
- - - - - - - -
-

- -

-
-
- - diff --git a/src/spaceone/identity/template/temp_pwd_when_user_added_ko.html b/src/spaceone/identity/template/temp_pwd_when_user_added_ko.html deleted file mode 100644 index fe01ebe4..00000000 --- a/src/spaceone/identity/template/temp_pwd_when_user_added_ko.html +++ /dev/null @@ -1,316 +0,0 @@ - - - - - - - - - - - - - - -
-
- 임시 비밀번호로 로그인하셔서 비밀번호를 변경하여 주시기 바랍니다. -
- - - - - - -
- - - - - - - -
-

- -

-
-
- - diff --git a/src/spaceone/identity/template/verification_MFA_code_en.html b/src/spaceone/identity/template/verification_MFA_code_en.html deleted file mode 100644 index b688f061..00000000 --- a/src/spaceone/identity/template/verification_MFA_code_en.html +++ /dev/null @@ -1,256 +0,0 @@ - - - - - - - - - - - - - - -
-
- Use this to verify your MFA email address. -
- - - - - - -
- - - - - - - -
-

- -

-
-
- - diff --git a/src/spaceone/identity/template/verification_MFA_code_jp.html b/src/spaceone/identity/template/verification_MFA_code_jp.html deleted file mode 100644 index 63f43b7f..00000000 --- a/src/spaceone/identity/template/verification_MFA_code_jp.html +++ /dev/null @@ -1,255 +0,0 @@ - - - - - - - - - - - - - - -
-
- この認証コードを使用してMFAメールアドレスを認証してください。 -
- - - - - - -
- - - - - - - -
-

- -

-
-
- - diff --git a/src/spaceone/identity/template/verification_MFA_code_ko.html b/src/spaceone/identity/template/verification_MFA_code_ko.html deleted file mode 100644 index 304159fb..00000000 --- a/src/spaceone/identity/template/verification_MFA_code_ko.html +++ /dev/null @@ -1,254 +0,0 @@ - - - - - - - - - - - - - - -
-
- 본 인증코드를 사용하여 다중 인증 이메일 주소를 인증하세요. -
- - - - - - -
- - - - - - - -
-

- -

-
-
- - diff --git a/src/spaceone/identity/template/verification_code_en.html b/src/spaceone/identity/template/verification_code_en.html deleted file mode 100644 index cb7c5cff..00000000 --- a/src/spaceone/identity/template/verification_code_en.html +++ /dev/null @@ -1,256 +0,0 @@ - - - - - - - - - - - - - - -
-
- Use this to verify your notification email address. -
- - - - - - -
- - - - - - - -
-

- -

-
-
- - diff --git a/src/spaceone/identity/template/verification_code_ja.html b/src/spaceone/identity/template/verification_code_ja.html deleted file mode 100644 index 1488cda4..00000000 --- a/src/spaceone/identity/template/verification_code_ja.html +++ /dev/null @@ -1,254 +0,0 @@ - - - - - - - - - - - - - - -
-
- この認証コードを使用して通知メールアドレスを認証してください。 -
- - - - - - -
- - - - - - - -
-

- -

-
-
- - diff --git a/src/spaceone/identity/template/verification_code_ko.html b/src/spaceone/identity/template/verification_code_ko.html deleted file mode 100644 index 27069889..00000000 --- a/src/spaceone/identity/template/verification_code_ko.html +++ /dev/null @@ -1,254 +0,0 @@ - - - - - - - - - - - - - - -
-
- 본 인증코드를 사용하여 알림 이메일 주소를 인증하세요. -
- - - - - - -
- - - - - - - -
-

- -

-
-
- - From 5e6bf61443923299eb567ecab3d964074fb8dfcc Mon Sep 17 00:00:00 2001 From: Jongmin Kim Date: Sun, 19 Nov 2023 22:42:59 +0900 Subject: [PATCH 13/45] refactor: refactor provider manager --- src/spaceone/identity/manager/provider_manager.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/spaceone/identity/manager/provider_manager.py b/src/spaceone/identity/manager/provider_manager.py index 0e723e90..5bb13550 100644 --- a/src/spaceone/identity/manager/provider_manager.py +++ b/src/spaceone/identity/manager/provider_manager.py @@ -15,11 +15,11 @@ def __init__(self, *args, **kwargs): self.provider_model = Provider def create_provider(self, params: dict) -> Provider: - def _rollback(provider_vo: Provider): - _LOGGER.info(f'[create_provider._rollback] Create provider : {provider_vo.provider}') - provider_vo.delete() + def _rollback(vo: Provider): + _LOGGER.info(f'[create_provider._rollback] Delete provider : {vo.provider}') + vo.delete() - provider_vo: Provider = self.provider_model.create(params) + provider_vo = self.provider_model.create(params) self.transaction.add_rollback(_rollback, provider_vo) return provider_vo @@ -29,7 +29,7 @@ def _rollback(old_data): _LOGGER.info(f'[update_provider._rollback] Revert Data : {old_data["provider"]}') provider_vo.update(old_data) - provider_vo: Provider = self.get_provider(params['provider'], params['domain_id']) + provider_vo = self.get_provider(params['provider'], params['domain_id']) self.transaction.add_rollback(_rollback, provider_vo.to_dict()) return provider_vo.update(params) @@ -41,7 +41,7 @@ def delete_provider(self, provider: str, domain_id: str) -> None: def get_provider(self, provider: str, domain_id: str) -> Provider: return self.provider_model.get(provider=provider, domain_id=domain_id) - def filter_providers(self, **conditions) -> Tuple[Provider, ...]: + def filter_providers(self, **conditions) -> List[Provider]: return self.provider_model.filter(**conditions) def list_providers(self, query: dict) -> Tuple[list, int]: From 9db915f3e40ad912711515c281a7d47c43b8f8a3 Mon Sep 17 00:00:00 2001 From: ImMin5 Date: Mon, 20 Nov 2023 00:18:34 +0900 Subject: [PATCH 14/45] feat: add most features exclude user role bindings during domain creation (#73) Signed-off-by: ImMin5 --- .../identity/interface/grpc/domain.py | 2 +- .../identity/manager/domain_manager.py | 14 +++++++ src/spaceone/identity/model/domain_request.py | 5 ++- .../identity/model/domain_response.py | 8 ++-- .../identity/service/domain_service.py | 37 ++++++++++++------- 5 files changed, 46 insertions(+), 20 deletions(-) diff --git a/src/spaceone/identity/interface/grpc/domain.py b/src/spaceone/identity/interface/grpc/domain.py index 06e8051e..3e3f817b 100644 --- a/src/spaceone/identity/interface/grpc/domain.py +++ b/src/spaceone/identity/interface/grpc/domain.py @@ -65,4 +65,4 @@ def stat(self, request, context): params, metadata = self.parse_request(request, context) domain_svc = DomainService(metadata) response: dict = domain_svc.stat(params) - return self.dict_to_message(response) + return self.struct_to_message(response) diff --git a/src/spaceone/identity/manager/domain_manager.py b/src/spaceone/identity/manager/domain_manager.py index b7985194..78ad9566 100644 --- a/src/spaceone/identity/manager/domain_manager.py +++ b/src/spaceone/identity/manager/domain_manager.py @@ -1,7 +1,10 @@ import logging +from datetime import datetime from spaceone.core import cache from spaceone.core.manager import BaseManager +from spaceone.core.utils import datetime_to_iso8601 + from spaceone.identity.model.domain_db_model import Domain _LOGGER = logging.getLogger(__name__) @@ -81,3 +84,14 @@ def get_domain(self, domain_id): def list_domains(self, query): return self.domain_model.query(**query) + + def stat_domains(self, query): + return self._convert_stat_request_result(self.domain_model.stat(**query)) + + @staticmethod + def _convert_stat_request_result(stats_data: dict) -> dict: + for index, stat_data in enumerate(stats_data["results"]): + for key, value in stat_data.items(): + if isinstance(value, datetime): + stats_data["results"][index][key] = datetime_to_iso8601(value) + return stats_data diff --git a/src/spaceone/identity/model/domain_request.py b/src/spaceone/identity/model/domain_request.py index 06be66db..0de92da2 100644 --- a/src/spaceone/identity/model/domain_request.py +++ b/src/spaceone/identity/model/domain_request.py @@ -1,5 +1,6 @@ from typing import Union, Literal from pydantic import BaseModel +from pydantic.utils import GetterDict __all__ = [ "DomainCreateRequest", @@ -7,7 +8,7 @@ "DomainRequest", "DomainGetMetadataRequest", "DomainSearchQueryRequest", - "DomainStatQuery", + "DomainStatQueryRequest", "State", ] @@ -40,5 +41,5 @@ class DomainSearchQueryRequest(BaseModel): state: Union[State, None] = None -class DomainStatQuery(BaseModel): +class DomainStatQueryRequest(BaseModel): query: dict diff --git a/src/spaceone/identity/model/domain_response.py b/src/spaceone/identity/model/domain_response.py index 9ce5aac7..2e301ba0 100644 --- a/src/spaceone/identity/model/domain_response.py +++ b/src/spaceone/identity/model/domain_response.py @@ -15,7 +15,7 @@ ExternalAuthState = Literal["ENABLED", "DISABLED"] -class DomainResponse(BaseModel, extra=Extra.ignore): +class DomainResponse(BaseModel, extra=Extra.ignore, orm_mode=True): domain_id: Union[str, None] = None name: Union[str, None] = None state: Union[State, None] = None @@ -27,19 +27,19 @@ class DomainResponse(BaseModel, extra=Extra.ignore): ) -class DomainMetadataResponse(BaseModel): +class DomainMetadataResponse(BaseModel, orm_mode=True): domain_id: Union[str, None] = None name: Union[str, None] = None external_auth_state: ExternalAuthState metadata_info: Union[dict, None] = None -class DomainSecretResponse(BaseModel): +class DomainSecretResponse(BaseModel, orm_mode=True): domain_id: Union[str, None] = None key: Union[str, None] = None key_type: Union[str, None] = None -class DomainsResponse(BaseModel): +class DomainsResponse(BaseModel, orm_mode=True): results: List[DomainResponse] total_count: int diff --git a/src/spaceone/identity/service/domain_service.py b/src/spaceone/identity/service/domain_service.py index 8f674b33..d9a14a4f 100644 --- a/src/spaceone/identity/service/domain_service.py +++ b/src/spaceone/identity/service/domain_service.py @@ -1,5 +1,6 @@ import logging -from typing import Union +from typing import Union, List + from spaceone.core.service import ( BaseService, transaction, @@ -41,14 +42,14 @@ def create(self, params: DomainCreateRequest) -> Union[DomainResponse, dict]: admin["domain_id"] = domain_vo.domain_id # create admin user with policy and role - self.user_mgr.create_user(admin, domain_vo) - return DomainResponse(**domain_vo.to_dict()) + self.user_mgr.create_user(params=admin) + return DomainResponse.from_orm(domain_vo) @transaction @convert_model def update(self, params: DomainUpdateRequest) -> Union[DomainResponse, dict]: domain_vo = self.domain_mgr.update_domain(params.dict()) - return DomainResponse(**domain_vo.to_dict()) + return DomainResponse.from_orm(domain_vo) @transaction @convert_model @@ -58,18 +59,20 @@ def delete(self, params: DomainRequest) -> None: @transaction @convert_model def enable(self, params: DomainRequest) -> Union[DomainResponse, dict]: - return {} + domain_vo = self.domain_mgr.enable_domain(params.dict().get("domain_id")) + return DomainResponse.from_orm(domain_vo) @transaction @convert_model def disable(self, params: DomainRequest) -> Union[DomainResponse, dict]: - return {} + domain_vo = self.domain_mgr.disable_domain(params.dict().get("domain_id")) + return DomainResponse.from_orm(domain_vo) @transaction @convert_model def get(self, params: DomainRequest) -> Union[DomainResponse, dict]: domain_vo = self.domain_mgr.get_domain(params.dict().get("domain_id")) - return DomainResponse(**domain_vo.to_dict()) + return DomainResponse.from_orm(domain_vo) @transaction @convert_model @@ -86,15 +89,23 @@ def get_public_key( return {} @transaction - @append_query_filter(["domain_id", "name"]) - @append_keyword_filter(["domain_id", "name"]) + # @append_query_filter(["domain_id", "name"]) + # @append_keyword_filter(["domain_id", "name"]) @convert_model def list(self, params: DomainSearchQueryRequest) -> Union[DomainsResponse, dict]: query = params.dict().get("query", {}) - domain_vos = self.domain_mgr.list_domains(query) - return {} + + # todo : remove when spacectl template is modified + only = [field for field in query.get("only", []) if "plugin_info" not in field] + query["only"] = only + + domain_vos, total_count = self.domain_mgr.list_domains(query) + + return DomainsResponse(results=list(domain_vos), total_count=total_count) @transaction @convert_model - def stat(self, params: DomainStatQuery) -> dict: - return {} + def stat(self, params: DomainStatQueryRequest) -> dict: + query = params.dict().get("query", {}) + + return self.domain_mgr.stat_domains(query) From 6ab313fa50aa2ff4c7da7fda7c212f3700095fe9 Mon Sep 17 00:00:00 2001 From: ImMin5 Date: Mon, 20 Nov 2023 00:46:58 +0900 Subject: [PATCH 15/45] feat: add endpoint_manager Signed-off-by: ImMin5 --- .../identity/manager/endpoint_manager.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/spaceone/identity/manager/endpoint_manager.py diff --git a/src/spaceone/identity/manager/endpoint_manager.py b/src/spaceone/identity/manager/endpoint_manager.py new file mode 100644 index 00000000..6df915e6 --- /dev/null +++ b/src/spaceone/identity/manager/endpoint_manager.py @@ -0,0 +1,18 @@ +import logging + +from spaceone.core import config +from spaceone.core.manager import BaseManager + +_LOGGER = logging.getLogger(__name__) + + +class EndpointManager(BaseManager): + def list_endpoints(self, service=None): + endpoints = config.get_global("ENDPOINTS", []) + + if service: + endpoints = [ + endpoint for endpoint in endpoints if endpoint.get("service") == service + ] + + return endpoints, len(endpoints) From d3aecac3e7c3aeb0fca96a0e98d46b92daf36ceb Mon Sep 17 00:00:00 2001 From: Jongmin Kim Date: Mon, 20 Nov 2023 02:17:05 +0900 Subject: [PATCH 16/45] feat: add func for validating schema --- src/spaceone/identity/manager/provider_manager.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/spaceone/identity/manager/provider_manager.py b/src/spaceone/identity/manager/provider_manager.py index 5bb13550..47e544f3 100644 --- a/src/spaceone/identity/manager/provider_manager.py +++ b/src/spaceone/identity/manager/provider_manager.py @@ -1,7 +1,9 @@ import logging +from jsonschema import validate from typing import Tuple, List from spaceone.core.manager import BaseManager +from spaceone.core.error import * from spaceone.identity.conf.provider_conf import DEFAULT_PROVIDERS from spaceone.identity.model.provider_db import Provider @@ -56,3 +58,13 @@ def create_default_providers(self, installed_providers: List[str], domain_id: st _LOGGER.debug(f'Create default provider: {provider["name"]}') provider['domain_id'] = domain_id self.create_provider(provider) + + def check_data_by_schema(self, provider: str, domain_id: str, data: dict) -> None: + provider_vo = self.get_provider(provider, domain_id) + schema = provider_vo.template.get('service_account', {}).get('schema') + + if schema: + try: + validate(instance=data, schema=schema) + except Exception as e: + raise ERROR_INVALID_PARAMETER(key='data', reason=e) From f77926b46317fb92b2a4dd9aa3359212f25f1245 Mon Sep 17 00:00:00 2001 From: Jongmin Kim Date: Mon, 20 Nov 2023 02:17:23 +0900 Subject: [PATCH 17/45] feat: add decorator for query --- src/spaceone/identity/service/provider_service.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/spaceone/identity/service/provider_service.py b/src/spaceone/identity/service/provider_service.py index 41a537a9..64aa6af3 100644 --- a/src/spaceone/identity/service/provider_service.py +++ b/src/spaceone/identity/service/provider_service.py @@ -133,6 +133,8 @@ def list(self, params: ProviderSearchQueryRequest) -> Union[ProvidersResponse, d return ProvidersResponse(results=providers_info, total_count=total_count) @transaction(append_meta={'authorization.scope': 'DOMAIN_READ'}) + @append_query_filter(['domain_id']) + @append_keyword_filter(['provider', 'name']) @convert_model def stat(self, params: ProviderStatQueryRequest) -> dict: """ stat providers From 9a25f9304eb7b8831f29de7907747b0a0f228b76 Mon Sep 17 00:00:00 2001 From: Jongmin Kim Date: Mon, 20 Nov 2023 02:17:47 +0900 Subject: [PATCH 18/45] feat: implement service account APIs --- .../interface/grpc/service_account.py | 6 - .../manager/service_account_manager.py | 143 +++++------ ...account_model.py => service_account_db.py} | 31 +-- .../identity/model/service_account_request.py | 16 +- .../service/service_account_service.py | 238 +++++++++++++++--- 5 files changed, 289 insertions(+), 145 deletions(-) rename src/spaceone/identity/model/{service_account_model.py => service_account_db.py} (58%) diff --git a/src/spaceone/identity/interface/grpc/service_account.py b/src/spaceone/identity/interface/grpc/service_account.py index f42f5ea1..e21f93e1 100644 --- a/src/spaceone/identity/interface/grpc/service_account.py +++ b/src/spaceone/identity/interface/grpc/service_account.py @@ -27,12 +27,6 @@ def change_trusted_service_account(self, request, context): response: dict = service_account_svc.change_trusted_service_account(params) return self.dict_to_message(response) - def change_project(self, request, context): - params, metadata = self.parse_request(request, context) - service_account_svc = ServiceAccountService(metadata) - response: dict = service_account_svc.change_project(params) - return self.dict_to_message(response) - def delete(self, request, context): params, metadata = self.parse_request(request, context) service_account_svc = ServiceAccountService(metadata) diff --git a/src/spaceone/identity/manager/service_account_manager.py b/src/spaceone/identity/manager/service_account_manager.py index f86374e5..4a487cd1 100644 --- a/src/spaceone/identity/manager/service_account_manager.py +++ b/src/spaceone/identity/manager/service_account_manager.py @@ -1,9 +1,9 @@ import logging +from typing import Tuple, List -from spaceone.core.error import * from spaceone.core.manager import BaseManager from spaceone.core.connector.space_connector import SpaceConnector -from spaceone.identity.model.service_account_model import ServiceAccount +from spaceone.identity.model.service_account_db import ServiceAccount _LOGGER = logging.getLogger(__name__) @@ -12,29 +12,32 @@ class ServiceAccountManager(BaseManager): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.service_account_model: ServiceAccount = self.locator.get_model('ServiceAccount') + self.service_account_model = ServiceAccount - def create_service_account(self, params): - def _rollback(service_account_vo): + def create_service_account(self, params: dict) -> ServiceAccount: + def _rollback(vo: ServiceAccount): _LOGGER.info(f'[create_service_account._rollback] ' - f'Create service_account : {service_account_vo.name} ' - f'({service_account_vo.service_account_id})') + f'Delete service account: {vo.name} ({vo.service_account_id})') service_account_vo.delete() - service_account_vo: ServiceAccount = self.service_account_model.create(params) + service_account_vo = self.service_account_model.create(params) self.transaction.add_rollback(_rollback, service_account_vo) return service_account_vo - def update_service_account(self, params): - service_account_vo: ServiceAccount = self.get_service_account(params['service_account_id'], - params['domain_id']) + def update_service_account(self, params: dict) -> ServiceAccount: + service_account_vo = self.get_service_account( + params['service_account_id'], + params['domain_id'], + params['workspace_id'], + params.get('user_projects') + ) return self.update_service_account_by_vo(params, service_account_vo) - def update_service_account_by_vo(self, params, service_account_vo): + def update_service_account_by_vo(self, params: dict, service_account_vo: ServiceAccount) -> ServiceAccount: def _rollback(old_data): - _LOGGER.info(f'[update_service_account._rollback] Revert Data : ' + _LOGGER.info(f'[update_service_account_by_vo._rollback] Revert Data : ' f'{old_data["service_account_id"]}') service_account_vo.update(old_data) @@ -42,62 +45,64 @@ def _rollback(old_data): return service_account_vo.update(params) - def delete_service_account(self, service_account_id, domain_id): - service_account_vo: ServiceAccount = self.get_service_account(service_account_id, domain_id) + @staticmethod + def delete_service_account_by_vo(service_account_vo: ServiceAccount) -> None: service_account_vo.delete() - def get_service_account(self, service_account_id, domain_id, only=None): - return self.service_account_model.get(service_account_id=service_account_id, domain_id=domain_id, only=only) + def get_service_account( + self, service_account_id: str, domain_id: str, workspace_id: str, user_projects: List[str] = None, + ) -> ServiceAccount: + + conditions = { + 'service_account_id': service_account_id, + 'domain_id': domain_id, + 'workspace_id': workspace_id, + } + + if user_projects: + conditions['project_id'] = user_projects + + return self.service_account_model.get(**conditions) - def list_service_accounts(self, query={}): + def filter_service_accounts(self, **conditions) -> List[ServiceAccount]: + return self.service_account_model.filter(**conditions) + + def list_service_accounts(self, query: dict) -> Tuple[list, int]: return self.service_account_model.query(**query) - def stat_service_accounts(self, query): + def stat_service_accounts(self, query: dict) -> dict: return self.service_account_model.stat(**query) - def update_secret_project(self, service_account_id, project_id, domain_id): - secret_connector: SpaceConnector = self.locator.get_connector('SpaceConnector', service='secret') - response = self._list_secrets(secret_connector, service_account_id, domain_id) - secrets = response.get('results', []) + def update_secret_project( + self, service_account_id: str, domain_id: str, workspace_id: str, project_id: str + ) -> None: + secret_connector = SpaceConnector(service='secret') + response = self._list_secrets(secret_connector, service_account_id, domain_id, workspace_id) - for secret_info in secrets: + for secret_info in response.get('results', []): secret_connector.dispatch('Secret.update', { 'secret_id': secret_info['secret_id'], 'project_id': project_id, - 'domain_id': domain_id + 'domain_id': domain_id, + 'workspace_id': workspace_id }) - def release_secret_project(self, service_account_id, domain_id): - secret_connector: SpaceConnector = self.locator.get_connector('SpaceConnector', service='secret') - response = self._list_secrets(secret_connector, service_account_id, domain_id) - secrets = response.get('results', []) + def delete_secrets( + self, service_account_id: str, domain_id: str, workspace_id: str + ) -> None: + secret_connector = SpaceConnector(service='secret') + response = self._list_secrets(secret_connector, service_account_id, domain_id, workspace_id) - for secret_info in secrets: - secret_connector.dispatch('Secret.update', { + for secret_info in response.get('results', []): + secret_connector.dispatch('Secret.delete', { 'secret_id': secret_info['secret_id'], - 'release_project': True, - 'domain_id': domain_id + 'domain_id': domain_id, + 'workspace_id': workspace_id }) - def delete_service_account_secrets(self, service_account_id, domain_id, service_account_type): - secret_connector: SpaceConnector = self.locator.get_connector('SpaceConnector', service='secret') - if service_account_type == 'TRUSTED': - response = self._list_trusted_secrets(secret_connector, service_account_id, domain_id) - for trusted_secret_info in response.get('results', []): - secret_connector.dispatch('TrustedSecret.delete', { - 'trusted_secret_id': trusted_secret_info['trusted_secret_id'], - 'domain_id': domain_id - }) - else: - response = self._list_secrets(secret_connector, service_account_id, domain_id) - for secret_info in response.get('results', []): - secret_connector.dispatch('Secret.delete', { - 'secret_id': secret_info['secret_id'], - 'domain_id': domain_id - }) - - def get_all_service_account_ids_using_secret(self, domain_id): - secret_connector: SpaceConnector = self.locator.get_connector('SpaceConnector', service='secret') + @staticmethod + def get_all_service_account_ids_using_secret(domain_id: str, workspace_id: str) -> List[str]: + secret_connector = SpaceConnector(service='secret') response = secret_connector.dispatch('Secret.stat', { 'query': { 'distinct': 'service_account_id', @@ -109,38 +114,18 @@ def get_all_service_account_ids_using_secret(self, domain_id): } ] }, - 'domain_id': domain_id + 'domain_id': domain_id, + 'workspace_id': workspace_id }) return response.get('results', []) - def check_service_account_secrets(self, service_account_id, domain_id, service_account_type): - secret_connector: SpaceConnector = self.locator.get_connector('SpaceConnector', service='secret') - - if service_account_type == 'TRUSTED': - response = self._list_trusted_secrets(secret_connector, service_account_id, domain_id) - total_count = response.get('total_count', 0) - - if total_count > 0: - raise ERROR_EXIST_RESOURCE(parent='ServiceAccount', child='TrustedSecret') - else: - response = self._list_secrets(secret_connector, service_account_id, domain_id) - - total_count = response.get('total_count', 0) - - if total_count > 0: - raise ERROR_EXIST_RESOURCE(parent='ServiceAccount', child='Secret') - @staticmethod - def _list_secrets(secret_connector, service_account_id, domain_id): + def _list_secrets( + secret_connector: SpaceConnector, service_account_id: str, domain_id: str, workspace_id: str + ) -> dict: return secret_connector.dispatch('Secret.list', { 'service_account_id': service_account_id, - 'domain_id': domain_id - }) - - @staticmethod - def _list_trusted_secrets(secret_connector, service_account_id, domain_id): - return secret_connector.dispatch('TrustedSecret.list', { - 'service_account_id': service_account_id, - 'domain_id': domain_id + 'domain_id': domain_id, + 'workspace_id': workspace_id }) \ No newline at end of file diff --git a/src/spaceone/identity/model/service_account_model.py b/src/spaceone/identity/model/service_account_db.py similarity index 58% rename from src/spaceone/identity/model/service_account_model.py rename to src/spaceone/identity/model/service_account_db.py index 916e3749..e87f16a7 100644 --- a/src/spaceone/identity/model/service_account_model.py +++ b/src/spaceone/identity/model/service_account_db.py @@ -5,15 +5,13 @@ class ServiceAccount(MongoModel): service_account_id = StringField(max_length=40, generate_id='sa', unique=True) - name = StringField(max_length=255, unique_with='domain_id') - data = DictField() - service_account_type = StringField(max_length=40, choices=('TRUSTED', 'GENERAL'), default='GENERAL') + name = StringField(max_length=255, unique_with=['domain_id', 'workspace_id']) + data = DictField(default=None) provider = StringField(max_length=40) - project = ReferenceField('Project', null=True, default=None, reverse_delete_rule=DENY) - project_id = StringField(max_length=40, default=None, null=True) - trusted_service_account_id = StringField(max_length=40, null=True, default=None) tags = DictField() - scope = StringField(max_length=40, choices=('DOMAIN', 'PROJECT'), default='PROJECT') + trusted_service_account_id = StringField(max_length=40, null=True, default=None) + project_id = StringField(max_length=40) + workspace_id = StringField(max_length=40) domain_id = StringField(max_length=255) created_at = DateTimeField(auto_now_add=True) @@ -21,33 +19,28 @@ class ServiceAccount(MongoModel): 'updatable_fields': [ 'name', 'data', - 'project', - 'project_id', + 'tags', 'trusted_service_account_id', - 'tags' + 'project_id' ], 'minimal_fields': [ 'service_account_id', - 'service_account_type', 'name', 'provider' + 'trusted_service_account_id', + 'project_id' ], 'change_query_keys': { 'user_projects': 'project_id', - 'project_id': 'project_id' - }, - 'reference_query_keys': { - 'project': Project + 'user_workspaces': 'workspace_id' }, 'ordering': ['name'], 'indexes': [ - # 'service_account_id', 'name', 'provider', - 'project', - 'project_id', 'trusted_service_account_id', - 'scope', + 'project_id', + 'workspace_id', 'domain_id' ] } diff --git a/src/spaceone/identity/model/service_account_request.py b/src/spaceone/identity/model/service_account_request.py index b0a444e0..c947d60f 100644 --- a/src/spaceone/identity/model/service_account_request.py +++ b/src/spaceone/identity/model/service_account_request.py @@ -6,7 +6,6 @@ "ServiceAccountCreateRequest", "ServiceAccountUpdateRequest", "ServiceAccountChangeTrustedServiceAccountRequest", - "ServiceAccountChangeProjectRequest", "ServiceAccountDeleteRequest", "ServiceAccountGetRequest", "ServiceAccountSearchQueryRequest", @@ -32,6 +31,7 @@ class ServiceAccountUpdateRequest(BaseModel): tags: Union[dict, None] = None workspace_id: str domain_id: str + user_projects: Union[list, None] = None class ServiceAccountChangeTrustedServiceAccountRequest(BaseModel): @@ -39,25 +39,21 @@ class ServiceAccountChangeTrustedServiceAccountRequest(BaseModel): trusted_service_account_id: str workspace_id: str domain_id: str - - -class ServiceAccountChangeProjectRequest(BaseModel): - service_account_id: str - project_id: str - workspace_id: str - domain_id: str + user_projects: Union[list, None] = None class ServiceAccountDeleteRequest(BaseModel): service_account_id: str workspace_id: str domain_id: str + user_projects: Union[list, None] = None class ServiceAccountGetRequest(BaseModel): service_account_id: str workspace_id: Union[str, None] = None domain_id: str + user_projects: Union[list, None] = None class ServiceAccountSearchQueryRequest(BaseModel): @@ -70,9 +66,13 @@ class ServiceAccountSearchQueryRequest(BaseModel): project_id: Union[str, None] = None workspace_id: Union[str, None] = None domain_id: str + user_workspaces: Union[list, None] = None + user_projects: Union[list, None] = None class ServiceAccountStatQueryRequest(BaseModel): query: dict workspace_id: Union[str, None] = None domain_id: str + user_workspaces: Union[list, None] = None + user_projects: Union[list, None] = None diff --git a/src/spaceone/identity/service/service_account_service.py b/src/spaceone/identity/service/service_account_service.py index fb6d7406..f78bf9ef 100644 --- a/src/spaceone/identity/service/service_account_service.py +++ b/src/spaceone/identity/service/service_account_service.py @@ -1,61 +1,233 @@ import logging from typing import Union -from spaceone.core.service import BaseService, transaction, convert_model +from spaceone.core.service import (BaseService, transaction, convert_model, append_query_filter, + append_keyword_filter, set_query_page_limit) from spaceone.identity.model.service_account_request import * from spaceone.identity.model.service_account_response import * +from spaceone.identity.manager.provider_manager import ProviderManager +from spaceone.identity.manager.service_account_manager import ServiceAccountManager +from spaceone.identity.manager.trusted_service_account_manager import TrustedServiceAccountManager + _LOGGER = logging.getLogger(__name__) class ServiceAccountService(BaseService): - @transaction + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.service_account_mgr = ServiceAccountManager() + + @transaction(append_meta={'authorization.scope': 'PROJECT'}) @convert_model - def create( - self, params: ServiceAccountCreateRequest - ) -> Union[ServiceAccountResponse, dict]: - return {} + def create(self, params: ServiceAccountCreateRequest) -> Union[ServiceAccountResponse, dict]: + """ create service account + + Args: + params (ServiceAccountCreateRequest): { + 'name': 'str', # required + 'data': 'dict', # required + 'provider': 'str', # required + 'trusted_service_account_id': 'str', + 'tags': 'dict', + 'project_id': 'str', # required + 'workspace_id': 'str', # required + 'domain_id': 'str' # required + } - @transaction + Returns: + ServiceAccountResponse + """ + + # Check data by schema + provider_mgr = ProviderManager() + provider_mgr.check_data_by_schema(params.provider, params.domain_id, params.data) + + # Check trusted service account + trusted_service_account_mgr = TrustedServiceAccountManager() + trusted_service_account_mgr.get_trusted_service_account( + params.trusted_service_account_id, params.domain_id, params.workspace_id + ) + + service_account_vo = self.service_account_mgr.create_service_account(params.dict()) + return ServiceAccountResponse(**service_account_vo.to_dict()) + + @transaction(append_meta={'authorization.scope': 'PROJECT'}) @convert_model - def update( - self, params: ServiceAccountUpdateRequest - ) -> Union[ServiceAccountResponse, dict]: - return {} + def update(self, params: ServiceAccountUpdateRequest) -> Union[ServiceAccountResponse, dict]: + """ update service account - @transaction + Args: + params (ServiceAccountUpdateRequest): { + 'service_account_id': 'str', # required + 'name': 'str', + 'data': 'dict', + 'tags': 'dict', + 'project_id': 'str', + 'workspace_id': 'str', # required + 'domain_id': 'str', # required + 'user_projects': 'list' # from meta + } + + Returns: + ServiceAccountResponse + """ + + service_account_vo = self.service_account_mgr.get_service_account( + params.service_account_id, params.domain_id, params.workspace_id, params.user_projects + ) + + if params.data: + # Check data by schema + provider_mgr = ProviderManager() + provider_mgr.check_data_by_schema(service_account_vo.provider, params.domain_id, params.data) + + service_account_vo = self.service_account_mgr.update_service_account_by_vo( + params.dict(), service_account_vo + ) + + return ServiceAccountResponse(**service_account_vo.to_dict()) + + @transaction(append_meta={'authorization.scope': 'PROJECT'}) @convert_model def change_trusted_service_account( self, params: ServiceAccountChangeTrustedServiceAccountRequest ) -> Union[ServiceAccountResponse, dict]: - return {} + """ change trusted service account - @transaction - @convert_model - def change_project( - self, params: ServiceAccountChangeProjectRequest - ) -> Union[ServiceAccountResponse, dict]: - return {} + Args: + params (ServiceAccountChangeTrustedServiceAccountRequest): { + 'service_account_id': 'str', # required + 'trusted_service_account_id': 'str', # required + 'workspace_id': 'str', # required + 'domain_id': 'str', # required + 'user_projects': 'list' # from meta + } + + Returns: + ServiceAccountResponse + """ + + service_account_vo = self.service_account_mgr.get_service_account( + params.service_account_id, params.domain_id, params.workspace_id, params.user_projects + ) - @transaction + # Check trusted service account + trusted_service_account_mgr = TrustedServiceAccountManager() + trusted_service_account_mgr.get_trusted_service_account( + params.trusted_service_account_id, params.domain_id, params.workspace_id + ) + + service_account_vo = self.service_account_mgr.update_service_account_by_vo( + {'trusted_service_account_id': params.trusted_service_account_id}, service_account_vo + ) + + return ServiceAccountResponse(**service_account_vo.to_dict()) + + @transaction(append_meta={'authorization.scope': 'PROJECT'}) @convert_model def delete(self, params: ServiceAccountDeleteRequest) -> None: - pass + """ delete service account + + Args: + params (ServiceAccountDeleteRequest): { + 'service_account_id': 'str', # required + 'workspace_id': 'str', # required + 'domain_id': 'str', # required + 'user_projects': 'list' # from meta + } + + Returns: + None + """ - @transaction + service_account_vo = self.service_account_mgr.get_service_account( + params.service_account_id, params.domain_id, params.workspace_id, params.user_projects + ) + + self.service_account_mgr.delete_service_account_by_vo(service_account_vo) + + @transaction(append_meta={'authorization.scope': 'PROJECT_READ'}) @convert_model - def get( - self, params: ServiceAccountGetRequest - ) -> Union[ServiceAccountResponse, dict]: - return {} + def get(self, params: ServiceAccountGetRequest) -> Union[ServiceAccountResponse, dict]: + """ delete service account + + Args: + params (ServiceAccountDeleteRequest): { + 'service_account_id': 'str', # required + 'workspace_id': 'str', # required + 'domain_id': 'str', # required + 'user_projects': 'list' # from meta + } + + Returns: + ServiceAccountResponse + """ - @transaction + service_account_vo = self.service_account_mgr.get_service_account( + params.service_account_id, params.domain_id, params.workspace_id, params.user_projects + ) + + return ServiceAccountResponse(**service_account_vo.to_dict()) + + @transaction(append_meta={'authorization.scope': 'PROJECT_READ'}) + @append_query_filter([ + 'service_account_id', 'name', 'provider', 'project_id', 'workspace_id', 'domain_id', 'user_projects', + 'user_workspaces' + ]) + @append_keyword_filter(['service_account_id', 'name']) + @set_query_page_limit(1000) @convert_model - def list( - self, params: ServiceAccountSearchQueryRequest - ) -> Union[ServiceAccountsResponse, dict]: - return {} + def list(self, params: ServiceAccountSearchQueryRequest) -> Union[ServiceAccountsResponse, dict]: + """ list service accounts + + Args: + params (ServiceAccountSearchQueryRequest): { + 'query': 'dict (spaceone.api.core.v1.Query)', + 'service_account_id': 'str', + 'name': 'str', + 'provider': 'str', + 'project_id': 'str', + 'workspace_id': 'str', + 'domain_id': 'str', # required + 'user_workspaces': 'list', # from meta + 'user_projects': 'list' # from meta + } + + Returns: + ServiceAccountsResponse + """ + + query = params.query or {} + service_account_vos, total_count = self.service_account_mgr.list_service_accounts(query) - @transaction + service_accounts_info = [service_account_vo.to_dict() for service_account_vo in service_account_vos] + return ServiceAccountsResponse(results=service_accounts_info, total_count=total_count) + + @transaction(append_meta={'authorization.scope': 'PROJECT_READ'}) + @append_query_filter(['domain_id', 'workspace_id', 'user_workspaces', 'user_projects']) + @append_keyword_filter(['service_account_id', 'name']) + @set_query_page_limit(1000) @convert_model def stat(self, params: ServiceAccountStatQueryRequest) -> dict: - return {} + """ stat service accounts + + Args: + params (ServiceAccountStatQueryRequest): { + 'query': 'dict (spaceone.api.core.v1.StatisticsQuery)', # required + 'workspace_id': 'str', + 'domain_id': 'str', # required + 'user_workspaces': 'list', # from meta + 'user_projects': 'list' # from meta + } + + Returns: + dict: { + 'results': 'list', + 'total_count': 'int' + } + + """ + + query = params.query or {} + return self.service_account_mgr.stat_service_accounts(query) From 4bb0514054420a8c20f1562cc1e120b17a39e829 Mon Sep 17 00:00:00 2001 From: Jongmin Kim Date: Mon, 20 Nov 2023 02:17:58 +0900 Subject: [PATCH 19/45] feat: implement trusted service account APIs --- .../trusted_service_account_manager.py | 94 +++++++++ .../model/trusted_service_account_db.py | 38 ++++ .../model/trusted_service_account_request.py | 2 + .../trusted_service_account_service.py | 185 +++++++++++++++--- 4 files changed, 294 insertions(+), 25 deletions(-) create mode 100644 src/spaceone/identity/manager/trusted_service_account_manager.py create mode 100644 src/spaceone/identity/model/trusted_service_account_db.py diff --git a/src/spaceone/identity/manager/trusted_service_account_manager.py b/src/spaceone/identity/manager/trusted_service_account_manager.py new file mode 100644 index 00000000..bda556f8 --- /dev/null +++ b/src/spaceone/identity/manager/trusted_service_account_manager.py @@ -0,0 +1,94 @@ +import logging +from typing import Tuple, List + +from spaceone.core.manager import BaseManager +from spaceone.core.connector.space_connector import SpaceConnector +from spaceone.identity.model.trusted_service_account_db import TrustedServiceAccount + +_LOGGER = logging.getLogger(__name__) + + +class TrustedServiceAccountManager(BaseManager): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.trusted_account_model = TrustedServiceAccount + + def create_trusted_service_account(self, params: dict) -> TrustedServiceAccount: + def _rollback(vo: TrustedServiceAccount): + _LOGGER.info(f'[create_trusted_service_account._rollback] ' + f'Delete trusted service account: {vo.name} ({vo.trusted_service_account_id})') + vo.delete() + + trusted_account_vo = self.trusted_account_model.create(params) + self.transaction.add_rollback(_rollback, trusted_account_vo) + + return trusted_account_vo + + def update_trusted_service_account(self, params: dict) -> TrustedServiceAccount: + trusted_account_vo = self.get_trusted_service_account( + params['trusted_service_account_id'], params['domain_id'], params.get('workspace_id')) + + return self.update_trusted_service_account_by_vo(params, trusted_account_vo) + + def update_trusted_service_account_by_vo( + self, params: dict, trusted_account_vo: TrustedServiceAccount + ) -> TrustedServiceAccount: + def _rollback(old_data): + _LOGGER.info(f'[update_trusted_service_account_by_vo._rollback] Revert Data : ' + f'{old_data["trusted_service_account_id"]}') + trusted_account_vo.update(old_data) + + self.transaction.add_rollback(_rollback, trusted_account_vo.to_dict()) + + return trusted_account_vo.update(params) + + @staticmethod + def delete_trusted_service_account_by_vo(trusted_account_vo: TrustedServiceAccount) -> None: + trusted_account_vo.delete() + + def get_trusted_service_account( + self, trusted_service_account_id: str, domain_id: str, workspace_id: str = None + ) -> TrustedServiceAccount: + return self.trusted_account_model.get( + trusted_service_account_id=trusted_service_account_id, + domain_id=domain_id, + workspace_id=workspace_id + ) + + def filter_trusted_service_accounts(self, **conditions) -> List[TrustedServiceAccount]: + return self.trusted_account_model.filter(**conditions) + + def list_trusted_service_accounts(self, query: dict) -> Tuple[list, int]: + return self.trusted_account_model.query(**query) + + def stat_trusted_service_accounts(self, query: dict) -> dict: + return self.trusted_account_model.stat(**query) + + def delete_trusted_secrets( + self, trusted_service_account_id: str, workspace_id: str, domain_id: str + ) -> None: + secret_connector = SpaceConnector(service='secret') + response = self._list_trusted_secrets( + secret_connector, + trusted_service_account_id, + workspace_id, + domain_id + ) + + for secret_info in response.get('results', []): + secret_connector.dispatch('TrustedSecret.delete', { + 'trusted_secret_id': secret_info['trusted_secret_id'], + 'domain_id': domain_id, + 'workspace_id': workspace_id + }) + + @staticmethod + def _list_trusted_secrets( + secret_connector: SpaceConnector, trusted_service_account_id: str, domain_id: str, workspace_id: str + ) -> dict: + return secret_connector.dispatch('TrustedSecret.list', { + 'trusted_service_account_id': trusted_service_account_id, + 'domain_id': domain_id, + 'workspace_id': workspace_id + }) \ No newline at end of file diff --git a/src/spaceone/identity/model/trusted_service_account_db.py b/src/spaceone/identity/model/trusted_service_account_db.py new file mode 100644 index 00000000..c60050fa --- /dev/null +++ b/src/spaceone/identity/model/trusted_service_account_db.py @@ -0,0 +1,38 @@ +from mongoengine import * +from spaceone.core.model.mongo_model import MongoModel + + +class TrustedServiceAccount(MongoModel): + trusted_service_account_id = StringField(max_length=40, generate_id='trusted-sa', unique=True) + name = StringField(max_length=255, unique_with=['workspace_id', 'domain_id']) + data = DictField(default=None) + provider = StringField(max_length=40) + tags = DictField() + scope = StringField(max_length=40, choices=('DOMAIN', 'WORKSPACE'), default='WORKSPACE') + workspace_id = StringField(max_length=40, default=None, null=True) + domain_id = StringField(max_length=255) + created_at = DateTimeField(auto_now_add=True) + + meta = { + 'updatable_fields': [ + 'name', + 'data', + 'tags' + ], + 'minimal_fields': [ + 'trusted_service_account_id', + 'name', + 'provider' + ], + 'change_query_keys': { + 'user_workspaces': 'workspace_id' + }, + 'ordering': ['name'], + 'indexes': [ + 'name', + 'provider', + 'scope', + 'workspace_id', + 'domain_id' + ] + } diff --git a/src/spaceone/identity/model/trusted_service_account_request.py b/src/spaceone/identity/model/trusted_service_account_request.py index 6263d3b0..10bf4807 100644 --- a/src/spaceone/identity/model/trusted_service_account_request.py +++ b/src/spaceone/identity/model/trusted_service_account_request.py @@ -52,9 +52,11 @@ class TrustedServiceAccountSearchQueryRequest(BaseModel): scope: Union[Scope, None] = None workspace_id: Union[str, None] = None domain_id: str + user_workspaces: Union[list, None] = None class TrustedServiceAccountStatQueryRequest(BaseModel): query: dict workspace_id: Union[str, None] = None domain_id: str + user_workspaces: Union[list, None] = None diff --git a/src/spaceone/identity/service/trusted_service_account_service.py b/src/spaceone/identity/service/trusted_service_account_service.py index e36c2b39..09aed364 100644 --- a/src/spaceone/identity/service/trusted_service_account_service.py +++ b/src/spaceone/identity/service/trusted_service_account_service.py @@ -1,47 +1,182 @@ import logging from typing import Union -from spaceone.core.service import BaseService, transaction, convert_model +from spaceone.core.service import BaseService, transaction, convert_model, append_query_filter, append_keyword_filter from spaceone.identity.model.trusted_service_account_request import * from spaceone.identity.model.trusted_service_account_response import * +from spaceone.identity.manager.provider_manager import ProviderManager +from spaceone.identity.manager.trusted_service_account_manager import TrustedServiceAccountManager _LOGGER = logging.getLogger(__name__) class TrustedServiceAccountService(BaseService): - @transaction + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.trusted_account_mgr = TrustedServiceAccountManager() + + @transaction(append_meta={'authorization.scope': 'DOMAIN_OR_WORKSPACE'}) @convert_model - def create( - self, params: TrustedServiceAccountCreateRequest - ) -> Union[TrustedServiceAccountResponse, dict]: - return {} + def create(self, params: TrustedServiceAccountCreateRequest) -> Union[TrustedServiceAccountResponse, dict]: + """ create trusted service account + + Args: + params (TrustedServiceAccountCreateRequest): { + 'name': 'str', # required + 'data': 'dict', # required + 'provider': 'str', # required + 'tags': 'dict', + 'scope': 'str', # required + 'workspace_id': 'str', + 'domain_id': 'str' # required + } + + Returns: + TrustedServiceAccountResponse + """ - @transaction + # Check data by schema + provider_mgr = ProviderManager() + provider_mgr.check_data_by_schema(params.provider, params.domain_id, params.data) + + trusted_account_vo = self.trusted_account_mgr.create_trusted_service_account(params.dict()) + return TrustedServiceAccountResponse(**trusted_account_vo.to_dict()) + + @transaction(append_meta={'authorization.scope': 'DOMAIN_OR_WORKSPACE'}) @convert_model - def update( - self, params: TrustedServiceAccountUpdateRequest - ) -> Union[TrustedServiceAccountResponse, dict]: - return {} + def update(self, params: TrustedServiceAccountUpdateRequest) -> Union[TrustedServiceAccountResponse, dict]: + """ update trusted service account + + Args: + params (TrustedServiceAccountUpdateRequest): { + 'trusted_service_account_id': 'str', # required + 'name': 'str', + 'data': 'dict', + 'tags': 'dict', + 'workspace_id': 'str', + 'domain_id': 'str' # required + } + + Returns: + TrustedServiceAccountResponse + """ - @transaction + trusted_account_vo = self.trusted_account_mgr.get_trusted_service_account( + params.trusted_service_account_id, params.domain_id, params.workspace_id + ) + + if params.data: + # Check data by schema + provider_mgr = ProviderManager() + provider_mgr.check_data_by_schema(trusted_account_vo.provider, params.domain_id, params.data) + + trusted_account_vo = self.trusted_account_mgr.update_trusted_service_account_by_vo( + params.dict(), trusted_account_vo + ) + + return TrustedServiceAccountResponse(**trusted_account_vo.to_dict()) + + @transaction(append_meta={'authorization.scope': 'DOMAIN_OR_WORKSPACE'}) @convert_model def delete(self, params: TrustedServiceAccountDeleteRequest) -> None: - pass + """ delete trusted service account + + Args: + params (TrustedServiceAccountDeleteRequest): { + 'trusted_service_account_id': 'str', # required + 'workspace_id': 'str', + 'domain_id': 'str' # required + } + + Returns: + None + """ + + trusted_account_vo = self.trusted_account_mgr.get_trusted_service_account( + params.trusted_service_account_id, params.domain_id, params.workspace_id + ) - @transaction + self.trusted_account_mgr.delete_trusted_secrets( + params.trusted_service_account_id, params.workspace_id, params.domain_id + ) + self.trusted_account_mgr.delete_trusted_service_account_by_vo(trusted_account_vo) + + @transaction(append_meta={'authorization.scope': 'DOMAIN_OR_WORKSPACE_READ'}) @convert_model - def get( - self, params: TrustedServiceAccountGetRequest - ) -> Union[TrustedServiceAccountResponse, dict]: - return {} + def get(self, params: TrustedServiceAccountGetRequest) -> Union[TrustedServiceAccountResponse, dict]: + """ get trusted service account + + Args: + params (TrustedServiceAccountGetRequest): { + 'trusted_service_account_id': 'str', # required + 'workspace_id': 'str', + 'domain_id': 'str' # required + } + + Returns: + TrustedServiceAccountResponse + """ + + trusted_account_vo = self.trusted_account_mgr.get_trusted_service_account( + params.trusted_service_account_id, params.domain_id, params.workspace_id + ) - @transaction + return TrustedServiceAccountResponse(**trusted_account_vo.to_dict()) + + @transaction(append_meta={'authorization.scope': 'DOMAIN_OR_WORKSPACE_READ'}) + @append_query_filter([ + 'trusted_service_account_id', 'name', 'provider', 'scope', 'workspace_id', 'domain_id', 'user_workspaces' + ]) + @append_keyword_filter(['trusted_service_account_id', 'name']) @convert_model - def list( - self, params: TrustedServiceAccountSearchQueryRequest - ) -> Union[TrustedServiceAccountsResponse, dict]: - return {} + def list(self, params: TrustedServiceAccountSearchQueryRequest) -> Union[TrustedServiceAccountsResponse, dict]: + """ list providers + + Args: + params (TrustedServiceAccountSearchQueryRequest): { + 'query': 'dict (spaceone.api.core.v1.Query)', + 'trusted_service_account_id': 'str', + 'name': 'str', + 'provider': 'str', + 'scope': 'str', + 'workspace_id': 'str', + 'domain_id': 'str', # required + 'user_workspaces': 'list' # from meta + } + + Returns: + TrustedServiceAccountsResponse + """ - @transaction + query = params.query or {} + + trusted_account_vos, total_count = self.trusted_account_mgr.list_trusted_service_accounts(query) + + trusted_accounts_info = [trusted_account_vo.to_dict() for trusted_account_vo in trusted_account_vos] + return TrustedServiceAccountsResponse(results=trusted_accounts_info, total_count=total_count) + + @transaction(append_meta={'authorization.scope': 'DOMAIN_OR_WORKSPACE_READ'}) + @append_query_filter(['domain_id', 'workspace_id', 'user_workspaces']) + @append_keyword_filter(['trusted_service_account_id', 'name']) @convert_model def stat(self, params: TrustedServiceAccountStatQueryRequest) -> dict: - return {} + """ stat trusted service accounts + + Args: + params (TrustedServiceAccountStatQueryRequest): { + 'query': 'dict (spaceone.api.core.v1.StatisticsQuery)', # required + 'workspace_id': 'str', + 'domain_id': 'str', # required + 'user_workspaces': 'list' # from meta + } + + Returns: + dict: { + 'results': 'list', + 'total_count': 'int' + } + + """ + + query = params.query or {} + return self.trusted_account_mgr.stat_trusted_service_accounts(query) From c64926efb5ae0b2313e63f4ab039fa060a990980 Mon Sep 17 00:00:00 2001 From: Jongmin Kim Date: Mon, 20 Nov 2023 02:20:01 +0900 Subject: [PATCH 20/45] refactor: remove unused errors --- src/spaceone/identity/error/error_service_account.py | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 src/spaceone/identity/error/error_service_account.py diff --git a/src/spaceone/identity/error/error_service_account.py b/src/spaceone/identity/error/error_service_account.py deleted file mode 100644 index 7f158ff2..00000000 --- a/src/spaceone/identity/error/error_service_account.py +++ /dev/null @@ -1,5 +0,0 @@ -from spaceone.core.error import * - - -class ERROR_NOT_FOUND_TRUSTED_SERVICE_ACCOUNT_ID(ERROR_BASE): - _message = 'A trusted_service_account_id "{trusted_service_account_id}" is not exist in domain.' From 005f3a64cfc54070c032faf06f22d4c736121a1a Mon Sep 17 00:00:00 2001 From: Jongmin Kim Date: Mon, 20 Nov 2023 02:21:28 +0900 Subject: [PATCH 21/45] feat: add default provider config --- src/spaceone/identity/conf/provider_conf.py | 234 ++++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 src/spaceone/identity/conf/provider_conf.py diff --git a/src/spaceone/identity/conf/provider_conf.py b/src/spaceone/identity/conf/provider_conf.py new file mode 100644 index 00000000..5f681ef8 --- /dev/null +++ b/src/spaceone/identity/conf/provider_conf.py @@ -0,0 +1,234 @@ +DEFAULT_PROVIDERS = [{ + "provider": "aws", + "name": "AWS", + "order": 1, + "template": { + "service_account": { + "schema": { + "type": "object", + "properties": { + "account_id": { + "title": "Account ID", + "type": "string", + "minLength": 4 + } + }, + "required": ["account_id"] + } + } + }, + "metadata": { + "view": { + "layouts": { + "help:service_account:create": { + "name": "Creation Help", + "type": "markdown", + "options": { + "markdown": { + "en": ( + "# Help for AWS Users\n" + "## Find Your AWS Account ID\n" + "Get your AWS Account ID.\n" + "[AWS Account ID](https://docs.aws.amazon.com/IAM/latest/UserGuide/console_account-alias.html)\n" + "## Get Your Assume role\n" + "Granting permissions to create temporary security credentials.\n" + "[AWS Assume Role](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_permissions-to-switch.html)\n" + "## Issue AWS Access Key \n" + "Get your AWS Access Key & AWS Secret Key\n" + "[AWS Access Key & AWS Secret Key](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html#Using_CreateAccessKey)\n" + ), + "ko": ( + "# AWS 이용자 가이드\n" + "## AWS 어카운트 아이디(Account ID) 찾기\n" + "사용자의 AWS 어카운트 아이디 AWS 콘솔(Console)에서 확인하기\n" + "[AWS Account ID](https://docs.aws.amazon.com/ko_kr/IAM/latest/UserGuide/console_account-alias.html)\n" + "## Assume role 획득하기\n" + "임시 보안 자격증명을 만들 수있는 권한을 부여하기.\n" + "[AWS Assume Role](https://docs.aws.amazon.com/ko_kr/IAM/latest/UserGuide/id_roles_use_permissions-to-switch.html)\n" + "## AWS Access Key 발급하기\n" + "AWS Access Key & AWS Secret Key 발급하기\n" + "[AWS Access Key & AWS Secret Key](https://docs.aws.amazon.com/ko_kr/IAM/latest/UserGuide/id_credentials_access-keys.html#Using_CreateAccessKey)\n" + ), + + } + } + } + } + } + }, + "capability": { + "general_service_account_schema": [ + "aws_assume_role" + ], + "trusted_service_account_schema": [ + "aws_access_key" + ], + "support_trusted_service_account": True, + "supported_schema": [ + "aws_access_key" + ] + }, + "tags": { + 'color': '#FF9900', + 'icon': 'https://spaceone-custom-assets.s3.ap-northeast-2.amazonaws.com/console-assets/icons/aws.svg', + 'external_link_template': 'https://<%- data.account_id %>.signin.aws.amazon.com/console' + } +}, { + "provider": "google_cloud", + "version": "v1", + "name": "Google Cloud", + "order": 2, + "template": { + "service_account": { + "schema": { + "type": "object", + "properties": { + "project_id": { + "title": "Project ID", + "type": "string", + "minLength": 4 + } + }, + "required": ["project_id"] + } + } + }, + "metadata": { + "view": { + "layouts": { + "help:service_account:create": { + "name": "Creation Help", + "type": "markdown", + "options": { + "markdown": { + "en": ( + "# Getting started with Google Cloud\n" + "## Identifying Your Project\n" + "Get your Project infos (Project Name, Project ID and Project number)\n" + "[Project Info](https://cloud.google.com/resource-manager/docs/creating-managing-projects#identifying_projects)\n" + "## Get Your Service Account Key(JSON)\n" + "Generate Your JSON Service Account Key.\n" + "[Service Account Key](https://cloud.google.com/docs/authentication/getting-started)\n" + ), + "ko": ( + "# Google Cloud 시작 가이드\n" + "## Project 정보 확인하기\n" + "프로젝트 명, 프로젝트 아이디, 프로젝트 번호 등의 프로젝트 정보 확인하기\n" + "[Project Info](https://cloud.google.com/resource-manager/docs/creating-managing-projects?hl=ko#identifying_projects)\n" + "## 서비스 어카운트 키(JSON) 받기\n" + "JSON 포멧의 서비스 어카운트 키를 생성하기.\n" + "[Service Account Key](https://cloud.google.com/docs/authentication/getting-started?hl=ko)\n" + ), + + } + } + } + } + } + }, + "capability": { + "general_service_account_schema": [ + "google_cloud_project_id" + ], + "trusted_service_account_schema": [ + "google_oauth2_credentials" + ], + "support_trusted_service_account": True, + "supported_schema": ["google_oauth2_credentials"] + }, + "tags": { + 'color': '#4285F4', + 'icon': 'https://spaceone-custom-assets.s3.ap-northeast-2.amazonaws.com/console-assets/icons/google_cloud.svg', + 'external_link_template': 'https://console.cloud.google.com/home/dashboard?project=<%- data.project_id %>', + 'label': 'Google' + } +}, { + "provider": "azure", + "name": "Azure", + "order": 3, + "template": { + "service_account": { + "schema": { + "type": "object", + "properties": { + "tenant_id": { + "title": "Tenant ID", + "type": "string", + "minLength": 4 + }, + "subscription_id": { + "title": "Subscription ID", + "type": "string", + "minLength": 4 + } + }, + "required": ["tenant_id", "subscription_id"] + } + } + }, + "metadata": { + "view": { + "layouts": { + "help:service_account:create": { + "name": "Creation Help", + "type": "markdown", + "options": { + "markdown": { + "en": ( + "# Help for Azure Users\n" + "## Find Your Azure Subscription ID\n" + "Azure Subscription ID via CLI.\n" + "[Azure Subscription CLI](https://docs.microsoft.com/en-us/cli/azure/ext/account/account/subscription?view=azure-cli-latest)\n" + "Azure Subscription ID via PowerShell.\n" + "[Azure Subscription PowerShell](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-authenticate-service-principal-powershell)\n" + "Create Azure Subscription via Portal.\n" + "[Azure Subscription Portal](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal)\n" + "## Find Your Azure Tenant ID\n" + "Azure Tenant ID via CLI.\n" + "[Azure Tenant CLI](https://docs.microsoft.com/en-us/cli/azure/ext/account/account/tenant?view=azure-cli-latest)\n" + "Azure Tenant ID via PowerShell.\n" + "[Azure Tenant PowerShell](https://docs.microsoft.com/en-us/powershell/module/az.accounts/get-aztenant?view=azps-5.0.0)\n" + "## Get Your Client Secret and ID\n" + "Check Client Secret via Portal.\n" + "[Azure Client Secret Portal](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal)\n" + ), + "ko": ( + "# Azure 이용자 가이드\n" + "## Azure 구독 아이디(Subscription ID) 찾기\n" + "CLI에서 사용자의 구독 아이디 확인하기.\n" + "[Azure Subscription CLI](https://docs.microsoft.com/en-us/cli/azure/ext/account/account/subscription?view=azure-cli-latest)\n" + "PowerShell에서 사용자의 구독 아이디 확인하기.\n" + "[Azure Subscription PowerShell](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-authenticate-service-principal-powershell)\n" + "포털에서 사용자의 구독 아이디 확인하기.\n" + "[Azure Subscription Portal](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal)\n" + "## Azure 테넌트 아이디(Tenant ID) 찾기\n" + "CLI에서 사용자의 테넌트 아이디 확인하기.\n" + "[Azure Tenant CLI](https://docs.microsoft.com/en-us/cli/azure/ext/account/account/tenant?view=azure-cli-latest)\n" + "PowerShell에서 사용자의 테넌트 아이디 확인하기.\n" + "[Azure Tenant PowerShell](https://docs.microsoft.com/en-us/powershell/module/az.accounts/get-aztenant?view=azps-5.0.0)\n" + "## 사용자의 클라이언트 시크릿 정보(Client Secret&ID) 가져오기\n" + "포털에서 사용자의 클라이언트 시크릿 정보 확인하기.\n" + "[Azure Client Secret Portal](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal)\n" + ), + + } + } + } + } + } + }, + "capability": { + "general_service_account_schema": [ + "azure_subscription_id" + ], + "trusted_service_account_schema": [ + "azure_credentials", + ], + "support_trusted_service_account": True, + "supported_schema": ["azure_client_secret"] + }, + "tags": { + 'color': '#00BCF2', + 'icon': 'https://spaceone-custom-assets.s3.ap-northeast-2.amazonaws.com/console-assets/icons/azure.svg' + } +}] \ No newline at end of file From 1d9ae1d6b52fa92ca4a3cd74ec6368ffc48d2c1c Mon Sep 17 00:00:00 2001 From: Jongmin Kim Date: Mon, 20 Nov 2023 02:26:30 +0900 Subject: [PATCH 22/45] refactor: optimize global config --- src/spaceone/identity/conf/global_conf.py | 90 ++++++++--------------- 1 file changed, 32 insertions(+), 58 deletions(-) diff --git a/src/spaceone/identity/conf/global_conf.py b/src/spaceone/identity/conf/global_conf.py index 3b7badbc..d1416d39 100644 --- a/src/spaceone/identity/conf/global_conf.py +++ b/src/spaceone/identity/conf/global_conf.py @@ -1,8 +1,14 @@ +# Root Domain Settings ROOT_DOMAIN_NAME = "root" + +# Email Settings EMAIL_CONSOLE_DOMAIN = "" EMAIL_SERVICE_NAME = "Cloudforet" -# ACCESS_TOKEN (default) / PASSWORD + +# Enums: ACCESS_TOKEN (default) | PASSWORD RESET_PASSWORD_TYPE = "ACCESS_TOKEN" + +# Database Settings DATABASE_AUTO_CREATE_INDEX = True DATABASES = { "default": { @@ -16,6 +22,7 @@ } } +# Cache Settings CACHES = { "default": {}, "local": { @@ -25,6 +32,7 @@ }, } +# Identity Settings IDENTITY = { "token": { "verify_code_timeout": 3600, @@ -34,24 +42,28 @@ "refresh_ttl": 18, "refresh_once": False, }, - "mfa": {"mfa_verify_code_timeout": 300}, + "mfa": { + "mfa_verify_code_timeout": 300 + }, } +# Handler Settings HANDLERS = { - # 'authentication': [{ - # 'backend': 'spaceone.core.handler.authentication_handler.AuthenticationGRPCHandler', - # 'uri': 'grpc://localhost:50051/v1/Domain/get_public_key' + # "authentication": [{ + # "backend": "spaceone.core.handler.authentication_handler.AuthenticationGRPCHandler", + # "uri": "grpc://localhost:50051/v1/Domain/get_public_key" # }], - # 'authorization': [{ - # 'backend': 'spaceone.core.handler.authorization_handler.AuthorizationGRPCHandler', - # 'uri': 'grpc://localhost:50051/v1/Authorization/verify' + # "authorization": [{ + # "backend": "spaceone.core.handler.authorization_handler.AuthorizationGRPCHandler", + # "uri": "grpc://localhost:50051/v1/Authorization/verify" # }], - # 'mutation': [{ - # 'backend': 'spaceone.core.handler.mutation_handler.SpaceONEMutationHandler' + # "mutation": [{ + # "backend": "spaceone.core.handler.mutation_handler.SpaceONEMutationHandler" # }], - # 'event': [] + # "event": [] } +# Connector Settings CONNECTORS = { "SpaceConnector": { "backend": "spaceone.core.connector.space_connector.SpaceConnector", @@ -70,54 +82,16 @@ }, } +# Endpoint Settings ENDPOINTS = [ # { - # 'service': 'identity', - # 'name': 'Identity Service', - # 'endpoint': 'grpc://>:/v1' + # "service": "identity", + # "name": "Identity Service", + # "endpoint": "grpc://>:" # }, -] - -# Internal Endpoint -INTERNAL_ENDPOINTS = [ - { - "service": "identity", - "name": "Identity Service", - "endpoint": "grpc://identity.spaceone.svc.cluster.local:50051/v1", - }, - { - "service": "secret", - "name": "Secret Service", - "endpoint": "grpc://secret.spaceone.svc.cluster.local:50051/v1", - }, - { - "service": "repository", - "name": "Repository Service", - "endpoint": "grpc://repository.spaceone.svc.cluster.local:50051/v1", - }, - { - "service": "plugin", - "name": "Plugin Service", - "endpoint": "grpc://plugin.spaceone.svc.cluster.local:50051/v1", - }, - { - "service": "config", - "name": "Config Service", - "endpoint": "grpc://config.spaceone.svc.cluster.local:50051/v1", - }, - { - "service": "inventory", - "name": "Inventory Service", - "endpoint": "grpc://inventory.spaceone.svc.cluster.local:50051/v1", - }, - { - "service": "monitoring", - "name": "Monitoring Service", - "endpoint": "grpc://monitoring.spaceone.svc.cluster.local:50051/v1", - }, - { - "service": "statistics", - "name": "Statistics Service", - "endpoint": "grpc://statistics.spaceone.svc.cluster.local:50051/v1", - }, + # { + # "service": "inventory", + # "name": "Inventory Service", + # "endpoint": "grpc+ssl://>:" + # } ] From f02fa413739cd869c379602eeba7b18dccc3a3ee Mon Sep 17 00:00:00 2001 From: Jongmin Kim Date: Mon, 20 Nov 2023 02:28:08 +0900 Subject: [PATCH 23/45] refactor: remove unused imports --- src/spaceone/identity/model/service_account_db.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/spaceone/identity/model/service_account_db.py b/src/spaceone/identity/model/service_account_db.py index e87f16a7..9bfb1a58 100644 --- a/src/spaceone/identity/model/service_account_db.py +++ b/src/spaceone/identity/model/service_account_db.py @@ -1,6 +1,5 @@ from mongoengine import * from spaceone.core.model.mongo_model import MongoModel -from spaceone.identity.model.project_model import Project class ServiceAccount(MongoModel): From 504c89f0badabf8fdd5f5539afc098dacc98a40e Mon Sep 17 00:00:00 2001 From: ImMin5 Date: Mon, 20 Nov 2023 17:22:16 +0900 Subject: [PATCH 24/45] feat: modify domain service (#73) Signed-off-by: ImMin5 --- src/spaceone/identity/conf/global_conf.py | 14 +- src/spaceone/identity/manager/__init__.py | 1 + .../identity/manager/domain_manager.py | 23 ++- src/spaceone/identity/manager/user_manager.py | 46 +++++- .../request.py} | 0 .../response.py} | 0 .../request.py} | 0 .../response.py} | 4 +- .../identity/model/domain/__init__.py | 0 .../database.py} | 2 +- .../{domain_request.py => domain/request.py} | 24 ++- .../identity/model/domain/response.py | 59 +++++++ .../identity/model/domain_response.py | 45 ------ .../identity/model/endpoint/__init__.py | 0 .../request.py} | 0 .../response.py} | 0 src/spaceone/identity/model/user/__init__.py | 0 .../{user_db_model.py => user/database.py} | 0 .../{user_request.py => user/request.py} | 0 .../{user_response.py => user/response.py} | 10 +- .../identity/model/workspace/__init__.py | 0 .../request.py} | 2 +- .../response.py} | 8 +- .../identity/service/api_key_service.py | 4 +- .../identity/service/authorization_service.py | 5 +- .../identity/service/domain_service.py | 140 ++++++++++++---- .../identity/service/endpoint_service.py | 21 ++- src/spaceone/identity/service/user_service.py | 52 +++++- .../identity/service/workspace_service.py | 149 +++++++++++++++--- 29 files changed, 461 insertions(+), 148 deletions(-) rename src/spaceone/identity/model/{api_key_request.py => api_key/request.py} (100%) rename src/spaceone/identity/model/{api_key_response.py => api_key/response.py} (100%) rename src/spaceone/identity/model/{authorization_request.py => authorization/request.py} (100%) rename src/spaceone/identity/model/{authorization_response.py => authorization/response.py} (66%) create mode 100644 src/spaceone/identity/model/domain/__init__.py rename src/spaceone/identity/model/{domain_db_model.py => domain/database.py} (97%) rename src/spaceone/identity/model/{domain_request.py => domain/request.py} (66%) create mode 100644 src/spaceone/identity/model/domain/response.py delete mode 100644 src/spaceone/identity/model/domain_response.py create mode 100644 src/spaceone/identity/model/endpoint/__init__.py rename src/spaceone/identity/model/{endpoint_request.py => endpoint/request.py} (100%) rename src/spaceone/identity/model/{endpoint_response.py => endpoint/response.py} (100%) create mode 100644 src/spaceone/identity/model/user/__init__.py rename src/spaceone/identity/model/{user_db_model.py => user/database.py} (100%) rename src/spaceone/identity/model/{user_request.py => user/request.py} (100%) rename src/spaceone/identity/model/{user_response.py => user/response.py} (73%) create mode 100644 src/spaceone/identity/model/workspace/__init__.py rename src/spaceone/identity/model/{workspace_request.py => workspace/request.py} (97%) rename src/spaceone/identity/model/{workspace_response.py => workspace/response.py} (72%) diff --git a/src/spaceone/identity/conf/global_conf.py b/src/spaceone/identity/conf/global_conf.py index d1416d39..510290eb 100644 --- a/src/spaceone/identity/conf/global_conf.py +++ b/src/spaceone/identity/conf/global_conf.py @@ -42,9 +42,7 @@ "refresh_ttl": 18, "refresh_once": False, }, - "mfa": { - "mfa_verify_code_timeout": 300 - }, + "mfa": {"mfa_verify_code_timeout": 300}, } # Handler Settings @@ -74,11 +72,11 @@ }, }, "SMTPConnector": { - "host": "smtp.mail.com", - "port": "1234", - "user": "cloudforet", - "password": "1234", - "from_email": "support@cloudforet.com", + # "host": "smtp.mail.com", + # "port": "1234", + # "user": "cloudforet", + # "password": "1234", + # "from_email": "support@cloudforet.com", }, } diff --git a/src/spaceone/identity/manager/__init__.py b/src/spaceone/identity/manager/__init__.py index e69de29b..78298c92 100644 --- a/src/spaceone/identity/manager/__init__.py +++ b/src/spaceone/identity/manager/__init__.py @@ -0,0 +1 @@ +from spaceone.identity.manager.mfa_manager.email_mfa_manger import EmailMFAManager diff --git a/src/spaceone/identity/manager/domain_manager.py b/src/spaceone/identity/manager/domain_manager.py index 78ad9566..b368398a 100644 --- a/src/spaceone/identity/manager/domain_manager.py +++ b/src/spaceone/identity/manager/domain_manager.py @@ -5,7 +5,7 @@ from spaceone.core.manager import BaseManager from spaceone.core.utils import datetime_to_iso8601 -from spaceone.identity.model.domain_db_model import Domain +from spaceone.identity.model.domain.database import Domain _LOGGER = logging.getLogger(__name__) @@ -13,9 +13,9 @@ class DomainManager(BaseManager): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.domain_model = Domain() + self.domain_model = Domain - def create_domain(self, params): + def create_domain(self, params: dict) -> Domain: def _rollback(vo): _LOGGER.info( f"[create_domain._rollback] Delete domain : {vo.name} ({vo.domain_id})" @@ -27,38 +27,35 @@ def _rollback(vo): return domain_vo - def update_domain(self, params): + def update_domain_by_vo(self, params: dict, domain_vo: Domain) -> Domain: def _rollback(old_data): _LOGGER.info( f'[update_domain._rollback] Revert Data : {old_data["name"]} ({old_data["domain_id"]})' ) domain_vo.update(old_data) - domain_vo: Domain = self.get_domain(params["domain_id"]) - self.transaction.add_rollback(_rollback, domain_vo.to_dict()) + self.transaction.add_rollback(_rollback, domain_vo.to_dict()) return domain_vo.update(params) - def delete_domain(self, domain_id): - domain_vo: Domain = self.get_domain(domain_id) + @staticmethod + def delete_domain_by_vo(domain_vo: Domain) -> None: domain_vo.delete() - cache.delete_pattern(f"domain-state:{domain_id}") + cache.delete_pattern(f"domain-state:{domain_vo.domain_id}") - def enable_domain(self, domain_id): + def enable_domain(self, domain_vo: Domain) -> Domain: def _rollback(old_data): _LOGGER.info( f'[enable_domain._rollback] Revert Data : {old_data["name"]} ({old_data["domain_id"]})' ) domain_vo.update(old_data) - domain_vo: Domain = self.get_domain(domain_id) - if domain_vo.state != "ENABLED": self.transaction.add_rollback(_rollback, domain_vo.to_dict()) domain_vo.update({"state": "ENABLED"}) - cache.delete_pattern(f"domain-state:{domain_id}") + cache.delete_pattern(f"domain-state:{domain_vo.domain_id}") return domain_vo diff --git a/src/spaceone/identity/manager/user_manager.py b/src/spaceone/identity/manager/user_manager.py index f9c1025c..b59e6254 100644 --- a/src/spaceone/identity/manager/user_manager.py +++ b/src/spaceone/identity/manager/user_manager.py @@ -5,8 +5,8 @@ from spaceone.identity.lib.cipher import PasswordCipher from spaceone.identity.error.error_user import * -from spaceone.identity.model.domain_db_model import Domain -from spaceone.identity.model.user_db_model import User +from spaceone.identity.model.domain.database import Domain +from spaceone.identity.model.user.database import User _LOGGER = logging.getLogger(__name__) @@ -14,14 +14,14 @@ class UserManager(BaseManager): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.user_model = User() + self.user_model = User - def create_user(self, params, domain_vo: Domain, is_first_login_user=False): - def _rollback(user_vo): + def create_user(self, params, is_first_login_user=False): + def _rollback(vo: User): _LOGGER.info( - f"[create_user._rollback] Delete user : {user_vo.name} ({user_vo.user_id})" + f"[create_user._rollback] Delete user : {vo.name} ({vo.user_id})" ) - user_vo.delete() + vo.delete() params["state"] = params.get("state", "ENABLED") @@ -64,6 +64,38 @@ def _rollback(user_vo): return user_vo + def update_user_by_vo(self, params, user_vo): + def _rollback(old_data): + _LOGGER.info( + f'[update_user._rollback] Revert Data : {old_data["name"], ({old_data["user_id"]})}' + ) + user_vo.update(old_data) + + required_actions = list(user_vo.required_actions) + is_change_required_actions = False + + if new_password := params.get("password"): + if PasswordCipher().checkpw(new_password, user_vo.password): + raise ERROR_PASSWORD_NOT_CHANGED(user_id=user_vo.user_id) + + self._check_password_format(params["password"]) + hashed_pw = PasswordCipher().hashpw(params["password"]) + params["password"] = hashed_pw + + if "UPDATE_PASSWORD" in required_actions: + required_actions.remove("UPDATE_PASSWORD") + is_change_required_actions = True + + if is_change_required_actions: + params["required_actions"] = required_actions + + self.transaction.add_rollback(_rollback, user_vo.to_dict()) + + return user_vo.update(params) + + def get_user(self, user_id, domain_id): + return self.user_model.get(user_id=user_id, domain_id=domain_id) + @staticmethod def _check_user_id_format(user_id): rule = r"[^@]+@[^@]+\.[^@]+" diff --git a/src/spaceone/identity/model/api_key_request.py b/src/spaceone/identity/model/api_key/request.py similarity index 100% rename from src/spaceone/identity/model/api_key_request.py rename to src/spaceone/identity/model/api_key/request.py diff --git a/src/spaceone/identity/model/api_key_response.py b/src/spaceone/identity/model/api_key/response.py similarity index 100% rename from src/spaceone/identity/model/api_key_response.py rename to src/spaceone/identity/model/api_key/response.py diff --git a/src/spaceone/identity/model/authorization_request.py b/src/spaceone/identity/model/authorization/request.py similarity index 100% rename from src/spaceone/identity/model/authorization_request.py rename to src/spaceone/identity/model/authorization/request.py diff --git a/src/spaceone/identity/model/authorization_response.py b/src/spaceone/identity/model/authorization/response.py similarity index 66% rename from src/spaceone/identity/model/authorization_response.py rename to src/spaceone/identity/model/authorization/response.py index 206ae239..25d94413 100644 --- a/src/spaceone/identity/model/authorization_response.py +++ b/src/spaceone/identity/model/authorization/response.py @@ -1,7 +1,7 @@ -from typing import Union, Literal, List +from typing import Union, List from pydantic import BaseModel -from spaceone.identity.model.authorization_request import RoleType +from spaceone.identity.model.authorization.request import RoleType class AuthorizationResponse(BaseModel): diff --git a/src/spaceone/identity/model/domain/__init__.py b/src/spaceone/identity/model/domain/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/spaceone/identity/model/domain_db_model.py b/src/spaceone/identity/model/domain/database.py similarity index 97% rename from src/spaceone/identity/model/domain_db_model.py rename to src/spaceone/identity/model/domain/database.py index 45c77b31..40c7b7b3 100644 --- a/src/spaceone/identity/model/domain_db_model.py +++ b/src/spaceone/identity/model/domain/database.py @@ -8,7 +8,7 @@ class Domain(MongoModel): domain_id = StringField(max_length=40, generate_id="domain", unique=True) name = StringField(max_length=255) state = StringField(max_length=20, default="ENABLED") - tags = DictField() + tags = DictField(default=None) created_at = DateTimeField(auto_now_add=True) deleted_at = DateTimeField(default=None, null=True) diff --git a/src/spaceone/identity/model/domain_request.py b/src/spaceone/identity/model/domain/request.py similarity index 66% rename from src/spaceone/identity/model/domain_request.py rename to src/spaceone/identity/model/domain/request.py index 0de92da2..2d19c0d9 100644 --- a/src/spaceone/identity/model/domain_request.py +++ b/src/spaceone/identity/model/domain/request.py @@ -5,8 +5,12 @@ __all__ = [ "DomainCreateRequest", "DomainUpdateRequest", - "DomainRequest", + "DomainDeleteRequest", + "DomainEnableRequest", + "DomainDisableRequest", + "DomainGetRequest", "DomainGetMetadataRequest", + "DomainGetPublicKeyRequest", "DomainSearchQueryRequest", "DomainStatQueryRequest", "State", @@ -26,7 +30,19 @@ class DomainUpdateRequest(BaseModel): tags: Union[dict, None] = {} -class DomainRequest(BaseModel): +class DomainDeleteRequest(BaseModel): + domain_id: str + + +class DomainEnableRequest(BaseModel): + domain_id: str + + +class DomainDisableRequest(BaseModel): + domain_id: str + + +class DomainGetRequest(BaseModel): domain_id: str @@ -34,6 +50,10 @@ class DomainGetMetadataRequest(BaseModel): name: str +class DomainGetPublicKeyRequest(BaseModel): + domain_id: str + + class DomainSearchQueryRequest(BaseModel): query: Union[dict, None] = None domain_id: Union[str, None] = None diff --git a/src/spaceone/identity/model/domain/response.py b/src/spaceone/identity/model/domain/response.py new file mode 100644 index 00000000..391be3f8 --- /dev/null +++ b/src/spaceone/identity/model/domain/response.py @@ -0,0 +1,59 @@ +from datetime import datetime +from typing import List, Union, Literal +from pydantic import BaseModel + +from spaceone.core import utils + +from spaceone.identity.model.domain.request import State + +__all__ = [ + "DomainResponse", + "DomainsResponse", + "DomainMetadataResponse", + "DomainSecretResponse", +] + +ExternalAuthState = Literal["ENABLED", "DISABLED"] + + +class DomainResponse(BaseModel): + domain_id: Union[str, None] = None + name: Union[str, None] = None + state: Union[State, None] = None + tags: Union[dict, None] = {} + created_at: Union[datetime, None] = None + + def dict(self, *args, **kwargs): + data = super().dict(*args, **kwargs) + data["created_at"] = utils.datetime_to_iso8601(data["created_at"]) + return data + + +class DomainMetadataResponse(BaseModel): + domain_id: Union[str, None] = None + name: Union[str, None] = None + external_auth_state: ExternalAuthState + metadata_info: Union[dict, None] = None + + def dict(self, *args, **kwargs): + data = super().dict(*args, **kwargs) + return data + + +class DomainSecretResponse(BaseModel): + domain_id: Union[str, None] = None + key: Union[str, None] = None + key_type: Union[str, None] = None + + def dict(self, *args, **kwargs): + data = super().dict(*args, **kwargs) + return data + + +class DomainsResponse(BaseModel): + results: List[DomainResponse] + total_count: int + + def dict(self, *args, **kwargs): + data = super().dict(*args, **kwargs) + return data diff --git a/src/spaceone/identity/model/domain_response.py b/src/spaceone/identity/model/domain_response.py deleted file mode 100644 index 2e301ba0..00000000 --- a/src/spaceone/identity/model/domain_response.py +++ /dev/null @@ -1,45 +0,0 @@ -from typing import List, Union, Literal -from pydantic import BaseModel, validator, Extra - -from spaceone.core.utils import datetime_to_iso8601 - -from spaceone.identity.model.domain_request import State - -__all__ = [ - "DomainResponse", - "DomainsResponse", - "DomainMetadataResponse", - "DomainSecretResponse", -] - -ExternalAuthState = Literal["ENABLED", "DISABLED"] - - -class DomainResponse(BaseModel, extra=Extra.ignore, orm_mode=True): - domain_id: Union[str, None] = None - name: Union[str, None] = None - state: Union[State, None] = None - tags: Union[dict, None] = {} - created_at: Union[str, None] = None - - _convert_datetime = validator("created_at", pre=True, allow_reuse=True)( - datetime_to_iso8601 - ) - - -class DomainMetadataResponse(BaseModel, orm_mode=True): - domain_id: Union[str, None] = None - name: Union[str, None] = None - external_auth_state: ExternalAuthState - metadata_info: Union[dict, None] = None - - -class DomainSecretResponse(BaseModel, orm_mode=True): - domain_id: Union[str, None] = None - key: Union[str, None] = None - key_type: Union[str, None] = None - - -class DomainsResponse(BaseModel, orm_mode=True): - results: List[DomainResponse] - total_count: int diff --git a/src/spaceone/identity/model/endpoint/__init__.py b/src/spaceone/identity/model/endpoint/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/spaceone/identity/model/endpoint_request.py b/src/spaceone/identity/model/endpoint/request.py similarity index 100% rename from src/spaceone/identity/model/endpoint_request.py rename to src/spaceone/identity/model/endpoint/request.py diff --git a/src/spaceone/identity/model/endpoint_response.py b/src/spaceone/identity/model/endpoint/response.py similarity index 100% rename from src/spaceone/identity/model/endpoint_response.py rename to src/spaceone/identity/model/endpoint/response.py diff --git a/src/spaceone/identity/model/user/__init__.py b/src/spaceone/identity/model/user/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/spaceone/identity/model/user_db_model.py b/src/spaceone/identity/model/user/database.py similarity index 100% rename from src/spaceone/identity/model/user_db_model.py rename to src/spaceone/identity/model/user/database.py diff --git a/src/spaceone/identity/model/user_request.py b/src/spaceone/identity/model/user/request.py similarity index 100% rename from src/spaceone/identity/model/user_request.py rename to src/spaceone/identity/model/user/request.py diff --git a/src/spaceone/identity/model/user_response.py b/src/spaceone/identity/model/user/response.py similarity index 73% rename from src/spaceone/identity/model/user_response.py rename to src/spaceone/identity/model/user/response.py index 9d31ec66..c849bca9 100644 --- a/src/spaceone/identity/model/user_response.py +++ b/src/spaceone/identity/model/user/response.py @@ -2,7 +2,9 @@ from typing import Union, List, Literal from pydantic import BaseModel -from spaceone.identity.model.user_request import State, UserType, AuthType +from spaceone.core import utils + +from spaceone.identity.model.user.request import State, UserType, AuthType __all__ = ["UserResponse", "UsersResponse"] @@ -28,6 +30,12 @@ class UserResponse(BaseModel): created_at: Union[datetime, None] = None last_accessed_at: Union[datetime, None] = None + def dict(self, *args, **kwargs): + data = super().dict(*args, **kwargs) + data["created_at"] = utils.datetime_to_iso8601(data["created_at"]) + data["last_accessed_at"] = utils.datetime_to_iso8601(data["last_accessed_at"]) + return data + class UsersResponse(BaseModel): results: List[UserResponse] diff --git a/src/spaceone/identity/model/workspace/__init__.py b/src/spaceone/identity/model/workspace/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/spaceone/identity/model/workspace_request.py b/src/spaceone/identity/model/workspace/request.py similarity index 97% rename from src/spaceone/identity/model/workspace_request.py rename to src/spaceone/identity/model/workspace/request.py index 148b19fb..96b796e2 100644 --- a/src/spaceone/identity/model/workspace_request.py +++ b/src/spaceone/identity/model/workspace/request.py @@ -23,7 +23,7 @@ class WorkspaceCreateRequest(BaseModel): class WorkspaceUpdateRequest(BaseModel): workspace_id: str name: Union[str, None] = None - tags: Union[dict, None] = {} + tags: Union[dict, None] = None domain_id: str diff --git a/src/spaceone/identity/model/workspace_response.py b/src/spaceone/identity/model/workspace/response.py similarity index 72% rename from src/spaceone/identity/model/workspace_response.py rename to src/spaceone/identity/model/workspace/response.py index 678d5205..b957d120 100644 --- a/src/spaceone/identity/model/workspace_response.py +++ b/src/spaceone/identity/model/workspace/response.py @@ -1,8 +1,9 @@ from datetime import datetime from typing import Union, List, Literal -from enum import Enum from pydantic import BaseModel +from spaceone.core import utils + __all__ = ["WorkspaceResponse", "WorkspacesResponse"] State = Literal["ENABLED", "DISABLED"] @@ -16,6 +17,11 @@ class WorkspaceResponse(BaseModel): domain_id: Union[str, None] = None created_at: Union[datetime, None] = None + def dict(self, *args, **kwargs): + data = super().dict(*args, **kwargs) + data["created_at"] = utils.datetime_to_iso8601(data["created_at"]) + return data + class WorkspacesResponse(BaseModel): results: List[WorkspaceResponse] diff --git a/src/spaceone/identity/service/api_key_service.py b/src/spaceone/identity/service/api_key_service.py index f04197c9..5ce0f167 100644 --- a/src/spaceone/identity/service/api_key_service.py +++ b/src/spaceone/identity/service/api_key_service.py @@ -1,8 +1,8 @@ import logging from typing import Union from spaceone.core.service import BaseService, transaction, convert_model -from spaceone.identity.model.api_key_request import * -from spaceone.identity.model.api_key_response import * +from spaceone.identity.model.api_key.request import * +from spaceone.identity.model.api_key.response import * _LOGGER = logging.getLogger(__name__) diff --git a/src/spaceone/identity/service/authorization_service.py b/src/spaceone/identity/service/authorization_service.py index d02dde42..a565a375 100644 --- a/src/spaceone/identity/service/authorization_service.py +++ b/src/spaceone/identity/service/authorization_service.py @@ -1,8 +1,7 @@ import logging -from typing import Union from spaceone.core.service import BaseService, transaction, convert_model -from spaceone.identity.model.authorization_request import * -from spaceone.identity.model.authorization_response import * +from spaceone.identity.model.authorization.request import * +from spaceone.identity.model.authorization.response import * _LOGGER = logging.getLogger(__name__) diff --git a/src/spaceone/identity/service/domain_service.py b/src/spaceone/identity/service/domain_service.py index d9a14a4f..ca579036 100644 --- a/src/spaceone/identity/service/domain_service.py +++ b/src/spaceone/identity/service/domain_service.py @@ -1,5 +1,5 @@ import logging -from typing import Union, List +from typing import Union from spaceone.core.service import ( BaseService, @@ -11,10 +11,8 @@ from spaceone.identity.manager.domain_manager import DomainManager from spaceone.identity.manager.user_manager import UserManager - -# from spaceone.identity.manager.role_manager import RoleManager -from spaceone.identity.model.domain_request import * -from spaceone.identity.model.domain_response import * +from spaceone.identity.model.domain.request import * +from spaceone.identity.model.domain.response import * _LOGGER = logging.getLogger(__name__) @@ -29,10 +27,15 @@ def __init__(self, *args, **kwargs): @transaction @convert_model def create(self, params: DomainCreateRequest) -> Union[DomainResponse, dict]: - """ + """Create Domain Args: - :param params: - :return: + params (dict): { + 'name': 'str', + 'admin': 'dict', + 'tags': 'dict' + } + Returns: + DomainResponse: """ domain_vo = self.domain_mgr.create_domain(params.dict()) @@ -42,57 +45,130 @@ def create(self, params: DomainCreateRequest) -> Union[DomainResponse, dict]: admin["domain_id"] = domain_vo.domain_id # create admin user with policy and role - self.user_mgr.create_user(params=admin) - return DomainResponse.from_orm(domain_vo) + self.user_mgr.create_user(admin) + return DomainResponse(**domain_vo.to_dict()) @transaction @convert_model def update(self, params: DomainUpdateRequest) -> Union[DomainResponse, dict]: - domain_vo = self.domain_mgr.update_domain(params.dict()) - return DomainResponse.from_orm(domain_vo) + """Update domain + Args: + params (dict): { + 'domain_id': 'str', + 'tags': 'dict' + } + Returns: + DomainResponse: + """ + domain_vo = self.domain_mgr.get_domain(params.domain_id) + domain_vo = self.domain_mgr.update_domain_by_vo(params.dict(), domain_vo) + return DomainResponse(**domain_vo.to_dict()) @transaction @convert_model - def delete(self, params: DomainRequest) -> None: - self.domain_mgr.delete_domain(params.dict().get("domain_id")) + def delete(self, params: DomainDeleteRequest) -> None: + """Delete Domain + Args: + params (dict): { + 'domain_id': 'str' + } + Returns: + Empty: + """ + domain_vo = self.domain_mgr.get_domain(params.domain_id) + self.domain_mgr.delete_domain_by_vo(domain_vo) @transaction @convert_model - def enable(self, params: DomainRequest) -> Union[DomainResponse, dict]: - domain_vo = self.domain_mgr.enable_domain(params.dict().get("domain_id")) - return DomainResponse.from_orm(domain_vo) + def enable(self, params: DomainEnableRequest) -> Union[DomainResponse, dict]: + """Enable Domain + Args: + params (dict): { + 'domain_id': 'str' + } + Returns: + DomainResponse: + """ + domain_vo = self.domain_mgr.get_domain(params.domain_id) + domain_vo = self.domain_mgr.enable_domain(domain_vo) + return DomainResponse(**domain_vo.to_dict()) @transaction @convert_model - def disable(self, params: DomainRequest) -> Union[DomainResponse, dict]: - domain_vo = self.domain_mgr.disable_domain(params.dict().get("domain_id")) - return DomainResponse.from_orm(domain_vo) + def disable(self, params: DomainDisableRequest) -> Union[DomainResponse, dict]: + """Disable Domain + Args: + params (dict): { + 'domain_id': 'str' + } + Returns: + DomainResponse: + """ + domain_vo = self.domain_mgr.disable_domain(params.domain_id) + return DomainResponse(**domain_vo.to_dict()) @transaction @convert_model - def get(self, params: DomainRequest) -> Union[DomainResponse, dict]: - domain_vo = self.domain_mgr.get_domain(params.dict().get("domain_id")) - return DomainResponse.from_orm(domain_vo) + def get(self, params: DomainGetRequest) -> Union[DomainResponse, dict]: + """Get Domain + Args: + params (dict): { + 'domain_id': 'str' + } + Returns: + DomainResponse: + """ + + domain_vo = self.domain_mgr.get_domain(params.domain_id) + return DomainResponse(**domain_vo.to_dict()) @transaction @convert_model def get_metadata( self, params: DomainGetMetadataRequest ) -> Union[DomainMetadataResponse, dict]: + """GetMetadata domain + Args: + params (dict): { + 'name': 'str' + } + Returns: + DomainMetadataResponse: + """ return {} @transaction @convert_model def get_public_key( - self, params: DomainRequest + self, params: DomainGetPublicKeyRequest ) -> Union[DomainSecretResponse, dict]: + """GetPublicKey domain + Args: + params (dict): { + 'domain_id': 'str' + } + Returns: + DomainSecretResponse: + """ return {} @transaction - # @append_query_filter(["domain_id", "name"]) - # @append_keyword_filter(["domain_id", "name"]) + @append_query_filter(["domain_id", "name"]) + @append_keyword_filter(["domain_id", "name"]) @convert_model def list(self, params: DomainSearchQueryRequest) -> Union[DomainsResponse, dict]: + """List domain + Args: + params (dict): { + 'query': 'dict', + 'domain_id': 'str', + 'name': 'str', + 'state': 'str' + } + Returns: + DomainsResponse: + """ + query = params.dict().get("query", {}) # todo : remove when spacectl template is modified @@ -100,12 +176,20 @@ def list(self, params: DomainSearchQueryRequest) -> Union[DomainsResponse, dict] query["only"] = only domain_vos, total_count = self.domain_mgr.list_domains(query) - - return DomainsResponse(results=list(domain_vos), total_count=total_count) + domains_info = [domain_vo.to_dict() for domain_vo in domain_vos] + return DomainsResponse(results=domains_info, total_count=total_count) @transaction @convert_model def stat(self, params: DomainStatQueryRequest) -> dict: + """Stat domain + Args: + params (dict): { + 'query': 'dict' + } + Returns: + dict: + """ query = params.dict().get("query", {}) return self.domain_mgr.stat_domains(query) diff --git a/src/spaceone/identity/service/endpoint_service.py b/src/spaceone/identity/service/endpoint_service.py index 6e5d798e..400da8e9 100644 --- a/src/spaceone/identity/service/endpoint_service.py +++ b/src/spaceone/identity/service/endpoint_service.py @@ -1,21 +1,22 @@ import logging from typing import Union -from spaceone.core.service import BaseService, transaction, convert_model, append_query_filter, append_keyword_filter -from spaceone.identity.model.endpoint_request import * -from spaceone.identity.model.endpoint_response import * +from spaceone.core.service import BaseService, transaction, convert_model +from spaceone.identity.model.endpoint.request import * +from spaceone.identity.model.endpoint.response import * from spaceone.identity.manager.endpoint_manager import EndpointManager - _LOGGER = logging.getLogger(__name__) class EndpointService(BaseService): - @transaction(append_meta={'authorization.scope': 'PUBLIC'}) + @transaction(append_meta={"authorization.scope": "PUBLIC"}) # @append_query_filter(['service']) # @append_keyword_filter(['service']) @convert_model - def list(self, params: EndpointSearchQueryRequest) -> Union[EndpointsResponse, dict]: - """ list endpoints of service + def list( + self, params: EndpointSearchQueryRequest + ) -> Union[EndpointsResponse, dict]: + """list endpoints of service Args: params (EndpointSearchQueryRequest): { @@ -30,8 +31,4 @@ def list(self, params: EndpointSearchQueryRequest) -> Union[EndpointsResponse, d endpoint_mgr: EndpointManager = EndpointManager() endpoints_info, total_count = endpoint_mgr.list_endpoints(params.service) - return EndpointsResponse( - results=endpoints_info, - total_count=total_count - ) - + return EndpointsResponse(results=endpoints_info, total_count=total_count) diff --git a/src/spaceone/identity/service/user_service.py b/src/spaceone/identity/service/user_service.py index 645f5df5..430c7662 100644 --- a/src/spaceone/identity/service/user_service.py +++ b/src/spaceone/identity/service/user_service.py @@ -1,13 +1,22 @@ import logging from typing import Union + from spaceone.core.service import BaseService, transaction, convert_model -from spaceone.identity.model.user_request import * -from spaceone.identity.model.user_response import * + +from spaceone.identity.error.error_mfa import * +from spaceone.identity.manager.user_manager import UserManager +from spaceone.identity.manager.mfa_manager import MFAManager +from spaceone.identity.model.user.request import * +from spaceone.identity.model.user.response import * _LOGGER = logging.getLogger(__name__) class UserService(BaseService): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.user_mgr = UserManager() + @transaction @convert_model def create(self, params: UserCreateRequest) -> Union[UserResponse, dict]: @@ -45,7 +54,44 @@ def set_required_actions( @transaction @convert_model def enable_mfa(self, params: UserEnableMFARequest) -> Union[UserResponse, dict]: - return {} + """Enable MFA + + Args: + params (UserEnableMFARequest): { + 'user_id': 'str', + 'mfa_type': 'str', + 'options': 'dict', + 'domain_id': 'str' + } + Returns: + UserResponse: + """ + user_id = params.user_id + mfa_type = params.mfa_type + options = params.options + domain_id = params.domain_id + + user_vo = self.user_mgr.get_user(user_id, domain_id) + user_mfa = user_vo.mfa.to_dict() if user_vo.mfa else {} + + if not options: + raise ERROR_REQUIRED_PARAMETER(key="options") + + if user_mfa.get("state", "DISABLED") == "ENABLED": + raise ERROR_MFA_ALREADY_ENABLED(user_id=user_id) + + mfa_manager = MFAManager.get_manager_by_mfa_type(mfa_type) + + if mfa_type == "EMAIL": + user_mfa["mfa_type"] = mfa_type + user_mfa["options"] = options + user_mfa["state"] = user_mfa.get("state", "DISABLED") + mfa_manager.enable_mfa(user_id, domain_id, user_mfa, user_vo.language) + user_vo = self.user_mgr.update_user_by_vo({"mfa": user_mfa}, user_vo) + else: + raise ERROR_NOT_SUPPORTED_MFA_TYPE(support_mfa_types=["EMAIL"]) + + return UserResponse(**user_vo.to_dict()) @transaction @convert_model diff --git a/src/spaceone/identity/service/workspace_service.py b/src/spaceone/identity/service/workspace_service.py index ffff6e1a..f5ace167 100644 --- a/src/spaceone/identity/service/workspace_service.py +++ b/src/spaceone/identity/service/workspace_service.py @@ -1,53 +1,164 @@ import logging -from typing import Generator, Union -from spaceone.core.service import BaseService, transaction, convert_model -from spaceone.identity.model.workspace_request import * -from spaceone.identity.model.workspace_response import * +from typing import Union + +from spaceone.core.service import ( + BaseService, + transaction, + convert_model, + append_query_filter, + append_keyword_filter, +) + +from spaceone.identity.manager.workspace_manager import WorkspaceManager +from spaceone.identity.model.workspace.request import * +from spaceone.identity.model.workspace.response import * _LOGGER = logging.getLogger(__name__) class WorkspaceService(BaseService): - @transaction + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.workspace_mgr = WorkspaceManager() + + @transaction(append_meta={"authorization.scope": "WORKSPACE"}) @convert_model def create(self, params: WorkspaceCreateRequest) -> Union[WorkspaceResponse, dict]: - return {} + """Create workspace + Args: + params (dict): { + 'name': 'str', + 'tags': 'dict', + 'domain_id': 'str' + } + Returns: + WorkspaceResponse: + """ - @transaction + workspace_vo = self.workspace_mgr.create_workspace(params.dict()) + + return WorkspaceResponse(**workspace_vo.to_dict()) + + @transaction(append_meta={"authorization.scope": "WORKSPACE"}) @convert_model def update(self, params: WorkspaceUpdateRequest) -> Union[WorkspaceResponse, dict]: - return {} + """Update workspace + Args: + params (dict): { + 'workspace_id': 'str', + 'name': 'str', + 'tags': 'dict' + 'domain_id': 'str' + } + Returns: + WorkspaceResponse: + """ + workspace_vo = self.workspace_mgr.get_workspace( + params.workspace_id, params.domain_id + ) + workspace_vo = self.workspace_mgr.update_workspace_by_vo( + params.dict(), workspace_vo + ) + return WorkspaceResponse(**workspace_vo.to_dict()) - @transaction + @transaction(append_meta={"authorization.scope": "WORKSPACE"}) @convert_model def delete(self, params: WorkspaceDeleteRequest) -> None: - pass + """Delete workspace + Args: + params (dict): { + 'workspace_id': 'str', + 'domain_id': 'str' + } + Returns: + None + """ + workspace_vo = self.workspace_mgr.get_workspace( + params.workspace_id, params.domain_id + ) + self.workspace_mgr.delete_workspace_by_vo(workspace_vo) - @transaction + @transaction(append_meta={"authorization.scope": "WORKSPACE"}) @convert_model def enable(self, params: WorkspaceEnableRequest) -> Union[WorkspaceResponse, dict]: - return {} + """Enable workspace + Args: + params (dict): { + 'workspace_id': 'str', + 'domain_id': 'str' + } + Returns: + WorkspaceResponse: + """ + workspace_vo = self.workspace_mgr.get_workspace( + params.workspace_id, params.domain_id + ) + workspace_vo = self.workspace_mgr.enable_workspace(workspace_vo) + return WorkspaceResponse(**workspace_vo.to_dict()) - @transaction + @transaction(append_meta={"authorization.scope": "WORKSPACE"}) @convert_model def disable( self, params: WorkspaceDisableRequest ) -> Union[WorkspaceResponse, dict]: - return {} + """Disable workspace + Args: + params (dict): { + 'workspace_id': 'str', + 'domain_id': 'str' + } + Returns: + WorkspaceResponse: + """ + + workspace_vo = self.workspace_mgr.get_workspace( + params.workspace_id, params.domain_id + ) + workspace_vo = self.workspace_mgr.disable_workspace(workspace_vo) + return WorkspaceResponse(**workspace_vo.to_dict()) - @transaction + @transaction(append_meta={"authorization.scope": "WORKSPACE_READ"}) @convert_model def get(self, params: WorkspaceGetRequest) -> Union[WorkspaceResponse, dict]: - return {} + """Get workspace + Args: + params (dict): { + 'workspace_id': 'str', + 'domain_id': 'str' + } + Returns: + WorkspaceResponse: + """ + workspace_vo = self.workspace_mgr.get_workspace( + params.workspace_id, params.domain_id + ) + return WorkspaceResponse(**workspace_vo.to_dict()) - @transaction + @transaction(append_meta={"authorization.scope": "WORKSPACE_READ"}) + @append_query_filter(["workspace_id", "name", "domain_id"]) + @append_keyword_filter(["workspace_id", "name"]) @convert_model def list( self, params: WorkspaceSearchQueryRequest ) -> Union[WorkspacesResponse, dict]: - return {} + """List workspace + Args: + params (dict): { + 'query': 'dict', + 'name': 'str', + 'workspace_id': 'str', + 'domain_id': 'str' #required + } + Returns: + WorkspacesResponse: + """ + query = params.query or {} + + workspace_vos, total_count = self.workspace_mgr.list_workspaces(query) + workspaces_info = [workspace_vo.to_dict() for workspace_vo in workspace_vos] + return WorkspacesResponse(results=workspaces_info, total_count=total_count) - @transaction + @transaction(append_meta={"authorization.scope": "WORKSPACE_READ"}) @convert_model def stat(self, params: WorkspaceStatQueryRequest) -> dict: return {} From 2747e2ea19bbb15b4e097795ab4d0224e62ec1f7 Mon Sep 17 00:00:00 2001 From: ImMin5 Date: Mon, 20 Nov 2023 17:59:11 +0900 Subject: [PATCH 25/45] feat: add workspace service, manager and model (#73) Signed-off-by: ImMin5 --- .../identity/manager/workspace_manager.py | 91 +++++++++++++++++++ .../identity/model/workspace/database.py | 43 +++++++++ .../identity/service/workspace_service.py | 26 +++--- 3 files changed, 147 insertions(+), 13 deletions(-) create mode 100644 src/spaceone/identity/manager/workspace_manager.py create mode 100644 src/spaceone/identity/model/workspace/database.py diff --git a/src/spaceone/identity/manager/workspace_manager.py b/src/spaceone/identity/manager/workspace_manager.py new file mode 100644 index 00000000..2f91e1e4 --- /dev/null +++ b/src/spaceone/identity/manager/workspace_manager.py @@ -0,0 +1,91 @@ +import logging +from typing import Tuple + +from spaceone.core import cache +from spaceone.core.manager import BaseManager + +from spaceone.identity.model.workspace.database import Workspace + +_LOGGER = logging.getLogger(__name__) + + +class WorkspaceManager(BaseManager): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.workspace_model = Workspace + + def create_workspace(self, params: dict) -> Workspace: + def _rollback(vo: Workspace): + _LOGGER.info( + f"[create_workspace._rollback] Delete workspace : {vo.name} ({vo.workspace_id}) ({vo.domain_id})" + ) + vo.delete() + + workspace_vo = self.workspace_model.create(params) + self.transaction.add_rollback(_rollback, workspace_vo) + + return workspace_vo + + def update_workspace_by_vo( + self, params: dict, workspace_vo: Workspace + ) -> Workspace: + def _rollback(old_data): + _LOGGER.info( + f'[update_workspace._rollback] Revert Data : {old_data["name"]} ({old_data["workspace_id"]})' + ) + workspace_vo.update(old_data) + + workspace_vo: Workspace = self.get_workspace( + params["workspace_id"], params["domain_id"] + ) + self.transaction.add_rollback(_rollback, workspace_vo.to_dict()) + + return workspace_vo.update(params) + + @staticmethod + def delete_workspace_by_vo(workspace_vo: Workspace) -> None: + workspace_vo.delete() + + cache.delete_pattern( + f"workspace-state:{workspace_vo.domain_id}:{workspace_vo.workspace_id}" + ) + + def enable_workspace(self, workspace_vo: Workspace) -> Workspace: + def _rollback(old_data): + _LOGGER.info( + f'[enable_workspace._rollback] Revert Data : {old_data["name"]} ({old_data["workspace_id"]})' + ) + workspace_vo.update(old_data) + + if workspace_vo.state != "ENABLED": + self.transaction.add_rollback(_rollback, workspace_vo.to_dict()) + workspace_vo.update({"state": "ENABLED"}) + + cache.delete_pattern( + f"workspace-state:{workspace_vo.domain_id}:{workspace_vo.workspace_id}" + ) + + return workspace_vo + + def disable_workspace(self, workspace_vo: Workspace) -> Workspace: + def _rollback(old_data): + _LOGGER.info( + f'[disable_workspace._rollback] Revert Data : {old_data["name"]} ({old_data["workspace_id"]})' + ) + workspace_vo.update(old_data) + + if workspace_vo.state != "DISABLED": + self.transaction.add_rollback(_rollback, workspace_vo.to_dict()) + workspace_vo.update({"state": "DISABLED"}) + + cache.delete_pattern( + f"workspace-state:{workspace_vo.domain_id}:{workspace_vo.workspace_id}" + ) + + return workspace_vo + + def get_workspace(self, workspace_id, domain_id) -> Workspace: + return self.workspace_model.get(workspace_id=workspace_id, domain_id=domain_id) + + def list_workspaces(self, query: dict) -> Tuple[list, int]: + return self.workspace_model.query(**query) diff --git a/src/spaceone/identity/model/workspace/database.py b/src/spaceone/identity/model/workspace/database.py new file mode 100644 index 00000000..585b12ce --- /dev/null +++ b/src/spaceone/identity/model/workspace/database.py @@ -0,0 +1,43 @@ +from mongoengine import * +from spaceone.core.error import * +from spaceone.core.model.mongo_model import MongoModel + + +class Workspace(MongoModel): + workspace_id = StringField(max_length=40, generate_id="workspace", unique=True) + name = StringField(max_length=255) + state = StringField(max_length=20, default="ENABLED") + tags = DictField(default=None) + domain_id = StringField(max_length=40) + created_at = DateTimeField(auto_now_add=True) + + meta = { + "updatable_fields": ["name", "state", "tags"], + "minimal_fields": [ + "workspace_id", + "name", + "state", + ], + "ordering": ["name"], + "indexes": [ + "state", + ], + } + + @classmethod + def create(cls, data): + workspace_vos = cls.filter(name=data["name"]) + if workspace_vos.count() > 0: + raise ERROR_NOT_UNIQUE(key="name", value=data["name"]) + + return super().create(data) + + def update(self, data): + if "name" in data: + workspace_vos = self.filter( + name=data["name"], workspace_id__ne=self.workspace_id + ) + if workspace_vos.count() > 0: + raise ERROR_NOT_UNIQUE(key="name", value=data["name"]) + + return super().update(data) diff --git a/src/spaceone/identity/service/workspace_service.py b/src/spaceone/identity/service/workspace_service.py index f5ace167..dde815ea 100644 --- a/src/spaceone/identity/service/workspace_service.py +++ b/src/spaceone/identity/service/workspace_service.py @@ -27,9 +27,9 @@ def create(self, params: WorkspaceCreateRequest) -> Union[WorkspaceResponse, dic """Create workspace Args: params (dict): { - 'name': 'str', - 'tags': 'dict', - 'domain_id': 'str' + 'name': 'str', # required + 'tags': 'dict', # required + 'domain_id': 'str' # required } Returns: WorkspaceResponse: @@ -45,10 +45,10 @@ def update(self, params: WorkspaceUpdateRequest) -> Union[WorkspaceResponse, dic """Update workspace Args: params (dict): { - 'workspace_id': 'str', + 'workspace_id': 'str', # required 'name': 'str', 'tags': 'dict' - 'domain_id': 'str' + 'domain_id': 'str' # required } Returns: WorkspaceResponse: @@ -67,8 +67,8 @@ def delete(self, params: WorkspaceDeleteRequest) -> None: """Delete workspace Args: params (dict): { - 'workspace_id': 'str', - 'domain_id': 'str' + 'workspace_id': 'str', # required + 'domain_id': 'str' # required } Returns: None @@ -84,8 +84,8 @@ def enable(self, params: WorkspaceEnableRequest) -> Union[WorkspaceResponse, dic """Enable workspace Args: params (dict): { - 'workspace_id': 'str', - 'domain_id': 'str' + 'workspace_id': 'str', # required + 'domain_id': 'str' # required } Returns: WorkspaceResponse: @@ -104,8 +104,8 @@ def disable( """Disable workspace Args: params (dict): { - 'workspace_id': 'str', - 'domain_id': 'str' + 'workspace_id': 'str', # required + 'domain_id': 'str' # required } Returns: WorkspaceResponse: @@ -123,8 +123,8 @@ def get(self, params: WorkspaceGetRequest) -> Union[WorkspaceResponse, dict]: """Get workspace Args: params (dict): { - 'workspace_id': 'str', - 'domain_id': 'str' + 'workspace_id': 'str', # required + 'domain_id': 'str' # required } Returns: WorkspaceResponse: From e5d98aa0c7f62bb3fe2930655d9f627fefebc3bd Mon Sep 17 00:00:00 2001 From: ImMin5 Date: Wed, 22 Nov 2023 13:46:14 +0900 Subject: [PATCH 26/45] feat: add project service (#73) Signed-off-by: ImMin5 --- .../identity/manager/project_manager.py | 89 +++++++ src/spaceone/identity/manager/user_manager.py | 3 + .../identity/model/project/__init__.py | 0 .../identity/model/project/database.py | 57 +++++ .../request.py} | 7 +- .../response.py} | 11 +- .../identity/model/workspace/database.py | 26 +-- .../identity/service/domain_service.py | 2 +- .../identity/service/project_service.py | 219 ++++++++++++++++-- .../identity/service/workspace_service.py | 3 + 10 files changed, 374 insertions(+), 43 deletions(-) create mode 100644 src/spaceone/identity/manager/project_manager.py create mode 100644 src/spaceone/identity/model/project/__init__.py create mode 100644 src/spaceone/identity/model/project/database.py rename src/spaceone/identity/model/{project_request.py => project/request.py} (93%) rename src/spaceone/identity/model/{project_response.py => project/response.py} (66%) diff --git a/src/spaceone/identity/manager/project_manager.py b/src/spaceone/identity/manager/project_manager.py new file mode 100644 index 00000000..24fdddd0 --- /dev/null +++ b/src/spaceone/identity/manager/project_manager.py @@ -0,0 +1,89 @@ +import logging +from typing import Tuple + +from spaceone.core import cache +from spaceone.core.manager import BaseManager + +from spaceone.identity.model.project.database import Project + +_LOGGER = logging.getLogger(__name__) + + +class ProjectManager(BaseManager): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.project_model = Project + + def create_project(self, params: dict) -> Project: + def _rollback(vo: Project): + _LOGGER.info( + f"[create_project._rollback] Delete project : {vo.name} ({vo.project_id}) ({vo.workspace_id}) ({vo.domain_id})" + ) + vo.delete() + + project_vo = self.project_model.create(params) + self.transaction.add_rollback(_rollback, project_vo) + + return project_vo + + def update_project_by_vo(self, params: dict, project_vo: Project) -> Project: + def _rollback(old_data): + _LOGGER.info( + f'[update_project._rollback] Revert Data : {old_data["name"]} ({old_data["project_id"]}, {old_data["domain_id"]})' + ) + project_vo.update(old_data) + + self.transaction.add_rollback(_rollback, project_vo.to_dict()) + + return project_vo.update(params) + + @staticmethod + def delete_project_by_vo(project_vo: Project) -> None: + project_vo.delete() + + cache.delete_pattern( + f"project-state:{project_vo.domain_id}:{project_vo.workspace_id}:{project_vo.project_id}" + ) + + # + # def enable_workspace(self, workspace_vo: Workspace) -> Workspace: + # def _rollback(old_data): + # _LOGGER.info( + # f'[enable_workspace._rollback] Revert Data : {old_data["name"]} ({old_data["workspace_id"]})' + # ) + # workspace_vo.update(old_data) + # + # if workspace_vo.state != "ENABLED": + # self.transaction.add_rollback(_rollback, workspace_vo.to_dict()) + # workspace_vo.update({"state": "ENABLED"}) + # + # cache.delete_pattern( + # f"workspace-state:{workspace_vo.domain_id}:{workspace_vo.workspace_id}" + # ) + # + # return workspace_vo + # + # def disable_workspace(self, workspace_vo: Workspace) -> Workspace: + # def _rollback(old_data): + # _LOGGER.info( + # f'[disable_workspace._rollback] Revert Data : {old_data["name"]} ({old_data["workspace_id"]})' + # ) + # workspace_vo.update(old_data) + # + # if workspace_vo.state != "DISABLED": + # self.transaction.add_rollback(_rollback, workspace_vo.to_dict()) + # workspace_vo.update({"state": "DISABLED"}) + # + # cache.delete_pattern( + # f"workspace-state:{workspace_vo.domain_id}:{workspace_vo.workspace_id}" + # ) + # + # return workspace_vo + # + def get_project(self, project_id, workspace_id, domain_id) -> Project: + return self.project_model.get( + project_id=project_id, workspace_id=workspace_id, domain_id=domain_id + ) + + def list_projects(self, query: dict) -> Tuple[list, int]: + return self.project_model.query(**query) diff --git a/src/spaceone/identity/manager/user_manager.py b/src/spaceone/identity/manager/user_manager.py index b59e6254..f6e99d2a 100644 --- a/src/spaceone/identity/manager/user_manager.py +++ b/src/spaceone/identity/manager/user_manager.py @@ -96,6 +96,9 @@ def _rollback(old_data): def get_user(self, user_id, domain_id): return self.user_model.get(user_id=user_id, domain_id=domain_id) + def list_users(self, query): + return self.user_model.query(**query) + @staticmethod def _check_user_id_format(user_id): rule = r"[^@]+@[^@]+\.[^@]+" diff --git a/src/spaceone/identity/model/project/__init__.py b/src/spaceone/identity/model/project/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/spaceone/identity/model/project/database.py b/src/spaceone/identity/model/project/database.py new file mode 100644 index 00000000..047d99c6 --- /dev/null +++ b/src/spaceone/identity/model/project/database.py @@ -0,0 +1,57 @@ +from mongoengine import * +from spaceone.core.error import * +from spaceone.core.model.mongo_model import MongoModel + + +class Project(MongoModel): + project_id = StringField(max_length=40, generate_id="project", unique=True) + name = StringField(max_length=40) + project_type = StringField(max_length=20, default="PRIVATE") + tags = DictField(default=None) + users = ListField(StringField(max_length=255), default=None) + user_groups = ListField(StringField(max_length=255), default=None) + project_group_id = StringField(max_length=40, default=None) + workspace_id = StringField(max_length=255) + domain_id = StringField(max_length=255) + created_at = DateTimeField(auto_now_add=True) + + meta = { + "updatable_fields": [ + "name", + "tags", + "project_type", + "project_group_id", + "users", + "user_groups", + ], + "minimal_fields": ["project_id", "name", "project_type"], + # "change_query_keys": { + # "user_groups": "project_id", + # "project_group_id": "project_group.project_group_id", + # }, + "ordering": ["name"], + "indexes": [ + # 'project_id', + "project_type", + "workspace_id", + "domain_id", + ], + } + + @classmethod + def create(cls, data): + project_vos = cls.filter(name=data["name"]) + if project_vos.count() > 0: + raise ERROR_NOT_UNIQUE(key="name", value=data["name"]) + + return super().create(data) + + def update(self, data): + if "name" in data: + project_vos = self.filter( + name=data["name"], project_id__ne=self.workspace_id + ) + if project_vos.count() > 0: + raise ERROR_NOT_UNIQUE(key="name", value=data["name"]) + + return super().update(data) diff --git a/src/spaceone/identity/model/project_request.py b/src/spaceone/identity/model/project/request.py similarity index 93% rename from src/spaceone/identity/model/project_request.py rename to src/spaceone/identity/model/project/request.py index 67ae76fb..096086c0 100644 --- a/src/spaceone/identity/model/project_request.py +++ b/src/spaceone/identity/model/project/request.py @@ -22,9 +22,9 @@ class ProjectCreateRequest(BaseModel): name: str - project_type: str - project_group_id: str + project_type: ProjectType tags: Union[dict, None] = {} + project_group_id: Union[str, None] = None workspace_id: str domain_id: str @@ -32,7 +32,7 @@ class ProjectCreateRequest(BaseModel): class ProjectUpdateRequest(BaseModel): project_id: str name: Union[str, None] = None - tags: Union[dict, None] = {} + tags: Union[dict, None] = None workspace_id: str domain_id: str @@ -97,6 +97,7 @@ class ProjectSearchQueryRequest(BaseModel): name: Union[str, None] = None user_id: Union[str, None] = None user_group_id: Union[str, None] = None + project_group_id: Union[str, None] = None workspace_id: Union[str, None] = None domain_id: str diff --git a/src/spaceone/identity/model/project_response.py b/src/spaceone/identity/model/project/response.py similarity index 66% rename from src/spaceone/identity/model/project_response.py rename to src/spaceone/identity/model/project/response.py index 0502314a..d6d0b0bb 100644 --- a/src/spaceone/identity/model/project_response.py +++ b/src/spaceone/identity/model/project/response.py @@ -1,7 +1,10 @@ from datetime import datetime from typing import Union, List from pydantic import BaseModel -from spaceone.identity.model.project_request import ProjectType + +from spaceone.core import utils + +from spaceone.identity.model.project.request import ProjectType __all__ = ["ProjectResponse", "ProjectsResponse"] @@ -13,10 +16,16 @@ class ProjectResponse(BaseModel): tags: Union[dict, None] = {} users: Union[List[str], None] = None user_groups: Union[List[str], None] = None + project_group_id: Union[str, None] = None workspace_id: Union[str, None] = None domain_id: Union[str, None] = None created_at: Union[datetime, None] = None + def dict(self, *args, **kwargs): + data = super().dict(*args, **kwargs) + data["created_at"] = utils.datetime_to_iso8601(data["created_at"]) + return data + class ProjectsResponse(BaseModel): results: List[ProjectResponse] = [] diff --git a/src/spaceone/identity/model/workspace/database.py b/src/spaceone/identity/model/workspace/database.py index 585b12ce..2a141402 100644 --- a/src/spaceone/identity/model/workspace/database.py +++ b/src/spaceone/identity/model/workspace/database.py @@ -1,3 +1,5 @@ +from datetime import datetime + from mongoengine import * from spaceone.core.error import * from spaceone.core.model.mongo_model import MongoModel @@ -10,34 +12,30 @@ class Workspace(MongoModel): tags = DictField(default=None) domain_id = StringField(max_length=40) created_at = DateTimeField(auto_now_add=True) + deleted_at = DateTimeField(default=None, null=True) meta = { - "updatable_fields": ["name", "state", "tags"], + "updatable_fields": ["name", "state", "tags", "deleted_at"], "minimal_fields": [ "workspace_id", "name", "state", ], "ordering": ["name"], - "indexes": [ - "state", - ], + "indexes": ["name", "domain_id"], } + @queryset_manager + def objects(doc_cls, queryset): + return queryset.filter(state__ne="DELETED") + @classmethod def create(cls, data): - workspace_vos = cls.filter(name=data["name"]) + workspace_vos = cls.filter(name=data["name"], domain_id=data["domain_id"]) if workspace_vos.count() > 0: raise ERROR_NOT_UNIQUE(key="name", value=data["name"]) return super().create(data) - def update(self, data): - if "name" in data: - workspace_vos = self.filter( - name=data["name"], workspace_id__ne=self.workspace_id - ) - if workspace_vos.count() > 0: - raise ERROR_NOT_UNIQUE(key="name", value=data["name"]) - - return super().update(data) + def delete(self): + self.update({"state": "DELETED", "deleted_at": datetime.utcnow()}) diff --git a/src/spaceone/identity/service/domain_service.py b/src/spaceone/identity/service/domain_service.py index ca579036..fdac2bcf 100644 --- a/src/spaceone/identity/service/domain_service.py +++ b/src/spaceone/identity/service/domain_service.py @@ -153,7 +153,7 @@ def get_public_key( return {} @transaction - @append_query_filter(["domain_id", "name"]) + @append_query_filter(["domain_id", "name", "state"]) @append_keyword_filter(["domain_id", "name"]) @convert_model def list(self, params: DomainSearchQueryRequest) -> Union[DomainsResponse, dict]: diff --git a/src/spaceone/identity/service/project_service.py b/src/spaceone/identity/service/project_service.py index 5a0c4a1d..b3aa0a56 100644 --- a/src/spaceone/identity/service/project_service.py +++ b/src/spaceone/identity/service/project_service.py @@ -1,79 +1,250 @@ import logging from typing import Union -from spaceone.core.service import BaseService, transaction, convert_model -from spaceone.identity.model.project_request import * -from spaceone.identity.model.project_response import * + +from spaceone.core.service import ( + BaseService, + transaction, + convert_model, + append_query_filter, + append_keyword_filter, +) + +from spaceone.identity.manager.user_manager import UserManager +from spaceone.identity.manager.project_manager import ProjectManager +from spaceone.identity.manager.workspace_manager import WorkspaceManager +from spaceone.identity.model.project.request import * +from spaceone.identity.model.project.response import * _LOGGER = logging.getLogger(__name__) class ProjectService(BaseService): - @transaction + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.user_mgr = UserManager() + self.project_mgr = ProjectManager() + self.workspace_mgr = WorkspaceManager() + + @transaction(append_meta={"authorization.scope": "WORKSPACE"}) @convert_model def create(self, params: ProjectCreateRequest) -> Union[ProjectResponse, dict]: - return {} + """Create project + Args: + params (dict): { + 'name': 'str', # required + 'project_type': 'str', + 'tags': 'dict', + 'project_group_id': 'str', + 'workspace_id': 'str', # required + 'domain_id': 'str' # required + } + Returns: + ProjectResponse: + """ + + self.workspace_mgr.get_workspace(params.workspace_id, params.domain_id) + project_vo = self.project_mgr.create_project(params.dict()) + + return ProjectResponse(**project_vo.to_dict()) - @transaction + @transaction(append_meta={"authorization.scope": "PROJECT"}) @convert_model def update(self, params: ProjectUpdateRequest) -> Union[ProjectResponse, dict]: - return {} + """Update project + Args: + params (dict): { + 'project_id': 'str', # required + 'name': 'str', + 'tags': 'dict', + 'workspace_id': 'str', # required + 'domain_id': 'str' # required + } + Returns: + ProjectResponse: + """ + + project_vo = self.project_mgr.get_project( + params.project_id, params.workspace_id, params.domain_id + ) + project_vo = self.project_mgr.update_project_by_vo(params.dict(), project_vo) + + return ProjectResponse(**project_vo.to_dict()) - @transaction + @transaction(append_meta={"authorization.scope": "WORKSPACE"}) @convert_model def update_project_type( self, params: ProjectUpdateProjectTypeRequest ) -> Union[ProjectResponse, dict]: - return {} + """Update project type + Args: + params (dict): { + 'project_id': 'str', # required + 'project_type': 'str', # required + 'workspace_id': 'str', # required + 'domain_id': 'str' # required + } + Returns: + ProjectResponse: + """ + + project_vo = self.project_mgr.get_project( + params.project_id, params.workspace_id, params.domain_id + ) + project_vo = self.project_mgr.update_project_by_vo(params.dict(), project_vo) + + return ProjectResponse(**project_vo.to_dict()) - @transaction + @transaction(append_meta={"authorization.scope": "WORKSPACE"}) @convert_model def change_project_group( self, params: ProjectChangeProjectGroupRequest ) -> Union[ProjectResponse, dict]: - return {} + """Change project group + Args: + params (dict): { + 'project_id': 'str', # required + 'project_group_id': 'str', # required + 'workspace_id': 'str', # required + 'domain_id': 'str' # required + } + Returns: + ProjectResponse: + """ + project_vo = self.project_mgr.get_project( + params.project_id, params.workspace_id, params.domain_id + ) + project_vo = self.project_mgr.update_project_by_vo(params.dict(), project_vo) + return ProjectResponse(**project_vo.to_dict()) - @transaction + @transaction(append_meta={"authorization.scope": "WORKSPACE"}) @convert_model def delete(self, params: ProjectDeleteRequest) -> None: - pass + """Delete project + Args: + params (dict): { + 'project_id': 'str', # required + 'workspace_id': 'str', # required + 'domain_id': 'str' # required + } + Returns: + None: + """ + project_vo = self.project_mgr.get_project( + params.project_id, params.workspace_id, params.domain_id + ) + self.project_mgr.delete_project_by_vo(project_vo) - @transaction + @transaction(append_meta={"authorization.scope": "PROJECT"}) @convert_model def add_users(self, params: ProjectAddUsersRequest) -> Union[ProjectResponse, dict]: - return {} + project_vo = self.project_mgr.get_project( + params.project_id, params.workspace_id, params.domain_id + ) + + if len(params.users) > 0: + self._check_exist_user(params.users, params.domain_id) + users = project_vo.users or [] + users.extend(params.users) + params.users = list(set(users)) - @transaction + project_vo = self.project_mgr.update_project_by_vo( + params.dict(), project_vo + ) + + return ProjectResponse(**project_vo.to_dict()) + + @transaction(append_meta={"authorization.scope": "PROJECT"}) @convert_model def remove_users( self, params: ProjectRemoveUsersRequest ) -> Union[ProjectResponse, dict]: - return {} + project_vo = self.project_mgr.get_project( + project_id=params.project_id, + workspace_id=params.workspace_id, + domain_id=params.domain_id, + ) - @transaction + params.users = list(set(project_vo.users) - set(params.users)) + project_vo = self.project_mgr.update_project_by_vo(params.dict(), project_vo) + + return ProjectResponse(**project_vo.to_dict()) + + @transaction(append_meta={"authorization.scope": "PROJECT"}) @convert_model def add_user_groups( self, params: ProjectAddUserGroupsRequest ) -> Union[ProjectResponse, dict]: return {} - @transaction + @transaction(append_meta={"authorization.scope": "PROJECT"}) @convert_model def remove_user_groups( self, params: ProjectRemoveUserGroupsRequest ) -> Union[ProjectResponse, dict]: return {} - @transaction + @transaction(append_meta={"authorization.scope": "PROJECT_READ"}) @convert_model def get(self, params: ProjectGetRequest) -> Union[ProjectResponse, dict]: - return {} + """Get project + Args: + params (dict): { + 'project_id': 'str', # required + 'workspace_id': 'str', # required + 'domain_id': 'str' # required + } + Returns: + ProjectResponse: + """ - @transaction + project_vo = self.project_mgr.get_project( + params.project_id, params.workspace_id, params.domain_id + ) + return ProjectResponse(**project_vo.to_dict()) + + @transaction(append_meta={"authorization.scope": "PROJECT_READ"}) + @append_query_filter( + [ + "project_id", + "name", + "user_id", + "user_group_id", + "project_group_id", + "workspace_id", + "domain_id", + ] + ) + @append_keyword_filter(["project_id", "name"]) @convert_model def list(self, params: ProjectSearchQueryRequest) -> Union[ProjectsResponse, dict]: - return {} + """List projects + Args: + params (dict): { + 'query': 'dict (spaceone.api.core.v1.Query)', + 'project_id': 'str', + 'name': 'str', + 'user_id': 'str', + 'user_group_id': 'str', + 'project_group_id': 'str', + 'workspace_id': 'str', + 'domain_id': 'str' # required + } + Returns: + ProjectsResponse: + """ + query = params.query or {} + project_vos, total_count = self.project_mgr.list_projects(query) + projects_info = [project_vo.to_dict() for project_vo in project_vos] + return ProjectsResponse(results=projects_info, total_count=total_count) - @transaction + @transaction(append_meta={"authorization.scope": "PROJECT_READ"}) @convert_model def stat(self, params: ProjectStatQueryRequest) -> dict: return {} + + def _check_exist_user(self, users, domain_id): + _filter = [ + {"k": "user_id", "v": users, "o": "in"}, + {"k": "domain_id", "v": domain_id, "o": "eq"}, + ] + self.user_mgr.list_users({"filter": _filter}) diff --git a/src/spaceone/identity/service/workspace_service.py b/src/spaceone/identity/service/workspace_service.py index dde815ea..1521b59b 100644 --- a/src/spaceone/identity/service/workspace_service.py +++ b/src/spaceone/identity/service/workspace_service.py @@ -9,6 +9,8 @@ append_keyword_filter, ) +from spaceone.identity.error.error_workspace import * +from spaceone.identity.manager.domain_manager import DomainManager from spaceone.identity.manager.workspace_manager import WorkspaceManager from spaceone.identity.model.workspace.request import * from spaceone.identity.model.workspace.response import * @@ -19,6 +21,7 @@ class WorkspaceService(BaseService): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + self.domain_mgr = DomainManager() self.workspace_mgr = WorkspaceManager() @transaction(append_meta={"authorization.scope": "WORKSPACE"}) From fdb345dbbe6eebbb69a28a3882d434c7079420b5 Mon Sep 17 00:00:00 2001 From: ImMin5 Date: Wed, 22 Nov 2023 19:31:51 +0900 Subject: [PATCH 27/45] feat: add token service, manager, model (#73) Signed-off-by: ImMin5 --- src/spaceone/identity/error/error_token.py | 5 + src/spaceone/identity/manager/__init__.py | 6 + .../identity/manager/domain_secret_manager.py | 98 +++++++++ .../manager/token_manager/__init__.py | 194 ++++++++++++++++++ .../token_manager/external_token_manager.py | 143 +++++++++++++ .../token_manager/local_token_manager.py | 101 +++++++++ .../identity/model/domain_secret/__init__.py | 0 .../identity/model/domain_secret/database.py | 23 +++ src/spaceone/identity/model/token/__init__.py | 0 .../{token_request.py => token/request.py} | 2 +- .../{token_response.py => token/response.py} | 0 .../identity/service/token_service.py | 169 ++++++++++++++- 12 files changed, 735 insertions(+), 6 deletions(-) create mode 100644 src/spaceone/identity/error/error_token.py create mode 100644 src/spaceone/identity/manager/domain_secret_manager.py create mode 100644 src/spaceone/identity/manager/token_manager/__init__.py create mode 100644 src/spaceone/identity/manager/token_manager/external_token_manager.py create mode 100644 src/spaceone/identity/manager/token_manager/local_token_manager.py create mode 100644 src/spaceone/identity/model/domain_secret/__init__.py create mode 100644 src/spaceone/identity/model/domain_secret/database.py create mode 100644 src/spaceone/identity/model/token/__init__.py rename src/spaceone/identity/model/{token_request.py => token/request.py} (88%) rename src/spaceone/identity/model/{token_response.py => token/response.py} (100%) diff --git a/src/spaceone/identity/error/error_token.py b/src/spaceone/identity/error/error_token.py new file mode 100644 index 00000000..6193d4c4 --- /dev/null +++ b/src/spaceone/identity/error/error_token.py @@ -0,0 +1,5 @@ +from spaceone.core.error import * + + +class ERROR_INVALID_AUTHENTICATION_TYPE(ERROR_INVALID_ARGUMENT): + _message = "Invalid authentication type (LOCAL OR EXTERNAL). (authentication_type = {authentication_type})" diff --git a/src/spaceone/identity/manager/__init__.py b/src/spaceone/identity/manager/__init__.py index 78298c92..5eac4cde 100644 --- a/src/spaceone/identity/manager/__init__.py +++ b/src/spaceone/identity/manager/__init__.py @@ -1 +1,7 @@ from spaceone.identity.manager.mfa_manager.email_mfa_manger import EmailMFAManager +from spaceone.identity.manager.token_manager.local_token_manager import ( + LocalTokenManager, +) +from spaceone.identity.manager.token_manager.external_token_manager import ( + ExternalTokenManager, +) diff --git a/src/spaceone/identity/manager/domain_secret_manager.py b/src/spaceone/identity/manager/domain_secret_manager.py new file mode 100644 index 00000000..e56f6db6 --- /dev/null +++ b/src/spaceone/identity/manager/domain_secret_manager.py @@ -0,0 +1,98 @@ +import logging +from spaceone.core.auth.jwt import JWTUtil +from spaceone.core.cache import cacheable +from spaceone.core.manager import * +from spaceone.core import utils +from spaceone.identity.model.domain_secret.database import DomainSecret +from spaceone.identity.model.domain.database import Domain + +_LOGGER = logging.getLogger(__name__) + + +class DomainSecretManager(BaseManager): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.domain_model = Domain + self.domain_secret_model = DomainSecret + + def create_domain_secret(self, domain_id: str) -> None: + def _rollback(vo): + _LOGGER.info( + f"[create_domain_secret._rollback] Delete domain-secret : {vo.domain_id} ({vo.secret[0:6]}...)" + ) + vo.delete() + + # Generate Domain-secret + secret = self._generate_domain_secret(domain_id) + + # Generate Domain Key + secret["domain_key"] = utils.random_string(16) + + domain_secret_vo: DomainSecret = self.domain_secret_model.create(secret) + self.transaction.add_rollback(_rollback, domain_secret_vo) + + @cacheable(key="public-jwk:{domain_id}", expire=600) + def get_domain_public_key(self, domain_id: str) -> dict: + domain_secret_vo: DomainSecret = self.domain_secret_model.get( + domain_id=domain_id + ) + return domain_secret_vo.pub_jwk + + @cacheable(key="private-jwk:{domain_id}", expire=600) + def get_domain_private_key(self, domain_id): + domain_secret_vo: DomainSecret = self.domain_secret_model.get( + domain_id=domain_id + ) + return domain_secret_vo.prv_jwk + + @cacheable(key="refresh-public-jwk:{domain_id}", expire=600) + def get_domain_refresh_public_key(self, domain_id): + domain_secret_vo: DomainSecret = self.domain_secret_model.get( + domain_id=domain_id + ) + if not domain_secret_vo.refresh_pub_jwk: + domain_secret_vo: DomainSecret = self._create_refresh_key( + domain_secret_vo, domain_id + ) + + return domain_secret_vo.refresh_pub_jwk + + @cacheable(key="refresh-private-jwk:{domain_id}", expire=600) + def get_domain_refresh_private_key(self, domain_id): + domain_secret_vo: DomainSecret = self.domain_secret_model.get( + domain_id=domain_id + ) + if not domain_secret_vo.refresh_pub_jwk: + domain_secret_vo: DomainSecret = self._create_refresh_key( + domain_secret_vo, domain_id + ) + + return domain_secret_vo.refresh_prv_jwk + + def _create_refresh_key(self, domain_secret_vo: DomainSecret, domain_id): + domain_vo = self.domain_model.get(domain_id=domain_id) + refresh_private_jwk, refresh_public_jwk = JWTUtil.generate_jwk() + + return domain_secret_vo.update( + { + "refresh_pub_jwk": refresh_public_jwk, + "refresh_prv_jwk": refresh_private_jwk, + "domain": domain_vo, + } + ) + + def _generate_domain_secret(self, domain_id: str) -> dict: + domain_vo = self.domain_model.get(domain_id=domain_id) + + private_jwk, public_jwk = JWTUtil.generate_jwk() + refresh_private_jwk, refresh_public_jwk = JWTUtil.generate_jwk() + data = { + "pub_jwk": public_jwk, + "prv_jwk": private_jwk, + "refresh_pub_jwk": refresh_public_jwk, + "refresh_prv_jwk": refresh_private_jwk, + "domain_id": domain_id, + "domain": domain_vo, + } + print("generate domain secrer", data) + return data diff --git a/src/spaceone/identity/manager/token_manager/__init__.py b/src/spaceone/identity/manager/token_manager/__init__.py new file mode 100644 index 00000000..3f927cb7 --- /dev/null +++ b/src/spaceone/identity/manager/token_manager/__init__.py @@ -0,0 +1,194 @@ +import logging +import time +import random +from abc import abstractmethod, ABC, ABCMeta + +from spaceone.core import config, utils, cache +from spaceone.core.manager import BaseManager +from spaceone.core.auth.jwt.jwt_util import JWTUtil + +from spaceone.identity.error.error_token import * +from spaceone.identity.error.error_authentication import * + +__all__ = ["TokenManager", "JWTManager"] +_LOGGER = logging.getLogger(__name__) + + +class TokenManager(BaseManager, ABC): + is_authenticated = False + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._load_conf() + + @abstractmethod + def issue_temporary_token(self, **kwargs): + pass + + @abstractmethod + def issue_token(self, **kwargs): + pass + + @abstractmethod + def refresh_token(self, user_id, domain_id, **kwargs): + pass + + @abstractmethod + def authenticate(self, user_id, domain_id, credentials): + pass + + @abstractmethod + def check_refreshable(self, key, ttl): + pass + + def _load_conf(self): + identity_conf = config.get_global("IDENTITY") or {} + token_conf = identity_conf.get("token", {}) + self.CONST_TOKEN_TIMEOUT = token_conf.get("token_timeout", 1800) + self.CONST_VERIFY_CODE_TIMEOUT = token_conf.get("verify_code_timeout", 3600) + self.CONST_REFRESH_TIMEOUT = token_conf.get("refresh_timeout", 3600) + self.CONST_REFRESH_TTL = token_conf.get("refresh_ttl", -1) + self.CONST_REFRESH_ONCE = token_conf.get("refresh_once", True) + + +class JWTManager(TokenManager, metaclass=ABCMeta): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.old_refresh_key = None + + def issue_temporary_token(self, **kwargs): + raise NotImplementedError("TokenManager.issue_temporary_token not implemented!") + + def issue_token(self, **kwargs): + raise NotImplementedError("TokenManager.issue_token not implemented!") + + def refresh_token(self, user_id, domain_id, **kwargs): + raise NotImplementedError("TokenManager.refresh_token not implemented!") + + def authenticate(self, user_id, domain_id, credentials): + raise NotImplementedError("TokenManager.authenticate not implemented!") + + def check_refreshable(self, refresh_key, ttl): + if self.CONST_REFRESH_ONCE: + if cache.is_set() and cache.get(f"refresh-token:{refresh_key}") is None: + raise ERROR_INVALID_REFRESH_TOKEN() + + if ttl == 0: + raise ERROR_REFRESH_COUNT() + + self.is_authenticated = True + self.old_refresh_key = refresh_key + + def issue_access_token(self, user_type, user_id, domain_id, **kwargs): + private_jwk = self._get_private_jwk(kwargs) + permissions = kwargs.get("permissions") + timeout = kwargs.get("timeout") + if timeout is None: + timeout = self.CONST_TOKEN_TIMEOUT + + payload = { + "cat": "ACCESS_TOKEN", + "user_type": user_type, + "did": domain_id, + "aud": user_id, + "iat": int(time.time()), + "exp": int(time.time() + timeout), + } + + if permissions: + payload["permissions"] = permissions + + encoded = JWTUtil.encode(payload, private_jwk) + return encoded + + def issue_refresh_token(self, user_type, user_id, domain_id, **kwargs): + refresh_private_jwk = self._get_refresh_private_jwk(kwargs) + ttl = kwargs.get("ttl") + timeout = kwargs.get("timeout") + + if ttl is None: + ttl = self.CONST_REFRESH_TTL + elif ttl > self.CONST_REFRESH_TTL: + raise ERROR_MAX_REFRESH_COUNT(max_refresh_count=self.CONST_REFRESH_TTL) + + if timeout is None: + timeout = self.CONST_REFRESH_TIMEOUT + + refresh_key = self._generate_refresh_key() + + payload = { + "cat": "REFRESH_TOKEN", + "user_type": user_type, + "did": domain_id, + "aud": user_id, + "iat": int(time.time()), + "exp": int(time.time() + timeout), + "key": refresh_key, + "ttl": ttl, + } + + encoded = JWTUtil.encode(payload, refresh_private_jwk) + + if self.CONST_REFRESH_ONCE: + self._set_refresh_token_cache(refresh_key) + + return encoded + + @staticmethod + def _generate_refresh_key(): + return utils.random_string(16) + + @staticmethod + def _get_private_jwk(kwargs): + if "private_jwk" not in kwargs: + raise ERROR_NOT_FOUND_PRIVATE_KEY(purpose="Access Token") + + return kwargs["private_jwk"] + + @staticmethod + def _get_refresh_private_jwk(kwargs): + if "refresh_private_jwk" not in kwargs: + raise ERROR_NOT_FOUND_PRIVATE_KEY(purpose="Refresh Token") + + return kwargs["refresh_private_jwk"] + + def _set_refresh_token_cache(self, new_refresh_key): + if cache.is_set(): + if self.old_refresh_key: + cache.delete(f"refresh-token:{self.old_refresh_key}") + + cache.set( + f"refresh-token:{new_refresh_key}", + "", + expire=self.CONST_REFRESH_TIMEOUT, + ) + + def create_verify_code(self, user_id, domain_id): + if cache.is_set(): + verify_code = self._generate_verify_code() + cache.delete(f"verify-code:{domain_id}:{user_id}") + cache.set( + f"verify-code:{domain_id}:{user_id}", + verify_code, + expire=self.CONST_VERIFY_CODE_TIMEOUT, + ) + return verify_code + + @staticmethod + def check_verify_code(user_id, domain_id, verify_code): + if cache.is_set(): + cached_verify_code = cache.get(f"verify-code:{domain_id}:{user_id}") + if cached_verify_code == verify_code: + return True + return False + + @staticmethod + def _generate_verify_code(): + return str(random.randint(100000, 999999)) + + @classmethod + def get_token_manager_by_auth_type(cls, auth_type): + for subclass in cls.__subclasses__(): + if subclass.auth_type == auth_type: + return subclass() + raise ERROR_INVALID_AUTHENTICATION_TYPE(auth_type=auth_type) diff --git a/src/spaceone/identity/manager/token_manager/external_token_manager.py b/src/spaceone/identity/manager/token_manager/external_token_manager.py new file mode 100644 index 00000000..b77c0c1e --- /dev/null +++ b/src/spaceone/identity/manager/token_manager/external_token_manager.py @@ -0,0 +1,143 @@ +import logging +from datetime import datetime +from spaceone.identity.connector import AuthPluginConnector +from spaceone.identity.error.error_authentication import * +from spaceone.identity.error.error_user import ERROR_USER_STATUS_CHECK_FAILURE +from spaceone.identity.manager.user_manager import UserManager +from spaceone.identity.manager.token_manager import JWTManager +from spaceone.identity.manager.domain_manager import DomainManager +from spaceone.identity.model.domain.database import Domain +from spaceone.identity.model.user.database import User + +_LOGGER = logging.getLogger(__name__) + + +class ExternalTokenManager(JWTManager): + domain: Domain = None + auth_type = "EXTERNAL" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.domain_mgr = DomainManager() + self.user_mgr = UserManager() + + def authenticate(self, user_id, domain_id, credentials): + _LOGGER.debug(f"[authenticate] domain_id: {domain_id}") + + # Add User ID for External Authentication + if user_id: + credentials["user_id"] = user_id + + self.domain: Domain = self.domain_mgr.get_domain(domain_id) + + self._check_domain_state() + + endpoint = self.domain_mgr.get_auth_plugin_endpoint_by_vo(self.domain) + auth_user_info = self._authenticate_with_plugin(endpoint, credentials) + + _LOGGER.info( + f'[authenticate] Authentication success. (user_id={auth_user_info.get("user_id")})' + ) + + auto_user_sync = self.domain.plugin_info.options.get("auto_user_sync", False) + + self._verify_user_from_plugin_user_info( + auth_user_info, domain_id, auto_user_sync + ) + self._check_user_state() + + self.is_authenticated = True + + def issue_token(self, **kwargs): + if self.is_authenticated is False: + raise ERROR_NOT_AUTHENTICATED() + + if self.user.state == "PENDING": + self.user: User = self.user.update({"state": "ENABLED"}) + + # Issue token + access_token = self.issue_access_token( + "USER", self.user.user_id, self.user.domain_id, **kwargs + ) + refresh_token = self.issue_refresh_token( + "USER", self.user.user_id, self.user.domain_id, **kwargs + ) + + # Update user's last_accessed_at field + self.user.update({"last_accessed_at": datetime.utcnow()}) + + return {"access_token": access_token, "refresh_token": refresh_token} + + def refresh_token(self, user_id, domain_id, **kwargs): + self.user: User = self.user_mgr.get_user(user_id, domain_id) + self._check_user_state() + + return self.issue_token(**kwargs) + + def _verify_user_from_plugin_user_info( + self, auth_user_info, domain_id, auto_user_sync=False + ): + if "user_id" not in auth_user_info: + _LOGGER.error( + f"[_verify_user_from_plugin_user_info] does not return user_id from plugin user info." + ) + raise ERROR_AUTHENTICATION_FAILURE_PLUGIN( + message="plugin response is invalid." + ) + + user_id = auth_user_info["user_id"] + state = auth_user_info.get("state", "ENABLED") + + user_vos = self.user_mgr.filter_users(user_id=user_id, domain_id=domain_id) + + if user_vos.count() > 0: + self.user: User = user_vos[0] + else: + if auto_user_sync: + name = auth_user_info.get("name") + email = auth_user_info.get("email") + self.user: User = self._create_external_user( + user_id, state, domain_id, name, email + ) + else: + raise ERROR_NOT_FOUND(key="user_id", value=user_id) + + def _authenticate_with_plugin(self, endpoint, credentials): + options = self.domain.plugin_info.options + + auth_plugin_conn: AuthPluginConnector = self.locator.get_connector( + "AuthPluginConnector" + ) + auth_plugin_conn.initialize(endpoint) + + return auth_plugin_conn.call_login(credentials, options, {}) + + def _check_domain_state(self): + if not self.domain.plugin_info: + _LOGGER.error( + "[_get_token_manager] This domain does not allow external authentication." + ) + raise ERROR_AUTHENTICATION_FAILURE(user_id=self.user.user_id) + + def _check_user_state(self): + if self.user.state not in ["ENABLED", "PENDING"]: + raise ERROR_USER_STATUS_CHECK_FAILURE(user_id=self.user.user_id) + + if self.user.backend != "EXTERNAL": + raise ERROR_NOT_FOUND(key="user_id", value=self.user.user_id) + + def _create_external_user(self, user_id, state, domain_id, name=None, email=None): + _LOGGER.error(f"[_create_external_user] create user on first login: {user_id}") + return self.user_mgr.create_user( + { + "user_id": user_id, + "state": state, + "name": name, + "email": email, + "user_type": "USER", + "backend": "EXTERNAL", + "domain_id": domain_id, + }, + self.domain, + is_first_login_user=True, + ) diff --git a/src/spaceone/identity/manager/token_manager/local_token_manager.py b/src/spaceone/identity/manager/token_manager/local_token_manager.py new file mode 100644 index 00000000..c485dfdb --- /dev/null +++ b/src/spaceone/identity/manager/token_manager/local_token_manager.py @@ -0,0 +1,101 @@ +import logging +from datetime import datetime + +from spaceone.identity.error.error_authentication import * +from spaceone.identity.error.error_user import ERROR_USER_STATUS_CHECK_FAILURE +from spaceone.identity.lib.cipher import PasswordCipher +from spaceone.identity.manager.user_manager import UserManager +from spaceone.identity.manager.token_manager import JWTManager +from spaceone.identity.model.user.database import User + +_LOGGER = logging.getLogger(__name__) + + +class LocalTokenManager(JWTManager): + auth_type = "LOCAL" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.user_mgr = UserManager() + + def authenticate(self, user_id, domain_id, credentials): + pw_to_check = self._parse_password(credentials) + + self.user = self.user_mgr.get_user(user_id, domain_id) + + self._check_user_state() + + # TODO: decrypt pw + is_correct = PasswordCipher().checkpw(pw_to_check, self.user.password) + _LOGGER.debug( + f"[authenticate] is_correct: {is_correct}, pw_to_check: {pw_to_check}, hashed_pw: {self.user.password}" + ) + + if is_correct: + self.is_authenticated = True + else: + raise ERROR_AUTHENTICATION_FAILURE(user_id=self.user.user_id) + + def issue_temporary_token(self, user_id, domain_id, **kwargs): + permissions = ["identity.User.get", "identity.User.update"] + + # Issue token + access_token = self.issue_access_token( + "USER", user_id, domain_id, permissions=permissions, **kwargs + ) + + return {"access_token": access_token} + + def issue_token(self, **kwargs): + if self.is_authenticated is False: + raise ERROR_NOT_AUTHENTICATED() + + permissions = self._get_permissions_from_required_actions() + + # Issue token + access_token = self.issue_access_token( + "USER", + self.user.user_id, + self.user.domain_id, + permissions=permissions, + **kwargs, + ) + refresh_token = self.issue_refresh_token( + "USER", self.user.user_id, self.user.domain_id, **kwargs + ) + + # Update user's last_accessed_at field + user = self.user.update({"last_accessed_at": datetime.utcnow()}) + + return {"access_token": access_token, "refresh_token": refresh_token} + + def refresh_token(self, user_id, domain_id, **kwargs): + self.user: User = self.user_mgr.get_user(user_id, domain_id) + self._check_user_state() + + return self.issue_token(**kwargs) + + def _get_permissions_from_required_actions(self): + if "UPDATE_PASSWORD" in self.user.required_actions: + return ["identity.User.get", "identity.User.update"] + + return None + + @staticmethod + def _parse_password(credentials): + pw_to_check = credentials.get("password", None) + + if pw_to_check is None: + raise ERROR_INVALID_CREDENTIALS() + + return pw_to_check + + def _check_user_state(self): + if self.user.user_type == "API_USER": + raise ERROR_NOT_ALLOWED_ISSUE_TOKEN_API_USER(user_id=self.user.user_id) + + if self.user.state != "ENABLED": + raise ERROR_USER_STATUS_CHECK_FAILURE(user_id=self.user.user_id) + + if self.user.auth_type != "LOCAL": + raise ERROR_NOT_FOUND(key="user_id", value=self.user.user_id) diff --git a/src/spaceone/identity/model/domain_secret/__init__.py b/src/spaceone/identity/model/domain_secret/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/spaceone/identity/model/domain_secret/database.py b/src/spaceone/identity/model/domain_secret/database.py new file mode 100644 index 00000000..18d24bbb --- /dev/null +++ b/src/spaceone/identity/model/domain_secret/database.py @@ -0,0 +1,23 @@ +from mongoengine import * + +from spaceone.core.model.mongo_model import MongoModel + + +class DomainSecret(MongoModel): + domain_key = StringField() + pub_jwk = DictField(required=True) + prv_jwk = DictField(required=True) + refresh_pub_jwk = DictField(required=True) + refresh_prv_jwk = DictField(required=True) + domain_id = StringField(max_length=40, unique=True) + domain = ReferenceField("Domain", reverse_delete_rule=CASCADE) + created_at = DateTimeField(auto_now_add=True) + + meta = { + "updatable_fields": ["refresh_pub_jwk", "refresh_prv_jwk", "domain"], + "ordering": ["domain_id"], + "indexes": [ + # 'domain_id', + "domain" + ], + } diff --git a/src/spaceone/identity/model/token/__init__.py b/src/spaceone/identity/model/token/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/spaceone/identity/model/token_request.py b/src/spaceone/identity/model/token/request.py similarity index 88% rename from src/spaceone/identity/model/token_request.py rename to src/spaceone/identity/model/token/request.py index e5fd9544..6e68db92 100644 --- a/src/spaceone/identity/model/token_request.py +++ b/src/spaceone/identity/model/token/request.py @@ -11,5 +11,5 @@ class TokenIssueRequest(BaseModel): auth_type: AuthType timeout: Union[int, None] = None refresh_count: Union[int, None] = None - verify_count: Union[int, None] = None + verify_code: Union[int, None] = None domain_id: str diff --git a/src/spaceone/identity/model/token_response.py b/src/spaceone/identity/model/token/response.py similarity index 100% rename from src/spaceone/identity/model/token_response.py rename to src/spaceone/identity/model/token/response.py diff --git a/src/spaceone/identity/service/token_service.py b/src/spaceone/identity/service/token_service.py index c48343c8..bfc4e69f 100644 --- a/src/spaceone/identity/service/token_service.py +++ b/src/spaceone/identity/service/token_service.py @@ -1,21 +1,180 @@ import logging -from typing import Union + +from spaceone.core import cache +from spaceone.core.auth.jwt import JWTAuthenticator, JWTUtil from spaceone.core.service import BaseService, transaction, convert_model -from spaceone.identity.model.token_request import * -from spaceone.identity.model.token_response import * +from spaceone.identity.error.error_authentication import * +from spaceone.identity.error.error_domain import ERROR_DOMAIN_STATE +from spaceone.identity.error.error_mfa import * +from spaceone.identity.manager.domain_manager import DomainManager +from spaceone.identity.manager.domain_secret_manager import DomainSecretManager +from spaceone.identity.manager.mfa_manager import MFAManager +from spaceone.identity.manager.token_manager import JWTManager +from spaceone.identity.manager.user_manager import UserManager +from spaceone.identity.model.token.request import * +from spaceone.identity.model.token.response import * _LOGGER = logging.getLogger(__name__) class TokenService(BaseService): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.domain_mgr = DomainManager() + self.domain_secret_mgr = DomainSecretManager() + self.user_mgr = UserManager() + @transaction @convert_model def issue(self, params: TokenIssueRequest) -> Union[TokenResponse, dict]: - return {} + """Issue token + Args: + params (dict): { + 'credentials': 'dict', # required + 'auth_type': 'str', # required + 'timeout': 'int', + 'refresh_count': 'int', + 'verify_code': 'str', + 'domain_id': 'str', # required + } + Returns: + TokenResponse: + """ + + user_id = params.credentials.get("user_id") + domain_id = params.domain_id + timeout = params.timeout + refresh_count = params.refresh_count + verify_code = params.verify_code + + private_jwk = self.domain_secret_mgr.get_domain_private_key(domain_id=domain_id) + refresh_private_jwk = self.domain_secret_mgr.get_domain_refresh_private_key( + domain_id=domain_id + ) + + # Check Domain state is ENABLED + self._check_domain_state(domain_id) + + token_mgr = JWTManager.get_token_manager_by_auth_type(params.auth_type) + token_mgr.authenticate(user_id, domain_id, params.credentials) + + user_vo = token_mgr.user + user_mfa = user_vo.mfa.to_dict() if user_vo.mfa else {} + + if user_mfa.get("state", "DISABLED") == "ENABLED": + if verify_code: + token_mgr.check_mfa_verify_code(user_id, domain_id, verify_code) + else: + mfa_email = user_mfa["options"].get("email") + mfa_manager = MFAManager.get_manager_by_mfa_type( + user_mfa.get("mfa_type") + ) + mfa_manager.send_mfa_authentication_email( + user_id, domain_id, mfa_email, user_vo.language + ) + raise ERROR_MFA_REQUIRED(user_id=user_id) + + token_info = token_mgr.issue_token( + private_jwk=private_jwk, + refresh_private_jwk=refresh_private_jwk, + timeout=timeout, + ttl=refresh_count, + ) + + return token_info @transaction @convert_model def refresh(self, params: dict) -> Union[TokenResponse, dict]: - return {} + """Refresh token + Args: + params (dict): {} + Returns: + TokenResponse: + """ + refresh_token = self.transaction.get_meta("token") + + if refresh_token is None: + raise ERROR_INVALID_REFRESH_TOKEN() + + domain_id = self._extract_domain_id(refresh_token) + + private_jwk = self.domain_secret_mgr.get_domain_private_key(domain_id=domain_id) + refresh_public_jwk = self.domain_secret_mgr.get_domain_refresh_public_key( + domain_id=domain_id + ) + refresh_private_jwk = self.domain_secret_mgr.get_domain_refresh_private_key( + domain_id=domain_id + ) + + token_info = self._verify_refresh_token(refresh_token, refresh_public_jwk) + user_auth_type = self._get_user_auth_type(token_info["user_id"], domain_id) + + token_mgr = JWTManager.get_token_manager_by_auth_type(user_auth_type) + token_mgr.check_refreshable(token_info["key"], token_info["ttl"]) + + token_response = token_mgr.refresh_token( + token_info["user_id"], + domain_id, + ttl=token_info["ttl"] - 1, + private_jwk=private_jwk, + refresh_private_jwk=refresh_private_jwk, + ) + + return TokenResponse(**token_response) + + @cache.cacheable(key="user-auth-type:{domain_id}:{user_id}", expire=600) + def _get_user_auth_type(self, user_id, domain_id): + try: + user_vo = self.user_mgr.get_user(user_id, domain_id) + except Exception as e: + _LOGGER.error( + f'[_get_user_backend] Authentication failure: {getattr(e, "message", e)}' + ) + raise ERROR_AUTHENTICATION_FAILURE(user_id=user_id) + + return user_vo.auth_type + + @cache.cacheable(key="domain-state:{domain_id}", expire=3600) + def _check_domain_state(self, domain_id): + domain_vo = self.domain_mgr.get_domain(domain_id) + + if domain_vo.state != "ENABLED": + raise ERROR_DOMAIN_STATE(domain_id=domain_vo.domain_id) + + @staticmethod + def _extract_domain_id(token): + try: + decoded = JWTUtil.unverified_decode(token) + except Exception as e: + _LOGGER.error(f"[_extract_domain_id] {e}") + _LOGGER.error(token) + raise ERROR_AUTHENTICATE_FAILURE(message="Cannot decode token.") + + domain_id = decoded.get("did") + + if domain_id is None: + raise ERROR_AUTHENTICATE_FAILURE(message="Empty domain_id provided.") + + return domain_id + + @staticmethod + def _verify_refresh_token(token, public_jwk): + try: + decoded = JWTAuthenticator(public_jwk).validate(token) + except Exception as e: + _LOGGER.error(f"[_verify_refresh_token] {e}") + raise ERROR_AUTHENTICATE_FAILURE(message="Token validation failed.") + + if decoded.get("cat") != "REFRESH_TOKEN": + raise ERROR_INVALID_REFRESH_TOKEN() + return { + "user_id": decoded["aud"], + "user_type": decoded["user_type"], + "key": decoded["key"], + "ttl": decoded["ttl"], + "iat": decoded["iat"], + "exp": decoded["exp"], + } From 16c013dd3e5d18e5165656d1ca00cdddc5f2ac56 Mon Sep 17 00:00:00 2001 From: ImMin5 Date: Wed, 22 Nov 2023 19:32:46 +0900 Subject: [PATCH 28/45] feat: modify DomainSecretResponse field name (#73) Signed-off-by: ImMin5 --- src/spaceone/identity/model/domain/response.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/spaceone/identity/model/domain/response.py b/src/spaceone/identity/model/domain/response.py index 391be3f8..480fbc65 100644 --- a/src/spaceone/identity/model/domain/response.py +++ b/src/spaceone/identity/model/domain/response.py @@ -42,8 +42,7 @@ def dict(self, *args, **kwargs): class DomainSecretResponse(BaseModel): domain_id: Union[str, None] = None - key: Union[str, None] = None - key_type: Union[str, None] = None + public_key: Union[str, None] = None def dict(self, *args, **kwargs): data = super().dict(*args, **kwargs) From 52bc96af7839262daaca918844c9f87d79d067ed Mon Sep 17 00:00:00 2001 From: ImMin5 Date: Wed, 22 Nov 2023 19:33:27 +0900 Subject: [PATCH 29/45] feat: add get_public_key api at Domain service (#73) Signed-off-by: ImMin5 --- src/spaceone/identity/interface/grpc/domain.py | 6 ++++-- src/spaceone/identity/service/domain_service.py | 16 ++++++++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/spaceone/identity/interface/grpc/domain.py b/src/spaceone/identity/interface/grpc/domain.py index 3e3f817b..71ab361a 100644 --- a/src/spaceone/identity/interface/grpc/domain.py +++ b/src/spaceone/identity/interface/grpc/domain.py @@ -1,4 +1,6 @@ from spaceone.core.pygrpc import BaseAPI +from spaceone.api.core.v2 import handler_pb2 +from google.protobuf.json_format import ParseDict from spaceone.api.identity.v2 import domain_pb2, domain_pb2_grpc from spaceone.identity.service.domain_service import DomainService @@ -53,7 +55,7 @@ def get_public_key(self, request, context): params, metadata = self.parse_request(request, context) domain_svc = DomainService(metadata) response: dict = domain_svc.get_public_key(params) - return self.dict_to_message(response) + return ParseDict(response, handler_pb2.AuthenticationResponse()) def list(self, request, context): params, metadata = self.parse_request(request, context) @@ -65,4 +67,4 @@ def stat(self, request, context): params, metadata = self.parse_request(request, context) domain_svc = DomainService(metadata) response: dict = domain_svc.stat(params) - return self.struct_to_message(response) + return self.dict_to_message(response) diff --git a/src/spaceone/identity/service/domain_service.py b/src/spaceone/identity/service/domain_service.py index fdac2bcf..92fe3a5d 100644 --- a/src/spaceone/identity/service/domain_service.py +++ b/src/spaceone/identity/service/domain_service.py @@ -10,6 +10,7 @@ ) from spaceone.identity.manager.domain_manager import DomainManager +from spaceone.identity.manager.domain_secret_manager import DomainSecretManager from spaceone.identity.manager.user_manager import UserManager from spaceone.identity.model.domain.request import * from spaceone.identity.model.domain.response import * @@ -21,6 +22,7 @@ class DomainService(BaseService): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.domain_mgr = DomainManager() + self.domain_secret_mgr = DomainSecretManager() self.user_mgr = UserManager() # self.role_manager = RoleManager() @@ -39,12 +41,16 @@ def create(self, params: DomainCreateRequest) -> Union[DomainResponse, dict]: """ domain_vo = self.domain_mgr.create_domain(params.dict()) + + # create domain secret + self.domain_secret_mgr.create_domain_secret(domain_vo.domain_id) + + # create admin user with policy and role admin = params.admin admin["auth_type"] = "LOCAL" admin["user_type"] = "USER" admin["domain_id"] = domain_vo.domain_id - # create admin user with policy and role self.user_mgr.create_user(admin) return DomainResponse(**domain_vo.to_dict()) @@ -150,7 +156,13 @@ def get_public_key( Returns: DomainSecretResponse: """ - return {} + pub_jwk = self.domain_secret_mgr.get_domain_public_key(params.domain_id) + return DomainSecretResponse( + **{ + "public_key": str(pub_jwk), + "domain_id": params.domain_id, + } + ) @transaction @append_query_filter(["domain_id", "name", "state"]) From e75e63c403adf66c670a890aa6d4f9b09ac07bc8 Mon Sep 17 00:00:00 2001 From: ImMin5 Date: Wed, 22 Nov 2023 22:54:54 +0900 Subject: [PATCH 30/45] feat: add type hint at domain_manager.py(#73) Signed-off-by: ImMin5 --- src/spaceone/identity/manager/domain_manager.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/spaceone/identity/manager/domain_manager.py b/src/spaceone/identity/manager/domain_manager.py index b368398a..25087966 100644 --- a/src/spaceone/identity/manager/domain_manager.py +++ b/src/spaceone/identity/manager/domain_manager.py @@ -59,7 +59,7 @@ def _rollback(old_data): return domain_vo - def disable_domain(self, domain_id): + def disable_domain(self, domain_id: str) -> Domain: def _rollback(old_data): _LOGGER.info( f'[disable_domain._rollback] Revert Data : {old_data["name"]} ({old_data["domain_id"]})' @@ -76,13 +76,16 @@ def _rollback(old_data): return domain_vo - def get_domain(self, domain_id): + def get_domain(self, domain_id: str) -> Domain: return self.domain_model.get(domain_id=domain_id) - def list_domains(self, query): + def get_domain_by_name(self, name: str) -> Domain: + return self.domain_model.get(name=name) + + def list_domains(self, query: dict) -> dict: return self.domain_model.query(**query) - def stat_domains(self, query): + def stat_domains(self, query: dict) -> dict: return self._convert_stat_request_result(self.domain_model.stat(**query)) @staticmethod From 848149d101b4c0effc010337b86ff54893ac00e8 Mon Sep 17 00:00:00 2001 From: ImMin5 Date: Wed, 22 Nov 2023 22:56:13 +0900 Subject: [PATCH 31/45] feat: modify field's name 'Auth_type' -> 'auth_type' at UserRequest model(#73) Signed-off-by: ImMin5 --- src/spaceone/identity/model/user/request.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spaceone/identity/model/user/request.py b/src/spaceone/identity/model/user/request.py index d5240c21..f65ac435 100644 --- a/src/spaceone/identity/model/user/request.py +++ b/src/spaceone/identity/model/user/request.py @@ -124,7 +124,7 @@ class UserSearchQueryRequest(BaseModel): state: Union[State, None] = None email: Union[str, None] = None user_type: Union[UserType, None] = None - AuthType: Union[AuthType, None] = None + auth_type: Union[AuthType, None] = None workspace_id: Union[str, None] = None domain_id: str From 5705ee722a6cb7ab1c135d556eef72125a956fde Mon Sep 17 00:00:00 2001 From: ImMin5 Date: Wed, 22 Nov 2023 22:59:04 +0900 Subject: [PATCH 32/45] feat: add User service, manager and model (#73) Signed-off-by: ImMin5 --- src/spaceone/identity/connector/__init__.py | 2 + .../identity/connector/smtp_connector.py | 52 ++ .../identity/manager/email_manager.py | 130 +++++ .../identity/manager/mfa_manager/__init__.py | 79 +++ .../manager/mfa_manager/email_mfa_manger.py | 85 +++ src/spaceone/identity/manager/user_manager.py | 50 +- src/spaceone/identity/service/user_service.py | 549 +++++++++++++++++- 7 files changed, 912 insertions(+), 35 deletions(-) create mode 100644 src/spaceone/identity/connector/__init__.py create mode 100644 src/spaceone/identity/connector/smtp_connector.py create mode 100644 src/spaceone/identity/manager/email_manager.py create mode 100644 src/spaceone/identity/manager/mfa_manager/__init__.py create mode 100644 src/spaceone/identity/manager/mfa_manager/email_mfa_manger.py diff --git a/src/spaceone/identity/connector/__init__.py b/src/spaceone/identity/connector/__init__.py new file mode 100644 index 00000000..ad992e61 --- /dev/null +++ b/src/spaceone/identity/connector/__init__.py @@ -0,0 +1,2 @@ +from spaceone.identity.connector.smtp_connector import SMTPConnector +from spaceone.identity.connector.auth_plugin_connector import AuthPluginConnector diff --git a/src/spaceone/identity/connector/smtp_connector.py b/src/spaceone/identity/connector/smtp_connector.py new file mode 100644 index 00000000..34375b06 --- /dev/null +++ b/src/spaceone/identity/connector/smtp_connector.py @@ -0,0 +1,52 @@ +import logging +import smtplib +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText + +from spaceone.identity.error.error_smtp import * + +from spaceone.core.connector import BaseConnector + + +__all__ = ["SMTPConnector"] + +_LOGGER = logging.getLogger(__name__) + + +class SMTPConnector(BaseConnector): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.smtp = None + host = self.config.get("host") + port = self.config.get("port") + user = self.config.get("user") + password = self.config.get("password") + self.from_email = self.config.get("from_email") + self.set_smtp(host, port, user, password) + + def set_smtp(self, host, port, user, password): + try: + self.smtp = smtplib.SMTP(host, port) + self.smtp.connect(host, port) + self.smtp.ehlo() + self.smtp.starttls() + self.smtp.login(user, password) + except Exception as e: + _LOGGER.error(f"[set_smtp] set smtp failed : Please check smtp config {e}") + raise ERROR_SMTP_CONNECTION_FAILED() + + def send_email(self, to_emails, subject, contents): + multipart_msg = MIMEMultipart("alternative") + + multipart_msg["Subject"] = subject + multipart_msg["From"] = self.from_email + multipart_msg["To"] = to_emails + + multipart_msg.attach(MIMEText(contents, "html")) + + self.smtp.sendmail( + self.from_email, to_emails.split(","), multipart_msg.as_string() + ) + + def quit_smtp(self): + self.smtp.quit() diff --git a/src/spaceone/identity/manager/email_manager.py b/src/spaceone/identity/manager/email_manager.py new file mode 100644 index 00000000..7e54f1b2 --- /dev/null +++ b/src/spaceone/identity/manager/email_manager.py @@ -0,0 +1,130 @@ +import logging +import os + +from jinja2 import Environment, FileSystemLoader, select_autoescape + +from spaceone.core import config, utils +from spaceone.core.manager import BaseManager +from spaceone.identity_v1.connector.smtp_connector import SMTPConnector + +_LOGGER = logging.getLogger(__name__) + +TEMPLATE_PATH = os.path.join(os.path.dirname(__file__), f"../template") +JINJA_ENV = Environment( + loader=FileSystemLoader(searchpath=TEMPLATE_PATH), autoescape=select_autoescape() +) + +LANGUAGE_MAPPER = { + "default": { + "reset_password": "Reset your password", + "temp_password": "Your password has been changed", + "verify_email": "Verify your notification email", + }, + "ko": { + "reset_password": "비밀번호 재설정 안내", + "temp_password": "임시 비밀번호 발급 안내", + "verify_email": "알림전용 이메일 계정 인증 안내", + }, + "en": { + "reset_password": "Reset your password", + "temp_password": "Your password has been changed", + "verify_email": "Verify your notification email", + }, + "ja": { + "reset_password": "パスワードリセットのご案内", + "temp_password": "仮パスワード発行のご案内", + "verify_email": "通知メールアカウント認証のご案内", + }, +} + + +class EmailManager(BaseManager): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.smtp_connector: SMTPConnector = self.locator.get_connector("SMTPConnector") + + def send_reset_password_email(self, user_id, email, reset_password_link, language): + service_name = self._get_service_name() + language_map_info = LANGUAGE_MAPPER.get(language, "default") + + template = JINJA_ENV.get_template( + f"reset_pwd_link_when_pw_forgotten_{language}.html" + ) + email_contents = template.render( + user_name=user_id, + reset_password_link=reset_password_link, + service_name=service_name, + ) + subject = f'[{service_name}] {language_map_info["reset_password"]}' + + self.smtp_connector.send_email(email, subject, email_contents) + + def send_temporary_password_email( + self, user_id, email, console_link, temp_password, language + ): + service_name = self._get_service_name() + language_map_info = LANGUAGE_MAPPER.get(language, "default") + + template = JINJA_ENV.get_template(f"temp_pwd_when_pw_forgotten_{language}.html") + email_contents = template.render( + user_name=user_id, + temp_password=temp_password, + service_name=service_name, + login_link=console_link, + ) + subject = f'[{service_name}] {language_map_info["temp_password"]}' + + self.smtp_connector.send_email(email, subject, email_contents) + + def send_reset_password_email_when_user_added( + self, user_id, email, reset_password_link, language + ): + service_name = self._get_service_name() + language_map_info = LANGUAGE_MAPPER.get(language, "default") + + template = JINJA_ENV.get_template( + f"reset_pwd_link_when_user_added_{language}.html" + ) + email_contents = template.render( + user_name=user_id, + reset_password_link=reset_password_link, + service_name=service_name, + ) + subject = f'[{service_name}] {language_map_info["reset_password"]}' + + self.smtp_connector.send_email(email, subject, email_contents) + + def send_temporary_password_email_when_user_added( + self, user_id, email, console_link, temp_password, language + ): + service_name = self._get_service_name() + language_map_info = LANGUAGE_MAPPER.get(language, "default") + + template = JINJA_ENV.get_template(f"temp_pwd_when_user_added_{language}.html") + email_contents = template.render( + user_name=user_id, + temp_password=temp_password, + service_name=service_name, + login_link=console_link, + ) + subject = f'[{service_name}] {language_map_info["temp_password"]}' + + self.smtp_connector.send_email(email, subject, email_contents) + + def send_verification_email(self, user_id, email, verification_code, language): + service_name = self._get_service_name() + language_map_info = LANGUAGE_MAPPER.get(language, "default") + + template = JINJA_ENV.get_template(f"verification_code_{language}.html") + email_contents = template.render( + user_name=user_id, + verification_code=verification_code, + service_name=service_name, + ) + subject = f'[{service_name}] {language_map_info["verify_email"]}' + + self.smtp_connector.send_email(email, subject, email_contents) + + @staticmethod + def _get_service_name(): + return config.get_global("EMAIL_SERVICE_NAME") diff --git a/src/spaceone/identity/manager/mfa_manager/__init__.py b/src/spaceone/identity/manager/mfa_manager/__init__.py new file mode 100644 index 00000000..7eeb624c --- /dev/null +++ b/src/spaceone/identity/manager/mfa_manager/__init__.py @@ -0,0 +1,79 @@ +import logging +import random +from abc import abstractmethod, ABC, ABCMeta + +from spaceone.core import config, utils, cache +from spaceone.core.manager import BaseManager + +from spaceone.identity.error.error_mfa import ERROR_NOT_SUPPORTED_MFA_TYPE + +__all__ = ["BaseMFAManager", "MFAManager"] +_LOGGER = logging.getLogger(__name__) + + +class BaseMFAManager(BaseManager, ABC): + @abstractmethod + def enable_mfa(self, **kwargs): + pass + + @abstractmethod + def disable_mfa(self, **kwargs): + pass + + def confirm_mfa(self, **kwargs): + pass + + def _load_conf(self): + identity_conf = config.get_global("IDENTITY") or {} + mfa_conf = identity_conf.get("token", {}) + self.CONST_MFA_VERIFICATION_CODE_TIMEOUT = mfa_conf.get( + "mfa_verify_code_timeout", 300 + ) + + +class MFAManager(BaseMFAManager, metaclass=ABCMeta): + mfa_type: str + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._load_conf() + + def enable_mfa(self, **kwargs): + raise NotImplementedError("MFAManager.enable_mfa not implemented!") + + def disable_mfa(self, **kwargs): + raise NotImplementedError("MFAManager.disable_mfa not implemented!") + + def confirm_mfa(self, **kwargs): + raise NotImplementedError("MFAManager.confirm_mfa not implemented!") + + def create_mfa_verify_code(self, user_id, domain_id): + if cache.is_set(): + verify_code = self._generate_verify_code() + cache.delete(f"mfa-verify-code:{domain_id}:{user_id}") + cache.set( + f"mfa-verify-code:{domain_id}:{user_id}", + verify_code, + expire=self.CONST_MFA_VERIFICATION_CODE_TIMEOUT, + ) + return verify_code + + @classmethod + def get_manager_by_mfa_type(cls, mfa_type): + for subclass in cls.__subclasses__(): + if subclass.mfa_type == mfa_type: + return subclass() + raise ERROR_NOT_SUPPORTED_MFA_TYPE(support_mfa_types=["EMAIL"]) + + @staticmethod + def check_mfa_verify_code(user_id, domain_id, verify_code): + if cache.is_set(): + cached_verify_code = cache.get(f"mfa-verify-code:{domain_id}:{user_id}") + if cached_verify_code == verify_code: + cache.delete(f"mfa-verify-code:{domain_id}:{user_id}") + return True + return False + + @staticmethod + def _generate_verify_code(): + return str(random.randint(100000, 999999)) diff --git a/src/spaceone/identity/manager/mfa_manager/email_mfa_manger.py b/src/spaceone/identity/manager/mfa_manager/email_mfa_manger.py new file mode 100644 index 00000000..07b8615d --- /dev/null +++ b/src/spaceone/identity/manager/mfa_manager/email_mfa_manger.py @@ -0,0 +1,85 @@ +import logging +import os + +from jinja2 import Environment, FileSystemLoader, select_autoescape + +from spaceone.core import config +from spaceone.identity.connector.smtp_connector import SMTPConnector +from spaceone.identity.manager.mfa_manager import MFAManager + +_LOGGER = logging.getLogger(__name__) + +TEMPLATE_PATH = os.path.join(os.path.dirname(__file__), f"../../template") +JINJA_ENV = Environment( + loader=FileSystemLoader(searchpath=TEMPLATE_PATH), autoescape=select_autoescape() +) + +LANGUAGE_MAPPER = { + "default": { + "verify_mfa_email": "Verify your MFA email", + "authentication_mfa_email": "Your multi-factor authentication code.", + }, + "ko": { + "verify_mfa_email": "MFA 이메일 계정 인증 안내", + "authentication_mfa_email": "MFA 인증 코드 발송", + }, + "en": { + "verify_mfa_email": "Verify your MFA email", + "authentication_mfa_email": "Your multi-factor authentication code.", + }, + "ja": { + "verify_mfa_email": "MFAメールアカウント認証のご案内", + "authentication_mfa_email": "MFA認証コード送信", + }, +} + + +class EmailMFAManager(MFAManager): + mfa_type = "EMAIL" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.smtp_connector: SMTPConnector = self.locator.get_connector("SMTPConnector") + + def enable_mfa(self, user_id, domain_id, user_mfa, language): + self.send_mfa_verify_email( + user_id, domain_id, user_mfa["options"].get("email"), language + ) + + def disable_mfa(self, user_id, domain_id, user_mfa, language): + self.send_mfa_verify_email( + user_id, domain_id, user_mfa["options"].get("email"), language + ) + + def confirm_mfa(self, user_id, domain_id, verify_code): + return self.check_mfa_verify_code(user_id, domain_id, verify_code) + + def send_mfa_verify_email(self, user_id, domain_id, email, language): + service_name = self._get_service_name() + language_map_info = LANGUAGE_MAPPER.get(language, "default") + verify_code = self.create_mfa_verify_code(user_id, domain_id) + + template = JINJA_ENV.get_template(f"verification_MFA_code_{language}.html") + email_contents = template.render( + user_name=user_id, verification_code=verify_code, service_name=service_name + ) + subject = f'[{service_name}] {language_map_info["verify_mfa_email"]}' + + self.smtp_connector.send_email(email, subject, email_contents) + + def send_mfa_authentication_email(self, user_id, domain_id, email, language): + service_name = self._get_service_name() + language_map_info = LANGUAGE_MAPPER.get(language, "default") + verify_code = self.create_mfa_verify_code(user_id, domain_id) + + template = JINJA_ENV.get_template(f"authentication_code_{language}.html") + email_contents = template.render( + user_name=user_id, verification_code=verify_code, service_name=service_name + ) + subject = f'[{service_name}] {language_map_info["authentication_mfa_email"]}' + + self.smtp_connector.send_email(email, subject, email_contents) + + @staticmethod + def _get_service_name(): + return config.get_global("EMAIL_SERVICE_NAME") diff --git a/src/spaceone/identity/manager/user_manager.py b/src/spaceone/identity/manager/user_manager.py index f6e99d2a..17e38af6 100644 --- a/src/spaceone/identity/manager/user_manager.py +++ b/src/spaceone/identity/manager/user_manager.py @@ -1,6 +1,7 @@ import logging import re +from spaceone.core import cache from spaceone.core.manager import BaseManager from spaceone.identity.lib.cipher import PasswordCipher @@ -16,7 +17,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.user_model = User - def create_user(self, params, is_first_login_user=False): + def create_user(self, params: dict, is_first_login_user=False) -> User: def _rollback(vo: User): _LOGGER.info( f"[create_user._rollback] Delete user : {vo.name} ({vo.user_id})" @@ -64,7 +65,7 @@ def _rollback(vo: User): return user_vo - def update_user_by_vo(self, params, user_vo): + def update_user_by_vo(self, params: dict, user_vo: User) -> User: def _rollback(old_data): _LOGGER.info( f'[update_user._rollback] Revert Data : {old_data["name"], ({old_data["user_id"]})}' @@ -93,20 +94,57 @@ def _rollback(old_data): return user_vo.update(params) - def get_user(self, user_id, domain_id): + @staticmethod + def delete_user_by_vo(user_vo: User) -> None: + domain_id = user_vo.domain_id + user_id = user_vo.user_id + user_vo.delete() + + cache.delete_pattern(f"user-state:{domain_id}:{user_id}") + cache.delete_pattern(f"role-bindings:{domain_id}:{user_id}*") + cache.delete_pattern(f"user-permissions:{domain_id}:{user_id}*") + cache.delete_pattern(f"user-scopes:{domain_id}:{user_id}*") + + def enable_user(self, user_vo: User) -> User: + def _rollback(old_data): + _LOGGER.info(f"[enable_user._rollback] Revert Data : {old_data}") + user_vo.update(old_data) + + if user_vo.state != "ENABLED": + self.transaction.add_rollback(_rollback, user_vo.to_dict()) + user_vo.update({"state": "ENABLED"}) + + cache.delete_pattern(f"user-state:{user_vo.domain_id}:{user_vo.user_id}") + + return user_vo + + def disable_user(self, user_vo: User) -> User: + def _rollback(old_data): + _LOGGER.info(f"[disable_user._rollback] Revert Data : {old_data}") + user_vo.update(old_data) + + if user_vo.state != "DISABLED": + self.transaction.add_rollback(_rollback, user_vo.to_dict()) + user_vo.update({"state": "DISABLED"}) + + cache.delete_pattern(f"user-state:{user_vo.domain_id}:{user_vo.user_id}") + + return user_vo + + def get_user(self, user_id: str, domain_id: str) -> User: return self.user_model.get(user_id=user_id, domain_id=domain_id) - def list_users(self, query): + def list_users(self, query: dict) -> tuple[list, int]: return self.user_model.query(**query) @staticmethod - def _check_user_id_format(user_id): + def _check_user_id_format(user_id: str) -> None: rule = r"[^@]+@[^@]+\.[^@]+" if not re.match(rule, user_id): raise ERROR_INCORRECT_USER_ID_FORMAT(rule="Email format required.") @staticmethod - def _check_password_format(password): + def _check_password_format(password: str) -> None: if len(password) < 8: raise ERROR_INCORRECT_PASSWORD_FORMAT(rule="At least 9 characters long.") elif not re.search("[a-z]", password): diff --git a/src/spaceone/identity/service/user_service.py b/src/spaceone/identity/service/user_service.py index 430c7662..665e43aa 100644 --- a/src/spaceone/identity/service/user_service.py +++ b/src/spaceone/identity/service/user_service.py @@ -1,11 +1,30 @@ import logging -from typing import Union +import pytz +import random +import re +import string +from typing import Union, List -from spaceone.core.service import BaseService, transaction, convert_model +from spaceone.core.service import ( + BaseService, + transaction, + convert_model, + append_query_filter, + append_keyword_filter, +) +from spaceone.core import config from spaceone.identity.error.error_mfa import * -from spaceone.identity.manager.user_manager import UserManager +from spaceone.identity.error.error_user import * +from spaceone.identity.manager.email_manager import EmailManager +from spaceone.identity.model.domain.database import Domain +from spaceone.identity.manager.domain_manager import DomainManager +from spaceone.identity.manager.domain_secret_manager import DomainSecretManager from spaceone.identity.manager.mfa_manager import MFAManager +from spaceone.identity.manager.token_manager.local_token_manager import ( + LocalTokenManager, +) +from spaceone.identity.manager.user_manager import UserManager from spaceone.identity.model.user.request import * from spaceone.identity.model.user.response import * @@ -16,42 +35,292 @@ class UserService(BaseService): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.user_mgr = UserManager() + self.domain_mgr = DomainManager() + self.domain_secret_mgr = DomainSecretManager() + self.email_manager = EmailManager() - @transaction + @transaction(append_meta={"authorization.scope": "DOMAIN_OR_WORKSPACE"}) @convert_model def create(self, params: UserCreateRequest) -> Union[UserResponse, dict]: - return {} + """Create user + Args: + params (dict): { + 'user_id': 'str', # required + 'password': 'str', + 'name': 'str', + 'email': 'str', + 'user_type': 'str', # required + 'auth_type': 'str', + 'language': 'str', + 'timezone': 'str', + 'tags': 'dict', + 'reset_password': 'bool', + 'role_binding': 'dict', + 'domain_id': 'str' # required + } + Returns: + UserResponse: + """ - @transaction + user_id = params.user_id + email = params.email + params.user_type = params.user_type or "USER" + params.auth_type = params.auth_type or "LOCAL" + reset_password = params.reset_password or False + domain_id = params.domain_id + + domain_vo = self.domain_mgr.get_domain(domain_id) + + default_language = self._get_default_config(domain_vo, "LANGUAGE") + default_timezone = self._get_default_config(domain_vo, "TIMEZONE") + + self._check_user_type_and_auth_type(params.user_type, params.auth_type) + + if "language" not in params: + params.language = default_language + + if "timezone" not in params: + params.timezone = default_timezone + + if "timezone" in params: + self._check_timezone(params.timezone) + + if reset_password: + self._check_reset_password_eligibility(user_id, params.auth_type, email) + + email_manager = EmailManager() + language = params.language + required_actions = {"required_actions": ["UPDATE_PASSWORD"]} + params.password = self._generate_temporary_password() + + reset_password_type = config.get_global("RESET_PASSWORD_TYPE") + if reset_password_type == "ACCESS_TOKEN": + token = self._issue_temporary_token(user_id, domain_id) + reset_password_link = self._get_console_sso_url( + domain_id, token["access_token"] + ) + + params = params.dict() + params.update(required_actions) + + user_vo = self.user_mgr.create_user(params, domain_vo) + email_manager.send_reset_password_email_when_user_added( + user_id, email, reset_password_link, language + ) + else: + temp_password = params.password + console_link = self._get_console_url(domain_id) + + user_vo = self.user_mgr.create_user(params.dict(), domain_vo) + email_manager.send_temporary_password_email_when_user_added( + user_id, email, console_link, temp_password, language + ) + else: + user_vo = self.user_mgr.create_user(params.dict(), domain_vo) + + return UserResponse(**user_vo.to_dict()) + + @transaction(append_meta={"authorization.scope": "DOMAIN_OR_USER"}) @convert_model def update(self, params: UserUpdateRequest) -> Union[UserResponse, dict]: - return {} + """ + Args: + params (dict): { + 'user_id': 'str', + 'password': 'str', + 'name': 'str', + 'email': 'str', + 'language': 'str', + 'timezone': 'str', + 'tags': 'dict', + 'reset_password': 'bool', + 'domain_id': 'str' + } + Returns: + UserResponse: - @transaction + """ + + if "timezone" in params: + self._check_timezone(params.timezone) + + user_vo = self.user_mgr.get_user(params.user_id, params.domain_id) + + if params.reset_password: + domain_id = params.domain_id + user_id = user_vo.user_id + auth_type = user_vo.auth_type + email = params.email or user_vo.email + email_verified = user_vo.email_verified + + language = user_vo.language + + self._check_reset_password_eligibility(user_id, auth_type, email) + + if email_verified is False: + raise ERROR_VERIFICATION_UNAVAILABLE(user_id=user_id) + + reset_password_type = config.get_global("RESET_PASSWORD_TYPE") + email_manager = EmailManager() + temp_password = self._generate_temporary_password() + params.password = temp_password + + user_vo = self.user_mgr.update_user_by_vo(params, user_vo) + user_vo = self.user_mgr.update_user_by_vo( + {"required_actions": ["UPDATE_PASSWORD"]}, user_vo + ) + + if reset_password_type == "ACCESS_TOKEN": + token = self._issue_temporary_token(user_id, domain_id) + reset_password_link = self._get_console_sso_url( + domain_id, token["access_token"] + ) + + email_manager.send_reset_password_email( + user_id, email, reset_password_link, language + ) + elif reset_password_type == "PASSWORD": + console_link = self._get_console_url(domain_id) + + email_manager.send_temporary_password_email( + user_id, email, console_link, temp_password, language + ) + else: + user_vo = self.user_mgr.update_user_by_vo(params, user_vo) + + return UserResponse(**user_vo.to_dict()) + + @transaction(append_meta={"authorization.scope": "DOMAIN_OR_USER"}) @convert_model def verify_email(self, params: UserVerifyEmailRequest) -> None: - pass + """Verify email - @transaction + Args: + params (dict): { + 'user_id': 'str', + 'email': 'str', + 'force': 'bool', + 'domain_id': 'str' + } + + + Returns: + None + """ + user_id = params.user_id + domain_id = params.domain_id + + user_vo = self.user_mgr.get_user(user_id, domain_id) + email = params.email or user_vo.email + force = params.force or False + + params = params.dict() + if force: + params.update({"email_verified": True}) + self.user_mgr.update_user_by_vo(params, user_vo) + else: + params.update({"email_verified": False}) + user_vo = self.user_mgr.update_user_by_vo(params, user_vo) + + token_manager = LocalTokenManager() + verify_code = token_manager.create_verify_code(user_id, domain_id) + + email_manager = EmailManager() + email_manager.send_verification_email( + user_id, email, verify_code, user_vo.language + ) + + @transaction(append_meta={"authorization.scope": "DOMAIN_OR_USER"}) @convert_model def confirm_email( self, params: UserConfirmEmailRequest ) -> Union[UserResponse, dict]: - return {} + """Confirm email + + Args: + params (dict): { + 'user_id': 'str', + 'verify_code': 'str', + 'domain_id': 'str' + } + + + Returns: + None + """ + + user_id = params.user_id + domain_id = params.domain_id + verify_code = params.verify_code + + token_manager = LocalTokenManager() + if token_manager.check_verify_code(user_id, domain_id, verify_code): + user_vo = self.user_mgr.get_user(user_id, domain_id) + + params = params.dict() + params["email_verified"] = True + return self.user_mgr.update_user_by_vo(params, user_vo) + else: + raise ERROR_INVALID_VERIFY_CODE(verify_code=verify_code) @transaction @convert_model def reset_password(self, params: UserResetPasswordRequest) -> None: - pass + """Reset password - @transaction + Args: + params (dict): { + 'user_id': 'str', + 'domain_id': 'str' + } + + Returns: + None + """ + + user_id = params.user_id + domain_id = params.domain_id + + user_vo = self.user_mgr.get_user(user_id, domain_id) + auth_type = user_vo.auth_type + email = user_vo.email + language = user_vo.language + + self._check_reset_password_eligibility(user_id, auth_type, email) + + if user_vo.email_verified is False: + raise ERROR_VERIFICATION_UNAVAILABLE(user_id=user_id) + + reset_password_type = config.get_global("RESET_PASSWORD_TYPE", "ACCESS_TOKEN") + email_manager = EmailManager() + if reset_password_type == "ACCESS_TOKEN": + token = self._issue_temporary_token(user_id, domain_id) + reset_password_link = self._get_console_sso_url( + domain_id, token["access_token"] + ) + email_manager.send_reset_password_email( + user_id, email, reset_password_link, language + ) + + elif reset_password_type == "PASSWORD": + temp_password = self._generate_temporary_password() + self.user_mgr.update_user_by_vo({"password": temp_password}, user_vo) + self.user_mgr.update_user_by_vo( + {"required_actions": ["UPDATE_PASSWORD"]}, user_vo + ) + console_link = self._get_console_url(domain_id) + email_manager.send_temporary_password_email( + user_id, email, console_link, temp_password, language + ) + + @transaction(append_meta={"authorization.scope": "DOMAIN"}) @convert_model def set_required_actions( self, params: UserSetRequiredActionsRequest ) -> Union[UserResponse, dict]: return {} - @transaction + @transaction(append_meta={"authorization.scope": "USER"}) @convert_model def enable_mfa(self, params: UserEnableMFARequest) -> Union[UserResponse, dict]: """Enable MFA @@ -93,42 +362,264 @@ def enable_mfa(self, params: UserEnableMFARequest) -> Union[UserResponse, dict]: return UserResponse(**user_vo.to_dict()) - @transaction + @transaction(append_meta={"authorization.scope": "DOMAIN_OR_USER"}) @convert_model def disable_mfa(self, params: UserDisableMFARequest) -> Union[UserResponse, dict]: - return {} + """Disable MFA - @transaction + Args: + params (dict): { + 'user_id': 'str', + 'force': 'bool', + 'domain_id': 'str' + Returns: + Empty: + """ + + user_id = params.user_id + domain_id = params.domain_id + force = params.force or False + + user_vo = self.user_mgr.get_user(user_id, domain_id) + user_mfa = user_vo.mfa.to_dict() if user_vo.mfa else {} + mfa_type = user_mfa.get("mfa_type") + + if user_mfa.get("state", "DISABLED") == "DISABLED" or mfa_type is None: + raise ERROR_MFA_ALREADY_DISABLED(user_id=user_id) + + mfa_manager = MFAManager.get_manager_by_mfa_type(mfa_type) + + if force: + user_mfa = {"state": "DISABLED"} + self.user_mgr.update_user_by_vo({"mfa": user_mfa}, user_vo) + elif mfa_type == "EMAIL": + mfa_manager.disable_mfa(user_id, domain_id, user_mfa, user_vo.language) + + return UserResponse(**user_vo.to_dict()) + + @transaction(append_meta={"authorization.scope": "USER"}) @convert_model def confirm_mfa(self, params: UserConfirmMFARequest) -> Union[UserResponse, dict]: - return {} + """Confirm MFA + Args: + params (dict): { + 'user_id': 'str', + 'verify_code': 'str', + 'domain_id': 'str' + Returns: + Empty: + """ - @transaction + user_id = params.user_id + domain_id = params.domain_id + verify_code = params.verify_code + + user_vo = self.user_mgr.get_user(user_id, domain_id) + mfa_type = user_vo.mfa.mfa_type + + if not mfa_type: + raise ERROR_MFA_NOT_ENABLED(user_id=user_id) + + mfa_manager = MFAManager.get_manager_by_mfa_type(mfa_type) + + if mfa_type == "EMAIL": + if mfa_manager.confirm_mfa(user_id, domain_id, verify_code): + user_mfa = user_vo.mfa.to_dict() if user_vo.mfa else {} + if user_mfa.get("state", "DISABLED") == "ENABLED": + user_mfa = {"state": "DISABLED"} + elif user_mfa.get("state", "DISABLED") == "DISABLED": + user_mfa["state"] = "ENABLED" + self.user_mgr.update_user_by_vo({"mfa": user_mfa}, user_vo) + else: + raise ERROR_INVALID_VERIFY_CODE(verify_code=verify_code) + return UserResponse(**user_vo.to_dict()) + + @transaction(append_meta={"authorization.scope": "DOMAIN"}) @convert_model def delete(self, params: UserDeleteRequest) -> None: - pass + """Delete user - @transaction + Args: + params (dict): { + 'user_id': 'str', + 'domain_id': 'str' + } + + Returns: + None + """ + user_vo = self.user_mgr.get_user(params.user_id, params.domain_id) + # todo : check this user is last admin + self.user_mgr.delete_user_by_vo(user_vo) + + @transaction(append_meta={"authorization.scope": "DOMAIN"}) @convert_model def enable(self, params: UserEnableRequest) -> Union[UserResponse, dict]: - return {} + """Enable user - @transaction + Args: + params (dict): { + 'user_id': 'str', + 'domain_id': 'str' + } + + Returns: + user_vo (object) + """ + user_vo = self.user_mgr.get_user(params.user_id, params.domain_id) + user_vo = self.user_mgr.enable_user(user_vo) + return UserResponse(**user_vo.to_dict()) + + @transaction(append_meta={"authorization.scope": "DOMAIN"}) @convert_model def disable(self, params: UserDisableRequest) -> Union[UserResponse, dict]: - return {} + """Disable user + Args: + params (dict): { + 'user_id': 'str', + 'domain_id': 'str' + } - @transaction + Returns: + UserResponse: + """ + + user_vo = self.user_mgr.get_user(params.user_id, params.domain_id) + user_vo = self.user_mgr.disable_user(user_vo) + # todo : check this user is last admin + return UserResponse(**user_vo.to_dict()) + + @transaction(append_meta={"authorization.scope": "DOMAIN_READ"}) @convert_model def get(self, params: UserGetRequest) -> Union[UserResponse, dict]: - return {} + """Get user - @transaction + Args: + params (dict): { + 'user_id': 'str', # required + 'domain_id': 'str' # required + } + + Returns: + user_vo (object) + """ + user_vo = self.user_mgr.get_user(params.user_id, params.domain_id) + return UserResponse(**user_vo.to_dict()) + + @transaction(append_meta={"authorization.scope": "DOMAIN_READ"}) + @append_query_filter( + [ + "user_id", + "name", + "state", + "email", + "user_type", + "auth_type", + "workspace_id", + "domain_id", + ] + ) + @append_keyword_filter(["user_id", "name", "email"]) @convert_model def list(self, params: UserSearchQueryRequest) -> Union[UsersResponse, dict]: - return {} + """List users + Args: + params (dict): { + 'query': 'dict', + 'user_id': 'str', + 'name': 'str', + 'state': 'str', + 'email': 'str', + 'user_type': 'str', + 'auth_type': 'str', + 'workspace_id': 'str', + 'domain_id': 'str' + } + Returns: + UsersResponse: + """ + query = params.query or {} - @transaction + user_vos, total_count = self.user_mgr.list_users(query) + users_info = [user_vo.to_dict() for user_vo in user_vos] + return UsersResponse(results=users_info, total_count=total_count) + + @transaction(append_meta={"authorization.scope": "DOMAIN_READ"}) @convert_model def stat(self, params: UserStatQueryRequest) -> dict: return {} + + def _get_domain_name(self, domain_id: str) -> str: + domain_vo = self.domain_mgr.get_domain(domain_id) + return domain_vo.name + + def _issue_temporary_token(self, user_id: str, domain_id: str) -> dict: + identity_conf = config.get_global("identity") or {} + timeout = identity_conf.get("temporary_token_timeout", 86400) + + private_jwk = self.domain_secret_mgr.get_domain_private_key(domain_id=domain_id) + + local_token_manager = LocalTokenManager() + return local_token_manager.issue_temporary_token( + user_id, domain_id, private_jwk=private_jwk, timeout=timeout + ) + + def _get_console_sso_url(self, domain_id: str, token: str) -> str: + domain_name = self._get_domain_name(domain_id) + + console_domain = config.get_global("EMAIL_CONSOLE_DOMAIN") + console_domain = console_domain.format(domain_name=domain_name) + + return f"{console_domain}?sso_access_token={token}" + + def _get_console_url(self, domain_id): + domain_name = self._get_domain_name(domain_id) + + console_domain = config.get_global("EMAIL_CONSOLE_DOMAIN") + return console_domain.format(domain_name=domain_name) + + @staticmethod + def _get_default_config(vo: Domain, item: str) -> str: + # todo : should be developed later + DEFAULT = {"TIMEZONE": "UTC", "LANGUAGE": "en"} + return DEFAULT.get(item) + + @staticmethod + def _check_timezone(timezone): + if timezone not in pytz.all_timezones: + raise ERROR_INVALID_PARAMETER(key="timezone", reason="Timezone is invalid.") + + @staticmethod + def _check_user_type_and_auth_type(user_type, auth_type): + # Check User Type and Backend + if user_type == "API_USER": + if auth_type == "EXTERNAL": + raise ERROR_EXTERNAL_USER_NOT_ALLOWED_API_USER() + + # Check External Authentication from Domain + if auth_type == "EXTERNAL": + # todo : should be developed later + raise ERROR_NOT_ALLOWED_EXTERNAL_AUTHENTICATION() + + @staticmethod + def _check_reset_password_eligibility(user_id, auth_type, email): + if auth_type == "EXTERNAL": + raise ERROR_UNABLE_TO_RESET_PASSWORD_IN_EXTERNAL_AUTH(user_id=user_id) + elif email is None: + raise ERROR_UNABLE_TO_RESET_PASSWORD_WITHOUT_EMAIL(user_id=user_id) + + @staticmethod + def _generate_temporary_password(): + while True: + random_password = "".join( + random.choice( + string.ascii_uppercase + string.ascii_lowercase + string.digits + ) + for _ in range(12) + ) + if ( + re.search("[a-z]", random_password) + and re.search("[A-Z]", random_password) + and re.search("[0-9]", random_password) + ): + return random_password From 414ab612f739aebc0090a6b6b21d7514513c5e47 Mon Sep 17 00:00:00 2001 From: ImMin5 Date: Wed, 22 Nov 2023 23:00:22 +0900 Subject: [PATCH 33/45] feat: add templates (#73) Signed-off-by: ImMin5 --- .../template/authentication_code_en.html | 256 ++++++++++++++ .../template/authentication_code_jp.html | 255 ++++++++++++++ .../template/authentication_code_ko.html | 254 ++++++++++++++ .../reset_pwd_link_when_pw_forgotten_en.html | 316 +++++++++++++++++ .../reset_pwd_link_when_pw_forgotten_ja.html | 310 +++++++++++++++++ .../reset_pwd_link_when_pw_forgotten_ko.html | 320 +++++++++++++++++ .../reset_pwd_link_when_user_added_en.html | 305 +++++++++++++++++ .../reset_pwd_link_when_user_added_ja.html | 305 +++++++++++++++++ .../reset_pwd_link_when_user_added_ko.html | 305 +++++++++++++++++ .../temp_pwd_when_pw_forgotten_en.html | 319 +++++++++++++++++ .../temp_pwd_when_pw_forgotten_ja.html | 318 +++++++++++++++++ .../temp_pwd_when_pw_forgotten_ko.html | 322 ++++++++++++++++++ .../template/temp_pwd_when_user_added_en.html | 313 +++++++++++++++++ .../template/temp_pwd_when_user_added_ja.html | 315 +++++++++++++++++ .../template/temp_pwd_when_user_added_ko.html | 316 +++++++++++++++++ .../template/verification_MFA_code_en.html | 256 ++++++++++++++ .../template/verification_MFA_code_jp.html | 255 ++++++++++++++ .../template/verification_MFA_code_ko.html | 254 ++++++++++++++ .../template/verification_code_en.html | 256 ++++++++++++++ .../template/verification_code_ja.html | 254 ++++++++++++++ .../template/verification_code_ko.html | 254 ++++++++++++++ 21 files changed, 6058 insertions(+) create mode 100644 src/spaceone/identity/template/authentication_code_en.html create mode 100644 src/spaceone/identity/template/authentication_code_jp.html create mode 100644 src/spaceone/identity/template/authentication_code_ko.html create mode 100644 src/spaceone/identity/template/reset_pwd_link_when_pw_forgotten_en.html create mode 100644 src/spaceone/identity/template/reset_pwd_link_when_pw_forgotten_ja.html create mode 100644 src/spaceone/identity/template/reset_pwd_link_when_pw_forgotten_ko.html create mode 100644 src/spaceone/identity/template/reset_pwd_link_when_user_added_en.html create mode 100644 src/spaceone/identity/template/reset_pwd_link_when_user_added_ja.html create mode 100644 src/spaceone/identity/template/reset_pwd_link_when_user_added_ko.html create mode 100644 src/spaceone/identity/template/temp_pwd_when_pw_forgotten_en.html create mode 100644 src/spaceone/identity/template/temp_pwd_when_pw_forgotten_ja.html create mode 100644 src/spaceone/identity/template/temp_pwd_when_pw_forgotten_ko.html create mode 100644 src/spaceone/identity/template/temp_pwd_when_user_added_en.html create mode 100644 src/spaceone/identity/template/temp_pwd_when_user_added_ja.html create mode 100644 src/spaceone/identity/template/temp_pwd_when_user_added_ko.html create mode 100644 src/spaceone/identity/template/verification_MFA_code_en.html create mode 100644 src/spaceone/identity/template/verification_MFA_code_jp.html create mode 100644 src/spaceone/identity/template/verification_MFA_code_ko.html create mode 100644 src/spaceone/identity/template/verification_code_en.html create mode 100644 src/spaceone/identity/template/verification_code_ja.html create mode 100644 src/spaceone/identity/template/verification_code_ko.html diff --git a/src/spaceone/identity/template/authentication_code_en.html b/src/spaceone/identity/template/authentication_code_en.html new file mode 100644 index 00000000..166087f0 --- /dev/null +++ b/src/spaceone/identity/template/authentication_code_en.html @@ -0,0 +1,256 @@ + + + + + + + + + + + + + + +
+
+ Use this to verify your MFA email address. +
+ + + + + + +
+ + + + + + + +
+

+ +

+
+
+ + diff --git a/src/spaceone/identity/template/authentication_code_jp.html b/src/spaceone/identity/template/authentication_code_jp.html new file mode 100644 index 00000000..294fe5ab --- /dev/null +++ b/src/spaceone/identity/template/authentication_code_jp.html @@ -0,0 +1,255 @@ + + + + + + + + + + + + + + +
+
+ この認証コードを使用してMFAメールアドレスを認証してください。 +
+ + + + + + +
+ + + + + + + +
+

+ +

+
+
+ + diff --git a/src/spaceone/identity/template/authentication_code_ko.html b/src/spaceone/identity/template/authentication_code_ko.html new file mode 100644 index 00000000..b31cafc2 --- /dev/null +++ b/src/spaceone/identity/template/authentication_code_ko.html @@ -0,0 +1,254 @@ + + + + + + + + + + + + + + +
+
+ 본 인증코드를 사용하여 다중 인증 이메일 주소를 인증하세요. +
+ + + + + + +
+ + + + + + + +
+

+ +

+
+
+ + diff --git a/src/spaceone/identity/template/reset_pwd_link_when_pw_forgotten_en.html b/src/spaceone/identity/template/reset_pwd_link_when_pw_forgotten_en.html new file mode 100644 index 00000000..4e0359a8 --- /dev/null +++ b/src/spaceone/identity/template/reset_pwd_link_when_pw_forgotten_en.html @@ -0,0 +1,316 @@ + + + + + + + + + + + + + + +
+
+ We received a request to reset the password for your account. +
+ + + + + + +
+ + + + + + + +
+

+ +

+
+
+ + diff --git a/src/spaceone/identity/template/reset_pwd_link_when_pw_forgotten_ja.html b/src/spaceone/identity/template/reset_pwd_link_when_pw_forgotten_ja.html new file mode 100644 index 00000000..b5c371a3 --- /dev/null +++ b/src/spaceone/identity/template/reset_pwd_link_when_pw_forgotten_ja.html @@ -0,0 +1,310 @@ + + + + + + + + + + + + + + +
+
+ {{service_name}}コンソールアカウントのパスワード変更がリクエストされました。 +
+ + + + + + +
+ + + + + + + +
+

+ +

+
+
+ + diff --git a/src/spaceone/identity/template/reset_pwd_link_when_pw_forgotten_ko.html b/src/spaceone/identity/template/reset_pwd_link_when_pw_forgotten_ko.html new file mode 100644 index 00000000..1f0d8d1a --- /dev/null +++ b/src/spaceone/identity/template/reset_pwd_link_when_pw_forgotten_ko.html @@ -0,0 +1,320 @@ + + + + + + + + + + + + + + +
+
+ 비밀번호 재설정을 진행해주세요. +
+ + + + + + +
+ + + + + + + +
+

+ +

+
+
+ + diff --git a/src/spaceone/identity/template/reset_pwd_link_when_user_added_en.html b/src/spaceone/identity/template/reset_pwd_link_when_user_added_en.html new file mode 100644 index 00000000..0b64b552 --- /dev/null +++ b/src/spaceone/identity/template/reset_pwd_link_when_user_added_en.html @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + +
+
+ Reset your password to continue using {{service_name}} +
+ + + + + + +
+ + + + + + + +
+

+ +

+
+
+ + diff --git a/src/spaceone/identity/template/reset_pwd_link_when_user_added_ja.html b/src/spaceone/identity/template/reset_pwd_link_when_user_added_ja.html new file mode 100644 index 00000000..c072d4e6 --- /dev/null +++ b/src/spaceone/identity/template/reset_pwd_link_when_user_added_ja.html @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + +
+
+ お客様のアカウントが、ドメイン管理者によって{{service_name}}に新しく追加または更新されたことを検知しました。 +
+ + + + + + +
+ + + + + + + +
+

+ +

+
+
+ + diff --git a/src/spaceone/identity/template/reset_pwd_link_when_user_added_ko.html b/src/spaceone/identity/template/reset_pwd_link_when_user_added_ko.html new file mode 100644 index 00000000..51e88bfc --- /dev/null +++ b/src/spaceone/identity/template/reset_pwd_link_when_user_added_ko.html @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + +
+
+ 고객님의 계정이 도메인 관리자에 의해 {{service_name}}에 신규 추가 또는 업데이트 되었음이 확인되었습니다. +
+ + + + + + +
+ + + + + + + +
+

+ +

+
+
+ + diff --git a/src/spaceone/identity/template/temp_pwd_when_pw_forgotten_en.html b/src/spaceone/identity/template/temp_pwd_when_pw_forgotten_en.html new file mode 100644 index 00000000..f46b9803 --- /dev/null +++ b/src/spaceone/identity/template/temp_pwd_when_pw_forgotten_en.html @@ -0,0 +1,319 @@ + + + + + + + + + + + + + + +
+
+ We received a request to reset the password for your account. +
+ + + + + + +
+ + + + + + + +
+

+ +

+
+
+ + diff --git a/src/spaceone/identity/template/temp_pwd_when_pw_forgotten_ja.html b/src/spaceone/identity/template/temp_pwd_when_pw_forgotten_ja.html new file mode 100644 index 00000000..4205e502 --- /dev/null +++ b/src/spaceone/identity/template/temp_pwd_when_pw_forgotten_ja.html @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + +
+
+ 以下の仮パスワードをご案内いたします。ログイン後パスワードを変更してください。 +
+ + + + + + +
+ + + + + + + +
+

+ +

+
+
+ + diff --git a/src/spaceone/identity/template/temp_pwd_when_pw_forgotten_ko.html b/src/spaceone/identity/template/temp_pwd_when_pw_forgotten_ko.html new file mode 100644 index 00000000..56b7532e --- /dev/null +++ b/src/spaceone/identity/template/temp_pwd_when_pw_forgotten_ko.html @@ -0,0 +1,322 @@ + + + + + + + + + + + + + + +
+
+ 임시 비밀번호로 로그인하셔서 비밀번호를 변경하여 주시기 바랍니다. +
+ + + + + + +
+ + + + + + + +
+

+ +

+
+
+ + diff --git a/src/spaceone/identity/template/temp_pwd_when_user_added_en.html b/src/spaceone/identity/template/temp_pwd_when_user_added_en.html new file mode 100644 index 00000000..a51cab89 --- /dev/null +++ b/src/spaceone/identity/template/temp_pwd_when_user_added_en.html @@ -0,0 +1,313 @@ + + + + + + + + + + + + + + +
+
+ Please use the temporary password below to sign in to console and reset it. + +
+ + + + + + +
+ + + + + + + +
+

+ +

+
+
+ + diff --git a/src/spaceone/identity/template/temp_pwd_when_user_added_ja.html b/src/spaceone/identity/template/temp_pwd_when_user_added_ja.html new file mode 100644 index 00000000..ce2b9b92 --- /dev/null +++ b/src/spaceone/identity/template/temp_pwd_when_user_added_ja.html @@ -0,0 +1,315 @@ + + + + + + + + + + + + + + +
+
+ 以下の仮パスワードをご案内いたします。ログイン後パスワードを変更してください。 +
+ + + + + + +
+ + + + + + + +
+

+ +

+
+
+ + diff --git a/src/spaceone/identity/template/temp_pwd_when_user_added_ko.html b/src/spaceone/identity/template/temp_pwd_when_user_added_ko.html new file mode 100644 index 00000000..fe01ebe4 --- /dev/null +++ b/src/spaceone/identity/template/temp_pwd_when_user_added_ko.html @@ -0,0 +1,316 @@ + + + + + + + + + + + + + + +
+
+ 임시 비밀번호로 로그인하셔서 비밀번호를 변경하여 주시기 바랍니다. +
+ + + + + + +
+ + + + + + + +
+

+ +

+
+
+ + diff --git a/src/spaceone/identity/template/verification_MFA_code_en.html b/src/spaceone/identity/template/verification_MFA_code_en.html new file mode 100644 index 00000000..b688f061 --- /dev/null +++ b/src/spaceone/identity/template/verification_MFA_code_en.html @@ -0,0 +1,256 @@ + + + + + + + + + + + + + + +
+
+ Use this to verify your MFA email address. +
+ + + + + + +
+ + + + + + + +
+

+ +

+
+
+ + diff --git a/src/spaceone/identity/template/verification_MFA_code_jp.html b/src/spaceone/identity/template/verification_MFA_code_jp.html new file mode 100644 index 00000000..63f43b7f --- /dev/null +++ b/src/spaceone/identity/template/verification_MFA_code_jp.html @@ -0,0 +1,255 @@ + + + + + + + + + + + + + + +
+
+ この認証コードを使用してMFAメールアドレスを認証してください。 +
+ + + + + + +
+ + + + + + + +
+

+ +

+
+
+ + diff --git a/src/spaceone/identity/template/verification_MFA_code_ko.html b/src/spaceone/identity/template/verification_MFA_code_ko.html new file mode 100644 index 00000000..304159fb --- /dev/null +++ b/src/spaceone/identity/template/verification_MFA_code_ko.html @@ -0,0 +1,254 @@ + + + + + + + + + + + + + + +
+
+ 본 인증코드를 사용하여 다중 인증 이메일 주소를 인증하세요. +
+ + + + + + +
+ + + + + + + +
+

+ +

+
+
+ + diff --git a/src/spaceone/identity/template/verification_code_en.html b/src/spaceone/identity/template/verification_code_en.html new file mode 100644 index 00000000..cb7c5cff --- /dev/null +++ b/src/spaceone/identity/template/verification_code_en.html @@ -0,0 +1,256 @@ + + + + + + + + + + + + + + +
+
+ Use this to verify your notification email address. +
+ + + + + + +
+ + + + + + + +
+

+ +

+
+
+ + diff --git a/src/spaceone/identity/template/verification_code_ja.html b/src/spaceone/identity/template/verification_code_ja.html new file mode 100644 index 00000000..1488cda4 --- /dev/null +++ b/src/spaceone/identity/template/verification_code_ja.html @@ -0,0 +1,254 @@ + + + + + + + + + + + + + + +
+
+ この認証コードを使用して通知メールアドレスを認証してください。 +
+ + + + + + +
+ + + + + + + +
+

+ +

+
+
+ + diff --git a/src/spaceone/identity/template/verification_code_ko.html b/src/spaceone/identity/template/verification_code_ko.html new file mode 100644 index 00000000..27069889 --- /dev/null +++ b/src/spaceone/identity/template/verification_code_ko.html @@ -0,0 +1,254 @@ + + + + + + + + + + + + + + +
+
+ 본 인증코드를 사용하여 알림 이메일 주소를 인증하세요. +
+ + + + + + +
+ + + + + + + +
+

+ +

+
+
+ + From d07f83a90916331a3e8ebad8a96b32e187da74a5 Mon Sep 17 00:00:00 2001 From: ImMin5 Date: Wed, 22 Nov 2023 23:12:08 +0900 Subject: [PATCH 34/45] feat: fix typo (#73) Signed-off-by: ImMin5 --- .gitignore | 2 ++ src/spaceone/identity/manager/mfa_manager/email_mfa_manger.py | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 199735d8..911af297 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ test/reports test/_trial_temp /test/api/config.yml skaffold.yaml + +/src/spaceone/identity_v1 \ No newline at end of file diff --git a/src/spaceone/identity/manager/mfa_manager/email_mfa_manger.py b/src/spaceone/identity/manager/mfa_manager/email_mfa_manger.py index 07b8615d..8d5a92e6 100644 --- a/src/spaceone/identity/manager/mfa_manager/email_mfa_manger.py +++ b/src/spaceone/identity/manager/mfa_manager/email_mfa_manger.py @@ -74,7 +74,9 @@ def send_mfa_authentication_email(self, user_id, domain_id, email, language): template = JINJA_ENV.get_template(f"authentication_code_{language}.html") email_contents = template.render( - user_name=user_id, verification_code=verify_code, service_name=service_name + user_name=user_id, + authentication_code=verify_code, + service_name=service_name, ) subject = f'[{service_name}] {language_map_info["authentication_mfa_email"]}' From 5718d4eaabaa06ced1e270856f6d13185faad063 Mon Sep 17 00:00:00 2001 From: Jongmin Kim Date: Thu, 23 Nov 2023 09:20:08 +0900 Subject: [PATCH 35/45] chore: change docstring --- src/spaceone/identity/service/endpoint_service.py | 2 +- src/spaceone/identity/service/provider_service.py | 8 ++++---- .../identity/service/service_account_service.py | 11 ++++++----- .../service/trusted_service_account_service.py | 8 ++++---- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/spaceone/identity/service/endpoint_service.py b/src/spaceone/identity/service/endpoint_service.py index 400da8e9..d189dd16 100644 --- a/src/spaceone/identity/service/endpoint_service.py +++ b/src/spaceone/identity/service/endpoint_service.py @@ -25,7 +25,7 @@ def list( } Returns: - EndpointsResponse + EndpointsResponse: """ endpoint_mgr: EndpointManager = EndpointManager() diff --git a/src/spaceone/identity/service/provider_service.py b/src/spaceone/identity/service/provider_service.py index 64aa6af3..6bbe7936 100644 --- a/src/spaceone/identity/service/provider_service.py +++ b/src/spaceone/identity/service/provider_service.py @@ -33,7 +33,7 @@ def create(self, params: ProviderCreateRequest) -> Union[ProviderResponse, dict] } Returns: - ProviderResponse + ProviderResponse: """ # TODO: validate a template data @@ -60,7 +60,7 @@ def update(self, params: ProviderUpdateRequest) -> Union[ProviderResponse, dict] } Returns: - ProviderResponse + ProviderResponse: """ # TODO: validate a template data @@ -98,7 +98,7 @@ def get(self, params: ProviderGetRequest) -> Union[ProviderResponse, dict]: } Returns: - ProviderResponse + ProviderResponse: """ provider_vo = self.provider_mgr.get_provider(params.provider, params.domain_id) @@ -120,7 +120,7 @@ def list(self, params: ProviderSearchQueryRequest) -> Union[ProvidersResponse, d } Returns: - ProvidersResponse + ProvidersResponse: """ query = params.query or {} diff --git a/src/spaceone/identity/service/service_account_service.py b/src/spaceone/identity/service/service_account_service.py index f78bf9ef..036c5a80 100644 --- a/src/spaceone/identity/service/service_account_service.py +++ b/src/spaceone/identity/service/service_account_service.py @@ -36,7 +36,7 @@ def create(self, params: ServiceAccountCreateRequest) -> Union[ServiceAccountRes } Returns: - ServiceAccountResponse + ServiceAccountResponse: """ # Check data by schema @@ -70,7 +70,7 @@ def update(self, params: ServiceAccountUpdateRequest) -> Union[ServiceAccountRes } Returns: - ServiceAccountResponse + ServiceAccountResponse: """ service_account_vo = self.service_account_mgr.get_service_account( @@ -105,7 +105,8 @@ def change_trusted_service_account( } Returns: - ServiceAccountResponse + + ServiceAccountResponse: """ service_account_vo = self.service_account_mgr.get_service_account( @@ -161,7 +162,7 @@ def get(self, params: ServiceAccountGetRequest) -> Union[ServiceAccountResponse, } Returns: - ServiceAccountResponse + ServiceAccountResponse: """ service_account_vo = self.service_account_mgr.get_service_account( @@ -195,7 +196,7 @@ def list(self, params: ServiceAccountSearchQueryRequest) -> Union[ServiceAccount } Returns: - ServiceAccountsResponse + ServiceAccountsResponse: """ query = params.query or {} diff --git a/src/spaceone/identity/service/trusted_service_account_service.py b/src/spaceone/identity/service/trusted_service_account_service.py index 09aed364..f716bdb2 100644 --- a/src/spaceone/identity/service/trusted_service_account_service.py +++ b/src/spaceone/identity/service/trusted_service_account_service.py @@ -32,7 +32,7 @@ def create(self, params: TrustedServiceAccountCreateRequest) -> Union[TrustedSer } Returns: - TrustedServiceAccountResponse + TrustedServiceAccountResponse: """ # Check data by schema @@ -58,7 +58,7 @@ def update(self, params: TrustedServiceAccountUpdateRequest) -> Union[TrustedSer } Returns: - TrustedServiceAccountResponse + TrustedServiceAccountResponse: """ trusted_account_vo = self.trusted_account_mgr.get_trusted_service_account( @@ -114,7 +114,7 @@ def get(self, params: TrustedServiceAccountGetRequest) -> Union[TrustedServiceAc } Returns: - TrustedServiceAccountResponse + TrustedServiceAccountResponse: """ trusted_account_vo = self.trusted_account_mgr.get_trusted_service_account( @@ -145,7 +145,7 @@ def list(self, params: TrustedServiceAccountSearchQueryRequest) -> Union[Trusted } Returns: - TrustedServiceAccountsResponse + TrustedServiceAccountsResponse: """ query = params.query or {} From c831c9577b08a9cd46c28c937e27b26011f9d98c Mon Sep 17 00:00:00 2001 From: Jongmin Kim Date: Thu, 23 Nov 2023 09:21:23 +0900 Subject: [PATCH 36/45] refactor: remove unused imports --- src/spaceone/identity/connector/__init__.py | 2 -- src/spaceone/identity/manager/__init__.py | 7 ------- 2 files changed, 9 deletions(-) diff --git a/src/spaceone/identity/connector/__init__.py b/src/spaceone/identity/connector/__init__.py index ad992e61..e69de29b 100644 --- a/src/spaceone/identity/connector/__init__.py +++ b/src/spaceone/identity/connector/__init__.py @@ -1,2 +0,0 @@ -from spaceone.identity.connector.smtp_connector import SMTPConnector -from spaceone.identity.connector.auth_plugin_connector import AuthPluginConnector diff --git a/src/spaceone/identity/manager/__init__.py b/src/spaceone/identity/manager/__init__.py index 5eac4cde..e69de29b 100644 --- a/src/spaceone/identity/manager/__init__.py +++ b/src/spaceone/identity/manager/__init__.py @@ -1,7 +0,0 @@ -from spaceone.identity.manager.mfa_manager.email_mfa_manger import EmailMFAManager -from spaceone.identity.manager.token_manager.local_token_manager import ( - LocalTokenManager, -) -from spaceone.identity.manager.token_manager.external_token_manager import ( - ExternalTokenManager, -) From 6a363ddf9d710e5ebb81a395d8e5018b1f327b4e Mon Sep 17 00:00:00 2001 From: Jongmin Kim Date: Thu, 23 Nov 2023 11:30:51 +0900 Subject: [PATCH 37/45] refactor: fix import bug --- src/spaceone/identity/manager/email_manager.py | 2 +- src/spaceone/identity/manager/provider_manager.py | 2 +- src/spaceone/identity/manager/service_account_manager.py | 2 +- .../identity/manager/trusted_service_account_manager.py | 2 +- src/spaceone/identity/model/provider/__init__.py | 0 .../identity/model/{provider_db.py => provider/database.py} | 0 .../model/{provider_request.py => provider/request.py} | 0 .../model/{provider_response.py => provider/response.py} | 0 src/spaceone/identity/model/service_account/__init__.py | 0 .../{service_account_db.py => service_account/database.py} | 0 .../request.py} | 0 .../response.py} | 0 .../identity/model/trusted_service_account/__init__.py | 0 .../database.py} | 0 .../request.py} | 0 .../response.py} | 0 src/spaceone/identity/service/provider_service.py | 4 ++-- src/spaceone/identity/service/service_account_service.py | 4 ++-- .../identity/service/trusted_service_account_service.py | 4 ++-- src/spaceone/identity/service/workspace_service.py | 1 - 20 files changed, 10 insertions(+), 11 deletions(-) create mode 100644 src/spaceone/identity/model/provider/__init__.py rename src/spaceone/identity/model/{provider_db.py => provider/database.py} (100%) rename src/spaceone/identity/model/{provider_request.py => provider/request.py} (100%) rename src/spaceone/identity/model/{provider_response.py => provider/response.py} (100%) create mode 100644 src/spaceone/identity/model/service_account/__init__.py rename src/spaceone/identity/model/{service_account_db.py => service_account/database.py} (100%) rename src/spaceone/identity/model/{service_account_request.py => service_account/request.py} (100%) rename src/spaceone/identity/model/{service_account_response.py => service_account/response.py} (100%) create mode 100644 src/spaceone/identity/model/trusted_service_account/__init__.py rename src/spaceone/identity/model/{trusted_service_account_db.py => trusted_service_account/database.py} (100%) rename src/spaceone/identity/model/{trusted_service_account_request.py => trusted_service_account/request.py} (100%) rename src/spaceone/identity/model/{trusted_service_account_response.py => trusted_service_account/response.py} (100%) diff --git a/src/spaceone/identity/manager/email_manager.py b/src/spaceone/identity/manager/email_manager.py index 7e54f1b2..ad9ec128 100644 --- a/src/spaceone/identity/manager/email_manager.py +++ b/src/spaceone/identity/manager/email_manager.py @@ -5,7 +5,7 @@ from spaceone.core import config, utils from spaceone.core.manager import BaseManager -from spaceone.identity_v1.connector.smtp_connector import SMTPConnector +from spaceone.identity.connector.smtp_connector import SMTPConnector _LOGGER = logging.getLogger(__name__) diff --git a/src/spaceone/identity/manager/provider_manager.py b/src/spaceone/identity/manager/provider_manager.py index 47e544f3..9d552266 100644 --- a/src/spaceone/identity/manager/provider_manager.py +++ b/src/spaceone/identity/manager/provider_manager.py @@ -5,7 +5,7 @@ from spaceone.core.manager import BaseManager from spaceone.core.error import * from spaceone.identity.conf.provider_conf import DEFAULT_PROVIDERS -from spaceone.identity.model.provider_db import Provider +from spaceone.identity.model.provider.database import Provider _LOGGER = logging.getLogger(__name__) diff --git a/src/spaceone/identity/manager/service_account_manager.py b/src/spaceone/identity/manager/service_account_manager.py index 4a487cd1..f740a9a0 100644 --- a/src/spaceone/identity/manager/service_account_manager.py +++ b/src/spaceone/identity/manager/service_account_manager.py @@ -3,7 +3,7 @@ from spaceone.core.manager import BaseManager from spaceone.core.connector.space_connector import SpaceConnector -from spaceone.identity.model.service_account_db import ServiceAccount +from spaceone.identity.model.service_account.database import ServiceAccount _LOGGER = logging.getLogger(__name__) diff --git a/src/spaceone/identity/manager/trusted_service_account_manager.py b/src/spaceone/identity/manager/trusted_service_account_manager.py index bda556f8..f98b5993 100644 --- a/src/spaceone/identity/manager/trusted_service_account_manager.py +++ b/src/spaceone/identity/manager/trusted_service_account_manager.py @@ -3,7 +3,7 @@ from spaceone.core.manager import BaseManager from spaceone.core.connector.space_connector import SpaceConnector -from spaceone.identity.model.trusted_service_account_db import TrustedServiceAccount +from spaceone.identity.model.trusted_service_account.database import TrustedServiceAccount _LOGGER = logging.getLogger(__name__) diff --git a/src/spaceone/identity/model/provider/__init__.py b/src/spaceone/identity/model/provider/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/spaceone/identity/model/provider_db.py b/src/spaceone/identity/model/provider/database.py similarity index 100% rename from src/spaceone/identity/model/provider_db.py rename to src/spaceone/identity/model/provider/database.py diff --git a/src/spaceone/identity/model/provider_request.py b/src/spaceone/identity/model/provider/request.py similarity index 100% rename from src/spaceone/identity/model/provider_request.py rename to src/spaceone/identity/model/provider/request.py diff --git a/src/spaceone/identity/model/provider_response.py b/src/spaceone/identity/model/provider/response.py similarity index 100% rename from src/spaceone/identity/model/provider_response.py rename to src/spaceone/identity/model/provider/response.py diff --git a/src/spaceone/identity/model/service_account/__init__.py b/src/spaceone/identity/model/service_account/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/spaceone/identity/model/service_account_db.py b/src/spaceone/identity/model/service_account/database.py similarity index 100% rename from src/spaceone/identity/model/service_account_db.py rename to src/spaceone/identity/model/service_account/database.py diff --git a/src/spaceone/identity/model/service_account_request.py b/src/spaceone/identity/model/service_account/request.py similarity index 100% rename from src/spaceone/identity/model/service_account_request.py rename to src/spaceone/identity/model/service_account/request.py diff --git a/src/spaceone/identity/model/service_account_response.py b/src/spaceone/identity/model/service_account/response.py similarity index 100% rename from src/spaceone/identity/model/service_account_response.py rename to src/spaceone/identity/model/service_account/response.py diff --git a/src/spaceone/identity/model/trusted_service_account/__init__.py b/src/spaceone/identity/model/trusted_service_account/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/spaceone/identity/model/trusted_service_account_db.py b/src/spaceone/identity/model/trusted_service_account/database.py similarity index 100% rename from src/spaceone/identity/model/trusted_service_account_db.py rename to src/spaceone/identity/model/trusted_service_account/database.py diff --git a/src/spaceone/identity/model/trusted_service_account_request.py b/src/spaceone/identity/model/trusted_service_account/request.py similarity index 100% rename from src/spaceone/identity/model/trusted_service_account_request.py rename to src/spaceone/identity/model/trusted_service_account/request.py diff --git a/src/spaceone/identity/model/trusted_service_account_response.py b/src/spaceone/identity/model/trusted_service_account/response.py similarity index 100% rename from src/spaceone/identity/model/trusted_service_account_response.py rename to src/spaceone/identity/model/trusted_service_account/response.py diff --git a/src/spaceone/identity/service/provider_service.py b/src/spaceone/identity/service/provider_service.py index 6bbe7936..c5fb4072 100644 --- a/src/spaceone/identity/service/provider_service.py +++ b/src/spaceone/identity/service/provider_service.py @@ -2,8 +2,8 @@ from typing import Union from spaceone.core import cache from spaceone.core.service import BaseService, transaction, convert_model, append_query_filter, append_keyword_filter -from spaceone.identity.model.provider_request import * -from spaceone.identity.model.provider_response import * +from spaceone.identity.model.provider.request import * +from spaceone.identity.model.provider.response import * from spaceone.identity.manager.provider_manager import ProviderManager _LOGGER = logging.getLogger(__name__) diff --git a/src/spaceone/identity/service/service_account_service.py b/src/spaceone/identity/service/service_account_service.py index 036c5a80..2d388a5e 100644 --- a/src/spaceone/identity/service/service_account_service.py +++ b/src/spaceone/identity/service/service_account_service.py @@ -2,8 +2,8 @@ from typing import Union from spaceone.core.service import (BaseService, transaction, convert_model, append_query_filter, append_keyword_filter, set_query_page_limit) -from spaceone.identity.model.service_account_request import * -from spaceone.identity.model.service_account_response import * +from spaceone.identity.model.service_account.request import * +from spaceone.identity.model.service_account.response import * from spaceone.identity.manager.provider_manager import ProviderManager from spaceone.identity.manager.service_account_manager import ServiceAccountManager from spaceone.identity.manager.trusted_service_account_manager import TrustedServiceAccountManager diff --git a/src/spaceone/identity/service/trusted_service_account_service.py b/src/spaceone/identity/service/trusted_service_account_service.py index f716bdb2..bef25fad 100644 --- a/src/spaceone/identity/service/trusted_service_account_service.py +++ b/src/spaceone/identity/service/trusted_service_account_service.py @@ -1,8 +1,8 @@ import logging from typing import Union from spaceone.core.service import BaseService, transaction, convert_model, append_query_filter, append_keyword_filter -from spaceone.identity.model.trusted_service_account_request import * -from spaceone.identity.model.trusted_service_account_response import * +from spaceone.identity.model.trusted_service_account.request import * +from spaceone.identity.model.trusted_service_account.response import * from spaceone.identity.manager.provider_manager import ProviderManager from spaceone.identity.manager.trusted_service_account_manager import TrustedServiceAccountManager diff --git a/src/spaceone/identity/service/workspace_service.py b/src/spaceone/identity/service/workspace_service.py index 1521b59b..99d5507f 100644 --- a/src/spaceone/identity/service/workspace_service.py +++ b/src/spaceone/identity/service/workspace_service.py @@ -9,7 +9,6 @@ append_keyword_filter, ) -from spaceone.identity.error.error_workspace import * from spaceone.identity.manager.domain_manager import DomainManager from spaceone.identity.manager.workspace_manager import WorkspaceManager from spaceone.identity.model.workspace.request import * From 48c95b7c99894a755d3390f7a1eeca927c907f24 Mon Sep 17 00:00:00 2001 From: Jongmin Kim Date: Thu, 23 Nov 2023 13:35:58 +0900 Subject: [PATCH 38/45] refactor: change error message format --- src/spaceone/identity/manager/provider_manager.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/spaceone/identity/manager/provider_manager.py b/src/spaceone/identity/manager/provider_manager.py index 9d552266..d16a5936 100644 --- a/src/spaceone/identity/manager/provider_manager.py +++ b/src/spaceone/identity/manager/provider_manager.py @@ -1,5 +1,5 @@ import logging -from jsonschema import validate +from jsonschema import validate, exceptions from typing import Tuple, List from spaceone.core.manager import BaseManager @@ -66,5 +66,5 @@ def check_data_by_schema(self, provider: str, domain_id: str, data: dict) -> Non if schema: try: validate(instance=data, schema=schema) - except Exception as e: - raise ERROR_INVALID_PARAMETER(key='data', reason=e) + except exceptions.ValidationError as e: + raise ERROR_INVALID_PARAMETER(key='data', reason=e.message) From 74d26634dffc230ab09c896a260f4eacb42328f4 Mon Sep 17 00:00:00 2001 From: Jongmin Kim Date: Thu, 23 Nov 2023 15:50:48 +0900 Subject: [PATCH 39/45] feat: initial commit for identity v2 --- .../identity/manager/provider_manager.py | 5 +- .../trusted_service_account_manager.py | 21 ++-- .../identity/model/policy/__init__.py | 0 .../identity/model/policy/database.py | 0 .../{policy_request.py => policy/request.py} | 0 .../response.py} | 1 + src/spaceone/identity/model/role/__init__.py | 0 .../{role_request.py => role/request.py} | 18 +++- .../{role_response.py => role/response.py} | 7 +- .../identity/model/role_binding/__init__.py | 0 .../request.py} | 5 +- .../response.py} | 4 +- .../model/service_account/response.py | 6 ++ .../model/trusted_service_account/database.py | 6 +- .../model/trusted_service_account/response.py | 6 ++ .../identity/model/workspace/request.py | 3 +- .../identity/service/policy_service.py | 93 ++++++++++++++++-- .../identity/service/provider_service.py | 7 +- .../identity/service/role_binding_service.py | 98 ++++++++++++++++--- src/spaceone/identity/service/role_service.py | 84 +++++++++++++++- .../service/service_account_service.py | 13 +-- .../trusted_service_account_service.py | 16 ++- 22 files changed, 329 insertions(+), 64 deletions(-) create mode 100644 src/spaceone/identity/model/policy/__init__.py create mode 100644 src/spaceone/identity/model/policy/database.py rename src/spaceone/identity/model/{policy_request.py => policy/request.py} (100%) rename src/spaceone/identity/model/{policy_response.py => policy/response.py} (93%) create mode 100644 src/spaceone/identity/model/role/__init__.py rename src/spaceone/identity/model/{role_request.py => role/request.py} (67%) rename src/spaceone/identity/model/{role_response.py => role/response.py} (68%) create mode 100644 src/spaceone/identity/model/role_binding/__init__.py rename src/spaceone/identity/model/{role_binding_request.py => role_binding/request.py} (92%) rename src/spaceone/identity/model/{role_binding_response.py => role_binding/response.py} (79%) diff --git a/src/spaceone/identity/manager/provider_manager.py b/src/spaceone/identity/manager/provider_manager.py index d16a5936..d6bda926 100644 --- a/src/spaceone/identity/manager/provider_manager.py +++ b/src/spaceone/identity/manager/provider_manager.py @@ -26,12 +26,13 @@ def _rollback(vo: Provider): return provider_vo - def update_provider(self, params: dict) -> Provider: + def update_provider_by_vo( + self, params: dict, provider_vo: Provider + ) -> Provider: def _rollback(old_data): _LOGGER.info(f'[update_provider._rollback] Revert Data : {old_data["provider"]}') provider_vo.update(old_data) - provider_vo = self.get_provider(params['provider'], params['domain_id']) self.transaction.add_rollback(_rollback, provider_vo.to_dict()) return provider_vo.update(params) diff --git a/src/spaceone/identity/manager/trusted_service_account_manager.py b/src/spaceone/identity/manager/trusted_service_account_manager.py index f98b5993..41ad92df 100644 --- a/src/spaceone/identity/manager/trusted_service_account_manager.py +++ b/src/spaceone/identity/manager/trusted_service_account_manager.py @@ -25,12 +25,6 @@ def _rollback(vo: TrustedServiceAccount): return trusted_account_vo - def update_trusted_service_account(self, params: dict) -> TrustedServiceAccount: - trusted_account_vo = self.get_trusted_service_account( - params['trusted_service_account_id'], params['domain_id'], params.get('workspace_id')) - - return self.update_trusted_service_account_by_vo(params, trusted_account_vo) - def update_trusted_service_account_by_vo( self, params: dict, trusted_account_vo: TrustedServiceAccount ) -> TrustedServiceAccount: @@ -50,11 +44,16 @@ def delete_trusted_service_account_by_vo(trusted_account_vo: TrustedServiceAccou def get_trusted_service_account( self, trusted_service_account_id: str, domain_id: str, workspace_id: str = None ) -> TrustedServiceAccount: - return self.trusted_account_model.get( - trusted_service_account_id=trusted_service_account_id, - domain_id=domain_id, - workspace_id=workspace_id - ) + + conditions = { + 'trusted_service_account_id': trusted_service_account_id, + 'domain_id': domain_id, + } + + if workspace_id: + conditions['workspace_id'] = workspace_id + + return self.trusted_account_model.get(**conditions) def filter_trusted_service_accounts(self, **conditions) -> List[TrustedServiceAccount]: return self.trusted_account_model.filter(**conditions) diff --git a/src/spaceone/identity/model/policy/__init__.py b/src/spaceone/identity/model/policy/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/spaceone/identity/model/policy/database.py b/src/spaceone/identity/model/policy/database.py new file mode 100644 index 00000000..e69de29b diff --git a/src/spaceone/identity/model/policy_request.py b/src/spaceone/identity/model/policy/request.py similarity index 100% rename from src/spaceone/identity/model/policy_request.py rename to src/spaceone/identity/model/policy/request.py diff --git a/src/spaceone/identity/model/policy_response.py b/src/spaceone/identity/model/policy/response.py similarity index 93% rename from src/spaceone/identity/model/policy_response.py rename to src/spaceone/identity/model/policy/response.py index 0c333bf6..1733e658 100644 --- a/src/spaceone/identity/model/policy_response.py +++ b/src/spaceone/identity/model/policy/response.py @@ -10,6 +10,7 @@ class PolicyResponse(BaseModel): name: Union[str, None] permissions: Union[List[str], None] tags: Union[dict, None] + is_managed: Union[bool, None] domain_id: Union[str, None] created_at: Union[datetime, None] diff --git a/src/spaceone/identity/model/role/__init__.py b/src/spaceone/identity/model/role/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/spaceone/identity/model/role_request.py b/src/spaceone/identity/model/role/request.py similarity index 67% rename from src/spaceone/identity/model/role_request.py rename to src/spaceone/identity/model/role/request.py index 777b9b88..9c258d07 100644 --- a/src/spaceone/identity/model/role_request.py +++ b/src/spaceone/identity/model/role/request.py @@ -8,16 +8,24 @@ "RoleGetRequest", "RoleSearchQueryRequest", "RoleStatQueryRequest", + 'RoleType', + 'PagePermissionType', ] -RoleType = Literal["DOMAIN", "PROJECT", "USER"] +RoleType = Literal["SYSTEM_ADMIN", "DOMAIN_ADMIN", "WORKSPACE_OWNER", "WORKSPACE_MEMBER"] +PagePermissionType = Literal["VIEW", "MANAGE"] + + +class PagePermission(BaseModel): + pages: List[str] + permission: PagePermissionType class RoleCreateRequest(BaseModel): name: str role_type: RoleType - policy_id: str - permissions: List[str] + policies: List[str] + page_permissions: Union[List[PagePermission], None] = None tags: Union[dict, None] = None domain_id: str @@ -25,8 +33,8 @@ class RoleCreateRequest(BaseModel): class RoleUpdateRequest(BaseModel): role_id: str name: Union[str, None] = None - policy_id: str - permissions: Union[List[str], None] + policies: Union[List[str], None] = None + page_permissions: Union[List[PagePermission], None] = None tags: Union[dict, None] domain_id: str diff --git a/src/spaceone/identity/model/role_response.py b/src/spaceone/identity/model/role/response.py similarity index 68% rename from src/spaceone/identity/model/role_response.py rename to src/spaceone/identity/model/role/response.py index eeeeb6ce..9bbb086e 100644 --- a/src/spaceone/identity/model/role_response.py +++ b/src/spaceone/identity/model/role/response.py @@ -2,7 +2,7 @@ from typing import Union, List from pydantic import BaseModel -from spaceone.identity.model.role_request import RoleType +from spaceone.identity.model.role.request import RoleType, PagePermissionType __all__ = ["RoleResponse", "RolesResponse"] @@ -11,9 +11,10 @@ class RoleResponse(BaseModel): role_id: Union[str, None] = None name: Union[str, None] = None role_type: Union[RoleType, None] = None - policy_id: Union[str, None] = None - permissions: Union[list, None] = None + policies: Union[List[str], None] = None + page_permissions: Union[List[PagePermissionType], None] = None tags: Union[dict, None] = None + is_managed: Union[bool, None] = None domain_id: Union[str, None] = None created_at: Union[datetime, None] = None diff --git a/src/spaceone/identity/model/role_binding/__init__.py b/src/spaceone/identity/model/role_binding/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/spaceone/identity/model/role_binding_request.py b/src/spaceone/identity/model/role_binding/request.py similarity index 92% rename from src/spaceone/identity/model/role_binding_request.py rename to src/spaceone/identity/model/role_binding/request.py index b56d1068..fa4ca005 100644 --- a/src/spaceone/identity/model/role_binding_request.py +++ b/src/spaceone/identity/model/role_binding/request.py @@ -1,4 +1,4 @@ -from typing import Union, List, Literal +from typing import Union, Literal from pydantic import BaseModel __all__ = [ @@ -17,7 +17,6 @@ class RoleBindingCreateRequest(BaseModel): user_id: str role_id: str - is_managed_role: bool scope: Scope workspace_id: Union[str, None] = None domain_id: str @@ -26,7 +25,7 @@ class RoleBindingCreateRequest(BaseModel): class RoleBindingUpdateRoleRequest(BaseModel): role_binding_id: str role_id: str - is_managed_role: bool + workspace_id: Union[str, None] = None domain_id: str diff --git a/src/spaceone/identity/model/role_binding_response.py b/src/spaceone/identity/model/role_binding/response.py similarity index 79% rename from src/spaceone/identity/model/role_binding_response.py rename to src/spaceone/identity/model/role_binding/response.py index b096d646..61cc3d3e 100644 --- a/src/spaceone/identity/model/role_binding_response.py +++ b/src/spaceone/identity/model/role_binding/response.py @@ -2,18 +2,16 @@ from typing import Union, List from pydantic import BaseModel -from spaceone.identity.model.role_binding_request import Scope +from spaceone.identity.model.role_binding.request import Scope __all__ = ["RoleBindingResponse", "RoleBindingsResponse"] class RoleBindingResponse(BaseModel): role_binding_id: Union[str, None] = None - is_managed_role: Union[bool, None] = None scope: Union[Scope, None] = None user_id: Union[str, None] = None role_id: Union[str, None] = None - repository_id: Union[str, None] = None workspace_id: Union[str, None] = None domain_id: Union[str, None] = None created_at: Union[datetime, None] = None diff --git a/src/spaceone/identity/model/service_account/response.py b/src/spaceone/identity/model/service_account/response.py index 933311c4..7f436777 100644 --- a/src/spaceone/identity/model/service_account/response.py +++ b/src/spaceone/identity/model/service_account/response.py @@ -1,6 +1,7 @@ from datetime import datetime from typing import Union, Literal, List from pydantic import BaseModel +from spaceone.core import utils __all__ = ["ServiceAccountResponse", "ServiceAccountsResponse"] @@ -19,6 +20,11 @@ class ServiceAccountResponse(BaseModel): domain_id: Union[str, None] = None created_at: Union[datetime, None] = None + def dict(self, *args, **kwargs): + data = super().dict(*args, **kwargs) + data['created_at'] = utils.datetime_to_iso8601(data['created_at']) + return data + class ServiceAccountsResponse(BaseModel): results: List[ServiceAccountResponse] diff --git a/src/spaceone/identity/model/trusted_service_account/database.py b/src/spaceone/identity/model/trusted_service_account/database.py index c60050fa..d4a57bd9 100644 --- a/src/spaceone/identity/model/trusted_service_account/database.py +++ b/src/spaceone/identity/model/trusted_service_account/database.py @@ -3,13 +3,13 @@ class TrustedServiceAccount(MongoModel): - trusted_service_account_id = StringField(max_length=40, generate_id='trusted-sa', unique=True) + trusted_service_account_id = StringField(max_length=40, generate_id='tsa', unique=True) name = StringField(max_length=255, unique_with=['workspace_id', 'domain_id']) data = DictField(default=None) provider = StringField(max_length=40) - tags = DictField() + tags = DictField(default=None) scope = StringField(max_length=40, choices=('DOMAIN', 'WORKSPACE'), default='WORKSPACE') - workspace_id = StringField(max_length=40, default=None, null=True) + workspace_id = StringField(max_length=40, default='*') domain_id = StringField(max_length=255) created_at = DateTimeField(auto_now_add=True) diff --git a/src/spaceone/identity/model/trusted_service_account/response.py b/src/spaceone/identity/model/trusted_service_account/response.py index 7014e66e..68c1c4a7 100644 --- a/src/spaceone/identity/model/trusted_service_account/response.py +++ b/src/spaceone/identity/model/trusted_service_account/response.py @@ -1,6 +1,7 @@ from datetime import datetime from typing import Union, Literal, List from pydantic import BaseModel +from spaceone.core import utils __all__ = ["TrustedServiceAccountResponse", "TrustedServiceAccountsResponse"] @@ -18,6 +19,11 @@ class TrustedServiceAccountResponse(BaseModel): domain_id: Union[str, None] = None created_at: Union[datetime, None] = None + def dict(self, *args, **kwargs): + data = super().dict(*args, **kwargs) + data['created_at'] = utils.datetime_to_iso8601(data['created_at']) + return data + class TrustedServiceAccountsResponse(BaseModel): results: List[TrustedServiceAccountResponse] diff --git a/src/spaceone/identity/model/workspace/request.py b/src/spaceone/identity/model/workspace/request.py index 96b796e2..358f1ce8 100644 --- a/src/spaceone/identity/model/workspace/request.py +++ b/src/spaceone/identity/model/workspace/request.py @@ -1,6 +1,5 @@ from typing import Union -from enum import Enum -from pydantic import BaseModel, Field +from pydantic import BaseModel __all__ = [ "WorkspaceCreateRequest", diff --git a/src/spaceone/identity/service/policy_service.py b/src/spaceone/identity/service/policy_service.py index dee9d383..344613fc 100644 --- a/src/spaceone/identity/service/policy_service.py +++ b/src/spaceone/identity/service/policy_service.py @@ -1,39 +1,116 @@ import logging from typing import Union from spaceone.core.service import BaseService, transaction, convert_model -from spaceone.identity.model.policy_request import * -from spaceone.identity.model.policy_response import * +from spaceone.identity.model.policy.request import * +from spaceone.identity.model.policy.response import * _LOGGER = logging.getLogger(__name__) class PolicyService(BaseService): - @transaction + @transaction(append_meta={'authorization.scope': 'DOMAIN'}) @convert_model def create(self, params: PolicyCreateRequest) -> Union[PolicyResponse, dict]: + """ create policy + + Args: + params (PolicyCreateRequest): { + 'name': 'str', # required + 'permissions': 'list', # required + 'tags': 'dict', + 'domain_id': 'str' # required + } + + Returns: + PolicyResponse: + """ return {} - @transaction + @transaction(append_meta={'authorization.scope': 'DOMAIN'}) @convert_model def update(self, params: PolicyUpdateRequest) -> Union[PolicyResponse, dict]: + """ update policy + + Args: + params (PolicyUpdateRequest): { + 'policy_id': 'str', # required + 'name': 'str', + 'permissions': 'list', + 'tags': 'dict', + 'domain_id': 'str', # required + } + + Returns: + PolicyResponse: + """ + return {} - @transaction + @transaction(append_meta={'authorization.scope': 'DOMAIN'}) @convert_model def delete(self, params: PolicyDeleteRequest) -> None: + """ delete policy + + Args: + params (PolicyDeleteRequest): { + 'policy_id': 'str', # required + 'domain_id': 'str', # required + } + + Returns: + None + """ pass - @transaction + @transaction(append_meta={'authorization.scope': 'DOMAIN_READ'}) @convert_model def get(self, params: PolicyGetRequest) -> Union[PolicyResponse, dict]: + """ get policy + + Args: + params (PolicyGetRequest): { + 'policy_id': 'str', # required + 'domain_id': 'str', # required + } + + Returns: + PolicyResponse: + """ return {} - @transaction + @transaction(append_meta={'authorization.scope': 'DOMAIN_READ'}) @convert_model def list(self, params: PolicySearchQueryRequest) -> Union[PoliciesResponse, dict]: + """ list policies + + Args: + params (PolicySearchQueryRequest): { + 'query': 'dict (spaceone.api.core.v1.Query)', + 'policy_id': 'str', + 'name': 'str', + 'domain_id': 'str', # required + } + + Returns: + PoliciesResponse: + """ return {} - @transaction + @transaction(append_meta={'authorization.scope': 'DOMAIN_READ'}) @convert_model def stat(self, params: PolicyStatQueryRequest) -> dict: + """ stat policies + + Args: + params (PolicyStatQueryRequest): { + 'query': 'dict (spaceone.api.core.v1.StatisticsQuery)', # required + 'domain_id': 'str', # required + } + + Returns: + dict: { + 'results': 'list', + 'total_count': 'int' + } + """ return {} diff --git a/src/spaceone/identity/service/provider_service.py b/src/spaceone/identity/service/provider_service.py index c5fb4072..0b4f189e 100644 --- a/src/spaceone/identity/service/provider_service.py +++ b/src/spaceone/identity/service/provider_service.py @@ -63,10 +63,15 @@ def update(self, params: ProviderUpdateRequest) -> Union[ProviderResponse, dict] ProviderResponse: """ + provider_vo = self.provider_mgr.get_provider(params.provider, params.domain_id) + # TODO: validate a template data # TODO: validate a capability data - provider_vo = self.provider_mgr.update_provider(params.dict()) + provider_vo = self.provider_mgr.update_provider_by_vo( + params.dict(exclude_unset=True), provider_vo + ) + return ProviderResponse(**provider_vo.to_dict()) @transaction(append_meta={'authorization.scope': 'DOMAIN'}) diff --git a/src/spaceone/identity/service/role_binding_service.py b/src/spaceone/identity/service/role_binding_service.py index 4d53bd27..7e130019 100644 --- a/src/spaceone/identity/service/role_binding_service.py +++ b/src/spaceone/identity/service/role_binding_service.py @@ -1,8 +1,8 @@ import logging from typing import Union from spaceone.core.service import BaseService, transaction, convert_model -from spaceone.identity.model.role_binding_request import * -from spaceone.identity.model.role_binding_response import * +from spaceone.identity.model.role_binding.request import * +from spaceone.identity.model.role_binding.response import * _LOGGER = logging.getLogger(__name__) @@ -10,36 +10,112 @@ class RoleBindingService(BaseService): @transaction @convert_model - def create( - self, params: RoleBindingCreateRequest - ) -> Union[RoleBindingResponse, dict]: + def create(self, params: RoleBindingCreateRequest) -> Union[RoleBindingResponse, dict]: + """ create role binding + + Args: + params (RoleBindingCreateRequest): { + 'user_id': 'str', # required + 'role_id': 'str', # required + 'scope': 'str', # required + 'workspace_id': 'str', + 'domain_id': 'str' # required + } + + Returns: + RoleBindingResponse: + """ return {} @transaction @convert_model - def update_role( - self, params: RoleBindingUpdateRoleRequest - ) -> Union[RoleBindingResponse, dict]: + def update_role(self, params: RoleBindingUpdateRoleRequest) -> Union[RoleBindingResponse, dict]: + """ update role of role binding + + Args: + params (RoleBindingUpdateRoleRequest): { + 'role_binding_id': 'str', # required + 'role_id': 'str', # required + 'workspace_id': 'str', + 'domain_id': 'str', # required + } + + Returns: + RoleBindingResponse: + """ return {} @transaction @convert_model def delete(self, params: RoleBindingDeleteRequest) -> None: + """ delete role binding + + Args: + params (RoleBindingDeleteRequest): { + 'role_binding_id': 'str', # required + 'workspace_id': 'str', + 'domain_id': 'str', # required + } + + Returns: + None + """ pass @transaction @convert_model def get(self, params: RoleBindingGetRequest) -> Union[RoleBindingResponse, dict]: + """ get role binding + + Args: + params (RoleBindingGetRequest): { + 'role_binding_id': 'str', # required + 'workspace_id': 'str', + 'domain_id': 'str', # required + } + + Returns: + RoleBindingResponse: + """ return {} @transaction @convert_model - def list( - self, params: RoleBindingSearchQueryRequest - ) -> Union[RoleBindingsResponse, dict]: + def list(self, params: RoleBindingSearchQueryRequest) -> Union[RoleBindingsResponse, dict]: + """ list role bindings + + Args: + params (RoleBindingSearchQueryRequest): { + 'query': 'dict (spaceone.api.core.v1.Query)', + 'role_binding_id': 'str', + 'scope': 'str', + 'user_id': 'str', + 'role_id': 'str', + 'workspace_id': 'str', + 'domain_id': 'str', # required + } + + Returns: + RoleBindingsResponse: + """ return {} @transaction @convert_model def stat(self, params: RoleBindingStatQueryRequest) -> dict: + """ stat role bindings + + Args: + params (RoleBindingStatQueryRequest): { + 'query': 'dict (spaceone.api.core.v1.StatisticsQuery)', # required + 'workspace_id': 'str', + 'domain_id': 'str', # required + } + + Returns: + dict: { + 'results': 'list', + 'total_count': 'int' + } + """ return {} diff --git a/src/spaceone/identity/service/role_service.py b/src/spaceone/identity/service/role_service.py index 2c1dd08e..4789a0c7 100644 --- a/src/spaceone/identity/service/role_service.py +++ b/src/spaceone/identity/service/role_service.py @@ -1,8 +1,8 @@ import logging from typing import Union from spaceone.core.service import BaseService, transaction, convert_model -from spaceone.identity.model.role_request import * -from spaceone.identity.model.role_response import * +from spaceone.identity.model.role.request import * +from spaceone.identity.model.role.response import * _LOGGER = logging.getLogger(__name__) @@ -11,29 +11,109 @@ class RoleService(BaseService): @transaction @convert_model def create(self, params: RoleCreateRequest) -> Union[RoleResponse, dict]: + """ create role + + Args: + params (RoleCreateRequest): { + 'name': 'str', # required + 'role_type': 'list', # required + 'policies': 'list', # required + 'page_permissions': 'list', + 'tags': 'dict', + 'domain_id': 'str' # required + } + + Returns: + RoleResponse: + """ return {} @transaction @convert_model def update(self, params: RoleUpdateRequest) -> Union[RoleResponse, dict]: + """ update role + + Args: + params (RoleUpdateRequest): { + 'role_id': 'str', # required + 'name': 'str', + 'policies': 'list', + 'page_permissions': 'list', + 'tags': 'dict', + 'domain_id': 'str' # required + } + + Returns: + RoleResponse: + """ return {} @transaction @convert_model def delete(self, params: RoleDeleteRequest) -> None: + """ delete role + + Args: + params (RoleDeleteRequest): { + 'role_id': 'str', # required + 'domain_id': 'str', # required + } + + Returns: + None + """ pass @transaction @convert_model def get(self, params: RoleGetRequest) -> Union[RoleResponse, dict]: + """ get role + + Args: + params (RoleGetRequest): { + 'role_id': 'str', # required + 'domain_id': 'str', # required + } + + Returns: + RoleResponse: + """ return {} @transaction @convert_model def list(self, params: RoleSearchQueryRequest) -> Union[RolesResponse, dict]: + """ list roles + + Args: + params (RoleSearchQueryRequest): { + 'query': 'dict (spaceone.api.core.v1.Query)', + 'role_id': 'str', + 'role_type': 'str', + 'policy_id': 'str', + 'domain_id': 'str', # required + } + + Returns: + RolesResponse: + """ return {} @transaction @convert_model def stat(self, params: RoleStatQueryRequest) -> dict: + """ stat roles + + Args: + params (PolicyStatQueryRequest): { + 'query': 'dict (spaceone.api.core.v1.StatisticsQuery)', # required + 'domain_id': 'str', # required + } + + Returns: + dict: { + 'results': 'list', + 'total_count': 'int' + } + """ return {} diff --git a/src/spaceone/identity/service/service_account_service.py b/src/spaceone/identity/service/service_account_service.py index 2d388a5e..217724f3 100644 --- a/src/spaceone/identity/service/service_account_service.py +++ b/src/spaceone/identity/service/service_account_service.py @@ -44,10 +44,11 @@ def create(self, params: ServiceAccountCreateRequest) -> Union[ServiceAccountRes provider_mgr.check_data_by_schema(params.provider, params.domain_id, params.data) # Check trusted service account - trusted_service_account_mgr = TrustedServiceAccountManager() - trusted_service_account_mgr.get_trusted_service_account( - params.trusted_service_account_id, params.domain_id, params.workspace_id - ) + if params.trusted_service_account_id: + trusted_service_account_mgr = TrustedServiceAccountManager() + trusted_service_account_mgr.get_trusted_service_account( + params.trusted_service_account_id, params.domain_id, params.workspace_id + ) service_account_vo = self.service_account_mgr.create_service_account(params.dict()) return ServiceAccountResponse(**service_account_vo.to_dict()) @@ -83,7 +84,7 @@ def update(self, params: ServiceAccountUpdateRequest) -> Union[ServiceAccountRes provider_mgr.check_data_by_schema(service_account_vo.provider, params.domain_id, params.data) service_account_vo = self.service_account_mgr.update_service_account_by_vo( - params.dict(), service_account_vo + params.dict(exclude_unset=True), service_account_vo ) return ServiceAccountResponse(**service_account_vo.to_dict()) @@ -151,7 +152,7 @@ def delete(self, params: ServiceAccountDeleteRequest) -> None: @transaction(append_meta={'authorization.scope': 'PROJECT_READ'}) @convert_model def get(self, params: ServiceAccountGetRequest) -> Union[ServiceAccountResponse, dict]: - """ delete service account + """ get service account Args: params (ServiceAccountDeleteRequest): { diff --git a/src/spaceone/identity/service/trusted_service_account_service.py b/src/spaceone/identity/service/trusted_service_account_service.py index bef25fad..af7c9dd4 100644 --- a/src/spaceone/identity/service/trusted_service_account_service.py +++ b/src/spaceone/identity/service/trusted_service_account_service.py @@ -1,6 +1,7 @@ import logging from typing import Union from spaceone.core.service import BaseService, transaction, convert_model, append_query_filter, append_keyword_filter +from spaceone.core.error import * from spaceone.identity.model.trusted_service_account.request import * from spaceone.identity.model.trusted_service_account.response import * from spaceone.identity.manager.provider_manager import ProviderManager @@ -35,6 +36,13 @@ def create(self, params: TrustedServiceAccountCreateRequest) -> Union[TrustedSer TrustedServiceAccountResponse: """ + # Check Scope + if params.scope == 'DOMAIN': + params.workspace_id = None + else: + if not params.workspace_id: + raise ERROR_REQUIRED_PARAMETER(key='workspace_id') + # Check data by schema provider_mgr = ProviderManager() provider_mgr.check_data_by_schema(params.provider, params.domain_id, params.data) @@ -71,7 +79,7 @@ def update(self, params: TrustedServiceAccountUpdateRequest) -> Union[TrustedSer provider_mgr.check_data_by_schema(trusted_account_vo.provider, params.domain_id, params.data) trusted_account_vo = self.trusted_account_mgr.update_trusted_service_account_by_vo( - params.dict(), trusted_account_vo + params.dict(exclude_unset=True), trusted_account_vo ) return TrustedServiceAccountResponse(**trusted_account_vo.to_dict()) @@ -96,9 +104,9 @@ def delete(self, params: TrustedServiceAccountDeleteRequest) -> None: params.trusted_service_account_id, params.domain_id, params.workspace_id ) - self.trusted_account_mgr.delete_trusted_secrets( - params.trusted_service_account_id, params.workspace_id, params.domain_id - ) + # self.trusted_account_mgr.delete_trusted_secrets( + # params.trusted_service_account_id, params.workspace_id, params.domain_id + # ) self.trusted_account_mgr.delete_trusted_service_account_by_vo(trusted_account_vo) @transaction(append_meta={'authorization.scope': 'DOMAIN_OR_WORKSPACE_READ'}) From e0e81be6b34d33242b58efd875bdb075f6100a78 Mon Sep 17 00:00:00 2001 From: Jongmin Kim Date: Thu, 23 Nov 2023 15:52:17 +0900 Subject: [PATCH 40/45] feat: change database model --- src/spaceone/identity/model/service_account/database.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spaceone/identity/model/service_account/database.py b/src/spaceone/identity/model/service_account/database.py index 9bfb1a58..46c427c3 100644 --- a/src/spaceone/identity/model/service_account/database.py +++ b/src/spaceone/identity/model/service_account/database.py @@ -7,7 +7,7 @@ class ServiceAccount(MongoModel): name = StringField(max_length=255, unique_with=['domain_id', 'workspace_id']) data = DictField(default=None) provider = StringField(max_length=40) - tags = DictField() + tags = DictField(default=None) trusted_service_account_id = StringField(max_length=40, null=True, default=None) project_id = StringField(max_length=40) workspace_id = StringField(max_length=40) From 2660d5093fb3ed05b60933d6b27bcf5c5f939d7c Mon Sep 17 00:00:00 2001 From: ImMin5 Date: Thu, 23 Nov 2023 16:44:42 +0900 Subject: [PATCH 41/45] feat: add exclude_unset option when updated (#73) Signed-off-by: ImMin5 --- .../identity/manager/mfa_manager/__init__.py | 3 ++- src/spaceone/identity/model/token/request.py | 2 +- src/spaceone/identity/model/user/database.py | 2 +- .../identity/service/domain_service.py | 5 ++++- .../identity/service/project_group_service.py | 4 ++-- .../identity/service/project_service.py | 19 +++++++++++++------ .../identity/service/token_service.py | 6 ++---- src/spaceone/identity/service/user_service.py | 17 +++++++++++------ 8 files changed, 36 insertions(+), 22 deletions(-) diff --git a/src/spaceone/identity/manager/mfa_manager/__init__.py b/src/spaceone/identity/manager/mfa_manager/__init__.py index 7eeb624c..b81bd1bc 100644 --- a/src/spaceone/identity/manager/mfa_manager/__init__.py +++ b/src/spaceone/identity/manager/mfa_manager/__init__.py @@ -6,6 +6,7 @@ from spaceone.core.manager import BaseManager from spaceone.identity.error.error_mfa import ERROR_NOT_SUPPORTED_MFA_TYPE +from spaceone.identity.error.error_user import ERROR_INVALID_VERIFY_CODE __all__ = ["BaseMFAManager", "MFAManager"] _LOGGER = logging.getLogger(__name__) @@ -72,7 +73,7 @@ def check_mfa_verify_code(user_id, domain_id, verify_code): if cached_verify_code == verify_code: cache.delete(f"mfa-verify-code:{domain_id}:{user_id}") return True - return False + raise ERROR_INVALID_VERIFY_CODE(verify_code=verify_code) @staticmethod def _generate_verify_code(): diff --git a/src/spaceone/identity/model/token/request.py b/src/spaceone/identity/model/token/request.py index 6e68db92..0629591d 100644 --- a/src/spaceone/identity/model/token/request.py +++ b/src/spaceone/identity/model/token/request.py @@ -11,5 +11,5 @@ class TokenIssueRequest(BaseModel): auth_type: AuthType timeout: Union[int, None] = None refresh_count: Union[int, None] = None - verify_code: Union[int, None] = None + verify_code: Union[str, None] = None domain_id: str diff --git a/src/spaceone/identity/model/user/database.py b/src/spaceone/identity/model/user/database.py index 47f6ce89..7faa3838 100644 --- a/src/spaceone/identity/model/user/database.py +++ b/src/spaceone/identity/model/user/database.py @@ -26,7 +26,7 @@ class User(MongoModel): required_actions = ListField(StringField(choices=("UPDATE_PASSWORD",)), default=[]) language = StringField(max_length=7, default="en") timezone = StringField(max_length=50, default="UTC") - tags = DictField() + tags = DictField(Default=None) domain_id = StringField(max_length=40) last_accessed_at = DateTimeField(default=None, null=True) created_at = DateTimeField(auto_now_add=True) diff --git a/src/spaceone/identity/service/domain_service.py b/src/spaceone/identity/service/domain_service.py index 92fe3a5d..7a1ad735 100644 --- a/src/spaceone/identity/service/domain_service.py +++ b/src/spaceone/identity/service/domain_service.py @@ -67,7 +67,9 @@ def update(self, params: DomainUpdateRequest) -> Union[DomainResponse, dict]: DomainResponse: """ domain_vo = self.domain_mgr.get_domain(params.domain_id) - domain_vo = self.domain_mgr.update_domain_by_vo(params.dict(), domain_vo) + domain_vo = self.domain_mgr.update_domain_by_vo( + params.dict(exclude_unset=True), domain_vo + ) return DomainResponse(**domain_vo.to_dict()) @transaction @@ -141,6 +143,7 @@ def get_metadata( Returns: DomainMetadataResponse: """ + return {} @transaction diff --git a/src/spaceone/identity/service/project_group_service.py b/src/spaceone/identity/service/project_group_service.py index a3ec6d7f..0ad2edd4 100644 --- a/src/spaceone/identity/service/project_group_service.py +++ b/src/spaceone/identity/service/project_group_service.py @@ -1,8 +1,8 @@ import logging from typing import Union from spaceone.core.service import BaseService, transaction, convert_model -from spaceone.identity.model.project_group_request import * -from spaceone.identity.model.project_group_response import * +from spaceone.identity.model.project_group.request import * +from spaceone.identity.model.project_group.response import * _LOGGER = logging.getLogger(__name__) diff --git a/src/spaceone/identity/service/project_service.py b/src/spaceone/identity/service/project_service.py index b3aa0a56..5858dcdb 100644 --- a/src/spaceone/identity/service/project_service.py +++ b/src/spaceone/identity/service/project_service.py @@ -42,7 +42,6 @@ def create(self, params: ProjectCreateRequest) -> Union[ProjectResponse, dict]: ProjectResponse: """ - self.workspace_mgr.get_workspace(params.workspace_id, params.domain_id) project_vo = self.project_mgr.create_project(params.dict()) return ProjectResponse(**project_vo.to_dict()) @@ -66,7 +65,9 @@ def update(self, params: ProjectUpdateRequest) -> Union[ProjectResponse, dict]: project_vo = self.project_mgr.get_project( params.project_id, params.workspace_id, params.domain_id ) - project_vo = self.project_mgr.update_project_by_vo(params.dict(), project_vo) + project_vo = self.project_mgr.update_project_by_vo( + params.dict(exclude_unset=True), project_vo + ) return ProjectResponse(**project_vo.to_dict()) @@ -90,7 +91,9 @@ def update_project_type( project_vo = self.project_mgr.get_project( params.project_id, params.workspace_id, params.domain_id ) - project_vo = self.project_mgr.update_project_by_vo(params.dict(), project_vo) + project_vo = self.project_mgr.update_project_by_vo( + params.dict(exclude_unset=True), project_vo + ) return ProjectResponse(**project_vo.to_dict()) @@ -113,7 +116,9 @@ def change_project_group( project_vo = self.project_mgr.get_project( params.project_id, params.workspace_id, params.domain_id ) - project_vo = self.project_mgr.update_project_by_vo(params.dict(), project_vo) + project_vo = self.project_mgr.update_project_by_vo( + params.dict(exclude_unset=True), project_vo + ) return ProjectResponse(**project_vo.to_dict()) @transaction(append_meta={"authorization.scope": "WORKSPACE"}) @@ -148,7 +153,7 @@ def add_users(self, params: ProjectAddUsersRequest) -> Union[ProjectResponse, di params.users = list(set(users)) project_vo = self.project_mgr.update_project_by_vo( - params.dict(), project_vo + params.dict(exclude_unset=True), project_vo ) return ProjectResponse(**project_vo.to_dict()) @@ -165,7 +170,9 @@ def remove_users( ) params.users = list(set(project_vo.users) - set(params.users)) - project_vo = self.project_mgr.update_project_by_vo(params.dict(), project_vo) + project_vo = self.project_mgr.update_project_by_vo( + params.dict(exclude_unset=True), project_vo + ) return ProjectResponse(**project_vo.to_dict()) diff --git a/src/spaceone/identity/service/token_service.py b/src/spaceone/identity/service/token_service.py index bfc4e69f..908a31c5 100644 --- a/src/spaceone/identity/service/token_service.py +++ b/src/spaceone/identity/service/token_service.py @@ -63,13 +63,11 @@ def issue(self, params: TokenIssueRequest) -> Union[TokenResponse, dict]: user_mfa = user_vo.mfa.to_dict() if user_vo.mfa else {} if user_mfa.get("state", "DISABLED") == "ENABLED": + mfa_manager = MFAManager.get_manager_by_mfa_type(user_mfa.get("mfa_type")) if verify_code: - token_mgr.check_mfa_verify_code(user_id, domain_id, verify_code) + mfa_manager.check_mfa_verify_code(user_id, domain_id, verify_code) else: mfa_email = user_mfa["options"].get("email") - mfa_manager = MFAManager.get_manager_by_mfa_type( - user_mfa.get("mfa_type") - ) mfa_manager.send_mfa_authentication_email( user_id, domain_id, mfa_email, user_vo.language ) diff --git a/src/spaceone/identity/service/user_service.py b/src/spaceone/identity/service/user_service.py index 665e43aa..2ec58878 100644 --- a/src/spaceone/identity/service/user_service.py +++ b/src/spaceone/identity/service/user_service.py @@ -165,7 +165,9 @@ def update(self, params: UserUpdateRequest) -> Union[UserResponse, dict]: temp_password = self._generate_temporary_password() params.password = temp_password - user_vo = self.user_mgr.update_user_by_vo(params, user_vo) + user_vo = self.user_mgr.update_user_by_vo( + params.dict(exclude_unset=True), user_vo + ) user_vo = self.user_mgr.update_user_by_vo( {"required_actions": ["UPDATE_PASSWORD"]}, user_vo ) @@ -186,7 +188,9 @@ def update(self, params: UserUpdateRequest) -> Union[UserResponse, dict]: user_id, email, console_link, temp_password, language ) else: - user_vo = self.user_mgr.update_user_by_vo(params, user_vo) + user_vo = self.user_mgr.update_user_by_vo( + params.dict(exclude_unset=True), user_vo + ) return UserResponse(**user_vo.to_dict()) @@ -214,7 +218,7 @@ def verify_email(self, params: UserVerifyEmailRequest) -> None: email = params.email or user_vo.email force = params.force or False - params = params.dict() + params = params.dict(exclude_unset=True) if force: params.update({"email_verified": True}) self.user_mgr.update_user_by_vo(params, user_vo) @@ -246,7 +250,7 @@ def confirm_email( Returns: - None + UserResponse: """ user_id = params.user_id @@ -257,9 +261,10 @@ def confirm_email( if token_manager.check_verify_code(user_id, domain_id, verify_code): user_vo = self.user_mgr.get_user(user_id, domain_id) - params = params.dict() + params = params.dict(exclude_unset=True) params["email_verified"] = True - return self.user_mgr.update_user_by_vo(params, user_vo) + user_vo = self.user_mgr.update_user_by_vo(params, user_vo) + return UserResponse(**user_vo.to_dict()) else: raise ERROR_INVALID_VERIFY_CODE(verify_code=verify_code) From 192fa53b038ee2f32af3020e33a2983287d13769 Mon Sep 17 00:00:00 2001 From: Jongmin Kim Date: Thu, 23 Nov 2023 17:15:03 +0900 Subject: [PATCH 42/45] feat: initial commit for identity v2 --- src/spaceone/identity/error/error_role.py | 39 +---- .../identity/manager/policy_manager.py | 143 +++++------------- .../identity/manager/provider_manager.py | 4 +- .../identity/manager/role_binding_manager.py | 65 ++++++++ src/spaceone/identity/manager/role_manager.py | 71 +++++++++ .../trusted_service_account_manager.py | 2 +- .../identity/model/policy/database.py | 33 ++++ src/spaceone/identity/model/role/database.py | 51 +++++++ .../identity/model/role_binding/database.py | 38 +++++ .../identity/model/role_binding/request.py | 2 + .../identity/service/policy_service.py | 37 ++++- .../identity/service/provider_service.py | 7 +- .../identity/service/role_binding_service.py | 92 +++++++++-- src/spaceone/identity/service/role_service.py | 50 ++++-- .../trusted_service_account_service.py | 1 - 15 files changed, 456 insertions(+), 179 deletions(-) create mode 100644 src/spaceone/identity/manager/role_binding_manager.py create mode 100644 src/spaceone/identity/manager/role_manager.py create mode 100644 src/spaceone/identity/model/role/database.py create mode 100644 src/spaceone/identity/model/role_binding/database.py diff --git a/src/spaceone/identity/error/error_role.py b/src/spaceone/identity/error/error_role.py index ecd15c87..25dccaaf 100644 --- a/src/spaceone/identity/error/error_role.py +++ b/src/spaceone/identity/error/error_role.py @@ -1,39 +1,12 @@ from spaceone.core.error import * +class ERROR_POLICY_IN_USED(ERROR_INVALID_ARGUMENT): + _message = 'Policy is used. (role_id = {role_id})' -class ERROR_NOT_ALLOWED_ROLE_TYPE(ERROR_INVALID_ARGUMENT): - _message = ( - "Duplicate assignment of system role and domain or project role is not allowed." - ) - - -class ERROR_REQUIRED_PROJECT_OR_PROJECT_GROUP(ERROR_INVALID_ARGUMENT): - _message = "Project role require project_id or project_group_id." - - -class ERROR_NOT_ALLOWED_PROJECT_ID(ERROR_INVALID_ARGUMENT): - _message = "Domain or system role dose not need project_id." - - -class ERROR_NOT_ALLOWED_PROJECT_GROUP_ID(ERROR_INVALID_ARGUMENT): - _message = "Domain or system role dose not need project_group_id." +class ERROR_ROLE_IN_USED(ERROR_INVALID_ARGUMENT): + _message = 'Role is used. (role_binding_id = {role_binding_id}, user_id = {user_id})' -class ERROR_DUPLICATE_ROLE_BOUND(ERROR_INVALID_ARGUMENT): - _message = ( - "Duplicate role bound. (role_id = {role_id}, resource_id = {resource_id})" - ) - -class ERROR_DUPLICATE_RESOURCE_IN_PROJECT(ERROR_INVALID_ARGUMENT): - _message = "There are duplicate resource in the project. (project_id = {project_id}, resource_id = {resource_id})" - - -class ERROR_DUPLICATE_RESOURCE_IN_PROJECT_GROUP(ERROR_INVALID_ARGUMENT): - _message = "There are duplicate resource in the project_group. (project_group_id = {project_group_id}, resource_id = {resource_id})" - - -class ERROR_POLICY_IS_IN_USE(ERROR_INVALID_ARGUMENT): - _message = ( - "The policy is in use by role. (policy_id = {policy_id}, role_id = {role_id})" - ) +class ERROR_NOT_ALLOWED_ROLE_TYPE(ERROR_INVALID_ARGUMENT): + _message = 'Role type is not allowed. (supported_role_type = {supported_role_type})' diff --git a/src/spaceone/identity/manager/policy_manager.py b/src/spaceone/identity/manager/policy_manager.py index 1c83b76c..62dc2f87 100644 --- a/src/spaceone/identity/manager/policy_manager.py +++ b/src/spaceone/identity/manager/policy_manager.py @@ -1,13 +1,12 @@ import logging -from datetime import datetime +from typing import Tuple, List -from spaceone.core import cache +from spaceone.core import cache, utils from spaceone.core.manager import BaseManager -from spaceone.core import utils from spaceone.core.connector.space_connector import SpaceConnector -from spaceone.identity.error.error_role import * -from spaceone.identity.model.policy_model import Policy +from spaceone.identity.model.policy.database import Policy from spaceone.identity.manager.role_manager import RoleManager +from spaceone.identity.error.error_role import ERROR_POLICY_IN_USED _LOGGER = logging.getLogger(__name__) @@ -16,128 +15,54 @@ class PolicyManager(BaseManager): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.policy_model: Policy = self.locator.get_model('Policy') + self.policy_model = Policy - def create_policy(self, params): - def _rollback(policy_vo): - _LOGGER.info(f'[create_policy._rollback] Create policy : {policy_vo.name} ({policy_vo.policy_id})') - policy_vo.delete() + def create_policy(self, params: dict) -> Policy: + def _rollback(vo: Policy): + _LOGGER.info(f'[create_policy._rollback] ' + f'Delete policy: {vo.name} ({vo.policy_id})') + vo.delete() - policy_vo: Policy = self.policy_model.create(params) + params['permissions_hash'] = utils.dict_to_hash(params['permissions']) + + policy_vo = self.policy_model.create(params) self.transaction.add_rollback(_rollback, policy_vo) return policy_vo - def update_policy(self, params): + def update_policy_by_vo( + self, params: dict, policy_vo: Policy + ) -> Policy: def _rollback(old_data): - _LOGGER.info(f'[update_policy._rollback] Revert Data : {old_data["name"]} ({old_data["policy_id"]})') + _LOGGER.info(f'[update_policy_by_vo._rollback] Revert Data : {old_data["policy_id"]}') policy_vo.update(old_data) - policy_vo: Policy = self.get_policy(params['policy_id'], params['domain_id']) self.transaction.add_rollback(_rollback, policy_vo.to_dict()) - params['updated_at'] = datetime.utcnow() - policy_vo = policy_vo.update(params) - - if 'permissions' in params: - self._delete_role_cache(policy_vo) + if permissions := params.get('permissions'): + params['permissions_hash'] = utils.dict_to_hash(permissions) - return policy_vo + return policy_vo.update(params) - def delete_policy(self, policy_id, domain_id): - policy_vo: Policy = self.get_policy(policy_id, domain_id) - role_vos = self._get_roles_using_policy(policy_vo) + @staticmethod + def delete_policy_by_vo(policy_vo: Policy) -> None: + # Check policy is used (role) + role_mgr = RoleManager() + role_vos = role_mgr.filter_roles(policy_id=policy_vo.policy_id, domain_id=policy_vo.domain_id) for role_vo in role_vos: - raise ERROR_POLICY_IS_IN_USE(policy_id=policy_id, role_id=role_vo.role_id) + raise ERROR_POLICY_IN_USED(role_id=role_vo.role_id) policy_vo.delete() - def get_policy(self, policy_id, domain_id, only=None): - return self.policy_model.get(policy_id=policy_id, domain_id=domain_id, policy_type='CUSTOM', only=only) - - def list_policies(self, query): - return self.policy_model.query(**query) - - def stat_policies(self, query): - return self.policy_model.stat(**query) - - def get_managed_policy(self, policy_id, domain_id): - repo_managed_policy_info = self._get_managed_policy_from_repository(policy_id, domain_id) - local_managed_policy_vo = self._get_managed_policy_from_local(policy_id, domain_id) - - if repo_managed_policy_info: - if local_managed_policy_vo: - if repo_managed_policy_info.get('updated_at') == local_managed_policy_vo.updated_at: - return local_managed_policy_vo - - return self._update_managed_policy(local_managed_policy_vo, repo_managed_policy_info) + def get_policy(self, policy_id: str, domain_id: str) -> Policy: + return self.policy_model.get(policy_id=policy_id, domain_id=domain_id) - return self._create_managed_policy(policy_id, domain_id, repo_managed_policy_info) - else: - if local_managed_policy_vo: - return local_managed_policy_vo + def filter_policies(self, **conditions) -> List[Policy]: + return self.policy_model.filter(**conditions) - raise ERROR_NOT_FOUND(key='policy_id', value=policy_id) - - def _create_managed_policy(self, policy_id, domain_id, repo_managed_policy_info): - policy_vo: Policy = self.create_policy({ - 'policy_id': policy_id, - 'name': repo_managed_policy_info['name'], - 'policy_type': 'MANAGED', - 'permissions': repo_managed_policy_info.get('permissions', []), - 'tags': repo_managed_policy_info.get('tags', []), - 'domain_id': domain_id, - 'updated_at': repo_managed_policy_info.get('updated_at') - }) - - return policy_vo - - def _update_managed_policy(self, local_managed_policy_vo, repo_managed_policy_info): - policy_vo: Policy = local_managed_policy_vo.update({ - 'name': repo_managed_policy_info['name'], - 'permissions': repo_managed_policy_info.get('permissions', []), - 'tags': repo_managed_policy_info.get('tags', []), - 'updated_at': repo_managed_policy_info.get('updated_at') - }) - - self._delete_role_cache(policy_vo) - - return policy_vo + def list_policies(self, query: dict) -> Tuple[list, int]: + return self.policy_model.query(**query) - @cache.cacheable(key='managed-policy:{domain_id}:{policy_id}', expire=600) - def _get_managed_policy_from_repository(self, policy_id, domain_id): - repo_connector: SpaceConnector = self.locator.get_connector('SpaceConnector', service='repository') - try: - return repo_connector.dispatch('Policy.get', {'policy_id': policy_id, 'domain_id': domain_id}) - except Exception as e: - _LOGGER.error(f'Failed to get managed policy. (policy_id = {policy_id})') - return None - - def _get_managed_policy_from_local(self, policy_id, domain_id): - managed_policy_vos = self.policy_model.filter(policy_id=policy_id, policy_type='MANAGED', domain_id=domain_id) - if managed_policy_vos.count() > 0: - return managed_policy_vos[0] - else: - return None - - def _delete_role_cache(self, policy_vo): - role_vos = self._get_roles_using_policy(policy_vo) - for role_vo in role_vos: - cache.delete_pattern(f'role-permissions:*{role_vo.role_id}') - cache.delete_pattern(f'user-permissions:*{role_vo.role_id}*') - - def _get_roles_using_policy(self, policy_vo): - role_mgr: RoleManager = self.locator.get_manager('RoleManager') - query = { - 'filter': [ - { - 'k': 'policies.policy', - 'v': policy_vo, - 'o': 'eq' - } - ] - } - - role_vos, total_count = role_mgr.list_roles(query) - return role_vos + def stat_policies(self, query: dict) -> dict: + return self.policy_model.stat(**query) \ No newline at end of file diff --git a/src/spaceone/identity/manager/provider_manager.py b/src/spaceone/identity/manager/provider_manager.py index d6bda926..88118b6e 100644 --- a/src/spaceone/identity/manager/provider_manager.py +++ b/src/spaceone/identity/manager/provider_manager.py @@ -37,8 +37,8 @@ def _rollback(old_data): return provider_vo.update(params) - def delete_provider(self, provider: str, domain_id: str) -> None: - provider_vo = self.get_provider(provider, domain_id) + @staticmethod + def delete_provider_by_vo(provider_vo: Provider) -> None: provider_vo.delete() def get_provider(self, provider: str, domain_id: str) -> Provider: diff --git a/src/spaceone/identity/manager/role_binding_manager.py b/src/spaceone/identity/manager/role_binding_manager.py new file mode 100644 index 00000000..d74b7d32 --- /dev/null +++ b/src/spaceone/identity/manager/role_binding_manager.py @@ -0,0 +1,65 @@ +import logging +from typing import Tuple, List + +from spaceone.core.manager import BaseManager +from spaceone.core.connector.space_connector import SpaceConnector +from spaceone.identity.model.role_binding.database import RoleBinding + +_LOGGER = logging.getLogger(__name__) + + +class RoleBindingManager(BaseManager): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.role_binding_model = RoleBinding + + def create_role_binding(self, params: dict) -> RoleBinding: + def _rollback(vo: RoleBinding): + _LOGGER.info(f'[create_role_binding._rollback] ' + f'Delete trusted service account: {vo.name} ({vo.role_binding_id})') + vo.delete() + + role_binding_vo = self.role_binding_model.create(params) + self.transaction.add_rollback(_rollback, role_binding_vo) + + return role_binding_vo + + def update_role_binding_by_vo( + self, params: dict, role_binding_vo: RoleBinding + ) -> RoleBinding: + def _rollback(old_data): + _LOGGER.info(f'[update_role_binding_by_vo._rollback] Revert Data : ' + f'{old_data["role_binding_id"]}') + role_binding_vo.update(old_data) + + self.transaction.add_rollback(_rollback, role_binding_vo.to_dict()) + + return role_binding_vo.update(params) + + @staticmethod + def delete_role_binding_by_vo(role_binding_vo: RoleBinding) -> None: + role_binding_vo.delete() + + def get_role_binding( + self, role_binding_id: str, domain_id: str, workspace_id: str = None + ) -> RoleBinding: + + conditions = { + 'role_binding_id': role_binding_id, + 'domain_id': domain_id, + } + + if workspace_id: + conditions['workspace_id'] = workspace_id + + return self.role_binding_model.get(**conditions) + + def filter_role_bindings(self, **conditions) -> List[RoleBinding]: + return self.role_binding_model.filter(**conditions) + + def list_role_bindings(self, query: dict) -> Tuple[list, int]: + return self.role_binding_model.query(**query) + + def stat_role_bindings(self, query: dict) -> dict: + return self.role_binding_model.stat(**query) diff --git a/src/spaceone/identity/manager/role_manager.py b/src/spaceone/identity/manager/role_manager.py new file mode 100644 index 00000000..3a72da46 --- /dev/null +++ b/src/spaceone/identity/manager/role_manager.py @@ -0,0 +1,71 @@ +import logging +from typing import Tuple, List + +from spaceone.core import cache, utils +from spaceone.core.manager import BaseManager +from spaceone.core.connector.space_connector import SpaceConnector +from spaceone.identity.model.role.database import Role +from spaceone.identity.manager.role_binding_manager import RoleBindingManager +from spaceone.identity.error.error_role import ERROR_ROLE_IN_USED + +_LOGGER = logging.getLogger(__name__) + + +class RoleManager(BaseManager): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.role_model = Role + + def create_role(self, params: dict) -> Role: + def _rollback(vo: Role): + _LOGGER.info(f'[create_role._rollback] ' + f'Delete role: {vo.name} ({vo.role_id})') + vo.delete() + + params['policies_hash'] = utils.dict_to_hash(params['policies']) + params['page_permissions_hash'] = utils.dict_to_hash(params.get('page_permissions', [])) + + role_vo = self.role_model.create(params) + self.transaction.add_rollback(_rollback, role_vo) + + return role_vo + + def update_role_by_vo( + self, params: dict, role_vo: Role + ) -> Role: + def _rollback(old_data): + _LOGGER.info(f'[update_role_by_vo._rollback] Revert Data : {old_data["role_id"]}') + role_vo.update(old_data) + + self.transaction.add_rollback(_rollback, role_vo.to_dict()) + + if policies := params.get('policies'): + params['policies_hash'] = utils.dict_to_hash(policies) + + if page_permissions := params.get('page_permissions'): + params['page_permissions_hash'] = utils.dict_to_hash(page_permissions) + + return role_vo.update(params) + + @staticmethod + def delete_role_by_vo(role_vo: Role) -> None: + rb_mgr = RoleBindingManager() + rb_vos = rb_mgr.filter_role_bindings(role_id=role_vo.role_id, domain_id=role_vo.domain_id) + + for rb_vo in rb_vos: + raise ERROR_ROLE_IN_USED(role_binding_id=rb_vo.role_binding_id, user_id=rb_vo.user_id) + + role_vo.delete() + + def get_role(self, role_id: str, domain_id: str) -> Role: + return self.role_model.get(role_id=role_id, domain_id=domain_id) + + def filter_roles(self, **conditions) -> List[Role]: + return self.role_model.filter(**conditions) + + def list_roles(self, query: dict) -> Tuple[list, int]: + return self.role_model.query(**query) + + def stat_roles(self, query: dict) -> dict: + return self.role_model.stat(**query) \ No newline at end of file diff --git a/src/spaceone/identity/manager/trusted_service_account_manager.py b/src/spaceone/identity/manager/trusted_service_account_manager.py index 41ad92df..2ef4ad60 100644 --- a/src/spaceone/identity/manager/trusted_service_account_manager.py +++ b/src/spaceone/identity/manager/trusted_service_account_manager.py @@ -90,4 +90,4 @@ def _list_trusted_secrets( 'trusted_service_account_id': trusted_service_account_id, 'domain_id': domain_id, 'workspace_id': workspace_id - }) \ No newline at end of file + }) diff --git a/src/spaceone/identity/model/policy/database.py b/src/spaceone/identity/model/policy/database.py index e69de29b..22af36a4 100644 --- a/src/spaceone/identity/model/policy/database.py +++ b/src/spaceone/identity/model/policy/database.py @@ -0,0 +1,33 @@ +from mongoengine import * +from spaceone.core.model.mongo_model import MongoModel + + +class Policy(MongoModel): + policy_id = StringField(max_length=40, generate_id='policy', unique_with='domain_id') + name = StringField(max_length=255) + permissions = ListField(StringField()) + permissions_hash = StringField() + tags = DictField(default=None) + is_managed = BooleanField(default=False) + domain_id = StringField(max_length=40) + created_at = DateTimeField(auto_now_add=True) + updated_at = DateTimeField(default=None, null=True) + + meta = { + 'updatable_fields': [ + 'name', + 'permissions', + 'permissions_hash', + 'tags', + 'updated_at' + ], + 'minimal_fields': [ + 'policy_id', + 'name' + ], + 'ordering': ['name'], + 'indexes': [ + 'permissions_hash', + 'domain_id', + ] + } diff --git a/src/spaceone/identity/model/role/database.py b/src/spaceone/identity/model/role/database.py new file mode 100644 index 00000000..ba46488c --- /dev/null +++ b/src/spaceone/identity/model/role/database.py @@ -0,0 +1,51 @@ +from mongoengine import * +from spaceone.core.model.mongo_model import MongoModel + + +class PagePermission(EmbeddedDocument): + pages = ListField(StringField()) + permission = StringField(max_length=20, choices=('VIEW', 'MANAGE')) + + +class Role(MongoModel): + role_id = StringField(max_length=40, generate_id='role', unique=True) + name = StringField(max_length=255, unique_with='domain_id') + role_type = StringField( + max_length=20, + choices=('SYSTEM_ADMIN', 'DOMAIN_ADMIN', 'WORKSPACE_OWNER', 'WORKSPACE_MEMBER') + ) + policies = ListField(StringField()) + policies_hash = StringField() + page_permissions = ListField(EmbeddedDocumentField(PagePermission), default=[]) + page_permissions_hash = StringField() + tags = DictField(default=None) + is_managed = BooleanField(default=False) + domain_id = StringField(max_length=40) + created_at = DateTimeField(auto_now_add=True) + + meta = { + 'updatable_fields': [ + 'name', + 'policies', + 'policies_hash', + 'page_permissions', + 'page_permissions_hash', + 'tags', + 'updated_at' + ], + 'minimal_fields': [ + 'role_id', + 'name', + 'role_type' + ], + 'change_query_keys': { + 'policy_id': 'policies' + }, + 'ordering': ['name'], + 'indexes': [ + 'role_type', + 'policies_hash', + 'page_permissions_hash', + 'domain_id' + ] + } \ No newline at end of file diff --git a/src/spaceone/identity/model/role_binding/database.py b/src/spaceone/identity/model/role_binding/database.py new file mode 100644 index 00000000..176e8fc5 --- /dev/null +++ b/src/spaceone/identity/model/role_binding/database.py @@ -0,0 +1,38 @@ +from mongoengine import * +from spaceone.core.model.mongo_model import MongoModel + + +class RoleBinding(MongoModel): + role_binding_id = StringField(max_length=40, generate_id='rb', unique=True) + role_type = StringField( + max_length=20, + choices=('SYSTEM_ADMIN', 'DOMAIN_ADMIN', 'WORKSPACE_OWNER', 'WORKSPACE_MEMBER') + ) + user_id = StringField(max_length=255) + role_id = StringField(max_length=40) + scope = StringField(max_length=40, choices=('DOMAIN', 'WORKSPACE'), default='WORKSPACE') + workspace_id = StringField(max_length=40, default='*') + domain_id = StringField(max_length=255) + created_at = DateTimeField(auto_now_add=True) + + meta = { + 'updatable_fields': [ + 'role_id', + 'role_type', + ], + 'minimal_fields': [ + 'role_binding_id', + 'role_type', + 'user_id', + 'role_id', + 'workspace_id' + ], + 'indexes': [ + 'role_type', + 'user_id', + 'role_id', + 'scope', + 'workspace_id', + 'domain_id', + ] + } \ No newline at end of file diff --git a/src/spaceone/identity/model/role_binding/request.py b/src/spaceone/identity/model/role_binding/request.py index fa4ca005..a22bc55b 100644 --- a/src/spaceone/identity/model/role_binding/request.py +++ b/src/spaceone/identity/model/role_binding/request.py @@ -17,6 +17,7 @@ class RoleBindingCreateRequest(BaseModel): user_id: str role_id: str + role_type: Union[str, None] = None scope: Scope workspace_id: Union[str, None] = None domain_id: str @@ -25,6 +26,7 @@ class RoleBindingCreateRequest(BaseModel): class RoleBindingUpdateRoleRequest(BaseModel): role_binding_id: str role_id: str + role_type: Union[str, None] = None workspace_id: Union[str, None] = None domain_id: str diff --git a/src/spaceone/identity/service/policy_service.py b/src/spaceone/identity/service/policy_service.py index 344613fc..b5b9f753 100644 --- a/src/spaceone/identity/service/policy_service.py +++ b/src/spaceone/identity/service/policy_service.py @@ -3,11 +3,17 @@ from spaceone.core.service import BaseService, transaction, convert_model from spaceone.identity.model.policy.request import * from spaceone.identity.model.policy.response import * +from spaceone.identity.manager.policy_manager import PolicyManager _LOGGER = logging.getLogger(__name__) class PolicyService(BaseService): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.policy_mgr = PolicyManager() + @transaction(append_meta={'authorization.scope': 'DOMAIN'}) @convert_model def create(self, params: PolicyCreateRequest) -> Union[PolicyResponse, dict]: @@ -24,7 +30,9 @@ def create(self, params: PolicyCreateRequest) -> Union[PolicyResponse, dict]: Returns: PolicyResponse: """ - return {} + + policy_vo = self.policy_mgr.create_policy(params.dict()) + return PolicyResponse(**policy_vo.to_dict()) @transaction(append_meta={'authorization.scope': 'DOMAIN'}) @convert_model @@ -44,7 +52,13 @@ def update(self, params: PolicyUpdateRequest) -> Union[PolicyResponse, dict]: PolicyResponse: """ - return {} + policy_vo = self.policy_mgr.get_policy(params.policy_id, params.domain_id) + + policy_vo = self.policy_mgr.update_policy_by_vo( + params.dict(exclude_unset=True), policy_vo + ) + + return PolicyResponse(**policy_vo.to_dict()) @transaction(append_meta={'authorization.scope': 'DOMAIN'}) @convert_model @@ -60,7 +74,9 @@ def delete(self, params: PolicyDeleteRequest) -> None: Returns: None """ - pass + + policy_vo = self.policy_mgr.get_policy(params.policy_id, params.domain_id) + self.policy_mgr.delete_policy_by_vo(policy_vo) @transaction(append_meta={'authorization.scope': 'DOMAIN_READ'}) @convert_model @@ -76,7 +92,9 @@ def get(self, params: PolicyGetRequest) -> Union[PolicyResponse, dict]: Returns: PolicyResponse: """ - return {} + + policy_vo = self.policy_mgr.get_policy(params.policy_id, params.domain_id) + return PolicyResponse(**policy_vo.to_dict()) @transaction(append_meta={'authorization.scope': 'DOMAIN_READ'}) @convert_model @@ -94,7 +112,12 @@ def list(self, params: PolicySearchQueryRequest) -> Union[PoliciesResponse, dict Returns: PoliciesResponse: """ - return {} + + query = params.query or {} + policy_vos, total_count = self.policy_mgr.list_policies(query) + + policies_info = [policy_vo.to_dict() for policy_vo in policy_vos] + return PoliciesResponse(results=policies_info, total_count=total_count) @transaction(append_meta={'authorization.scope': 'DOMAIN_READ'}) @convert_model @@ -113,4 +136,6 @@ def stat(self, params: PolicyStatQueryRequest) -> dict: 'total_count': 'int' } """ - return {} + + query = params.query or {} + return self.policy_mgr.stat_policies(query) diff --git a/src/spaceone/identity/service/provider_service.py b/src/spaceone/identity/service/provider_service.py index 0b4f189e..753a404c 100644 --- a/src/spaceone/identity/service/provider_service.py +++ b/src/spaceone/identity/service/provider_service.py @@ -89,7 +89,8 @@ def delete(self, params: ProviderDeleteRequest) -> None: None """ - self.provider_mgr.delete_provider(params.provider, params.domain_id) + provider_vo = self.provider_mgr.get_provider(params.provider, params.domain_id) + self.provider_mgr.delete_provider_by_vo(provider_vo) @transaction(append_meta={'authorization.scope': 'DOMAIN_READ'}) @convert_model @@ -132,9 +133,9 @@ def list(self, params: ProviderSearchQueryRequest) -> Union[ProvidersResponse, d self._create_default_provider(params.domain_id) - providers_vos, total_count = self.provider_mgr.list_providers(query) + provider_vos, total_count = self.provider_mgr.list_providers(query) - providers_info = [provider_vo.to_dict() for provider_vo in providers_vos] + providers_info = [provider_vo.to_dict() for provider_vo in provider_vos] return ProvidersResponse(results=providers_info, total_count=total_count) @transaction(append_meta={'authorization.scope': 'DOMAIN_READ'}) diff --git a/src/spaceone/identity/service/role_binding_service.py b/src/spaceone/identity/service/role_binding_service.py index 7e130019..5b14f1e7 100644 --- a/src/spaceone/identity/service/role_binding_service.py +++ b/src/spaceone/identity/service/role_binding_service.py @@ -1,14 +1,24 @@ import logging from typing import Union from spaceone.core.service import BaseService, transaction, convert_model +from spaceone.core.error import * from spaceone.identity.model.role_binding.request import * from spaceone.identity.model.role_binding.response import * +from spaceone.identity.manager.role_binding_manager import RoleBindingManager +from spaceone.identity.manager.role_manager import RoleManager +from spaceone.identity.manager.user_manager import UserManager +from spaceone.identity.error.error_role import ERROR_NOT_ALLOWED_ROLE_TYPE _LOGGER = logging.getLogger(__name__) class RoleBindingService(BaseService): - @transaction + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.role_binding_manager = RoleBindingManager() + + @transaction(append_meta={'authorization.scope': 'DOMAIN_OR_WORKSPACE'}) @convert_model def create(self, params: RoleBindingCreateRequest) -> Union[RoleBindingResponse, dict]: """ create role binding @@ -25,9 +35,27 @@ def create(self, params: RoleBindingCreateRequest) -> Union[RoleBindingResponse, Returns: RoleBindingResponse: """ - return {} - @transaction + # Check Scope + if params.scope == 'DOMAIN': + params.workspace_id = None + else: + if not params.workspace_id: + raise ERROR_REQUIRED_PARAMETER(key='workspace_id') + + # Check user + user_mgr = UserManager() + user_mgr.get_user(params.user_id, params.domain_id) + + # Check role + role_mgr = RoleManager() + role_vo = role_mgr.get_role(params.role_id, params.domain_id) + params.role_type = role_vo.role_type + + rb_vo = self.role_binding_manager.create_role_binding(params.dict()) + return RoleBindingResponse(**rb_vo.to_dict()) + + @transaction(append_meta={'authorization.scope': 'DOMAIN_OR_WORKSPACE'}) @convert_model def update_role(self, params: RoleBindingUpdateRoleRequest) -> Union[RoleBindingResponse, dict]: """ update role of role binding @@ -43,9 +71,32 @@ def update_role(self, params: RoleBindingUpdateRoleRequest) -> Union[RoleBinding Returns: RoleBindingResponse: """ - return {} - @transaction + rb_vo = self.role_binding_manager.get_role_binding( + params.role_binding_id, params.domain_id, params.workspace_id + ) + + # Check role + role_mgr = RoleManager() + role_vo = role_mgr.get_role(params.role_id, params.domain_id) + + if rb_vo.role_type in ['WORKSPACE_OWNER', 'WORKSPACE_MEMBER']: + if role_vo.role_type not in ['WORKSPACE_OWNER', 'WORKSPACE_MEMBER']: + raise ERROR_NOT_ALLOWED_ROLE_TYPE(supported_role_type=['WORKSPACE_OWNER', 'WORKSPACE_MEMBER']) + elif rb_vo.role_type != role_vo.role_type: + raise ERROR_NOT_ALLOWED_ROLE_TYPE(supported_role_type=[rb_vo.role_type]) + + rb_vo = self.role_binding_manager.update_role_binding_by_vo( + { + 'role_id': params.role_id, + 'role_type': role_vo.role_type + }, + rb_vo + ) + + return RoleBindingResponse(**rb_vo.to_dict()) + + @transaction(append_meta={'authorization.scope': 'DOMAIN_OR_WORKSPACE'}) @convert_model def delete(self, params: RoleBindingDeleteRequest) -> None: """ delete role binding @@ -60,9 +111,14 @@ def delete(self, params: RoleBindingDeleteRequest) -> None: Returns: None """ - pass - @transaction + rb_vo = self.role_binding_manager.get_role_binding( + params.role_binding_id, params.domain_id, params.workspace_id + ) + + self.role_binding_manager.delete_role_binding_by_vo(rb_vo) + + @transaction(append_meta={'authorization.scope': 'DOMAIN_OR_WORKSPACE_READ'}) @convert_model def get(self, params: RoleBindingGetRequest) -> Union[RoleBindingResponse, dict]: """ get role binding @@ -77,9 +133,14 @@ def get(self, params: RoleBindingGetRequest) -> Union[RoleBindingResponse, dict] Returns: RoleBindingResponse: """ - return {} - @transaction + rb_vo = self.role_binding_manager.get_role_binding( + params.role_binding_id, params.domain_id, params.workspace_id + ) + + return RoleBindingResponse(**rb_vo.to_dict()) + + @transaction(append_meta={'authorization.scope': 'DOMAIN_OR_WORKSPACE_READ'}) @convert_model def list(self, params: RoleBindingSearchQueryRequest) -> Union[RoleBindingsResponse, dict]: """ list role bindings @@ -98,9 +159,14 @@ def list(self, params: RoleBindingSearchQueryRequest) -> Union[RoleBindingsRespo Returns: RoleBindingsResponse: """ - return {} - @transaction + query = params.query or {} + rb_vos, total_count = self.role_binding_manager.list_role_bindings(query) + + rbs_info = [rb_vo.to_dict() for rb_vo in rb_vos] + return RoleBindingsResponse(results=rbs_info, total_count=total_count) + + @transaction(append_meta={'authorization.scope': 'DOMAIN_OR_WORKSPACE_READ'}) @convert_model def stat(self, params: RoleBindingStatQueryRequest) -> dict: """ stat role bindings @@ -118,4 +184,6 @@ def stat(self, params: RoleBindingStatQueryRequest) -> dict: 'total_count': 'int' } """ - return {} + + query = params.query or {} + return self.role_binding_manager.stat_role_bindings(query) diff --git a/src/spaceone/identity/service/role_service.py b/src/spaceone/identity/service/role_service.py index 4789a0c7..e13099b6 100644 --- a/src/spaceone/identity/service/role_service.py +++ b/src/spaceone/identity/service/role_service.py @@ -3,12 +3,18 @@ from spaceone.core.service import BaseService, transaction, convert_model from spaceone.identity.model.role.request import * from spaceone.identity.model.role.response import * +from spaceone.identity.manager.role_manager import RoleManager _LOGGER = logging.getLogger(__name__) class RoleService(BaseService): - @transaction + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.role_mgr = RoleManager() + + @transaction(append_meta={'authorization.scope': 'DOMAIN'}) @convert_model def create(self, params: RoleCreateRequest) -> Union[RoleResponse, dict]: """ create role @@ -26,9 +32,11 @@ def create(self, params: RoleCreateRequest) -> Union[RoleResponse, dict]: Returns: RoleResponse: """ - return {} - @transaction + role_vo = self.role_mgr.create_role(params.dict()) + return RoleResponse(**role_vo.to_dict()) + + @transaction(append_meta={'authorization.scope': 'DOMAIN'}) @convert_model def update(self, params: RoleUpdateRequest) -> Union[RoleResponse, dict]: """ update role @@ -46,9 +54,16 @@ def update(self, params: RoleUpdateRequest) -> Union[RoleResponse, dict]: Returns: RoleResponse: """ - return {} - @transaction + role_vo = self.role_mgr.get_role(params.role_id, params.domain_id) + + role_vo = self.role_mgr.update_role_by_vo( + params.dict(exclude_unset=True), role_vo + ) + + return RoleResponse(**role_vo.to_dict()) + + @transaction(append_meta={'authorization.scope': 'DOMAIN'}) @convert_model def delete(self, params: RoleDeleteRequest) -> None: """ delete role @@ -62,9 +77,11 @@ def delete(self, params: RoleDeleteRequest) -> None: Returns: None """ - pass - @transaction + role_vo = self.role_mgr.get_role(params.role_id, params.domain_id) + self.role_mgr.delete_role_by_vo(role_vo) + + @transaction(append_meta={'authorization.scope': 'DOMAIN_READ'}) @convert_model def get(self, params: RoleGetRequest) -> Union[RoleResponse, dict]: """ get role @@ -78,9 +95,11 @@ def get(self, params: RoleGetRequest) -> Union[RoleResponse, dict]: Returns: RoleResponse: """ - return {} - @transaction + role_vo = self.role_mgr.get_role(params.role_id, params.domain_id) + return RoleResponse(**role_vo.to_dict()) + + @transaction(append_meta={'authorization.scope': 'DOMAIN_READ'}) @convert_model def list(self, params: RoleSearchQueryRequest) -> Union[RolesResponse, dict]: """ list roles @@ -97,9 +116,14 @@ def list(self, params: RoleSearchQueryRequest) -> Union[RolesResponse, dict]: Returns: RolesResponse: """ - return {} - @transaction + query = params.query or {} + role_vos, total_count = self.role_mgr.list_roles(query) + + roles_info = [role_vo.to_dict() for role_vo in role_vos] + return RolesResponse(results=roles_info, total_count=total_count) + + @transaction(append_meta={'authorization.scope': 'DOMAIN_READ'}) @convert_model def stat(self, params: RoleStatQueryRequest) -> dict: """ stat roles @@ -116,4 +140,6 @@ def stat(self, params: RoleStatQueryRequest) -> dict: 'total_count': 'int' } """ - return {} + + query = params.query or {} + return self.role_mgr.stat_roles(query) diff --git a/src/spaceone/identity/service/trusted_service_account_service.py b/src/spaceone/identity/service/trusted_service_account_service.py index af7c9dd4..661be0c4 100644 --- a/src/spaceone/identity/service/trusted_service_account_service.py +++ b/src/spaceone/identity/service/trusted_service_account_service.py @@ -157,7 +157,6 @@ def list(self, params: TrustedServiceAccountSearchQueryRequest) -> Union[Trusted """ query = params.query or {} - trusted_account_vos, total_count = self.trusted_account_mgr.list_trusted_service_accounts(query) trusted_accounts_info = [trusted_account_vo.to_dict() for trusted_account_vo in trusted_account_vos] From 824bd3cefdc872ad2b2f8b25bd8750ecadf3a12d Mon Sep 17 00:00:00 2001 From: ImMin5 Date: Thu, 23 Nov 2023 17:38:36 +0900 Subject: [PATCH 43/45] fix: fix init smtp connector (#73) Signed-off-by: ImMin5 --- src/spaceone/identity/manager/mfa_manager/email_mfa_manger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spaceone/identity/manager/mfa_manager/email_mfa_manger.py b/src/spaceone/identity/manager/mfa_manager/email_mfa_manger.py index 8d5a92e6..8118975d 100644 --- a/src/spaceone/identity/manager/mfa_manager/email_mfa_manger.py +++ b/src/spaceone/identity/manager/mfa_manager/email_mfa_manger.py @@ -39,7 +39,7 @@ class EmailMFAManager(MFAManager): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.smtp_connector: SMTPConnector = self.locator.get_connector("SMTPConnector") + self.smtp_connector = SMTPConnector() def enable_mfa(self, user_id, domain_id, user_mfa, language): self.send_mfa_verify_email( From e202c3c58b2493acd7f2beb0448f86efbaf70e88 Mon Sep 17 00:00:00 2001 From: Jongmin Kim Date: Thu, 23 Nov 2023 17:41:06 +0900 Subject: [PATCH 44/45] feat: initial commit for identity v2 --- .../identity/manager/policy_manager.py | 14 ++++++++---- src/spaceone/identity/manager/role_manager.py | 22 ++++++++++++++----- .../identity/model/policy/database.py | 2 +- src/spaceone/identity/model/policy/request.py | 2 +- .../identity/model/policy/response.py | 6 +++++ src/spaceone/identity/model/role/response.py | 6 +++++ .../identity/model/role_binding/response.py | 6 +++++ src/spaceone/identity/service/role_service.py | 10 +++++++++ 8 files changed, 56 insertions(+), 12 deletions(-) diff --git a/src/spaceone/identity/manager/policy_manager.py b/src/spaceone/identity/manager/policy_manager.py index 62dc2f87..e9b6e367 100644 --- a/src/spaceone/identity/manager/policy_manager.py +++ b/src/spaceone/identity/manager/policy_manager.py @@ -23,7 +23,10 @@ def _rollback(vo: Policy): f'Delete policy: {vo.name} ({vo.policy_id})') vo.delete() - params['permissions_hash'] = utils.dict_to_hash(params['permissions']) + params['permissions'] = list(set(params['permissions'])) + params['permissions_hash'] = utils.dict_to_hash( + {'permissions': params['permissions']} + ) policy_vo = self.policy_model.create(params) self.transaction.add_rollback(_rollback, policy_vo) @@ -39,8 +42,11 @@ def _rollback(old_data): self.transaction.add_rollback(_rollback, policy_vo.to_dict()) - if permissions := params.get('permissions'): - params['permissions_hash'] = utils.dict_to_hash(permissions) + if 'permissions' in params: + params['permissions'] = list(set(params['permissions'])) + params['permissions_hash'] = utils.dict_to_hash( + {'permissions': params['permissions']} + ) return policy_vo.update(params) @@ -48,7 +54,7 @@ def _rollback(old_data): def delete_policy_by_vo(policy_vo: Policy) -> None: # Check policy is used (role) role_mgr = RoleManager() - role_vos = role_mgr.filter_roles(policy_id=policy_vo.policy_id, domain_id=policy_vo.domain_id) + role_vos = role_mgr.filter_roles(policies=policy_vo.policy_id, domain_id=policy_vo.domain_id) for role_vo in role_vos: raise ERROR_POLICY_IN_USED(role_id=role_vo.role_id) diff --git a/src/spaceone/identity/manager/role_manager.py b/src/spaceone/identity/manager/role_manager.py index 3a72da46..15c360c3 100644 --- a/src/spaceone/identity/manager/role_manager.py +++ b/src/spaceone/identity/manager/role_manager.py @@ -23,8 +23,13 @@ def _rollback(vo: Role): f'Delete role: {vo.name} ({vo.role_id})') vo.delete() - params['policies_hash'] = utils.dict_to_hash(params['policies']) - params['page_permissions_hash'] = utils.dict_to_hash(params.get('page_permissions', [])) + params['policies'] = list(set(params['policies'])) + params['policies_hash'] = utils.dict_to_hash( + {'policies': params['policies']} + ) + params['page_permissions_hash'] = utils.dict_to_hash( + {'page_permissions': params.get('page_permissions', [])} + ) role_vo = self.role_model.create(params) self.transaction.add_rollback(_rollback, role_vo) @@ -40,11 +45,16 @@ def _rollback(old_data): self.transaction.add_rollback(_rollback, role_vo.to_dict()) - if policies := params.get('policies'): - params['policies_hash'] = utils.dict_to_hash(policies) + if 'policies' in params: + params['policies'] = list(set(params['policies'])) + params['policies_hash'] = utils.dict_to_hash( + {'policies': params['policies']} + ) - if page_permissions := params.get('page_permissions'): - params['page_permissions_hash'] = utils.dict_to_hash(page_permissions) + if 'page_permissions' in params: + params['page_permissions_hash'] = utils.dict_to_hash( + {'page_permissions': params['page_permissions']} + ) return role_vo.update(params) diff --git a/src/spaceone/identity/model/policy/database.py b/src/spaceone/identity/model/policy/database.py index 22af36a4..7a12d56a 100644 --- a/src/spaceone/identity/model/policy/database.py +++ b/src/spaceone/identity/model/policy/database.py @@ -4,7 +4,7 @@ class Policy(MongoModel): policy_id = StringField(max_length=40, generate_id='policy', unique_with='domain_id') - name = StringField(max_length=255) + name = StringField(max_length=255, unique_with='domain_id') permissions = ListField(StringField()) permissions_hash = StringField() tags = DictField(default=None) diff --git a/src/spaceone/identity/model/policy/request.py b/src/spaceone/identity/model/policy/request.py index f60f6a66..eaaabff2 100644 --- a/src/spaceone/identity/model/policy/request.py +++ b/src/spaceone/identity/model/policy/request.py @@ -20,7 +20,7 @@ class PolicyCreateRequest(BaseModel): class PolicyUpdateRequest(BaseModel): policy_id: str - name: str + name: Union[str, None] = None permissions: Union[List[str], None] = None tags: Union[dict, None] = None domain_id: str diff --git a/src/spaceone/identity/model/policy/response.py b/src/spaceone/identity/model/policy/response.py index 1733e658..a345d5a0 100644 --- a/src/spaceone/identity/model/policy/response.py +++ b/src/spaceone/identity/model/policy/response.py @@ -1,6 +1,7 @@ from datetime import datetime from typing import Union, List from pydantic import BaseModel +from spaceone.core import utils __all__ = ["PolicyResponse", "PoliciesResponse"] @@ -14,6 +15,11 @@ class PolicyResponse(BaseModel): domain_id: Union[str, None] created_at: Union[datetime, None] + def dict(self, *args, **kwargs): + data = super().dict(*args, **kwargs) + data['created_at'] = utils.datetime_to_iso8601(data['created_at']) + return data + class PoliciesResponse(BaseModel): results: List[PolicyResponse] diff --git a/src/spaceone/identity/model/role/response.py b/src/spaceone/identity/model/role/response.py index 9bbb086e..7567b8db 100644 --- a/src/spaceone/identity/model/role/response.py +++ b/src/spaceone/identity/model/role/response.py @@ -1,6 +1,7 @@ from datetime import datetime from typing import Union, List from pydantic import BaseModel +from spaceone.core import utils from spaceone.identity.model.role.request import RoleType, PagePermissionType @@ -18,6 +19,11 @@ class RoleResponse(BaseModel): domain_id: Union[str, None] = None created_at: Union[datetime, None] = None + def dict(self, *args, **kwargs): + data = super().dict(*args, **kwargs) + data['created_at'] = utils.datetime_to_iso8601(data['created_at']) + return data + class RolesResponse(BaseModel): results: List[RoleResponse] diff --git a/src/spaceone/identity/model/role_binding/response.py b/src/spaceone/identity/model/role_binding/response.py index 61cc3d3e..0d8aa015 100644 --- a/src/spaceone/identity/model/role_binding/response.py +++ b/src/spaceone/identity/model/role_binding/response.py @@ -1,6 +1,7 @@ from datetime import datetime from typing import Union, List from pydantic import BaseModel +from spaceone.core import utils from spaceone.identity.model.role_binding.request import Scope @@ -16,6 +17,11 @@ class RoleBindingResponse(BaseModel): domain_id: Union[str, None] = None created_at: Union[datetime, None] = None + def dict(self, *args, **kwargs): + data = super().dict(*args, **kwargs) + data['created_at'] = utils.datetime_to_iso8601(data['created_at']) + return data + class RoleBindingsResponse(BaseModel): results: List[RoleBindingResponse] diff --git a/src/spaceone/identity/service/role_service.py b/src/spaceone/identity/service/role_service.py index e13099b6..411e18b4 100644 --- a/src/spaceone/identity/service/role_service.py +++ b/src/spaceone/identity/service/role_service.py @@ -4,6 +4,7 @@ from spaceone.identity.model.role.request import * from spaceone.identity.model.role.response import * from spaceone.identity.manager.role_manager import RoleManager +from spaceone.identity.manager.policy_manager import PolicyManager _LOGGER = logging.getLogger(__name__) @@ -33,6 +34,10 @@ def create(self, params: RoleCreateRequest) -> Union[RoleResponse, dict]: RoleResponse: """ + policy_mgr = PolicyManager() + for policy_id in params.policies: + policy_mgr.get_policy(policy_id, params.domain_id) + role_vo = self.role_mgr.create_role(params.dict()) return RoleResponse(**role_vo.to_dict()) @@ -57,6 +62,11 @@ def update(self, params: RoleUpdateRequest) -> Union[RoleResponse, dict]: role_vo = self.role_mgr.get_role(params.role_id, params.domain_id) + if params.policies: + policy_mgr = PolicyManager() + for policy_id in params.policies: + policy_mgr.get_policy(policy_id, params.domain_id) + role_vo = self.role_mgr.update_role_by_vo( params.dict(exclude_unset=True), role_vo ) From ab0b0573643acc2a6cc0fd977aa81dd964a7c596 Mon Sep 17 00:00:00 2001 From: Jongmin Kim Date: Thu, 23 Nov 2023 18:01:03 +0900 Subject: [PATCH 45/45] feat: initial commit for identity v2 --- .../identity/interface/grpc/__init__.py | 4 ++-- src/spaceone/identity/manager/email_manager.py | 2 +- .../identity/model/role_binding/database.py | 3 +++ .../identity/model/role_binding/response.py | 1 + .../identity/service/policy_service.py | 6 +++++- .../identity/service/role_binding_service.py | 18 +++++++++++++++++- src/spaceone/identity/service/role_service.py | 6 +++++- src/spaceone/identity/service/user_service.py | 1 - 8 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/spaceone/identity/interface/grpc/__init__.py b/src/spaceone/identity/interface/grpc/__init__.py index acd5406b..881846d2 100644 --- a/src/spaceone/identity/interface/grpc/__init__.py +++ b/src/spaceone/identity/interface/grpc/__init__.py @@ -3,7 +3,7 @@ from spaceone.identity.interface.grpc.endpoint import Endpoint from spaceone.identity.interface.grpc.provider import Provider from spaceone.identity.interface.grpc.workspace import Workspace -from spaceone.identity.interface.grpc.project_group import ProjectGroup +# from spaceone.identity.interface.grpc.project_group import ProjectGroup from spaceone.identity.interface.grpc.project import Project from spaceone.identity.interface.grpc.trusted_service_account import ( TrustedServiceAccount, @@ -25,7 +25,7 @@ app.add_service(Endpoint) app.add_service(Provider) app.add_service(Workspace) -app.add_service(ProjectGroup) +# app.add_service(ProjectGroup) app.add_service(Project) app.add_service(TrustedServiceAccount) app.add_service(ServiceAccount) diff --git a/src/spaceone/identity/manager/email_manager.py b/src/spaceone/identity/manager/email_manager.py index ad9ec128..5b2cee16 100644 --- a/src/spaceone/identity/manager/email_manager.py +++ b/src/spaceone/identity/manager/email_manager.py @@ -41,7 +41,7 @@ class EmailManager(BaseManager): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.smtp_connector: SMTPConnector = self.locator.get_connector("SMTPConnector") + self.smtp_connector = SMTPConnector() def send_reset_password_email(self, user_id, email, reset_password_link, language): service_name = self._get_service_name() diff --git a/src/spaceone/identity/model/role_binding/database.py b/src/spaceone/identity/model/role_binding/database.py index 176e8fc5..b4ac9232 100644 --- a/src/spaceone/identity/model/role_binding/database.py +++ b/src/spaceone/identity/model/role_binding/database.py @@ -27,6 +27,9 @@ class RoleBinding(MongoModel): 'role_id', 'workspace_id' ], + 'change_query_keys': { + 'user_workspaces': 'workspace_id' + }, 'indexes': [ 'role_type', 'user_id', diff --git a/src/spaceone/identity/model/role_binding/response.py b/src/spaceone/identity/model/role_binding/response.py index 0d8aa015..077cecc5 100644 --- a/src/spaceone/identity/model/role_binding/response.py +++ b/src/spaceone/identity/model/role_binding/response.py @@ -13,6 +13,7 @@ class RoleBindingResponse(BaseModel): scope: Union[Scope, None] = None user_id: Union[str, None] = None role_id: Union[str, None] = None + role_type: Union[str, None] = None workspace_id: Union[str, None] = None domain_id: Union[str, None] = None created_at: Union[datetime, None] = None diff --git a/src/spaceone/identity/service/policy_service.py b/src/spaceone/identity/service/policy_service.py index b5b9f753..bb0f660c 100644 --- a/src/spaceone/identity/service/policy_service.py +++ b/src/spaceone/identity/service/policy_service.py @@ -1,6 +1,6 @@ import logging from typing import Union -from spaceone.core.service import BaseService, transaction, convert_model +from spaceone.core.service import BaseService, transaction, convert_model, append_query_filter, append_keyword_filter from spaceone.identity.model.policy.request import * from spaceone.identity.model.policy.response import * from spaceone.identity.manager.policy_manager import PolicyManager @@ -97,6 +97,8 @@ def get(self, params: PolicyGetRequest) -> Union[PolicyResponse, dict]: return PolicyResponse(**policy_vo.to_dict()) @transaction(append_meta={'authorization.scope': 'DOMAIN_READ'}) + @append_query_filter(['policy_id', 'name', 'domain_id']) + @append_keyword_filter(['policy_id', 'name']) @convert_model def list(self, params: PolicySearchQueryRequest) -> Union[PoliciesResponse, dict]: """ list policies @@ -120,6 +122,8 @@ def list(self, params: PolicySearchQueryRequest) -> Union[PoliciesResponse, dict return PoliciesResponse(results=policies_info, total_count=total_count) @transaction(append_meta={'authorization.scope': 'DOMAIN_READ'}) + @append_query_filter(['domain_id']) + @append_keyword_filter(['policy_id', 'name']) @convert_model def stat(self, params: PolicyStatQueryRequest) -> dict: """ stat policies diff --git a/src/spaceone/identity/service/role_binding_service.py b/src/spaceone/identity/service/role_binding_service.py index 5b14f1e7..ff397250 100644 --- a/src/spaceone/identity/service/role_binding_service.py +++ b/src/spaceone/identity/service/role_binding_service.py @@ -1,6 +1,6 @@ import logging from typing import Union -from spaceone.core.service import BaseService, transaction, convert_model +from spaceone.core.service import BaseService, transaction, convert_model, append_query_filter, append_keyword_filter from spaceone.core.error import * from spaceone.identity.model.role_binding.request import * from spaceone.identity.model.role_binding.response import * @@ -50,6 +50,14 @@ def create(self, params: RoleBindingCreateRequest) -> Union[RoleBindingResponse, # Check role role_mgr = RoleManager() role_vo = role_mgr.get_role(params.role_id, params.domain_id) + + if params.scope == 'DOMAIN': + if role_vo.role_type not in ['DOMAIN_ADMIN', 'SYSTEM_ADMIN']: + raise ERROR_NOT_ALLOWED_ROLE_TYPE(supported_role_type=['DOMAIN_ADMIN']) + elif params.scope == 'WORKSPACE': + if role_vo.role_type not in ['WORKSPACE_ADMIN', 'WORKSPACE_MEMBER']: + raise ERROR_NOT_ALLOWED_ROLE_TYPE(supported_role_type=['WORKSPACE_ADMIN', 'WORKSPACE_MEMBER']) + params.role_type = role_vo.role_type rb_vo = self.role_binding_manager.create_role_binding(params.dict()) @@ -141,6 +149,10 @@ def get(self, params: RoleBindingGetRequest) -> Union[RoleBindingResponse, dict] return RoleBindingResponse(**rb_vo.to_dict()) @transaction(append_meta={'authorization.scope': 'DOMAIN_OR_WORKSPACE_READ'}) + @append_query_filter([ + 'role_binding_id', 'user_id', 'role_id', 'scope', 'workspace_id', 'domain_id', 'user_workspaces' + ]) + @append_keyword_filter(['role_binding_id', 'user_id', 'role_id']) @convert_model def list(self, params: RoleBindingSearchQueryRequest) -> Union[RoleBindingsResponse, dict]: """ list role bindings @@ -154,6 +166,7 @@ def list(self, params: RoleBindingSearchQueryRequest) -> Union[RoleBindingsRespo 'role_id': 'str', 'workspace_id': 'str', 'domain_id': 'str', # required + 'user_workspaces': 'list' # from meta } Returns: @@ -167,6 +180,8 @@ def list(self, params: RoleBindingSearchQueryRequest) -> Union[RoleBindingsRespo return RoleBindingsResponse(results=rbs_info, total_count=total_count) @transaction(append_meta={'authorization.scope': 'DOMAIN_OR_WORKSPACE_READ'}) + @append_query_filter(['domain_id', 'workspace_id', 'user_workspaces']) + @append_keyword_filter(['role_binding_id', 'user_id', 'role_id']) @convert_model def stat(self, params: RoleBindingStatQueryRequest) -> dict: """ stat role bindings @@ -176,6 +191,7 @@ def stat(self, params: RoleBindingStatQueryRequest) -> dict: 'query': 'dict (spaceone.api.core.v1.StatisticsQuery)', # required 'workspace_id': 'str', 'domain_id': 'str', # required + 'user_workspaces': 'list' # from meta } Returns: diff --git a/src/spaceone/identity/service/role_service.py b/src/spaceone/identity/service/role_service.py index 411e18b4..6d5ccdb3 100644 --- a/src/spaceone/identity/service/role_service.py +++ b/src/spaceone/identity/service/role_service.py @@ -1,6 +1,6 @@ import logging from typing import Union -from spaceone.core.service import BaseService, transaction, convert_model +from spaceone.core.service import BaseService, transaction, convert_model, append_query_filter, append_keyword_filter from spaceone.identity.model.role.request import * from spaceone.identity.model.role.response import * from spaceone.identity.manager.role_manager import RoleManager @@ -110,6 +110,8 @@ def get(self, params: RoleGetRequest) -> Union[RoleResponse, dict]: return RoleResponse(**role_vo.to_dict()) @transaction(append_meta={'authorization.scope': 'DOMAIN_READ'}) + @append_query_filter(['role_id', 'role_type', 'policy_id', 'domain_id']) + @append_keyword_filter(['role_id', 'name']) @convert_model def list(self, params: RoleSearchQueryRequest) -> Union[RolesResponse, dict]: """ list roles @@ -134,6 +136,8 @@ def list(self, params: RoleSearchQueryRequest) -> Union[RolesResponse, dict]: return RolesResponse(results=roles_info, total_count=total_count) @transaction(append_meta={'authorization.scope': 'DOMAIN_READ'}) + @append_query_filter(['domain_id']) + @append_keyword_filter(['role_id', 'name']) @convert_model def stat(self, params: RoleStatQueryRequest) -> dict: """ stat roles diff --git a/src/spaceone/identity/service/user_service.py b/src/spaceone/identity/service/user_service.py index 2ec58878..859a0160 100644 --- a/src/spaceone/identity/service/user_service.py +++ b/src/spaceone/identity/service/user_service.py @@ -37,7 +37,6 @@ def __init__(self, *args, **kwargs): self.user_mgr = UserManager() self.domain_mgr = DomainManager() self.domain_secret_mgr = DomainSecretManager() - self.email_manager = EmailManager() @transaction(append_meta={"authorization.scope": "DOMAIN_OR_WORKSPACE"}) @convert_model