Patch/v1.14.0 api (#1432)
* change(ui): iframe handle routes * chore(helm): Updating frontend image release * feat(chalice): support service account * feat(chalice): support service account --------- Co-authored-by: Shekar Siri <sshekarsiri@gmail.com> Co-authored-by: rjshrjndrn <rjshrjndrn@gmail.com>
This commit is contained in:
parent
bdf5dbba0a
commit
19935dc105
16 changed files with 154 additions and 99 deletions
|
|
@ -18,7 +18,7 @@ class JWTAuth(HTTPBearer):
|
|||
if credentials:
|
||||
if not credentials.scheme == "Bearer":
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid authentication scheme.")
|
||||
jwt_payload = authorizers.jwt_authorizer(credentials.scheme + " " + credentials.credentials)
|
||||
jwt_payload = authorizers.jwt_authorizer(scheme=credentials.scheme, token=credentials.credentials)
|
||||
auth_exists = jwt_payload is not None \
|
||||
and users.auth_exists(user_id=jwt_payload.get("userId", -1),
|
||||
tenant_id=jwt_payload.get("tenantId", -1),
|
||||
|
|
@ -27,18 +27,13 @@ class JWTAuth(HTTPBearer):
|
|||
if jwt_payload is None \
|
||||
or jwt_payload.get("iat") is None or jwt_payload.get("aud") is None \
|
||||
or not auth_exists:
|
||||
print("JWTAuth: Token issue")
|
||||
if jwt_payload is not None:
|
||||
print(jwt_payload)
|
||||
print(f"JWTAuth: user_id={jwt_payload.get('userId')} tenant_id={jwt_payload.get('tenantId')}")
|
||||
if jwt_payload is None:
|
||||
print("JWTAuth: jwt_payload is None")
|
||||
print(credentials.scheme + " " + credentials.credentials)
|
||||
if jwt_payload is not None and jwt_payload.get("iat") is None:
|
||||
print("JWTAuth: iat is None")
|
||||
if jwt_payload is not None and jwt_payload.get("aud") is None:
|
||||
print("JWTAuth: aud is None")
|
||||
if jwt_payload is not None and not auth_exists:
|
||||
if jwt_payload.get("iat") is None:
|
||||
print("JWTAuth: iat is None")
|
||||
if jwt_payload.get("aud") is None:
|
||||
print("JWTAuth: aud is None")
|
||||
if not auth_exists:
|
||||
print("JWTAuth: not users.auth_exists")
|
||||
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Invalid token or expired token.")
|
||||
|
|
@ -47,7 +42,6 @@ class JWTAuth(HTTPBearer):
|
|||
print("JWTAuth: User not found.")
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="User not found.")
|
||||
jwt_payload["authorizer_identity"] = "jwt"
|
||||
print(jwt_payload)
|
||||
request.state.authorizer_identity = "jwt"
|
||||
request.state.currentContext = schemas.CurrentContext(tenant_id=jwt_payload.get("tenantId", -1),
|
||||
user_id=jwt_payload.get("userId", -1),
|
||||
|
|
|
|||
|
|
@ -6,13 +6,12 @@ from chalicelib.core import tenants
|
|||
from chalicelib.core import users
|
||||
|
||||
|
||||
def jwt_authorizer(token):
|
||||
token = token.split(" ")
|
||||
if len(token) != 2 or token[0].lower() != "bearer":
|
||||
def jwt_authorizer(scheme: str, token: str):
|
||||
if scheme.lower() != "bearer":
|
||||
return None
|
||||
try:
|
||||
payload = jwt.decode(
|
||||
token[1],
|
||||
token,
|
||||
config("jwt_secret"),
|
||||
algorithms=config("jwt_algorithm"),
|
||||
audience=[f"front:{helper.get_stage_name()}"]
|
||||
|
|
@ -22,6 +21,7 @@ def jwt_authorizer(token):
|
|||
return None
|
||||
except BaseException as e:
|
||||
print("! JWT Base Exception")
|
||||
print(e)
|
||||
return None
|
||||
return payload
|
||||
|
||||
|
|
|
|||
|
|
@ -548,16 +548,12 @@ def auth_exists(user_id, tenant_id, jwt_iat, jwt_aud):
|
|||
WHERE user_id = %(userId)s
|
||||
AND deleted_at IS NULL
|
||||
LIMIT 1;""",
|
||||
{"userId": user_id})
|
||||
{"userId": user_id})
|
||||
)
|
||||
r = cur.fetchone()
|
||||
return r is not None \
|
||||
and r.get("jwt_iat") is not None \
|
||||
and (abs(jwt_iat - TimeUTC.datetime_to_timestamp(r["jwt_iat"]) // 1000) <= 1 \
|
||||
or (jwt_aud.startswith("plugin") \
|
||||
and (r["changed_at"] is None \
|
||||
or jwt_iat >= (TimeUTC.datetime_to_timestamp(r["changed_at"]) // 1000)))
|
||||
)
|
||||
and abs(jwt_iat - TimeUTC.datetime_to_timestamp(r["jwt_iat"]) // 1000) <= 1
|
||||
|
||||
|
||||
def change_jwt_iat(user_id):
|
||||
|
|
@ -566,7 +562,7 @@ def change_jwt_iat(user_id):
|
|||
SET jwt_iat = timezone('utc'::text, now())
|
||||
WHERE user_id = %(user_id)s
|
||||
RETURNING jwt_iat;""",
|
||||
{"user_id": user_id})
|
||||
{"user_id": user_id})
|
||||
cur.execute(query)
|
||||
return cur.fetchone().get("jwt_iat")
|
||||
|
||||
|
|
|
|||
|
|
@ -18,20 +18,6 @@ from routers.base import get_routers
|
|||
public_app, app, app_apikey = get_routers()
|
||||
|
||||
|
||||
@app.post('/{projectId}/sessions/search', tags=["sessions"])
|
||||
def sessions_search(projectId: int, data: schemas.FlatSessionsSearchPayloadSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = sessions.search_sessions(data=data, project_id=projectId, user_id=context.user_id)
|
||||
return {'data': data}
|
||||
|
||||
|
||||
@app.post('/{projectId}/sessions/search/ids', tags=["sessions"])
|
||||
def session_ids_search(projectId: int, data: schemas.FlatSessionsSearchPayloadSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = sessions.search_sessions(data=data, project_id=projectId, user_id=context.user_id, ids_only=True)
|
||||
return {'data': data}
|
||||
|
||||
|
||||
@app.get('/{projectId}/events/search', tags=["events"])
|
||||
def events_search(projectId: int, q: str,
|
||||
type: Union[schemas.FilterType, schemas.EventType,
|
||||
|
|
|
|||
|
|
@ -207,6 +207,20 @@ def get_session(projectId: int, sessionId: Union[int, str], background_tasks: Ba
|
|||
}
|
||||
|
||||
|
||||
@app.post('/{projectId}/sessions/search', tags=["sessions"])
|
||||
def sessions_search(projectId: int, data: schemas.FlatSessionsSearchPayloadSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = sessions.search_sessions(data=data, project_id=projectId, user_id=context.user_id)
|
||||
return {'data': data}
|
||||
|
||||
|
||||
@app.post('/{projectId}/sessions/search/ids', tags=["sessions"])
|
||||
def session_ids_search(projectId: int, data: schemas.FlatSessionsSearchPayloadSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = sessions.search_sessions(data=data, project_id=projectId, user_id=context.user_id, ids_only=True)
|
||||
return {'data': data}
|
||||
|
||||
|
||||
@app.get('/{projectId}/sessions/{sessionId}/replay', tags=["sessions", "replay"])
|
||||
def get_session_events(projectId: int, sessionId: Union[int, str], background_tasks: BackgroundTasks,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
|
|
|
|||
|
|
@ -11,15 +11,17 @@ pyjwt = "==2.7.0"
|
|||
psycopg2-binary = "==2.9.6"
|
||||
elasticsearch = "==8.8.0"
|
||||
jira = "==3.5.1"
|
||||
fastapi = "==0.96.0"
|
||||
uvicorn = {version = "==0.22.0", extras = ["standard"]}
|
||||
fastapi = "==0.97.0"
|
||||
python-decouple = "==3.8"
|
||||
pydantic = {version = "==1.10.8", extras = ["email"]}
|
||||
apscheduler = "==3.10.1"
|
||||
clickhouse-driver = {version = "==0.2.5", extras = ["lz4"]}
|
||||
python-multipart = "==0.0.6"
|
||||
redis = "==4.5.5"
|
||||
azure-storage-blob = "==12.16.0"
|
||||
uvicorn = {version = "==0.22.0", extras = ["standard"]}
|
||||
gunicorn = "==20.1.0"
|
||||
pydantic = {version = "==1.10.8", extras = ["email"]}
|
||||
clickhouse-driver = {version = "==0.2.6", extras = ["lz4"]}
|
||||
python3-saml = "==1.15.0"
|
||||
|
||||
[dev-packages]
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class JWTAuth(HTTPBearer):
|
|||
if credentials:
|
||||
if not credentials.scheme == "Bearer":
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid authentication scheme.")
|
||||
jwt_payload = authorizers.jwt_authorizer(credentials.scheme + " " + credentials.credentials)
|
||||
jwt_payload = authorizers.jwt_authorizer(scheme=credentials.scheme, token=credentials.credentials)
|
||||
auth_exists = jwt_payload is not None \
|
||||
and users.auth_exists(user_id=jwt_payload.get("userId", -1),
|
||||
tenant_id=jwt_payload.get("tenantId", -1),
|
||||
|
|
@ -27,18 +27,13 @@ class JWTAuth(HTTPBearer):
|
|||
if jwt_payload is None \
|
||||
or jwt_payload.get("iat") is None or jwt_payload.get("aud") is None \
|
||||
or not auth_exists:
|
||||
print("JWTAuth: Token issue")
|
||||
if jwt_payload is not None:
|
||||
print(jwt_payload)
|
||||
print(f"JWTAuth: user_id={jwt_payload.get('userId')} tenant_id={jwt_payload.get('tenantId')}")
|
||||
if jwt_payload is None:
|
||||
print("JWTAuth: jwt_payload is None")
|
||||
print(credentials.scheme + " " + credentials.credentials)
|
||||
if jwt_payload is not None and jwt_payload.get("iat") is None:
|
||||
print("JWTAuth: iat is None")
|
||||
if jwt_payload is not None and jwt_payload.get("aud") is None:
|
||||
print("JWTAuth: aud is None")
|
||||
if jwt_payload is not None and not auth_exists:
|
||||
if jwt_payload.get("iat") is None:
|
||||
print("JWTAuth: iat is None")
|
||||
if jwt_payload.get("aud") is None:
|
||||
print("JWTAuth: aud is None")
|
||||
if not auth_exists:
|
||||
print("JWTAuth: not users.auth_exists")
|
||||
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Invalid token or expired token.")
|
||||
|
|
@ -47,12 +42,14 @@ class JWTAuth(HTTPBearer):
|
|||
print("JWTAuth: User not found.")
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="User not found.")
|
||||
jwt_payload["authorizer_identity"] = "jwt"
|
||||
print(jwt_payload)
|
||||
request.state.authorizer_identity = "jwt"
|
||||
if user["serviceAccount"]:
|
||||
user["permissions"] = [p.value for p in schemas_ee.ServicePermissions]
|
||||
request.state.currentContext = schemas_ee.CurrentContext(tenant_id=jwt_payload.get("tenantId", -1),
|
||||
user_id=jwt_payload.get("userId", -1),
|
||||
email=user["email"],
|
||||
permissions=user["permissions"])
|
||||
permissions=user["permissions"],
|
||||
service_account=user["serviceAccount"])
|
||||
return request.state.currentContext
|
||||
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -7,13 +7,12 @@ from chalicelib.utils import helper
|
|||
from chalicelib.utils.TimeUTC import TimeUTC
|
||||
|
||||
|
||||
def jwt_authorizer(token):
|
||||
token = token.split(" ")
|
||||
if len(token) != 2 or token[0].lower() != "bearer":
|
||||
def jwt_authorizer(scheme: str, token: str):
|
||||
if scheme.lower() != "bearer":
|
||||
return None
|
||||
try:
|
||||
payload = jwt.decode(
|
||||
token[1],
|
||||
token,
|
||||
config("jwt_secret"),
|
||||
algorithms=config("jwt_algorithm"),
|
||||
audience=[f"front:{helper.get_stage_name()}"]
|
||||
|
|
@ -23,6 +22,7 @@ def jwt_authorizer(token):
|
|||
return None
|
||||
except BaseException as e:
|
||||
print("! JWT Base Exception")
|
||||
print(e)
|
||||
return None
|
||||
return payload
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ import json
|
|||
import secrets
|
||||
|
||||
from decouple import config
|
||||
from fastapi import BackgroundTasks
|
||||
from fastapi import BackgroundTasks, HTTPException
|
||||
from starlette import status
|
||||
|
||||
import schemas
|
||||
import schemas_ee
|
||||
|
|
@ -282,7 +283,8 @@ def get(user_id, tenant_id):
|
|||
roles.name AS role_name,
|
||||
roles.permissions,
|
||||
roles.all_projects,
|
||||
basic_authentication.password IS NOT NULL AS has_password
|
||||
basic_authentication.password IS NOT NULL AS has_password,
|
||||
users.service_account
|
||||
FROM public.users LEFT JOIN public.basic_authentication ON users.user_id=basic_authentication.user_id
|
||||
LEFT JOIN public.roles USING (role_id)
|
||||
WHERE
|
||||
|
|
@ -472,7 +474,9 @@ def get_members(tenant_id):
|
|||
FROM public.users
|
||||
LEFT JOIN public.basic_authentication ON users.user_id=basic_authentication.user_id
|
||||
LEFT JOIN public.roles USING (role_id)
|
||||
WHERE users.tenant_id = %(tenant_id)s AND users.deleted_at IS NULL
|
||||
WHERE users.tenant_id = %(tenant_id)s
|
||||
AND users.deleted_at IS NULL
|
||||
AND NOT users.service_account
|
||||
ORDER BY name, user_id""",
|
||||
{"tenant_id": tenant_id})
|
||||
)
|
||||
|
|
@ -626,17 +630,24 @@ def auth_exists(user_id, tenant_id, jwt_iat, jwt_aud):
|
|||
with pg_client.PostgresClient() as cur:
|
||||
cur.execute(
|
||||
cur.mogrify(
|
||||
f"SELECT user_id AS id,jwt_iat, changed_at FROM public.users INNER JOIN public.basic_authentication USING(user_id) WHERE user_id = %(userId)s AND tenant_id = %(tenant_id)s AND deleted_at IS NULL LIMIT 1;",
|
||||
f"""SELECT user_id,
|
||||
jwt_iat,
|
||||
changed_at,
|
||||
service_account,
|
||||
basic_authentication.user_id IS NOT NULL AS has_basic_auth
|
||||
FROM public.users
|
||||
LEFT JOIN public.basic_authentication USING(user_id)
|
||||
WHERE user_id = %(userId)s
|
||||
AND tenant_id = %(tenant_id)s
|
||||
AND deleted_at IS NULL
|
||||
LIMIT 1;""",
|
||||
{"userId": user_id, "tenant_id": tenant_id})
|
||||
)
|
||||
r = cur.fetchone()
|
||||
return r is not None \
|
||||
and r.get("jwt_iat") is not None \
|
||||
and (abs(jwt_iat - TimeUTC.datetime_to_timestamp(r["jwt_iat"]) // 1000) <= 1 \
|
||||
or (jwt_aud.startswith("plugin") \
|
||||
and (r["changed_at"] is None \
|
||||
or jwt_iat >= (TimeUTC.datetime_to_timestamp(r["changed_at"]) // 1000)))
|
||||
)
|
||||
and (r["service_account"] and not r["has_basic_auth"]
|
||||
or r.get("jwt_iat") is not None \
|
||||
and (abs(jwt_iat - TimeUTC.datetime_to_timestamp(r["jwt_iat"]) // 1000) <= 1))
|
||||
|
||||
|
||||
def change_jwt_iat(user_id):
|
||||
|
|
@ -665,7 +676,8 @@ def authenticate(email, password, for_change_password=False) -> dict | None:
|
|||
users.origin,
|
||||
users.role_id,
|
||||
roles.name AS role_name,
|
||||
roles.permissions
|
||||
roles.permissions,
|
||||
users.service_account
|
||||
FROM public.users AS users INNER JOIN public.basic_authentication USING(user_id)
|
||||
LEFT JOIN public.roles ON (roles.role_id = users.role_id AND roles.tenant_id = users.tenant_id)
|
||||
WHERE users.email = %(email)s
|
||||
|
|
@ -694,7 +706,10 @@ def authenticate(email, password, for_change_password=False) -> dict | None:
|
|||
if for_change_password:
|
||||
return True
|
||||
r = helper.dict_to_camel_case(r)
|
||||
if config("enforce_SSO", cast=bool, default=False) and helper.is_saml2_available():
|
||||
if r["serviceAccount"]:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="service account is not authorized to login")
|
||||
elif config("enforce_SSO", cast=bool, default=False) and helper.is_saml2_available():
|
||||
return {"errors": ["must sign-in with SSO, enforced by admin"]}
|
||||
|
||||
jwt_iat = change_jwt_iat(r['userId'])
|
||||
|
|
@ -722,8 +737,9 @@ def authenticate_sso(email, internal_id, exp=None):
|
|||
(CASE WHEN users.role = 'admin' THEN TRUE ELSE FALSE END) AS admin,
|
||||
(CASE WHEN users.role = 'member' THEN TRUE ELSE FALSE END) AS member,
|
||||
origin,
|
||||
role_id
|
||||
FROM public.users AS users
|
||||
role_id,
|
||||
service_account
|
||||
FROM public.users
|
||||
WHERE users.email = %(email)s AND internal_id = %(internal_id)s;""",
|
||||
{"email": email, "internal_id": internal_id})
|
||||
|
||||
|
|
@ -732,6 +748,9 @@ def authenticate_sso(email, internal_id, exp=None):
|
|||
|
||||
if r is not None:
|
||||
r = helper.dict_to_camel_case(r)
|
||||
if r["serviceAccount"]:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="service account is not authorized to login")
|
||||
jwt_iat = TimeUTC.datetime_to_timestamp(change_jwt_iat(r['userId']))
|
||||
return authorizers.generate_jwt(r['userId'], r['tenantId'],
|
||||
iat=jwt_iat, aud=f"front:{helper.get_stage_name()}",
|
||||
|
|
|
|||
|
|
@ -56,10 +56,19 @@ class ORRoute(APIRoute):
|
|||
|
||||
|
||||
def __check(security_scopes: SecurityScopes, context: schemas_ee.CurrentContext = Depends(OR_context)):
|
||||
s_p = 0
|
||||
for scope in security_scopes.scopes:
|
||||
if isinstance(scope, schemas_ee.ServicePermissions):
|
||||
s_p += 1
|
||||
if context.service_account and not isinstance(scope, schemas_ee.ServicePermissions) \
|
||||
or not context.service_account and not isinstance(scope, schemas_ee.Permissions):
|
||||
continue
|
||||
if scope not in context.permissions:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Not enough permissions")
|
||||
if context.service_account and s_p == 0:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Not enough permissions (service account)")
|
||||
|
||||
|
||||
def OR_scope(*scopes):
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ from chalicelib.utils.TimeUTC import TimeUTC
|
|||
from or_dependencies import OR_context, OR_scope
|
||||
from routers import saml
|
||||
from routers.base import get_routers
|
||||
from schemas_ee import Permissions
|
||||
from schemas_ee import Permissions, ServicePermissions
|
||||
|
||||
public_app, app, app_apikey = get_routers()
|
||||
|
||||
|
|
@ -203,7 +203,7 @@ def get_projects(context: schemas.CurrentContext = Depends(OR_context)):
|
|||
|
||||
# for backward compatibility
|
||||
@app.get('/{projectId}/sessions/{sessionId}', tags=["sessions", "replay"],
|
||||
dependencies=[OR_scope(Permissions.session_replay)])
|
||||
dependencies=[OR_scope(Permissions.session_replay, ServicePermissions.session_replay)])
|
||||
def get_session(projectId: int, sessionId: Union[int, str], background_tasks: BackgroundTasks,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
if isinstance(sessionId, str):
|
||||
|
|
@ -220,8 +220,24 @@ def get_session(projectId: int, sessionId: Union[int, str], background_tasks: Ba
|
|||
}
|
||||
|
||||
|
||||
@app.post('/{projectId}/sessions/search', tags=["sessions"],
|
||||
dependencies=[OR_scope(Permissions.session_replay)])
|
||||
def sessions_search(projectId: int, data: schemas.FlatSessionsSearchPayloadSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = sessions.search_sessions(data=data, project_id=projectId, user_id=context.user_id)
|
||||
return {'data': data}
|
||||
|
||||
|
||||
@app.post('/{projectId}/sessions/search/ids', tags=["sessions"],
|
||||
dependencies=[OR_scope(Permissions.session_replay)])
|
||||
def session_ids_search(projectId: int, data: schemas.FlatSessionsSearchPayloadSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = sessions.search_sessions(data=data, project_id=projectId, user_id=context.user_id, ids_only=True)
|
||||
return {'data': data}
|
||||
|
||||
|
||||
@app.get('/{projectId}/sessions/{sessionId}/replay', tags=["sessions", "replay"],
|
||||
dependencies=[OR_scope(Permissions.session_replay)])
|
||||
dependencies=[OR_scope(Permissions.session_replay, ServicePermissions.session_replay)])
|
||||
def get_session_events(projectId: int, sessionId: Union[int, str], background_tasks: BackgroundTasks,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
if isinstance(sessionId, str):
|
||||
|
|
@ -239,7 +255,7 @@ def get_session_events(projectId: int, sessionId: Union[int, str], background_ta
|
|||
|
||||
|
||||
@app.get('/{projectId}/sessions/{sessionId}/events', tags=["sessions", "replay"],
|
||||
dependencies=[OR_scope(Permissions.session_replay)])
|
||||
dependencies=[OR_scope(Permissions.session_replay, ServicePermissions.session_replay)])
|
||||
def get_session_events(projectId: int, sessionId: Union[int, str],
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
if isinstance(sessionId, str):
|
||||
|
|
@ -326,7 +342,8 @@ def add_remove_favorite_error(projectId: int, errorId: str, action: str, startDa
|
|||
return {"errors": ["undefined action"]}
|
||||
|
||||
|
||||
@app.get('/{projectId}/assist/sessions/{sessionId}', tags=["assist"], dependencies=[OR_scope(Permissions.assist_live)])
|
||||
@app.get('/{projectId}/assist/sessions/{sessionId}', tags=["assist"],
|
||||
dependencies=[OR_scope(Permissions.assist_live, ServicePermissions.assist_live)])
|
||||
def get_live_session(projectId: int, sessionId: str, background_tasks: BackgroundTasks,
|
||||
context: schemas_ee.CurrentContext = Depends(OR_context)):
|
||||
data = assist.get_live_session_by_id(project_id=projectId, session_id=sessionId)
|
||||
|
|
@ -342,7 +359,8 @@ def get_live_session(projectId: int, sessionId: str, background_tasks: Backgroun
|
|||
|
||||
|
||||
@app.get('/{projectId}/unprocessed/{sessionId}/dom.mob', tags=["assist"],
|
||||
dependencies=[OR_scope(Permissions.assist_live, Permissions.session_replay)])
|
||||
dependencies=[OR_scope(Permissions.assist_live, Permissions.session_replay,
|
||||
ServicePermissions.assist_live, ServicePermissions.session_replay)])
|
||||
def get_live_session_replay_file(projectId: int, sessionId: Union[int, str],
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
not_found = {"errors": ["Replay file not found"]}
|
||||
|
|
@ -363,7 +381,9 @@ def get_live_session_replay_file(projectId: int, sessionId: Union[int, str],
|
|||
|
||||
|
||||
@app.get('/{projectId}/unprocessed/{sessionId}/devtools.mob', tags=["assist"],
|
||||
dependencies=[OR_scope(Permissions.assist_live, Permissions.session_replay, Permissions.dev_tools)])
|
||||
dependencies=[OR_scope(Permissions.assist_live, Permissions.session_replay, Permissions.dev_tools,
|
||||
ServicePermissions.assist_live, ServicePermissions.session_replay,
|
||||
ServicePermissions.dev_tools)])
|
||||
def get_live_session_devtools_file(projectId: int, sessionId: Union[int, str],
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
not_found = {"errors": ["Devtools file not found"]}
|
||||
|
|
|
|||
|
|
@ -18,8 +18,16 @@ class Permissions(str, Enum):
|
|||
feature_flags = "FEATURE_FLAGS"
|
||||
|
||||
|
||||
class ServicePermissions(str, Enum):
|
||||
session_replay = "SERVICE_SESSION_REPLAY"
|
||||
dev_tools = "SERVICE_DEV_TOOLS"
|
||||
assist_live = "SERVICE_ASSIST_LIVE"
|
||||
assist_call = "SERVICE_ASSIST_CALL"
|
||||
|
||||
|
||||
class CurrentContext(schemas.CurrentContext):
|
||||
permissions: List[Optional[Permissions]] = Field(...)
|
||||
permissions: List[Union[Permissions, ServicePermissions]] = Field(...)
|
||||
service_account: bool = Field(default=False)
|
||||
|
||||
|
||||
class RolePayloadSchema(BaseModel):
|
||||
|
|
|
|||
|
|
@ -71,6 +71,14 @@ UPDATE public.roles
|
|||
SET permissions = (SELECT array_agg(distinct e) FROM unnest(permissions || '{FEATURE_FLAGS}') AS e)
|
||||
where not permissions @> '{FEATURE_FLAGS}';
|
||||
|
||||
ALTER TYPE public.user_role ADD VALUE IF NOT EXISTS 'service';
|
||||
|
||||
ALTER TABLE IF EXISTS public.users
|
||||
ADD COLUMN IF NOT EXISTS service_account bool NOT NULL DEFAULT FALSE;
|
||||
|
||||
ALTER TABLE IF EXISTS public.roles
|
||||
ADD COLUMN IF NOT EXISTS service_role bool NOT NULL DEFAULT FALSE;
|
||||
|
||||
COMMIT;
|
||||
|
||||
\elif :is_next
|
||||
|
|
|
|||
|
|
@ -172,32 +172,34 @@ $$
|
|||
protected bool NOT NULL DEFAULT FALSE,
|
||||
all_projects bool NOT NULL DEFAULT TRUE,
|
||||
created_at timestamp NOT NULL DEFAULT timezone('utc'::text, now()),
|
||||
deleted_at timestamp NULL DEFAULT NULL
|
||||
deleted_at timestamp NULL DEFAULT NULL,
|
||||
service_role bool NOT NULL DEFAULT FALSE
|
||||
);
|
||||
|
||||
IF NOT EXISTS(SELECT *
|
||||
FROM pg_type typ
|
||||
WHERE typ.typname = 'user_role') THEN
|
||||
CREATE TYPE user_role AS ENUM ('owner','admin','member');
|
||||
CREATE TYPE user_role AS ENUM ('owner','admin','member','service');
|
||||
END IF;
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users
|
||||
(
|
||||
user_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
tenant_id integer NOT NULL REFERENCES tenants (tenant_id) ON DELETE CASCADE,
|
||||
email text NOT NULL UNIQUE,
|
||||
role user_role NOT NULL DEFAULT 'member',
|
||||
name text NOT NULL,
|
||||
created_at timestamp without time zone NOT NULL DEFAULT (now() at time zone 'utc'),
|
||||
deleted_at timestamp without time zone NULL DEFAULT NULL,
|
||||
api_key text UNIQUE DEFAULT generate_api_key(20) NOT NULL,
|
||||
jwt_iat timestamp without time zone NULL DEFAULT NULL,
|
||||
data jsonb NOT NULL DEFAULT'{}'::jsonb,
|
||||
weekly_report boolean NOT NULL DEFAULT TRUE,
|
||||
origin text NULL DEFAULT NULL,
|
||||
role_id integer REFERENCES roles (role_id) ON DELETE SET NULL,
|
||||
internal_id text NULL DEFAULT NULL
|
||||
user_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
tenant_id integer NOT NULL REFERENCES tenants (tenant_id) ON DELETE CASCADE,
|
||||
email text NOT NULL UNIQUE,
|
||||
role user_role NOT NULL DEFAULT 'member',
|
||||
name text NOT NULL,
|
||||
created_at timestamp without time zone NOT NULL DEFAULT (now() at time zone 'utc'),
|
||||
deleted_at timestamp without time zone NULL DEFAULT NULL,
|
||||
api_key text UNIQUE DEFAULT generate_api_key(20) NOT NULL,
|
||||
jwt_iat timestamp without time zone NULL DEFAULT NULL,
|
||||
data jsonb NOT NULL DEFAULT'{}'::jsonb,
|
||||
weekly_report boolean NOT NULL DEFAULT TRUE,
|
||||
origin text NULL DEFAULT NULL,
|
||||
role_id integer REFERENCES roles (role_id) ON DELETE SET NULL,
|
||||
internal_id text NULL DEFAULT NULL,
|
||||
service_account bool NOT NULL DEFAULT FALSE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS users_tenant_id_deleted_at_N_idx ON users (tenant_id) WHERE deleted_at ISNULL;
|
||||
CREATE INDEX IF NOT EXISTS users_name_gin_idx ON users USING GIN (name gin_trgm_ops);
|
||||
|
|
|
|||
|
|
@ -22,5 +22,5 @@ MINIO_ACCESS_KEY = ''
|
|||
MINIO_SECRET_KEY = ''
|
||||
|
||||
# APP and TRACKER VERSIONS
|
||||
VERSION = 1.14.0
|
||||
VERSION = 1.14.1
|
||||
TRACKER_VERSION = '9.0.0'
|
||||
|
|
|
|||
|
|
@ -15,10 +15,10 @@ type: application
|
|||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (frontends://semver.org/)
|
||||
version: 0.1.10
|
||||
version: 0.1.11
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
AppVersion: "v1.14.0"
|
||||
AppVersion: "v1.14.1"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue