Esempio n. 1
0
class UserAccount(db.Model):  # type: ignore
    __bind_key__ = 'dashboard_service'
    __tablename__ = 'user_account'
    id = db.Column(UUIDType(binary=False),
                   primary_key=True,
                   default=uuid.uuid4)
    joined_dt = db.Column(db.DateTime(), server_default=func.now())
    closed_dt = db.Column(db.DateTime())
    inactive_dt = db.Column(db.DateTime())
    is_closed = db.Column(db.Boolean, default=False)
    is_active = db.Column(db.Boolean, default=True)
    info = db.relationship('UserInfo',
                           backref='account',
                           uselist=False,
                           cascade="all, delete-orphan")
    privacy = db.relationship('UserPrivacy',
                              backref='account',
                              uselist=False,
                              cascade="all, delete-orphan")
    workspaces = db.relationship('Workspace',
                                 secondary="workspaces_members",
                                 lazy='subquery',
                                 backref=db.backref('accounts', lazy=True))
    orgs = db.relationship('Org',
                           secondary="orgs_members",
                           lazy='subquery',
                           backref=db.backref('accounts', lazy=True))
    # direct access to the unique personal org
    # but this org is also part of the many to many relationship
    personal_org = db.relationship('Org',
                                   backref='account',
                                   uselist=False,
                                   cascade="all, delete-orphan")

    def to_public_dict(self):
        return {
            "id": shortuuid.encode(self.id),
            "joined": "{}Z".format(self.joined_dt.isoformat()),
            "workspaces": [w.to_dict() for w in self.workspaces],
            "orgs": [o.to_dict() for o in self.orgs]
        }

    def to_short_dict(self):
        return {
            "id": shortuuid.encode(self.id),
            "joined": "{}Z".format(self.joined_dt.isoformat()),
            "org": {
                "name": self.personal_org.name
            },
            "profile": self.info.to_public_dict()
        }
Esempio n. 2
0
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
        }
Esempio n. 3
0
class Schedule(db.Model):  # type: ignore
    __bind_key__ = 'experiment_service'
    id = db.Column(UUIDType(binary=False),
                   primary_key=True,
                   default=uuid.uuid4)
    account_id = db.Column(UUIDType(binary=False), nullable=False, index=True)
    scheduled = db.Column(db.DateTime(), nullable=False)
    status = db.Column(db.Enum(ScheduleStatus),
                       nullable=False,
                       default=ScheduleStatus.pending)
    org_id = db.Column(UUIDType(binary=False), nullable=False, index=True)
    workspace_id = db.Column(UUIDType(binary=False),
                             nullable=False,
                             index=True)
    experiment_id = db.Column(UUIDType(binary=False),
                              nullable=False,
                              index=True)
    token_id = db.Column(UUIDType(binary=False), nullable=False)
    definition = db.Column(JSONB())
    info = db.Column(JSONB())

    def to_dict(self):
        return {
            "id": shortuuid.encode(self.id),
            "account_id": shortuuid.encode(self.account_id),
            "org_id": shortuuid.encode(self.org_id),
            "workspace_id": shortuuid.encode(self.workspace_id),
            "experiment_id": shortuuid.encode(self.experiment_id),
            "token_id": shortuuid.encode(self.token_id),
            "scheduled": "{}Z".format(self.scheduled.isoformat()),
            "definition": self.definition,
            "info": self.info
        }
Esempio n. 4
0
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
        }
Esempio n. 5
0
class UserPrivacy(db.Model):  # type: ignore
    __bind_key__ = 'dashboard_service'
    id = db.Column(UUIDType(binary=False),
                   primary_key=True,
                   default=uuid.uuid4)
    account_id = db.Column(UUIDType(binary=False),
                           db.ForeignKey('user_account.id'),
                           nullable=False)
    last_changed = db.Column(db.DateTime(), server_default=func.now())
    details = db.Column(JSONB())
Esempio n. 6
0
class Discovery(db.Model):  # type: ignore
    __bind_key__ = 'experiment_service'
    id = db.Column(UUIDType(binary=False),
                   primary_key=True,
                   default=uuid.uuid4)
    account_id = db.Column(UUIDType(binary=False), nullable=False, index=True)
    received_date = db.Column(db.DateTime(), server_default=func.now())
    workspace_id = db.Column(UUIDType(binary=False),
                             nullable=False,
                             index=True)
    payload = db.Column(JSONB())
Esempio n. 7
0
class Account(db.Model):  # type: ignore
    __bind_key__ = 'auth_service'
    __table_args__ = (db.UniqueConstraint('oauth_provider',
                                          'oauth_provider_sub',
                                          name='oauth_provider_sub_uniq'), )

    id = db.Column(UUIDType(binary=False),
                   primary_key=True,
                   unique=True,
                   default=uuid.uuid4)
    joined_on = db.Column(db.DateTime(timezone=True),
                          server_default=func.now(),
                          nullable=False)
    closed_since = db.Column(db.DateTime(timezone=True), nullable=True)
    inactive_since = db.Column(db.DateTime(timezone=True), nullable=True)
    is_closed = db.Column(db.Boolean, default=False)
    is_active = db.Column(db.Boolean, default=True)
    oauth_provider = db.Column(db.String, nullable=True)
    oauth_provider_sub = db.Column(db.String, nullable=True)

    access_tokens = db.relationship('AccessToken',
                                    backref='account',
                                    cascade="all, delete-orphan")
    client = db.relationship('Client',
                             backref='account',
                             uselist=False,
                             cascade="all, delete-orphan")
    local = db.relationship('LocalAccount',
                            backref='account',
                            uselist=False,
                            cascade="all, delete-orphan")

    def to_dict(self):
        inactive = closed = None
        if self.inactive_since:
            inactive = "{}Z".format(self.inactive_since.isoformat())
        if self.closed_since:
            closed = "{}Z".format(self.closed_since.isoformat())

        return {
            "id": str(self.id),
            "short_id": shortuuid.encode(self.id),
            "closed": self.is_closed,
            "active": self.is_active,
            "joined_on": "{}Z".format(self.joined_on.isoformat()),
            "inactive_since": inactive,
            "closed_since": closed,
            "client": self.client.to_dict() if self.client else None,
            "tokens": [t.to_dict() for t in self.access_tokens]
        }

    def turn_inactive(self):
        self.is_active = False
        self.inactive_since = datetime.utcnow()

    def turn_active(self):
        self.is_active = True
        self.inactive_since = None

    def close_account(self):
        self.is_closed = False
        self.closed_since = datetime.utcnow()
Esempio n. 8
0
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()]
Esempio n. 9
0
class Experiment(db.Model):  # type: ignore
    __bind_key__ = 'experiment_service'
    id = db.Column(UUIDType(binary=False),
                   primary_key=True,
                   default=uuid.uuid4)
    shared_ref = db.Column(UUIDType(binary=False), nullable=False, index=True)
    account_id = db.Column(UUIDType(binary=False), nullable=False, index=True)
    created_date = db.Column(db.DateTime(), server_default=func.now())
    updated_date = db.Column(db.DateTime(),
                             server_default=func.now(),
                             server_onupdate=func.now())
    suggested_experiment_id = db.Column(UUIDType(binary=False), index=True)
    org_id = db.Column(UUIDType(binary=False), nullable=False, index=True)
    workspace_id = db.Column(UUIDType(binary=False),
                             nullable=False,
                             index=True)
    executions = db.relationship('Execution',
                                 backref='experiment',
                                 cascade="all, delete-orphan")
    payload = db.Column(NestedMutable.as_mutable(JSONB), nullable=False)

    def to_dict(self, with_payload: bool = True):
        updated_date = None
        timestamp = self.created_date.timestamp()
        if self.updated_date:
            updated_date = "{}Z".format(self.updated_date.isoformat())
            timestamp = self.updated_date.timestamp()

        d = {
            "id": shortuuid.encode(self.id),
            "ref": shortuuid.encode(self.shared_ref),
            "created_date": "{}Z".format(self.created_date.isoformat()),
            "updated_date": updated_date,
            "timestamp": timestamp,
            "org": shortuuid.encode(self.org_id),
            "workspace": shortuuid.encode(self.workspace_id),
            "title": self.payload.get("title"),
            "description": self.payload.get("description")
        }

        if with_payload:
            d["payload"] = self.payload

        return d

    def to_public_dict(self, with_payload: bool = True):
        updated_date = None
        timestamp = self.created_date.timestamp()
        if self.updated_date:
            updated_date = "{}Z".format(self.updated_date.isoformat())
            timestamp = self.updated_date.timestamp()

        d = {
            "id": shortuuid.encode(self.id),
            "ref": shortuuid.encode(self.shared_ref),
            "created_date": "{}Z".format(self.created_date.isoformat()),
            "updated_date": updated_date,
            "timestamp": timestamp,
            "org": shortuuid.encode(self.org_id),
            "workspace": shortuuid.encode(self.workspace_id),
            "title": self.payload.get("title"),
            "description": self.payload.get("description"),
            "tags": [tag for tag in self.payload.get("tags", [])]
        }

        if with_payload:
            d["payload"] = self.payload

        return d

    @staticmethod
    def get_by_id(exp_id: Union[str, uuid.UUID]) -> Optional['Experiment']:
        if not exp_id:
            return None

        if isinstance(exp_id, str):
            try:
                exp_id = shortuuid.decode(exp_id)
            except ValueError:
                return None

        return Experiment.query.filter(Experiment.id == exp_id).first()

    def get_execution(self, timestamp: int) -> Optional['Execution']:
        return Execution.query.filter(
            Execution.experiment_id == self.id,
            Execution.timestamp == timestamp).first()
Esempio n. 10
0
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]
Esempio n. 11
0
class UserInfo(db.Model):  # type: ignore
    __bind_key__ = 'dashboard_service'
    id = db.Column(UUIDType(binary=False),
                   primary_key=True,
                   default=uuid.uuid4)
    account_id = db.Column(UUIDType(binary=False),
                           db.ForeignKey('user_account.id'),
                           nullable=False)
    last_updated = db.Column(db.DateTime(),
                             server_default=func.now(),
                             onupdate=func.current_timestamp())
    verified_email = db.Column(db.Boolean(), default=False)

    # values for search purpose mostly
    username = db.Column(db.String, index=True, nullable=True)
    fullname = db.Column(db.String, index=True, nullable=True)

    details = db.Column(EncryptedType(db.String, get_user_info_secret_key,
                                      AesEngine, 'pkcs5'),
                        nullable=False)

    @staticmethod
    def get_for_account(account_id: Union[str, uuid.UUID]) -> 'UserInfo':
        """
        Lookup user info for the given user account
        """
        return UserInfo.query.filter(UserInfo.account_id == account_id).first()

    @property
    def profile(self) -> Dict[str, Any]:
        """
        The user's profile
        """
        return json.loads(self.details)

    @profile.setter
    def profile(self, p: Dict[str, Any]):
        """
        Set the user's profile from the given payload

        The payload is serialized to JSON and stored in the `details`
        property
        """
        self.details = json.dumps(p)

    def to_dict(self):
        return {
            "id": shortuuid.encode(self.id),
            "account": shortuuid.encode(self.account.id),
            "profile": self.profile
        }

    def to_public_dict(self):
        p = self.profile

        return {
            "id": shortuuid.encode(self.id),
            "username": p.get("preferred_username"),
            "name": p.get("name"),
            "picture": p.get("picture")
        }