feat(api): fixed edition

feat(api): fixed expiration date
feat(api): fixed change name
feat(api): fixed change role
feat(api): fixed has password
feat(api): refactored edit user
feat(api): refactored edit member
This commit is contained in:
Taha Yassine Kraiem 2022-06-07 18:12:08 +02:00
parent cbe78cc58e
commit 3a70c8bef6
20 changed files with 149 additions and 128 deletions

View file

@ -1,12 +1,9 @@
from chalicelib.utils import pg_client
EDITION = 'foss'
def get_status(tenant_id=None):
with pg_client.PostgresClient() as cur:
# cur.execute("SELECT * FROM public.tenants;")
cur.execute("SELECT edition FROM public.tenants;")
r = cur.fetchone()
return {
"hasActivePlan": True,
"edition": r.get("edition", "").upper()
"edition": EDITION,
"expirationDate": -1
}

View file

@ -67,8 +67,8 @@ def create_step1(data: schemas.UserSignupSchema):
}
query = f"""\
WITH t AS (
INSERT INTO public.tenants (name, version_number, edition)
VALUES (%(organizationName)s, (SELECT openreplay_version()), 'fos')
INSERT INTO public.tenants (name, version_number)
VALUES (%(organizationName)s, (SELECT openreplay_version()))
RETURNING api_key
),
u AS (

View file

@ -1,10 +1,11 @@
from chalicelib.utils import pg_client
import requests
from chalicelib.core import license
def process_data(data, edition='fos'):
def process_data(data):
return {
'edition': edition,
'edition': license.EDITION,
'tracking': data["opt_out"],
'version': data["version_number"],
'user_id': data["user_id"],

View file

@ -1,7 +1,7 @@
import schemas
from chalicelib.utils import pg_client
from chalicelib.utils import helper
from chalicelib.core import users
from chalicelib.core import users, license
def get_by_tenant_id(tenant_id):
@ -13,7 +13,7 @@ def get_by_tenant_id(tenant_id):
name,
api_key,
created_at,
edition,
'{license.EDITION}' AS edition,
version_number,
opt_out
FROM public.tenants
@ -67,7 +67,7 @@ def update(tenant_id, user_id, data: schemas.UpdateTenantSchema):
admin = users.get(user_id=user_id, tenant_id=tenant_id)
if not admin["admin"] and not admin["superAdmin"]:
return {"error": "unauthorized"}
return {"errors": ["unauthorized, needs admin or owner"]}
if data.name is None and data.opt_out is None:
return {"errors": ["please provide 'name' of 'optOut' attribute for update"]}
changes = {}

View file

@ -4,6 +4,7 @@ import secrets
from decouple import config
from fastapi import BackgroundTasks
import schemas
from chalicelib.core import authorizers, metadata, projects
from chalicelib.core import tenants, assist
from chalicelib.utils import dev, email_helper
@ -240,7 +241,8 @@ def get(user_id, tenant_id):
(CASE WHEN role = 'owner' THEN TRUE ELSE FALSE END) AS super_admin,
(CASE WHEN role = 'admin' THEN TRUE ELSE FALSE END) AS admin,
(CASE WHEN role = 'member' THEN TRUE ELSE FALSE END) AS member,
api_key
api_key,
TRUE AS has_password
FROM public.users LEFT JOIN public.basic_authentication ON users.user_id=basic_authentication.user_id
WHERE
users.user_id = %(userId)s
@ -268,37 +270,36 @@ def generate_new_api_key(user_id):
return helper.dict_to_camel_case(r)
def edit(user_id_to_update, tenant_id, changes, editor_id):
ALLOW_EDIT = ["name", "email", "admin"]
def edit(user_id_to_update, tenant_id, changes: schemas.EditUserSchema, editor_id):
user = get(user_id=user_id_to_update, tenant_id=tenant_id)
if editor_id != user_id_to_update or "admin" in changes and changes["admin"] != user["admin"]:
if editor_id != user_id_to_update or changes.admin is not None and changes.admin != user["admin"]:
admin = get(tenant_id=tenant_id, user_id=editor_id)
if not admin["superAdmin"] and not admin["admin"]:
return {"errors": ["unauthorized"]}
_changes = {}
if editor_id == user_id_to_update:
if user["superAdmin"]:
changes.pop("admin")
elif user["admin"] != changes["admin"]:
return {"errors": ["cannot change your own role"]}
if changes.admin is not None:
if user["superAdmin"]:
changes.admin = None
elif changes.admin != user["admin"]:
return {"errors": ["cannot change your own role"]}
keys = list(changes.keys())
for k in keys:
if k not in ALLOW_EDIT or changes[k] is None:
changes.pop(k)
keys = list(changes.keys())
if changes.email is not None and changes.email != user["email"]:
if email_exists(changes.email):
return {"errors": ["email already exists."]}
if get_deleted_user_by_email(changes.email) is not None:
return {"errors": ["email previously deleted."]}
_changes["email"] = changes.email
if len(keys) > 0:
if "email" in keys and changes["email"] != user["email"]:
if email_exists(changes["email"]):
return {"errors": ["email already exists."]}
if get_deleted_user_by_email(changes["email"]) is not None:
return {"errors": ["email previously deleted."]}
if "admin" in keys:
changes["role"] = "admin" if changes.pop("admin") else "member"
if len(changes.keys()) > 0:
updated_user = update(tenant_id=tenant_id, user_id=user_id_to_update, changes=changes)
if changes.name is not None and len(changes.name) > 0:
_changes["name"] = changes.name
return {"data": updated_user}
if changes.admin is not None:
_changes["role"] = "admin" if changes.admin else "member"
if len(_changes.keys()) > 0:
updated_user = update(tenant_id=tenant_id, user_id=user_id_to_update, changes=_changes)
return {"data": updated_user}
return {"data": user}

View file

@ -365,10 +365,6 @@ def has_smtp():
return config("EMAIL_HOST") is not None and len(config("EMAIL_HOST")) > 0
def get_edition():
return "ee" if "ee" in config("ENTERPRISE_BUILD", default="").lower() else "foss"
def old_search_payload_to_flat(values):
# in case the old search body was passed
if values.get("events") is not None:

View file

@ -1076,7 +1076,7 @@ def generate_new_user_token(context: schemas.CurrentContext = Depends(OR_context
@app.put('/account', tags=["account"])
def edit_account(data: schemas.EditUserSchema = Body(...),
context: schemas.CurrentContext = Depends(OR_context)):
return users.edit(tenant_id=context.tenant_id, user_id_to_update=context.user_id, changes=data.dict(),
return users.edit(tenant_id=context.tenant_id, user_id_to_update=context.user_id, changes=data,
editor_id=context.user_id)

View file

@ -24,7 +24,7 @@ def get_all_signup():
return {"data": {"tenants": tenants.tenants_exists(),
"sso": None,
"ssoProvider": None,
"edition": helper.get_edition()}}
"edition": license.EDITION}}
@public_app.post('/login', tags=["authentication"])
@ -181,7 +181,7 @@ def change_password_by_invitation(data: schemas.EditPasswordByInvitationSchema =
@app.post('/client/members/{memberId}', tags=["client"])
def edit_member(memberId: int, data: schemas.EditMemberSchema,
context: schemas.CurrentContext = Depends(OR_context)):
return users.edit(tenant_id=context.tenant_id, editor_id=context.user_id, changes=data.dict(),
return users.edit(tenant_id=context.tenant_id, editor_id=context.user_id, changes=data,
user_id_to_update=memberId)

View file

@ -12,7 +12,7 @@ def attribute_to_camel_case(snake_str):
def transform_email(email: str) -> str:
return email.lower() if isinstance(email, str) else email
return email.lower().strip() if isinstance(email, str) else email
class _Grecaptcha(BaseModel):
@ -37,7 +37,7 @@ class UserSignupSchema(UserLoginSchema):
class EditUserSchema(BaseModel):
name: Optional[str] = Field(None)
email: Optional[EmailStr] = Field(None)
admin: Optional[bool] = Field(False)
admin: Optional[bool] = Field(None)
_transform_email = validator('email', pre=True, allow_reuse=True)(transform_email)
@ -127,13 +127,11 @@ class CreateMemberSchema(BaseModel):
_transform_email = validator('email', pre=True, allow_reuse=True)(transform_email)
class EditMemberSchema(BaseModel):
class EditMemberSchema(EditUserSchema):
name: str = Field(...)
email: EmailStr = Field(...)
admin: bool = Field(False)
_transform_email = validator('email', pre=True, allow_reuse=True)(transform_email)
class EditPasswordByInvitationSchema(BaseModel):
invitation: str = Field(...)
@ -796,6 +794,7 @@ class MetricTableViewType(str, Enum):
class MetricType(str, Enum):
timeseries = "timeseries"
table = "table"
predefined = "predefined"
class TableMetricOfType(str, Enum):

View file

@ -1,17 +1,12 @@
from decouple import config
from chalicelib.core import unlock
from chalicelib.utils import pg_client
EDITION = 'ee'
def get_status(tenant_id):
with pg_client.PostgresClient() as cur:
cur.execute(
# cur.mogrify("SELECT * FROM public.tenants WHERE tenant_id=%(tenant_id)s;", {"tenant_id": tenant_id}))
cur.mogrify("SELECT edition FROM public.tenants WHERE tenant_id=%(tenant_id)s;", {"tenant_id": tenant_id}))
r = cur.fetchone()
license = unlock.get_license()
return {
"hasActivePlan": unlock.is_valid(),
"edition": r.get("edition", "").lower(),
"edition": EDITION,
"expirationDate": unlock.get_expiration_date()
}

View file

@ -64,8 +64,8 @@ def create_step1(data: schemas.UserSignupSchema):
"data": json.dumps({"lastAnnouncementView": TimeUTC.now()})}
query = """\
WITH t AS (
INSERT INTO public.tenants (name, version_number, edition)
VALUES (%(companyName)s, (SELECT openreplay_version()), 'ee')
INSERT INTO public.tenants (name, version_number)
VALUES (%(companyName)s, (SELECT openreplay_version()))
RETURNING tenant_id, api_key
),
r AS (

View file

@ -1,10 +1,11 @@
from chalicelib.utils import pg_client
from chalicelib.core import license
import requests
def process_data(data, edition='fos'):
def process_data(data):
return {
'edition': edition,
'edition': license.EDITION,
'tracking': data["opt_out"],
'version': data["version_number"],
'user_id': data["user_id"],
@ -56,7 +57,7 @@ def compute():
)
data = cur.fetchall()
requests.post('https://api.openreplay.com/os/telemetry',
json={"stats": [process_data(d, edition='ee') for d in data]})
json={"stats": [process_data(d) for d in data]})
def new_client(tenant_id):
@ -67,4 +68,4 @@ def new_client(tenant_id):
FROM public.tenants
WHERE tenant_id=%(tenant_id)s;""", {"tenant_id": tenant_id}))
data = cur.fetchone()
requests.post('https://api.openreplay.com/os/signup', json=process_data(data, edition='ee'))
requests.post('https://api.openreplay.com/os/signup', json=process_data(data))

View file

@ -1,4 +1,4 @@
from chalicelib.core import users
from chalicelib.core import users, license
from chalicelib.utils import helper
from chalicelib.utils import pg_client
@ -12,7 +12,7 @@ def get_by_tenant_key(tenant_key):
t.name,
t.api_key,
t.created_at,
t.edition,
'{license.EDITION}' AS edition,
t.version_number,
t.opt_out
FROM public.tenants AS t
@ -32,7 +32,7 @@ def get_by_tenant_id(tenant_id):
t.name,
t.api_key,
t.created_at,
t.edition,
'{license.EDITION}' AS edition,
t.version_number,
t.opt_out,
t.user_id AS tenant_key
@ -90,7 +90,7 @@ def update(tenant_id, user_id, data):
admin = users.get(user_id=user_id, tenant_id=tenant_id)
if not admin["admin"] and not admin["superAdmin"]:
return {"error": "unauthorized"}
return {"errors": ["unauthorized, needs admin or owner"]}
if "name" not in data and "optOut" not in data:
return {"errors": ["please provide 'name' of 'optOut' attribute for update"]}
changes = {}

View file

@ -4,6 +4,8 @@ import secrets
from decouple import config
from fastapi import BackgroundTasks
import schemas
import schemas_ee
from chalicelib.core import authorizers, metadata, projects, roles
from chalicelib.core import tenants, assist
from chalicelib.utils import dev, SAML2_helper
@ -303,37 +305,44 @@ def generate_new_api_key(user_id):
return helper.dict_to_camel_case(r)
def edit(user_id_to_update, tenant_id, changes, editor_id):
ALLOW_EDIT = ["name", "email", "admin", "roleId"]
def edit(user_id_to_update, tenant_id, changes: schemas_ee.EditUserSchema, editor_id):
user = get(user_id=user_id_to_update, tenant_id=tenant_id)
if editor_id != user_id_to_update or "admin" in changes and changes["admin"] != user["admin"]:
if editor_id != user_id_to_update or changes.admin is not None and changes.admin != user["admin"]:
admin = get(tenant_id=tenant_id, user_id=editor_id)
if not admin["superAdmin"] and not admin["admin"]:
return {"errors": ["unauthorized"]}
_changes = {}
if editor_id == user_id_to_update:
if user["superAdmin"]:
changes.pop("admin")
elif user["admin"] != changes["admin"]:
return {"errors": ["cannot change your own role"]}
if changes.admin is not None:
if user["superAdmin"]:
changes.admin = None
elif changes.admin != user["admin"]:
return {"errors": ["cannot change your own role"]}
if changes.roleId is not None:
if user["superAdmin"]:
changes.roleId = None
elif changes.roleId != user["roleId"]:
return {"errors": ["cannot change your own role"]}
keys = list(changes.keys())
for k in keys:
if k not in ALLOW_EDIT or changes[k] is None:
changes.pop(k)
keys = list(changes.keys())
if changes.email is not None and changes.email != user["email"]:
if email_exists(changes.email):
return {"errors": ["email already exists."]}
if get_deleted_user_by_email(changes.email) is not None:
return {"errors": ["email previously deleted."]}
_changes["email"] = changes.email
if len(keys) > 0:
if "email" in keys and changes["email"] != user["email"]:
if email_exists(changes["email"]):
return {"errors": ["email already exists."]}
if get_deleted_user_by_email(changes["email"]) is not None:
return {"errors": ["email previously deleted."]}
if "admin" in keys:
changes["role"] = "admin" if changes.pop("admin") else "member"
if len(changes.keys()) > 0:
updated_user = update(tenant_id=tenant_id, user_id=user_id_to_update, changes=changes)
if changes.name is not None and len(changes.name) > 0:
_changes["name"] = changes.name
return {"data": updated_user}
if changes.admin is not None:
_changes["role"] = "admin" if changes.admin else "member"
if changes.roleId is not None:
_changes["roleId"] = changes.roleId
if len(_changes.keys()) > 0:
updated_user = update(tenant_id=tenant_id, user_id=user_id_to_update, changes=_changes)
return {"data": updated_user}
return {"data": user}

View file

@ -187,7 +187,7 @@ def change_password_by_invitation(data: schemas.EditPasswordByInvitationSchema =
@app.post('/client/members/{memberId}', tags=["client"])
def edit_member(memberId: int, data: schemas_ee.EditMemberSchema,
context: schemas.CurrentContext = Depends(OR_context)):
return users.edit(tenant_id=context.tenant_id, editor_id=context.user_id, changes=data.dict(),
return users.edit(tenant_id=context.tenant_id, editor_id=context.user_id, changes=data,
user_id_to_update=memberId)

View file

@ -1,6 +1,6 @@
from typing import Optional, List, Literal
from pydantic import BaseModel, Field
from pydantic import BaseModel, Field, EmailStr
import schemas
from chalicelib.utils.TimeUTC import TimeUTC
@ -21,7 +21,14 @@ class CreateMemberSchema(schemas.CreateMemberSchema):
roleId: Optional[int] = Field(None)
class EditMemberSchema(schemas.EditMemberSchema):
class EditUserSchema(schemas.EditUserSchema):
roleId: Optional[int] = Field(None)
class EditMemberSchema(EditUserSchema):
name: str = Field(...)
email: EmailStr = Field(...)
admin: bool = Field(False)
roleId: int = Field(...)

View file

@ -1,25 +1,42 @@
BEGIN;
CREATE OR REPLACE
FUNCTION openreplay_version()
FUNCTION openreplay_version()
RETURNS text AS
$$
SELECT 'v1.6.1-ee' $$ LANGUAGE sql IMMUTABLE;
SELECT 'v1.6.1-ee'
$$ LANGUAGE sql IMMUTABLE;
ALTER TABLE IF EXISTS dashboards
ADD COLUMN IF NOT
EXISTS description text NOT NULL DEFAULT '';
EXISTS description text NOT NULL DEFAULT '';
CREATE
INDEX IF NOT
EXISTS traces_created_at_idx ON traces (created_at);
INDEX IF NOT
EXISTS traces_created_at_idx ON traces (created_at);
CREATE
INDEX IF NOT
EXISTS traces_action_idx ON traces (action);
INDEX IF NOT
EXISTS traces_action_idx ON traces (action);
CREATE
INDEX IF NOT
EXISTS users_name_gin_idx ON users USING GIN (name gin_trgm_ops);
INDEX IF NOT
EXISTS users_name_gin_idx ON users USING GIN (name gin_trgm_ops);
ALTER TABLE users
DROP COLUMN IF EXISTS appearance;
ALTER TABLE basic_authentication
DROP COLUMN IF EXISTS generated_password;
ALTER TABLE tenants
DROP COLUMN IF EXISTS edition;
ALTER TABLE dashboards
ALTER COLUMN user_id DROP NOT NULL;
COMMIT;
INSERT INTO metrics (name, category, default_config, is_predefined, is_template, is_public, predefined_key, metric_type,
view_type)
@ -122,21 +139,13 @@ VALUES ('Captured sessions', 'web vitals', '{
"col": 1,
"row": 1,
"position": 0
}', true, true, true, 'avg_fps', 'predefined', 'overview') ON CONFLICT (predefined_key) DO
UPDATE
SET name =excluded.name,
}', true, true, true, 'avg_fps', 'predefined', 'overview')
ON CONFLICT (predefined_key) DO UPDATE
SET name =excluded.name,
category=excluded.category,
default_config=excluded.default_config,
is_predefined=excluded.is_predefined,
is_template=excluded.is_template,
is_public=excluded.is_public,
metric_type=excluded.metric_type,
view_type=excluded.view_type;
ALTER TABLE users
DROP COLUMN appearance;
ALTER TABLE basic_authentication
DROP COLUMN generated_password;
COMMIT;
view_type=excluded.view_type;

View file

@ -147,7 +147,6 @@ $$
api_key text UNIQUE default generate_api_key(20) 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,
edition varchar(3) NOT NULL,
version_number text NOT NULL,
license text NULL,
opt_out bool NOT NULL DEFAULT FALSE,
@ -777,7 +776,7 @@ $$
(
dashboard_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY,
project_id integer NOT NULL REFERENCES projects (project_id) ON DELETE CASCADE,
user_id integer NOT NULL REFERENCES users (user_id) ON DELETE SET NULL,
user_id integer REFERENCES users (user_id) ON DELETE SET NULL,
name text NOT NULL,
description text NOT NULL DEFAULT '',
is_public boolean NOT NULL DEFAULT TRUE,

View file

@ -9,6 +9,22 @@ $$ LANGUAGE sql IMMUTABLE;
ALTER TABLE IF EXISTS dashboards
ADD COLUMN IF NOT EXISTS description text NOT NULL DEFAULT '';
ALTER TABLE users
DROP COLUMN IF EXISTS appearance;
ALTER TABLE basic_authentication
DROP COLUMN IF EXISTS generated_password;
ALTER TABLE tenants
DROP COLUMN IF EXISTS edition;
ALTER TABLE dashboards
ALTER COLUMN user_id DROP NOT NULL;
COMMIT;
INSERT INTO metrics (name, category, default_config, is_predefined, is_template, is_public, predefined_key, metric_type,
view_type)
VALUES ('Captured sessions', 'web vitals', '{
@ -119,12 +135,4 @@ ON CONFLICT (predefined_key) DO UPDATE
is_template=excluded.is_template,
is_public=excluded.is_public,
metric_type=excluded.metric_type,
view_type=excluded.view_type;
ALTER TABLE users
DROP COLUMN appearance;
ALTER TABLE basic_authentication
DROP COLUMN generated_password;
COMMIT;
view_type=excluded.view_type;

View file

@ -121,7 +121,6 @@ $$
name text NOT NULL,
api_key text NOT NULL DEFAULT generate_api_key(20),
created_at timestamp without time zone NOT NULL DEFAULT (now() at time zone 'utc'),
edition varchar(3) NOT NULL,
version_number text NOT NULL,
license text NULL,
opt_out bool NOT NULL DEFAULT FALSE,
@ -928,7 +927,7 @@ $$
(
dashboard_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY,
project_id integer NOT NULL REFERENCES projects (project_id) ON DELETE CASCADE,
user_id integer NOT NULL REFERENCES users (user_id) ON DELETE SET NULL,
user_id integer REFERENCES users (user_id) ON DELETE SET NULL,
name text NOT NULL,
description text NOT NULL DEFAULT '',
is_public boolean NOT NULL DEFAULT TRUE,