Skip to content

Commit 7f790a8

Browse files
authored
Merge pull request #1155 from janmatzek/SVS-1238-prevent-modification-of-current-user-during-user-provisioning
feat(gooddata-pipelines): prevent modification of current user during user provisioning
2 parents c24c528 + e5e0f8c commit 7f790a8

File tree

12 files changed

+290
-5
lines changed

12 files changed

+290
-5
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
.idea
22
.vscode
3+
.cursor
34
*.iml
45
.env
56
.env.test

gooddata-pipelines/gooddata_pipelines/api/gooddata_api.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,11 @@ def get_all_dashboards(self, workspace_id: str) -> requests.Response:
212212
headers = {**self.headers, "X-GDC-VALIDATE-RELATIONS": "true"}
213213
return self._get(endpoint, headers=headers)
214214

215+
def get_profile(self) -> requests.Response:
216+
"""Returns organization and current user information."""
217+
endpoint = "/profile"
218+
return self._get(endpoint)
219+
215220
def _get(
216221
self, endpoint: str, headers: dict[str, str] | None = None
217222
) -> requests.Response:

gooddata-pipelines/gooddata_pipelines/provisioning/entities/users/models/users.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,16 @@
33
from typing import Any
44

55
from gooddata_sdk.catalog.user.entity_model.user import CatalogUser
6-
from pydantic import BaseModel
6+
from pydantic import BaseModel, Field
7+
8+
9+
class UserProfile(BaseModel):
10+
"""Minimal model of api/v1/profile response.
11+
12+
Does not contain all fields from the response.
13+
"""
14+
15+
user_id: str = Field(alias="userId")
716

817

918
class BaseUser(BaseModel):

gooddata-pipelines/gooddata_pipelines/provisioning/entities/users/users.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from gooddata_pipelines.provisioning.entities.users.models.users import (
1212
UserFullLoad,
1313
UserIncrementalLoad,
14+
UserProfile,
1415
)
1516
from gooddata_pipelines.provisioning.provisioning import Provisioning
1617
from gooddata_pipelines.provisioning.utils.context_objects import UserContext
@@ -30,13 +31,28 @@ class UserProvisioner(Provisioning[UserFullLoad, UserIncrementalLoad]):
3031
source_group_incremental: list[UserIncrementalLoad]
3132
source_group_full: list[UserFullLoad]
3233

34+
current_user_id: str
35+
3336
FULL_LOAD_TYPE: type[UserFullLoad] = UserFullLoad
3437
INCREMENTAL_LOAD_TYPE: type[UserIncrementalLoad] = UserIncrementalLoad
3538

3639
def __init__(self, host: str, token: str) -> None:
3740
super().__init__(host, token)
3841
self.upstream_user_cache: dict[UserId, UserModel] = {}
3942

43+
def _get_current_user_id(self) -> str:
44+
"""Gets the current user ID."""
45+
46+
profile_response = self._api.get_profile()
47+
48+
if not profile_response.ok:
49+
raise Exception("Failed to get current user profile")
50+
51+
profile_json = profile_response.json()
52+
profile = UserProfile.model_validate(profile_json)
53+
54+
return profile.user_id
55+
4056
def _try_get_user(
4157
self, user: UserModel, model: type[UserModel]
4258
) -> UserModel | None:
@@ -99,6 +115,14 @@ def _create_or_update_user(
99115
for its existence and create it if needed.
100116
101117
"""
118+
119+
if user.user_id == self.current_user_id:
120+
self.logger.warning(
121+
f"Skipping creation/update of current user: {user.user_id}. "
122+
+ "Current user should not be modified.",
123+
)
124+
return
125+
102126
user_context = UserContext(
103127
user_id=user.user_id,
104128
user_groups=user.user_groups,
@@ -118,6 +142,13 @@ def _create_or_update_user(
118142

119143
def _delete_user(self, user_id: str) -> None:
120144
"""Deletes user from the project."""
145+
if user_id == self.current_user_id:
146+
self.logger.warning(
147+
f"Skipping deletion of current user: {user_id}."
148+
+ " Current user should not be deleted.",
149+
)
150+
return
151+
121152
try:
122153
self._api._sdk.catalog_user.get_user(user_id)
123154
except NotFoundException:
@@ -135,6 +166,9 @@ def _manage_user(self, user: UserIncrementalLoad) -> None:
135166

136167
def _provision_incremental_load(self) -> None:
137168
"""Runs the incremental provisioning logic."""
169+
# Set the current user ID
170+
self.current_user_id = self._get_current_user_id()
171+
138172
for user in self.source_group_incremental:
139173
# Attempt to process each user. On failure, log the error and continue
140174
try:
@@ -146,6 +180,10 @@ def _provision_incremental_load(self) -> None:
146180

147181
def _provision_full_load(self) -> None:
148182
"""Runs the full load provisioning logic."""
183+
184+
# Set the current user ID
185+
self.current_user_id = self._get_current_user_id()
186+
149187
# Get all upstream users
150188
catalog_upstream_users: list[CatalogUser] = self._api.list_users()
151189

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
# (C) 2025 GoodData Corporation
2-
mock_profile:
3-
host: http://localhost:3000
4-
token: some_user_token
2+
profiles:
3+
mock_profile:
4+
host: http://localhost:3000
5+
token: $MOCK_TOKEN
6+
default_profile: mock_profile
7+
access: {}

gooddata-pipelines/tests/data/provisioning/entities/users/existing_upstream_users.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,13 @@
2222
"email": "[email protected]",
2323
"authentication_id": "auth_4",
2424
"user_groups": ["group_4", "group_5"]
25+
},
26+
{
27+
"user_id": "protected_user_id",
28+
"firstname": "Protected",
29+
"lastname": "User",
30+
"email": "[email protected]",
31+
"authentication_id": "auth_protected",
32+
"user_groups": ["group_protected"]
2533
}
2634
]
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{
2+
"entitlements": [
3+
{
4+
"expiry": "2019-08-24",
5+
"name": "CacheStrategy",
6+
"value": "string"
7+
}
8+
],
9+
"features": {
10+
"live": {
11+
"context": {
12+
"earlyAccess": "string",
13+
"earlyAccessValues": ["string"]
14+
},
15+
"configuration": {
16+
"host": "string",
17+
"key": "string"
18+
}
19+
}
20+
},
21+
"links": {
22+
"organization": "string",
23+
"self": "string",
24+
"user": "string"
25+
},
26+
"name": "string",
27+
"organizationId": "string",
28+
"organizationName": "string",
29+
"permissions": ["MANAGE"],
30+
"telemetryConfig": {
31+
"context": {
32+
"deploymentId": "string",
33+
"organizationHash": "string",
34+
"userHash": "string"
35+
},
36+
"services": {
37+
"amplitude": {
38+
"aiProjectApiKey": "string",
39+
"endpoint": "string",
40+
"gdCommonApiKey": "string",
41+
"reportingEndpoint": "string"
42+
},
43+
"matomo": {
44+
"host": "string",
45+
"reportingEndpoint": "string",
46+
"siteId": 0
47+
},
48+
"openTelemetry": {
49+
"host": "string"
50+
}
51+
}
52+
},
53+
"userId": "protected_user_id"
54+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
[
2+
{
3+
"user_id": "user_1",
4+
"firstname": "John",
5+
"lastname": "Doe",
6+
"email": "[email protected]",
7+
"auth_id": "auth_1",
8+
"user_groups": ["group_1", "group_2"]
9+
},
10+
{
11+
"user_id": "user_2",
12+
"firstname": "Jane",
13+
"lastname": "Doe",
14+
"email": "[email protected]",
15+
"auth_id": "auth_2",
16+
"user_groups": ["group_2", "group_3"]
17+
},
18+
{
19+
"user_id": "user_3",
20+
"firstname": "Jim",
21+
"lastname": "Rock",
22+
"email": "[email protected]",
23+
"auth_id": "auth_3",
24+
"user_groups": ["group_3", "group_4"]
25+
},
26+
{
27+
"user_id": "protected_user_id",
28+
"firstname": "Some",
29+
"lastname": "New",
30+
"email": "[email protected]",
31+
"auth_id": "take",
32+
"user_groups": ["effect"]
33+
}
34+
]
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
[
2+
{
3+
"user_id": "user_1",
4+
"firstname": "John",
5+
"lastname": "Doe",
6+
"email": "[email protected]",
7+
"auth_id": "auth_1",
8+
"user_groups": ["group_1", "group_2"],
9+
"is_active": true
10+
},
11+
{
12+
"user_id": "user_2",
13+
"firstname": "Jane",
14+
"lastname": "Doe",
15+
"email": "[email protected]",
16+
"auth_id": "auth_2",
17+
"user_groups": ["group_2", "group_3"],
18+
"is_active": true
19+
},
20+
{
21+
"user_id": "user_3",
22+
"firstname": "Jim",
23+
"lastname": "Rock",
24+
"email": "[email protected]",
25+
"auth_id": "auth_3",
26+
"user_groups": ["group_3", "group_4"],
27+
"is_active": true
28+
},
29+
{
30+
"user_id": "user_4",
31+
"firstname": "Jack",
32+
"lastname": "Cliff",
33+
"email": "[email protected]",
34+
"auth_id": "auth_4",
35+
"user_groups": ["group_4", "group_5"],
36+
"is_active": false
37+
},
38+
{
39+
"user_id": "protected_user_id",
40+
"firstname": "Some",
41+
"lastname": "New",
42+
"email": "[email protected]",
43+
"auth_id": "take",
44+
"user_groups": ["effect"],
45+
"is_active": false
46+
}
47+
]
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
[
2+
{
3+
"user_id": "user_1",
4+
"firstname": "John",
5+
"lastname": "Doe",
6+
"email": "[email protected]",
7+
"auth_id": "auth_1",
8+
"user_groups": ["group_1", "group_2"],
9+
"is_active": true
10+
},
11+
{
12+
"user_id": "user_2",
13+
"firstname": "Jane",
14+
"lastname": "Doe",
15+
"email": "[email protected]",
16+
"auth_id": "auth_2",
17+
"user_groups": ["group_2", "group_3"],
18+
"is_active": true
19+
},
20+
{
21+
"user_id": "user_3",
22+
"firstname": "Jim",
23+
"lastname": "Rock",
24+
"email": "[email protected]",
25+
"auth_id": "auth_3",
26+
"user_groups": ["group_3", "group_4"],
27+
"is_active": true
28+
},
29+
{
30+
"user_id": "user_4",
31+
"firstname": "Jack",
32+
"lastname": "Cliff",
33+
"email": "[email protected]",
34+
"auth_id": "auth_4",
35+
"user_groups": ["group_4", "group_5"],
36+
"is_active": false
37+
},
38+
{
39+
"user_id": "protected_user_id",
40+
"firstname": "Some",
41+
"lastname": "New",
42+
"email": "[email protected]",
43+
"auth_id": "take",
44+
"user_groups": ["effect"],
45+
"is_active": true
46+
}
47+
]

0 commit comments

Comments
 (0)