Skip to content

Commit 06b69ea

Browse files
authored
Admin check fix (#390)
* Admin check fix * More admin routes unified * fix admin check * Submodule change
1 parent 0adcfdf commit 06b69ea

File tree

11 files changed

+135
-86
lines changed

11 files changed

+135
-86
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"""helper admin field
2+
3+
Revision ID: 1133d27823e3
4+
Revises: b2c3d4e5f6a7
5+
Create Date: 2026-02-23 09:42:28.698186
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
11+
12+
# revision identifiers, used by Alembic.
13+
revision = '1133d27823e3'
14+
down_revision = 'b2c3d4e5f6a7'
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
# ### commands auto generated by Alembic - please adjust! ###
21+
op.add_column('user', sa.Column('is_admin', sa.Boolean(), nullable=True))
22+
# ### end Alembic commands ###
23+
24+
25+
def downgrade():
26+
# ### commands auto generated by Alembic - please adjust! ###
27+
op.drop_column('user', 'is_admin')
28+
# ### end Alembic commands ###

controller/auth/kratos.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ def __parse_identity_to_simple(identity: Dict[str, Any]) -> Dict[str, str]:
129129
"mail": None,
130130
"firstName": None,
131131
"lastName": None,
132+
"is_admin": get_identity_is_admin(identity),
132133
}
133134
if "traits" in identity:
134135
r["mail"] = identity["traits"]["email"]
@@ -305,3 +306,17 @@ def get_admin_users_by_public_metadata() -> List[Dict[str, Any]]:
305306
][0]["verified"]:
306307
admins.append(cache[key]["simple"])
307308
return admins
309+
310+
311+
def get_identity_is_admin(identity: Dict[str, Any]) -> bool:
312+
313+
if (identity.get("metadata_public") or {}).get("role") == "ADMIN" and identity[
314+
"verifiable_addresses"
315+
][0]["verified"]:
316+
return True
317+
if (
318+
identity["traits"]["email"].split("@")[1] == "kern.ai"
319+
and identity["verifiable_addresses"][0]["verified"]
320+
):
321+
return True
322+
return False

controller/auth/manager.py

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import re
2-
from typing import Any, Dict, List, Optional
2+
from typing import Any, Dict, List, Optional, Tuple
33

44
from controller.auth import kratos
55
from fastapi import Request
@@ -16,12 +16,20 @@
1616
from submodules.model.business_objects.user import check_email_in_full_admin
1717
from submodules.model.models import Organization, Project, User
1818
import sqlalchemy
19+
from dataclasses import dataclass
20+
from .kratos import get_identity_is_admin
1921

2022
DEV_USER_ID = "741df1c2-a531-43b6-b259-df23bc78e9a2"
2123

2224
EMAIL_RE = re.compile(r"^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$")
2325

2426

27+
@dataclass(frozen=True)
28+
class AdminData:
29+
is_admin: bool
30+
is_full_admin: bool
31+
32+
2533
def get_organization_id_by_info(info) -> Organization:
2634
user = get_user_by_info(info)
2735
if not user or not user.organization_id:
@@ -90,8 +98,8 @@ def check_project_access(info, project_id: str) -> None:
9098
raise AuthManagerError("Project not found")
9199

92100

93-
def check_admin_access(info) -> None:
94-
if not check_is_admin(info.context["request"]):
101+
def check_admin_access(request_state: Any) -> None:
102+
if not request_state.adm.is_admin:
95103
raise AuthManagerError("Admin access required")
96104

97105

@@ -114,25 +122,26 @@ def check_project_access_from_user_id(
114122
return True
115123

116124

117-
def check_is_admin(request: Any) -> bool:
125+
126+
def __check_is_admin_header(request: Request) -> Tuple[bool, bool]:
118127
if "Authorization" in request.headers:
119128
jwt_decoded: Dict[str, Any] = jwt.decode(
120129
request.headers["Authorization"].split(" ")[1],
121130
options={"verify_signature": False},
122131
)
123-
subject: Dict[str, Any] = jwt_decoded["session"]["identity"]
124-
if (
125-
subject["traits"]["email"].split("@")[1] == "kern.ai"
126-
and subject["verifiable_addresses"][0]["verified"]
127-
):
128-
return True
129-
elif (
130-
# subject metadata_public can be None so we use or {} instead of get with default
131-
(subject.get("metadata_public") or {}).get("role") == "ADMIN"
132-
and subject["verifiable_addresses"][0]["verified"]
133-
):
134-
return True
135-
return False
132+
identity = jwt_decoded["session"]["identity"]
133+
email = identity["traits"]["email"]
134+
135+
return get_identity_is_admin(identity), check_email_in_full_admin(email)
136+
return False, False
137+
138+
139+
def parse_admin_info(request: Any) -> None:
140+
is_admin, is_full_admin = __check_is_admin_header(request)
141+
request.state.adm = AdminData(
142+
is_admin=is_admin,
143+
is_full_admin=is_full_admin,
144+
)
136145

137146

138147
def check_is_single_organization() -> bool:
@@ -148,11 +157,7 @@ def extract_state_info(request: Request, key: str) -> Any:
148157
user = get_user_by_info(request.state.info)
149158
if user and user.organization_id:
150159
value = str(user.organization_id)
151-
elif key == "is_admin":
152-
value = check_is_admin(request)
153-
elif key == "log_request":
154-
# lazy and => db access only if admin is true
155-
if extract_state_info(request, "is_admin"):
160+
elif key == "log_request" and request.state.adm.is_admin:
156161
value = organization.log_admin_requests(
157162
extract_state_info(request, "organization_id")
158163
)
@@ -168,15 +173,10 @@ def extract_state_info(request: Request, key: str) -> Any:
168173
def check_is_full_admin(request: Any) -> bool:
169174
if request.url.hostname == "localhost" and request.url.port == 7051:
170175
return True
171-
if check_is_admin(request):
172-
jwt_decoded: Dict[str, Any] = jwt.decode(
173-
request.headers["Authorization"].split(" ")[1],
174-
options={"verify_signature": False},
175-
)
176-
subject: Dict[str, Any] = jwt_decoded["session"]["identity"]
177-
if check_email_in_full_admin(subject["traits"]["email"]):
178-
return True
179-
return False
176+
state = getattr(request.state, "adm", None)
177+
if not state:
178+
raise AuthManagerError("Admin state is not set in request.state.adm")
179+
return state.is_full_admin
180180

181181

182182
def invite_users(

controller/organization/manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ def delete_organization(name: str) -> None:
105105
all_users = user.get_all(org.id)
106106
unassigned = False
107107
for u in all_users:
108-
if (u.email or "").endswith("@kern.ai"):
108+
if u.is_admin:
109109
unassigned = True
110110
u.organization_id = None
111111
if unassigned:

controller/user/manager.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ def __migrate_kratos_users():
181181
if user_id not in users_kratos or users_kratos[user_id] is None:
182182
continue
183183
user_identity = users_kratos[user_id]["identity"]
184+
184185
if user_database.email != user_identity["traits"]["email"]:
185186
user_database.email = user_identity["traits"]["email"]
186187
if (
@@ -210,5 +211,6 @@ def __migrate_kratos_users():
210211
)
211212
if user_database.sso_provider != sso_provider:
212213
user_database.sso_provider = sso_provider
213-
214+
if user_database.is_admin != users_kratos[user_id]["simple"]["is_admin"]:
215+
user_database.is_admin = users_kratos[user_id]["simple"]["is_admin"]
214216
general.commit()

fast_api/routes/comment.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def get_all_comments(request: Request):
3434
if project_id:
3535
auth_manager.check_project_access(request.state.info, project_id)
3636
else:
37-
auth_manager.check_admin_access(request.state.info)
37+
auth_manager.check_admin_access(request.state)
3838

3939
if comment_id:
4040
data = manager.get_comment_by_comment_id(user_id, comment_id)
@@ -65,7 +65,7 @@ def create_comment(request: Request, body: CreateCommentBody = Body(...)):
6565
if body.project_id:
6666
auth_manager.check_project_access(request.state.info, body.project_id)
6767
else:
68-
auth_manager.check_admin_access(request.state.info)
68+
auth_manager.check_admin_access(request.state)
6969

7070
if body.xftype == CommentCategory.USER.value:
7171
user_id = body.xfkey
@@ -92,7 +92,7 @@ def delete_comment(
9292
if body.project_id:
9393
auth_manager.check_project_access(request.state.info, body.project_id)
9494
else:
95-
auth_manager.check_admin_access(request.state.info)
95+
auth_manager.check_admin_access(request.state)
9696

9797
user_id = auth_manager.get_user_id_by_info(request.state.info)
9898
manager.delete_comment(body.comment_id, user_id)
@@ -117,7 +117,7 @@ def update_comment(
117117
if body.project_id:
118118
auth_manager.check_project_access(request.state.info, body.project_id)
119119
else:
120-
auth_manager.check_admin_access(request.state.info)
120+
auth_manager.check_admin_access(request.state)
121121

122122
user = auth_manager.get_user_by_info(request.state.info)
123123
item = manager.update_comment(body.comment_id, user, body.changes)
@@ -137,5 +137,5 @@ def update_comment(
137137
@router.get("/get-unique-comments-keys-for")
138138
def get_unique_comments_keys_for(request: Request, xftype: str):
139139
if xftype in [CommentCategory.ORGANIZATION.value, CommentCategory.USER.value]:
140-
auth_manager.check_admin_access(request.state.info)
140+
auth_manager.check_admin_access(request.state)
141141
return manager.get_unique_comments_keys_for(xftype)

fast_api/routes/inbox_mail.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
@router.post("")
1919
def create_inbox_mail_by_thread(request: Request, inbox_mail: InboxMailCreateRequest):
20-
user_is_admin = auth_manager.check_is_admin(request)
20+
user_is_admin = auth_manager.check_admin_access(request.state)
2121
user = auth_manager.get_user_by_info(request.state.info)
2222

2323
if inbox_mail.threadId:
@@ -52,7 +52,7 @@ def create_inbox_mail_by_thread(request: Request, inbox_mail: InboxMailCreateReq
5252

5353
@router.get("/thread/{thread_id}")
5454
def get_inbox_mails_by_thread(request: Request, thread_id: str) -> List[Dict[str, Any]]:
55-
user_is_admin = auth_manager.check_is_admin(request)
55+
user_is_admin = auth_manager.check_admin_access(request.state)
5656

5757
user = auth_manager.get_user_by_info(request.state.info)
5858

@@ -72,7 +72,7 @@ def get_inbox_mail_thread_overview_paginated(
7272
limit: int = 10,
7373
filters: Optional[List[str]] = Query(default=None),
7474
):
75-
user_is_admin = auth_manager.check_is_admin(request)
75+
user_is_admin = auth_manager.check_admin_access(request.state)
7676
user = auth_manager.get_user_by_info(request.state.info)
7777
mail = inbox_mail_manager.get_inbox_mail_threads_overview(
7878
org_id=user.organization_id,
@@ -91,7 +91,7 @@ def update_inbox_mail_thread_progress(
9191
thread_id: str,
9292
inbox_mail_thread_update: UpdateInboxMailThreadProgressRequest,
9393
):
94-
user_is_admin = auth_manager.check_is_admin(request)
94+
user_is_admin = auth_manager.check_admin_access(request.state)
9595
user = auth_manager.get_user_by_info(request.state.info)
9696

9797
inbox_mail_thread = inbox_mail_go.get_inbox_mail_thread_by_id(thread_id=thread_id)
@@ -133,7 +133,7 @@ def delete_inbox_mail_by_id(request: Request, mail_id: str):
133133

134134
@router.get("/new")
135135
def has_new_inbox_mails(request: Request):
136-
user_is_admin = auth_manager.check_is_admin(request)
136+
user_is_admin = auth_manager.check_admin_access(request.state)
137137
user = auth_manager.get_user_by_info(request.state.info)
138138

139139
total_new_inbox_mails = inbox_mail_manager.get_new_inbox_mails_info(
@@ -150,7 +150,7 @@ def has_new_inbox_mails(request: Request):
150150

151151
@router.put("/thread/{thread_id}/unread/project")
152152
def update_inbox_mail_threads_unread_by_project(request: Request, thread_id: str):
153-
user_is_admin = auth_manager.check_is_admin(request)
153+
user_is_admin = auth_manager.check_admin_access(request.state)
154154
if not user_is_admin:
155155
raise HTTPException(status_code=403, detail="Not authorized")
156156
inbox_mail_thread = inbox_mail_go.get_inbox_mail_thread_by_id(thread_id=thread_id)
@@ -165,7 +165,7 @@ def update_inbox_mail_threads_unread_by_project(request: Request, thread_id: str
165165

166166
@router.put("/thread/{thread_id}/unread/content")
167167
def update_inbox_mail_threads_unread_by_content(request: Request, thread_id: str):
168-
user_is_admin = auth_manager.check_is_admin(request)
168+
user_is_admin = auth_manager.check_admin_access(request.state)
169169
if not user_is_admin:
170170
raise HTTPException(status_code=403, detail="Not authorized")
171171
inbox_mail_thread = inbox_mail_go.get_inbox_mail_thread_by_id(thread_id=thread_id)
@@ -180,7 +180,7 @@ def update_inbox_mail_threads_unread_by_content(request: Request, thread_id: str
180180

181181
@router.put("/thread/{thread_id}/unread-last")
182182
def mark_inbox_mail_thread_as_unread(request: Request, thread_id: str):
183-
user_is_admin = auth_manager.check_is_admin(request)
183+
user_is_admin = auth_manager.check_admin_access(request.state)
184184
if not user_is_admin:
185185
raise HTTPException(status_code=403, detail="Not authorized")
186186
inbox_mail_thread = inbox_mail_go.get_inbox_mail_thread_by_id(thread_id=thread_id)
@@ -193,7 +193,7 @@ def mark_inbox_mail_thread_as_unread(request: Request, thread_id: str):
193193

194194
@router.delete("/thread/{thread_id}/similar")
195195
def delete_similar_system_threads(request: Request, thread_id: str):
196-
user_is_admin = auth_manager.check_is_admin(request)
196+
user_is_admin = auth_manager.check_admin_access(request.state)
197197
if not user_is_admin:
198198
raise HTTPException(status_code=403, detail="Not authorized")
199199

0 commit comments

Comments
 (0)