class Recommendation(db.Model): # type: ignore __bind_key__ = 'experiment_service' id = db.Column(UUIDType(binary=False), primary_key=True, default=uuid.uuid4) recommendation_type = db.Column(db.String(), index=True) tags = db.relationship('RecommendationTag', secondary="recommendation_tags_assoc", backref=db.backref('recommendations', lazy=True)) checksum = db.Column(db.String(), nullable=False) last_updated = db.Column(db.DateTime()) rating = db.Column(db.Float, default=3.0) meta = db.Column(JSONB()) data = db.Column(JSONB(), nullable=False) def to_dict(self): data = self.data if self.recommendation_type == "experiment": data = data["hashed"] return { "id": str(self.id), "type": self.recommendation_type, "tags": [tag.value for tag in self.tags], "checksum": self.checksum, "last_updated": "{}Z".format(self.last_updated.isoformat()), "meta": self.meta, "data": data }
class AccessToken(db.Model, OAuth2TokenMixin): # type: ignore __bind_key__ = 'auth_service' id = db.Column(UUIDType(binary=False), primary_key=True, unique=True, default=uuid.uuid4) name = db.Column(db.String(), nullable=False) account_id = db.Column(UUIDType(binary=False), db.ForeignKey('account.id', ondelete='CASCADE'), nullable=False) last_used_on = db.Column(db.DateTime()) def to_dict(self): last_used = None if self.last_used_on: last_used = "{}Z".format(self.last_used_on.isoformat()) return { "id": shortuuid.encode(self.id), "name": self.name, "account_id": shortuuid.encode(self.account_id), "access_token": self.access_token, "refresh_token": self.refresh_token, "client_id": self.client_id, "scope": self.scope, "token_type": self.token_type, "issued_at": self.issued_at, "expires_in": self.expires_in, "last_used": last_used, "revoked": self.revoked }
class ProviderToken(db.Model, OAuth2TokenMixin): # type: ignore __bind_key__ = 'auth_service' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(20), nullable=False) account_id = db.Column(UUIDType(binary=False), db.ForeignKey('account.id', ondelete='CASCADE')) account = db.relationship('Account') def to_dict(self): return { "id": self.id, "access_token": self.access_token, "token_type": self.token_type, "refresh_token": self.refresh_token, "expires_at": self.expires_in, "revoked": self.revoked }
class APIAccessToken(db.Model): # type: ignore __bind_key__ = 'api_service' __tablename__ = 'api_access_token' __table_args__ = (db.UniqueConstraint('name', 'account_id', name='name_account_uniq'), ) id = db.Column(UUIDType(binary=False), primary_key=True, default=uuid.uuid4) name = db.Column(db.String, nullable=False) account_id = db.Column(UUIDType(binary=False), nullable=False) last_used_on = db.Column(db.DateTime()) client_id = db.Column(db.String(48)) token_type = db.Column(db.String(40)) access_token = db.Column(db.String(255), unique=True, nullable=False) refresh_token = db.Column(db.String(255), index=True) scope = db.Column(db.Text, default='') # be conservative revoked = db.Column(db.Boolean, nullable=False, default=True) issued_at = db.Column(db.Integer, nullable=False) expires_in = db.Column(db.Integer, nullable=False, default=0) def get_scope(self): return self.scope def get_expires_in(self): return self.expires_in def get_expires_at(self): return self.issued_at + self.expires_in def is_expired(self): now = datetime.utcnow().timestamp() return self.get_expires_at() < now def is_active(self): now = datetime.utcnow().timestamp() return self.get_expires_at() >= now def revoke(self): self.revoked = True def to_dict(self): last_used = None if self.last_used_on: last_used = self.last_used_on.replace( tzinfo=timezone.utc).timestamp() return { "id": str(self.id), "account_id": str(self.account_id), "access_token": self.access_token, "refresh_token": self.refresh_token, "client_id": self.client_id, "scope": self.scope, "token_type": self.token_type, "issued_at": self.issued_at, "expires_in": self.expires_in, "last_used": last_used, "revoked": self.revoked } @staticmethod def from_dict(token: Dict[str, Any]) -> 'APIAccessToken': """ Create or update a token from the source access token. On update, only the scope, revoked and dates properties are changed. Others are left as they are. """ access_token = APIAccessToken.get_by_token(token["access_token"]) if not access_token: access_token = APIAccessToken() access_token.id = shortuuid.decode(token["id"]) access_token.account_id = shortuuid.decode(token["account_id"]) access_token.access_token = token["access_token"] access_token.client_id = token["client_id"] access_token.name = token["name"] access_token.refresh_token = token["refresh_token"] access_token.scope = token["scope"] access_token.revoked = token["revoked"] access_token.issued_at = token["issued_at"] access_token.expires_in = token["expires_in"] return access_token @staticmethod def get_by_token(access_token: str) -> Optional['APIAccessToken']: return APIAccessToken.query.filter( APIAccessToken.access_token == access_token).first() @staticmethod def get_by_id_for_account(account_id: str, token_id: str) -> Optional['APIAccessToken']: return APIAccessToken.query.filter( APIAccessToken.account_id == account_id, APIAccessToken.id == token_id).first() @staticmethod def get_all_for_account(account_id: str) -> List['APIAccessToken']: return APIAccessToken.query.filter( APIAccessToken.account_id == account_id).all() @staticmethod def get_active_for_account(account_id: str) -> List['APIAccessToken']: non_revoked_tokens = APIAccessToken.query.filter( APIAccessToken.revoked == False, APIAccessToken.account_id == account_id).all() return [token for token in non_revoked_tokens if token.is_active()]
class RecommendationTag(db.Model): # type: ignore __bind_key__ = 'experiment_service' __tablename__ = "recommendation_tag" id = db.Column(db.Integer, primary_key=True) value = db.Column(db.String(), nullable=False, unique=True)
class Org(db.Model): # type: ignore __bind_key__ = 'dashboard_service' id = db.Column(UUIDType(binary=False), primary_key=True, default=uuid.uuid4) # only set when this is a personal org linked to a single account, # otherwise it's not set account_id = db.Column(UUIDType(binary=False), db.ForeignKey('user_account.id'), nullable=True) name = db.Column(db.String(), nullable=False, unique=True) name_lower = db.Column(db.String(), nullable=False, unique=True) kind = db.Column(db.Enum(OrgType), nullable=False, default=OrgType.personal) created_on = db.Column(db.DateTime(), server_default=func.now()) workspaces = db.relationship('Workspace', backref='org', cascade="all, delete-orphan") settings = db.Column(JSONB(), nullable=False, default=DEFAULT_ORG_SETTINGS) def to_dict(self, public_workspaces_only: bool = False): workspaces = [] for w in self.workspaces: if public_workspaces_only and w.kind != WorkspaceType.public: continue workspaces.append({"id": shortuuid.encode(w.id), "name": w.name}) return { "id": shortuuid.encode(self.id), "name": self.name, "settings": self.settings, "type": self.kind.value, "created_on": "{}Z".format(self.created_on.isoformat()), "workspaces": workspaces } def to_short_dict(self): return { "id": shortuuid.encode(self.id), "name": self.name, "created_on": "{}Z".format(self.created_on.isoformat()), "settings": self.settings, "type": self.kind.value } @staticmethod def get_next_available_name(suggested_name: str) -> str: """ Return the next available name prefixed by the given suggested name and suffixed by a number between 0 and 1000. If `suggested_name` is not used yet, return it as the available name """ while True: has_org = Org.query.filter(Org.name == suggested_name).first() if not has_org: return suggested_name suggested_name = "{}{}".format(suggested_name, secrets.randbelow(1000)) @staticmethod def get_by_id(org_id: Union[str, uuid.UUID]) -> 'Org': """ Lookup an organization by its identifier """ return Org.query.filter(Org.id == org_id).first() @staticmethod def find_by_name(org_name: str) -> 'Org': """ Lookup an organization by its name """ return Org.query.filter(Org.name_lower == org_name.lower()).first() def find_workspace_by_name(self, workspace_name: str) -> Optional[Workspace]: """ Lookup a workspace in the organization by its name """ w_name = workspace_name.lower() for workspace in self.workspaces: if workspace.name_lower == w_name: return workspace return None def is_member(self, account_id: Union[str, uuid.UUID]) -> bool: """ Return `True` when the given account is a member of the organization """ return OrgsMembers.query.filter( OrgsMembers.org_id == self.id, OrgsMembers.account_id == account_id).first() is not None def is_owner(self, account_id: Union[str, uuid.UUID]) -> bool: """ Return `True` when the given account is an owner of the organization """ return OrgsMembers.query.filter( OrgsMembers.org_id == self.id, OrgsMembers.is_owner == True, OrgsMembers.account_id == account_id).first() is not None def has_single_owner(self) -> bool: """ Return `True` if only one owner exists for this organization """ return OrgsMembers.query.filter( OrgsMembers.org_id == self.id, OrgsMembers.is_owner == True).count() == 1 def make_member(self, account_id: Union[str, uuid.UUID]): """ Turn an user as a member only of this organization. The user must already be part of this organization, this is mostly therefore useful when moving an owner down to simple member. """ membership = OrgsMembers.query.filter( OrgsMembers.org_id == self.id, OrgsMembers.account_id == account_id).first() if membership: membership.is_owner = False def make_owner(self, account_id: Union[str, uuid.UUID]): """ Turn an user as an owner of this organization. """ membership = OrgsMembers.query.filter( OrgsMembers.org_id == self.id, OrgsMembers.account_id == account_id).first() if membership: membership.is_owner = True def add_member(self, account_id: Union[str, uuid.UUID]) -> OrgsMembers: """ Add this user to the organization as a member """ membership = OrgsMembers(org_id=self.id, account_id=account_id) db.session.add(membership) return membership def remove_member(self, account_id: Union[str, uuid.UUID]): """ Remove this member from the organization """ OrgsMembers.query.filter( OrgsMembers.org_id == self.id, OrgsMembers.account_id == account_id).delete() def get_public_workspace_ids(self) -> List[uuid.UUID]: """ List all public workspaces in this organization and return their identifiers """ result = db.session.query(Workspace.id).filter( Workspace.org_id == self.id, Workspace.kind == WorkspaceType.public).all() if not result: return [] return result[0]
class Workspace(db.Model): # type: ignore __bind_key__ = 'dashboard_service' __table_args__ = (db.UniqueConstraint('name', 'org_id', name='workspace_org_uniq'), ) id = db.Column(UUIDType(binary=False), primary_key=True, default=uuid.uuid4) name = db.Column(db.String(), nullable=False) name_lower = db.Column(db.String(), nullable=False) kind = db.Column(db.Enum(WorkspaceType), nullable=False, default=WorkspaceType.personal) org_id = db.Column(UUIDType(binary=False), db.ForeignKey('org.id'), nullable=False) settings = db.Column(JSONB(), nullable=False, default=DEFAULT_WORKSPACE_SETTINGS) def to_dict(self): return { "id": shortuuid.encode(self.id), "name": self.name, "type": self.kind.value, "org": { "id": shortuuid.encode(self.org_id), "name": self.org.name, "type": self.org.kind.value }, "settings": self.settings } def to_short_dict(self): return { "id": shortuuid.encode(self.id), "name": self.name, "type": self.kind.value, "settings": self.settings } @staticmethod def find_by_name(workspace_name: str) -> 'Workspace': """ Get a workspace by its name """ return Workspace.query.filter( Workspace.name_lower == workspace_name.lower()).first() @staticmethod def get_by_id(workspace_id: Union[str, uuid.UUID]) -> 'Workspace': """ Get a workspace by its identifier """ return Workspace.query.filter(Workspace.id == workspace_id).first() def is_collaborator(self, account_id: Union[str, uuid.UUID]) -> bool: """ Return `True` when the given account is a collaborator to this workspace """ return WorkpacesMembers.query.filter( WorkpacesMembers.workspace_id == self.id, WorkpacesMembers.account_id == account_id).first() is not None def is_owner(self, account_id: Union[str, uuid.UUID]) -> bool: """ Return `True` when the given account is an owner of the workspace """ return WorkpacesMembers.query.filter( WorkpacesMembers.workspace_id == self.id, WorkpacesMembers.is_owner == True, WorkpacesMembers.account_id == account_id).first() is not None def has_single_owner(self) -> bool: """ Return `True` if only one owner exists for this workspace """ return WorkpacesMembers.query.filter( WorkpacesMembers.workspace_id == self.id, WorkpacesMembers.is_owner == True).count() == 1 def make_collaborator(self, account_id: Union[str, uuid.UUID]): """ Turn an user as a collaborator only of this workspace. """ membership = WorkpacesMembers.query.filter( WorkpacesMembers.workspace_id == self.id, WorkpacesMembers.account_id == account_id).first() if membership: membership.is_owner = False def make_owner(self, account_id: Union[str, uuid.UUID]): """ Turn an user as an owner of this workspace. """ membership = WorkpacesMembers.query.filter( WorkpacesMembers.workspace_id == self.id, WorkpacesMembers.account_id == account_id).first() if membership: membership.is_owner = True def add_collaborator(self, account_id: Union[str, uuid.UUID]) \ -> WorkpacesMembers: """ Add this user to the organization as a collaborator """ membership = WorkpacesMembers(workspace_id=self.id, account_id=account_id) db.session.add(membership) return membership def remove_collaborator(self, account_id: Union[str, uuid.UUID]): """ Remove this collaborator from the organization """ WorkpacesMembers.query.filter( WorkpacesMembers.workspace_id == self.id, WorkpacesMembers.account_id == account_id).delete()