Skip to content

Commit cd87b52

Browse files
authored
Merge pull request #219 from yjinjo/master
Add service account agent mode
2 parents 099bca0 + 70034ae commit cd87b52

File tree

12 files changed

+404
-21
lines changed

12 files changed

+404
-21
lines changed

pkg/pip_requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ langcodes
33
bcrypt
44
jinja2
55
fakeredis
6-
pytz
6+
pytz

src/spaceone/identity/interface/grpc/service_account.py

+32-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
from google.protobuf.json_format import ParseDict
12
from spaceone.core.pygrpc import BaseAPI
2-
from spaceone.api.identity.v2 import service_account_pb2, service_account_pb2_grpc
3+
from spaceone.api.identity.v2 import service_account_pb2, service_account_pb2_grpc, app_pb2
34
from spaceone.identity.service.service_account_service import (
45
ServiceAccountService,
56
)
@@ -15,6 +16,12 @@ def create(self, request, context):
1516
response: dict = service_account_svc.create(params)
1617
return self.dict_to_message(response)
1718

19+
def create_app(self, request, context):
20+
params, metadata = self.parse_request(request, context)
21+
service_account_svc = ServiceAccountService(metadata)
22+
response: dict = service_account_svc.create_app(params)
23+
return ParseDict(response, app_pb2.AppInfo())
24+
1825
def update(self, request, context):
1926
params, metadata = self.parse_request(request, context)
2027
service_account_svc = ServiceAccountService(metadata)
@@ -39,6 +46,30 @@ def delete(self, request, context):
3946
service_account_svc.delete(params)
4047
return self.empty()
4148

49+
def enable_app(self, request, context):
50+
params, metadata = self.parse_request(request, context)
51+
service_account_svc = ServiceAccountService(metadata)
52+
response: dict = service_account_svc.enable_app(params)
53+
return ParseDict(response, app_pb2.AppInfo())
54+
55+
def disable_app(self, request, context):
56+
params, metadata = self.parse_request(request, context)
57+
service_account_svc = ServiceAccountService(metadata)
58+
response: dict = service_account_svc.disable_app(params)
59+
return ParseDict(response, app_pb2.AppInfo())
60+
61+
def regenerate_app(self, request, context):
62+
params, metadata = self.parse_request(request, context)
63+
service_account_svc = ServiceAccountService(metadata)
64+
response: dict = service_account_svc.regenerate_app(params)
65+
return ParseDict(response, app_pb2.AppInfo())
66+
67+
def delete_app(self, request, context):
68+
params, metadata = self.parse_request(request, context)
69+
service_account_svc = ServiceAccountService(metadata)
70+
service_account_svc.delete_app(params)
71+
return self.empty()
72+
4273
def get(self, request, context):
4374
params, metadata = self.parse_request(request, context)
4475
service_account_svc = ServiceAccountService(metadata)

src/spaceone/identity/lib/key_generator.py

+20-15
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@
1111

1212
class KeyGenerator:
1313
def __init__(
14-
self,
15-
prv_jwk: dict,
16-
domain_id: str,
17-
owner_type: str,
18-
audience: str,
19-
client_id: str = None,
20-
refresh_prv_jwk: dict = None,
14+
self,
15+
prv_jwk: dict,
16+
domain_id: str,
17+
owner_type: str,
18+
audience: str,
19+
client_id: str = None,
20+
refresh_prv_jwk: dict = None,
2121
):
2222
self.prv_jwk = prv_jwk
2323
self.domain_id = domain_id
@@ -33,14 +33,15 @@ def _check_parameter(self):
3333
raise ERROR_GENERATE_KEY_FAILURE()
3434

3535
def generate_token(
36-
self,
37-
token_type: str,
38-
expired_at: str = None,
39-
timeout: int = None,
40-
role_type: str = None,
41-
workspace_id: str = None,
42-
permissions: list = None,
43-
projects: list = None,
36+
self,
37+
token_type: str,
38+
expired_at: str = None,
39+
timeout: int = None,
40+
role_type: str = None,
41+
workspace_id: str = None,
42+
permissions: list = None,
43+
projects: list = None,
44+
injected_params: dict = None,
4445
) -> str:
4546
payload = {
4647
"iss": "spaceone.identity",
@@ -70,6 +71,9 @@ def generate_token(
7071
if projects and len(projects) > 0:
7172
payload["projects"] = projects
7273

74+
if injected_params:
75+
payload["injected_params"] = injected_params
76+
7377
if token_type == "REFRESH_TOKEN":
7478
return JWTUtil.encode(payload, self.refresh_prv_jwk)
7579
else:
@@ -91,5 +95,6 @@ def _print_key(payload: dict):
9195
f'jti: {payload.get("jti")}, '
9296
f'projects: {payload.get("projects")},'
9397
f'permissions: {payload.get("permissions")},'
98+
f'injected_params: {payload.get("injected_params")},'
9499
f'ver: {payload.get("ver")} )'
95100
)

src/spaceone/identity/manager/app_manager.py

+4
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def get_app(
5454
app_id: str,
5555
domain_id: str,
5656
workspace_id: Union[str, None] = None,
57+
service_account_id: Union[str, None] = None,
5758
) -> App:
5859
conditions = {
5960
"app_id": app_id,
@@ -63,6 +64,9 @@ def get_app(
6364
if workspace_id:
6465
conditions["workspace_id"] = workspace_id
6566

67+
if service_account_id:
68+
conditions["service_account_id"] = service_account_id
69+
6670
return self.app_model.get(**conditions)
6771

6872
def filter_apps(self, **conditions) -> QuerySet:

src/spaceone/identity/manager/client_secret_manager.py

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ def generate_client_secret(
1919
role_type: str,
2020
workspace_id: str = None,
2121
permissions: list = None,
22+
injected_params: dict = None,
2223
) -> Tuple[str, str]:
2324
domain_secret_mgr = DomainSecretManager()
2425
prv_jwk = domain_secret_mgr.get_domain_private_key(domain_id)
@@ -40,6 +41,7 @@ def generate_client_secret(
4041
role_type=role_type,
4142
workspace_id=workspace_id,
4243
permissions=permissions,
44+
injected_params=injected_params,
4345
)
4446

4547
return client_id, client_secret

src/spaceone/identity/model/app/database.py

+3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ class App(MongoModel):
88
state = StringField(
99
max_length=20, default="ENABLED", choices=("ENABLED", "DISABLED", "EXPIRED")
1010
)
11+
is_managed = BooleanField(default=False)
1112
tags = DictField(default=None)
1213
role_type = StringField(
1314
max_length=20,
@@ -18,6 +19,7 @@ class App(MongoModel):
1819
)
1920
client_id = StringField(max_length=40, default=None, null=True)
2021
role_id = StringField(max_length=40, required=True)
22+
service_account_id = StringField(max_length=40, default=None, null=True)
2123
resource_group = StringField(max_length=40, choices=("DOMAIN", "WORKSPACE"))
2224
workspace_id = StringField(max_length=40)
2325
domain_id = StringField(max_length=40)
@@ -30,6 +32,7 @@ class App(MongoModel):
3032
"name",
3133
"state",
3234
"client_id",
35+
"service_account_id",
3336
"tags",
3437
"expired_at",
3538
"last_accessed_at",

src/spaceone/identity/model/app/response.py

+2
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@ class AppResponse(BaseModel):
1919
name: Union[str, None] = None
2020
state: Union[State, None] = None
2121
tags: Union[dict, None] = None
22+
is_managed: Union[bool, None] = None
2223
role_type: Union[RoleType, None] = None
2324
client_id: Union[str, None] = None
2425
role_id: Union[str, None] = None
26+
service_account_id: Union[str, None] = None
2527
resource_group: Union[ResourceGroup, None] = None
2628
workspace_id: Union[str, None] = None
2729
domain_id: Union[str, None] = None

src/spaceone/identity/model/service_account/database.py

+4
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ class ServiceAccount(MongoModel):
77
name = StringField(max_length=255, unique_with=["domain_id", "workspace_id"])
88
data = DictField(default=None)
99
provider = StringField(max_length=40)
10+
options = DictField(default=None)
1011
tags = DictField(default=None)
1112
secret_schema_id = StringField(max_length=40)
1213
secret_id = StringField(max_length=40)
14+
app_id = StringField(max_length=40, null=True, default=None)
1315
trusted_account_id = StringField(max_length=40, null=True, default=None)
1416
project_id = StringField(max_length=40)
1517
workspace_id = StringField(max_length=40)
@@ -20,9 +22,11 @@ class ServiceAccount(MongoModel):
2022
"updatable_fields": [
2123
"name",
2224
"data",
25+
"options",
2326
"tags",
2427
"secret_schema_id",
2528
"secret_id",
29+
"app_id",
2630
"trusted_account_id",
2731
"project_id",
2832
],

src/spaceone/identity/model/service_account/request.py

+42-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
1-
from datetime import datetime
2-
from typing import Union, Literal
1+
from typing import Union
32
from pydantic import BaseModel
43

54
__all__ = [
65
"ServiceAccountCreateRequest",
6+
"ServiceAccountCreateAppRequest",
77
"ServiceAccountUpdateRequest",
88
"ServiceAccountUpdateSecretRequest",
99
"ServiceAccountDeleteSecretRequest",
1010
"ServiceAccountDeleteRequest",
11+
"ServiceAccountEnableAppRequest",
12+
"ServiceAccountDisableAppRequest",
13+
"ServiceAccountRegenerateAppRequest",
14+
"ServiceAccountDeleteAppRequest",
1115
"ServiceAccountGetRequest",
1216
"ServiceAccountSearchQueryRequest",
1317
"ServiceAccountStatQueryRequest",
@@ -27,6 +31,14 @@ class ServiceAccountCreateRequest(BaseModel):
2731
domain_id: str
2832

2933

34+
class ServiceAccountCreateAppRequest(BaseModel):
35+
service_account_id: str
36+
options: Union[dict, None] = None
37+
workspace_id: Union[str, None] = None
38+
domain_id: str
39+
user_projects: Union[list, None] = None
40+
41+
3042
class ServiceAccountUpdateRequest(BaseModel):
3143
service_account_id: str
3244
name: Union[str, None] = None
@@ -62,6 +74,34 @@ class ServiceAccountDeleteRequest(BaseModel):
6274
user_projects: Union[list, None] = None
6375

6476

77+
class ServiceAccountEnableAppRequest(BaseModel):
78+
service_account_id: str
79+
workspace_id: Union[str, None] = None
80+
domain_id: str
81+
user_projects: Union[list, None] = None
82+
83+
84+
class ServiceAccountDisableAppRequest(BaseModel):
85+
service_account_id: str
86+
workspace_id: Union[str, None] = None
87+
domain_id: str
88+
user_projects: Union[list, None] = None
89+
90+
91+
class ServiceAccountRegenerateAppRequest(BaseModel):
92+
service_account_id: str
93+
workspace_id: Union[str, None] = None
94+
domain_id: str
95+
user_projects: Union[list, None] = None
96+
97+
98+
class ServiceAccountDeleteAppRequest(BaseModel):
99+
service_account_id: str
100+
workspace_id: Union[str, None] = None
101+
domain_id: str
102+
user_projects: Union[list, None] = None
103+
104+
65105
class ServiceAccountGetRequest(BaseModel):
66106
service_account_id: str
67107
workspace_id: Union[str, None] = None

src/spaceone/identity/model/service_account/response.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from datetime import datetime
2-
from typing import Union, Literal, List
2+
from typing import Union, List
33
from pydantic import BaseModel
44
from spaceone.core import utils
55

@@ -11,9 +11,11 @@ class ServiceAccountResponse(BaseModel):
1111
name: Union[str, None] = None
1212
data: Union[dict, None] = None
1313
provider: Union[str, None] = None
14+
options: Union[dict, None] = None
1415
tags: Union[dict, None] = None
1516
secret_schema_id: Union[str, None] = None
1617
secret_id: Union[str, None] = None
18+
app_id: Union[str, None] = None
1719
trusted_account_id: Union[str, None] = None
1820
project_id: Union[str, None] = None
1921
workspace_id: Union[str, None] = None

src/spaceone/identity/service/app_service.py

+12
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,10 @@ def enable(self, params: AppEnableRequest) -> Union[AppResponse, dict]:
192192
params.workspace_id,
193193
)
194194
app_vo = self.app_mgr.enable_app(app_vo)
195+
196+
if app_vo.is_managed:
197+
raise ERROR_PERMISSION_DENIED(key="app_id", _message="Managed App cannot be enabled.")
198+
195199
return AppResponse(**app_vo.to_dict())
196200

197201
@transaction(
@@ -214,6 +218,10 @@ def disable(self, params: AppDisableRequest) -> Union[AppResponse, dict]:
214218
params.workspace_id,
215219
)
216220
app_vo = self.app_mgr.disable_app(app_vo)
221+
222+
if app_vo.is_managed:
223+
raise ERROR_PERMISSION_DENIED(key="app_id", _message="Managed App cannot be disabled.")
224+
217225
return AppResponse(**app_vo.to_dict())
218226

219227
@transaction(
@@ -237,6 +245,10 @@ def delete(self, params: AppDeleteRequest) -> None:
237245
params.domain_id,
238246
params.workspace_id,
239247
)
248+
249+
if app_vo.is_managed:
250+
raise ERROR_PERMISSION_DENIED(key="app_id", _message="Managed App cannot be deleted.")
251+
240252
self.app_mgr.delete_app_by_vo(app_vo)
241253

242254
@transaction(

0 commit comments

Comments
 (0)