class Permission(db.Model, IdModel, SoftDeleteModel): """A set of rights granted to a role on a resource.""" __tablename__ = 'permission' COLLECTION = 'collection' SOURCE = 'source' RESOURCE_TYPES = [COLLECTION, SOURCE] id = db.Column(db.Integer, primary_key=True) role_id = db.Column(db.Integer, db.ForeignKey('role.id'), index=True) read = db.Column(db.Boolean, default=False) write = db.Column(db.Boolean, default=False) resource_id = db.Column(db.Integer, nullable=False) resource_type = db.Column(db.Enum(*RESOURCE_TYPES, name='permission_type'), nullable=False) @classmethod def grant_foreign(cls, resource, foreign_id, read, write): from aleph.model import Source, Collection, Role role = Role.by_foreign_id(foreign_id) if role is None: return if isinstance(resource, Source): cls.grant_resource(cls.SOURCE, resource.id, role, read, write) if isinstance(resource, Collection): cls.grant_resource(cls.COLLECTION, resource.id, role, read, write) @classmethod def grant_resource(cls, resource_type, resource_id, role, read, write): q = cls.all() q = q.filter(Permission.role_id == role.id) q = q.filter(Permission.resource_type == resource_type) q = q.filter(Permission.resource_id == resource_id) permission = q.first() if permission is None: permission = Permission() permission.role_id = role.id permission.resource_type = resource_type permission.resource_id = resource_id permission.read = read permission.write = write db.session.add(permission) return permission def to_dict(self): return { # 'id': self.id, 'role': self.role_id, 'read': self.read, 'write': self.write, 'resource_id': self.resource_id, 'resource_type': self.resource_type }
class Role(db.Model, IdModel, SoftDeleteModel): """A user, group or other access control subject.""" __tablename__ = 'role' USER = '******' GROUP = 'group' SYSTEM = 'system' TYPES = [USER, GROUP, SYSTEM] SYSTEM_GUEST = 'guest' SYSTEM_USER = '******' #: Generates URL-safe signatures for invitations. SIGNATURE = URLSafeTimedSerializer(settings.SECRET_KEY) #: Signature maximum age, defaults to 1 day SIGNATURE_MAX_AGE = 60 * 60 * 24 foreign_id = db.Column(db.Unicode(2048), nullable=False, unique=True) name = db.Column(db.Unicode, nullable=False) email = db.Column(db.Unicode, nullable=True) type = db.Column(db.Enum(*TYPES, name='role_type'), nullable=False) api_key = db.Column(db.Unicode, nullable=True) is_admin = db.Column(db.Boolean, nullable=False, default=False) is_muted = db.Column(db.Boolean, nullable=False, default=False) is_tester = db.Column(db.Boolean, nullable=False, default=False) is_blocked = db.Column(db.Boolean, nullable=False, default=False) password_digest = db.Column(db.Unicode, nullable=True) password = None reset_token = db.Column(db.Unicode, nullable=True) locale = db.Column(db.Unicode, nullable=True) permissions = db.relationship('Permission', backref='role') @property def has_password(self): return self.password_digest is not None @property def is_public(self): return self.id in self.public_roles() @property def is_alertable(self): if self.email is None: return False if self.is_muted is True: return False # TODO: ignore people that have not logged in for a certain time? return True @property def label(self): return anonymize_email(self.name, self.email) def update(self, data): self.name = data.get('name', self.name) self.is_muted = data.get('is_muted', self.is_muted) self.is_tester = data.get('is_tester', self.is_tester) if data.get('password'): self.set_password(data.get('password')) self.locale = stringify(data.get('locale', self.locale)) self.updated_at = datetime.utcnow() def clear_roles(self): """Removes any existing roles from group membership.""" self.roles = [] self.updated_at = datetime.utcnow() db.session.add(self) db.session.flush() def add_role(self, role): """Adds an existing role as a membership of a group.""" self.roles.append(role) db.session.add(role) db.session.add(self) self.updated_at = datetime.utcnow() def to_dict(self): data = self.to_dict_dates() data.update({ 'id': stringify(self.id), 'type': self.type, 'name': self.name, 'label': self.label, 'email': self.email, 'locale': self.locale, 'api_key': self.api_key, 'is_admin': self.is_admin, 'is_muted': self.is_muted, 'is_tester': self.is_tester, 'has_password': self.has_password, # 'notified_at': self.notified_at }) return data @classmethod def by_foreign_id(cls, foreign_id): if foreign_id is not None: return cls.all().filter_by(foreign_id=foreign_id).first() @classmethod def by_email(cls, email): if email is None: return None q = cls.all() q = q.filter(func.lower(cls.email) == email.lower()) return q.first() @classmethod def by_api_key(cls, api_key): if api_key is not None: return cls.all().filter_by(api_key=api_key).first() @classmethod def load_or_create(cls, foreign_id, type, name, email=None, is_admin=None): role = cls.by_foreign_id(foreign_id) if role is None: role = cls() role.foreign_id = foreign_id role.name = name or email role.type = type role.is_admin = False role.is_muted = False role.is_tester = False role.is_blocked = False role.notified_at = datetime.utcnow() if role.api_key is None: role.api_key = make_textid() if email is not None: role.email = email if is_admin is not None: role.is_admin = is_admin # see: https://github.com/alephdata/aleph/issues/111 auto_admins = [a.lower() for a in settings.ADMINS] if email is not None and email.lower() in auto_admins: role.is_admin = True db.session.add(role) db.session.flush() return role @classmethod def load_cli_user(cls): return cls.load_or_create(foreign_id=settings.SYSTEM_USER, name='Aleph', type=cls.USER, is_admin=True) @classmethod def load_id(cls, foreign_id): """Load a role and return the ID.""" if not hasattr(settings, '_roles'): settings._roles = {} if foreign_id not in settings._roles: role_id = cls.all_ids().filter_by(foreign_id=foreign_id).first() if role_id is not None: settings._roles[foreign_id] = role_id[0] return settings._roles.get(foreign_id) @classmethod def public_roles(cls): """Roles which make a collection to be considered public.""" return set([ cls.load_id(cls.SYSTEM_USER), cls.load_id(cls.SYSTEM_GUEST), ]) @classmethod def by_prefix(cls, prefix, exclude=[]): """Load a list of roles matching a name, email address, or foreign_id. :param str pattern: Pattern to match. """ query = prefix.replace('%', ' ').replace('_', ' ') query = '%%%s%%' % query q = cls.all() q = q.filter(Role.type == Role.USER) if len(exclude): q = q.filter(not_(Role.id.in_(exclude))) q = q.filter( or_( func.lower(cls.email) == prefix.lower(), cls.name.ilike(query))) q = q.order_by(Role.id.asc()) return q @classmethod def all_groups(cls, authz): q = cls.all() q = q.filter(Role.type == Role.GROUP) q = q.order_by(Role.name.asc()) q = q.order_by(Role.foreign_id.asc()) if not authz.is_admin: q = q.filter(Role.id.in_(authz.roles)) return q @classmethod def all_users(cls): return cls.all().filter(Role.type == Role.USER) @classmethod def all_system(cls): return cls.all().filter(Role.type == Role.SYSTEM) def set_password(self, secret): """Hashes and sets the role password. :param str secret: The password to be set. """ self.password_digest = generate_password_hash(secret) def check_password(self, secret): """Checks the password if it matches the role password hash. :param str secret: The password to be checked. :rtype: bool """ digest = self.password_digest or '' return check_password_hash(digest, secret) def __repr__(self): return '<Role(%r,%r)>' % (self.id, self.foreign_id)
class EntitySetItem(db.Model, SoftDeleteModel): __tablename__ = "entityset_item" id = db.Column(db.Integer, primary_key=True) entityset_id = db.Column(db.String(ENTITY_ID_LEN), db.ForeignKey("entityset.id"), index=True) entity_id = db.Column(db.String(ENTITY_ID_LEN), index=True) collection_id = db.Column(db.Integer, db.ForeignKey("collection.id"), index=True) compared_to_entity_id = db.Column(db.String(ENTITY_ID_LEN)) added_by_id = db.Column(db.Integer, db.ForeignKey("role.id")) judgement = db.Column(db.Enum(Judgement)) entityset = db.relationship(EntitySet) collection = db.relationship(Collection) added_by = db.relationship(Role) @classmethod def by_entity_id(cls, entityset, entity_id): q = cls.all() q = q.filter(cls.entityset_id == entityset.id) q = q.filter(cls.entity_id == entity_id) q = q.order_by(cls.created_at.desc()) return q.first() @classmethod def save(cls, entityset, entity_id, judgement=None, collection_id=None, **data): if judgement is None: judgement = Judgement.POSITIVE else: judgement = Judgement(judgement) existing = cls.by_entity_id(entityset, entity_id) if existing is not None: if existing.judgement == judgement: return existing existing.delete() if judgement == Judgement.NO_JUDGEMENT: return item = cls( entityset_id=entityset.id, entity_id=entity_id, judgement=judgement, compared_to_entity_id=data.get("compared_to_entity_id"), collection_id=collection_id or entityset.collection_id, added_by_id=data.get("added_by_id"), ) db.session.add(item) return item @classmethod def delete_by_collection(cls, collection_id): pq = db.session.query(cls) pq = pq.filter(cls.collection_id == collection_id) pq.delete(synchronize_session=False) pq = db.session.query(cls) pq = pq.filter(EntitySet.collection_id == collection_id) pq = pq.filter(EntitySet.id == cls.entityset_id) pq.delete(synchronize_session=False) @classmethod def delete_by_entity(cls, entity_id): pq = db.session.query(cls) pq = pq.filter(cls.entity_id == entity_id) pq.delete(synchronize_session=False) def to_dict(self): data = self.to_dict_dates() data.update({ "entityset_id": self.entityset_id, "entity_id": self.entity_id, "collection_id": self.collection_id, "added_by_id": self.added_by_id, "compared_to_entity_id": self.compared_to_entity_id, }) if self.judgement: data["judgement"] = self.judgement.value return data def __repr__(self): return "<EntitySetItem(%r, %r)>" % (self.entityset_id, self.entity_id)
class Role(db.Model, IdModel, SoftDeleteModel): """A user, group or other access control subject.""" __tablename__ = 'role' USER = '******' GROUP = 'group' SYSTEM = 'system' TYPES = [USER, GROUP, SYSTEM] SYSTEM_GUEST = 'guest' SYSTEM_USER = '******' #: Generates URL-safe signatures for invitations. SIGNATURE = URLSafeTimedSerializer(settings.SECRET_KEY) #: Signature maximum age, defaults to 1 day SIGNATURE_MAX_AGE = 60 * 60 * 24 #: Password minimum length PASSWORD_MIN_LENGTH = 6 foreign_id = db.Column(db.Unicode(2048), nullable=False, unique=True) name = db.Column(db.Unicode, nullable=False) email = db.Column(db.Unicode, nullable=True) api_key = db.Column(db.Unicode, nullable=True) is_admin = db.Column(db.Boolean, nullable=False, default=False) type = db.Column(db.Enum(*TYPES, name='role_type'), nullable=False) password_digest = db.Column(db.Unicode, nullable=True) password = None reset_token = db.Column(db.Unicode, nullable=True) permissions = db.relationship('Permission', backref='role') @property def has_password(self): return self.password_digest is not None def update(self, data): self.name = data.get('name', self.name) if data.get('password'): self.set_password(data.get('password')) def clear_roles(self): """Removes any existing roles from group membership.""" self.roles = [] db.session.add(self) def add_role(self, role): """Adds an existing role as a membership of a group.""" self.roles.append(role) db.session.add(role) db.session.add(self) @classmethod def notifiable(cls): return cls.all_ids().filter(cls.email != None) # noqa @classmethod def by_foreign_id(cls, foreign_id): if foreign_id is not None: return cls.all().filter_by(foreign_id=foreign_id).first() @classmethod def by_email(cls, email): if email: return cls.all().filter_by(email=email) @classmethod def by_api_key(cls, api_key): if api_key is not None: return cls.all().filter_by(api_key=api_key).first() @classmethod def load_or_create(cls, foreign_id, type, name, email=None, is_admin=None): role = cls.by_foreign_id(foreign_id) if role is None: role = cls() role.foreign_id = foreign_id role.name = name role.type = type role.is_admin = False if role.api_key is None: role.api_key = make_textid() role.email = email if is_admin is not None: role.is_admin = is_admin # see: https://github.com/alephdata/aleph/issues/111 auto_admins = [a.lower() for a in settings.ADMINS] if email is not None and email.lower() in auto_admins: role.is_admin = True db.session.add(role) db.session.flush() return role @classmethod def load_id(cls, foreign_id, type=None, name=None): """Load a role and return the ID. If type is given and no role is found, a new role will be created. """ if not hasattr(current_app, '_authz_roles'): current_app._authz_roles = {} if foreign_id not in current_app._authz_roles: role = cls.by_foreign_id(foreign_id) if role is None: if type is None: return name = name or foreign_id role = cls.load_or_create(foreign_id, type, name) current_app._authz_roles[foreign_id] = role.id return current_app._authz_roles[foreign_id] @classmethod def public_roles(cls): """Roles which make a collection to be considered public.""" return set([ cls.load_id(cls.SYSTEM_USER), cls.load_id(cls.SYSTEM_GUEST), ]) @classmethod def by_prefix(cls, prefix): """Load a list of roles matching a name, email address, or foreign_id. :param str pattern: Pattern to match. """ q = cls.all() q = q.filter(Role.type == Role.USER) q = q.filter( or_(cls.foreign_id.ilike('%' + prefix + '%'), cls.email.ilike('%' + prefix + '%'), cls.name.ilike('%' + prefix + '%'))) return q @classmethod def all_groups(cls): return cls.all().filter(Role.type != Role.USER) def set_password(self, secret): """Hashes and sets the role password. :param str secret: The password to be set. """ self.password_digest = generate_password_hash(secret) def check_password(self, secret): """Checks the password if it matches the role password hash. :param str secret: The password to be checked. :rtype: bool """ return check_password_hash(self.password_digest or '', secret) def __repr__(self): return '<Role(%r,%r)>' % (self.id, self.foreign_id)
class Role(db.Model, IdModel, SoftDeleteModel): """A user, group or other access control subject.""" _schema = 'role.json#' __tablename__ = 'role' USER = '******' GROUP = 'group' SYSTEM = 'system' TYPES = [USER, GROUP, SYSTEM] SYSTEM_GUEST = 'guest' SYSTEM_USER = '******' foreign_id = db.Column(db.Unicode(2048), nullable=False, unique=True) name = db.Column(db.Unicode, nullable=False) email = db.Column(db.Unicode, nullable=True) api_key = db.Column(db.Unicode, nullable=True) is_admin = db.Column(db.Boolean, nullable=False, default=False) type = db.Column(db.Enum(*TYPES, name='role_type'), nullable=False) permissions = db.relationship("Permission", backref="role") def update(self, data): validate(data, self._schema) self.name = data.get('name', self.name) self.email = data.get('email', self.email) def clear_roles(self): self.roles = [] db.session.add(self) def add_role(self, role): self.roles.append(role) db.session.add(role) db.session.add(self) @classmethod def notifiable(cls): return cls.all_ids().filter(cls.email != None) # noqa @classmethod def by_foreign_id(cls, foreign_id): if foreign_id is not None: return cls.all().filter_by(foreign_id=foreign_id).first() @classmethod def by_api_key(cls, api_key): if api_key is not None: return cls.all().filter_by(api_key=api_key).first() @classmethod def load_or_create(cls, foreign_id, type, name, email=None, is_admin=None): role = cls.by_foreign_id(foreign_id) if role is None: role = cls() role.foreign_id = foreign_id role.name = name role.type = type role.is_admin = False if role.api_key is None: role.api_key = uuid4().hex role.email = email if is_admin is not None: role.is_admin = is_admin # see: https://github.com/pudo/aleph/issues/111 auto_admins = get_config('AUTHZ_ADMINS') or '' auto_admins = [a.lower() for a in auto_admins.split(',')] if email is not None and email.lower() in auto_admins: role.is_admin = True db.session.add(role) db.session.flush() return role @classmethod def load_id(cls, foreign_id, type=None, name=None): """Load a role and return the ID. If type is given and no role is found, a new role will be created. """ if not hasattr(current_app, '_authz_roles'): current_app._authz_roles = {} if foreign_id not in current_app._authz_roles: role = cls.by_foreign_id(foreign_id) if role is None: if type is None: return name = name or foreign_id role = cls.load_or_create(foreign_id, type, name) current_app._authz_roles[foreign_id] = role.id return current_app._authz_roles[foreign_id] def __repr__(self): return '<Role(%r,%r)>' % (self.id, self.foreign_id) def __unicode__(self): return self.name def to_dict(self): data = super(Role, self).to_dict() data.update({ 'api_url': url_for('roles_api.view', id=self.id), 'foreign_id': self.foreign_id, 'is_admin': self.is_admin, 'email': self.email, 'name': self.name, 'type': self.type }) return data
class Role(db.Model, IdModel, SoftDeleteModel): """A user, group or other access control subject.""" _schema = 'role.json#' __tablename__ = 'role' USER = '******' GROUP = 'group' SYSTEM = 'system' TYPES = [USER, GROUP, SYSTEM] SYSTEM_GUEST = 'guest' SYSTEM_USER = '******' #: Generates URL-safe signatures for invitations. SIGNATURE_SERIALIZER = URLSafeTimedSerializer(secret_key) #: Signature maximum age, defaults to 1 day SIGNATURE_MAX_AGE = 60 * 60 * 24 #: Password minimum length PASSWORD_MIN_LENGTH = 6 foreign_id = db.Column(db.Unicode(2048), nullable=False, unique=True) name = db.Column(db.Unicode, nullable=False) email = db.Column(db.Unicode, nullable=True) api_key = db.Column(db.Unicode, nullable=True) is_admin = db.Column(db.Boolean, nullable=False, default=False) type = db.Column(db.Enum(*TYPES, name='role_type'), nullable=False) password_digest = db.Column(db.Unicode, nullable=True) reset_token = db.Column(db.Unicode, nullable=True) permissions = db.relationship('Permission', backref='role') def update(self, data): validate(data, self._schema) self.name = data.get('name', self.name) self.email = data.get('email', self.email) def clear_roles(self): """Removes any existing roles from group membership.""" self.roles = [] db.session.add(self) def add_role(self, role): """Adds an existing role as a membership of a group.""" self.roles.append(role) db.session.add(role) db.session.add(self) @classmethod def notifiable(cls): return cls.all_ids().filter(cls.email != None) # noqa @classmethod def by_foreign_id(cls, foreign_id): if foreign_id is not None: return cls.all().filter_by(foreign_id=foreign_id).first() @classmethod def by_email(cls, email): if email: return cls.all().filter_by(email=email) @classmethod def by_api_key(cls, api_key): if api_key is not None: return cls.all().filter_by(api_key=api_key).first() @classmethod def load_or_create(cls, foreign_id, type, name, email=None, is_admin=None): role = cls.by_foreign_id(foreign_id) if role is None: role = cls() role.foreign_id = foreign_id role.name = name role.type = type role.is_admin = False if role.api_key is None: role.api_key = make_textid() role.email = email if is_admin is not None: role.is_admin = is_admin # see: https://github.com/pudo/aleph/issues/111 auto_admins = [a.lower() for a in get_config('AUTHZ_ADMINS')] if email is not None and email.lower() in auto_admins: role.is_admin = True db.session.add(role) db.session.flush() return role @classmethod def load_id(cls, foreign_id, type=None, name=None): """Load a role and return the ID. If type is given and no role is found, a new role will be created. """ if not hasattr(current_app, '_authz_roles'): current_app._authz_roles = {} if foreign_id not in current_app._authz_roles: role = cls.by_foreign_id(foreign_id) if role is None: if type is None: return name = name or foreign_id role = cls.load_or_create(foreign_id, type, name) current_app._authz_roles[foreign_id] = role.id return current_app._authz_roles[foreign_id] def set_password(self, secret): """Hashes and sets the role password. :param str secret: The password to be set. """ self.password_digest = generate_password_hash(secret) def check_password(self, secret): """Checks the password if it matches the role password hash. :param str secret: The password to be checked. :rtype: bool """ return check_password_hash(self.password_digest or '', secret) def __repr__(self): return '<Role(%r,%r)>' % (self.id, self.foreign_id) def __unicode__(self): return self.name def to_dict(self): data = super(Role, self).to_dict() data.update({ 'api_url': url_for('roles_api.view', id=self.id), 'foreign_id': self.foreign_id, 'is_admin': self.is_admin, 'email': self.email, 'name': self.name, 'type': self.type }) return data
class Role(db.Model, IdModel, SoftDeleteModel, SchemaModel): """A user, group or other access control subject.""" _schema = 'role.json#' __tablename__ = 'role' USER = '******' GROUP = 'group' SYSTEM = 'system' TYPES = [USER, GROUP, SYSTEM] SYSTEM_GUEST = 'guest' SYSTEM_USER = '******' foreign_id = db.Column(db.Unicode(2048), nullable=False, unique=True) name = db.Column(db.Unicode, nullable=False) email = db.Column(db.Unicode, nullable=True) api_key = db.Column(db.Unicode, nullable=True) is_admin = db.Column(db.Boolean, nullable=False, default=False) type = db.Column(db.Enum(*TYPES, name='role_type'), nullable=False) permissions = db.relationship("Permission", backref="role") def update(self, data): self.schema_update(data) @classmethod def notifiable(cls): return cls.all_ids().filter(cls.email != None) # noqa @classmethod def by_foreign_id(cls, foreign_id): if foreign_id is not None: return cls.all().filter_by(foreign_id=foreign_id).first() @classmethod def by_api_key(cls, api_key): if api_key is not None: return cls.all().filter_by(api_key=api_key).first() @classmethod def load_or_create(cls, foreign_id, type, name, email=None, is_admin=None): role = cls.by_foreign_id(foreign_id) if role is None: role = cls() role.foreign_id = foreign_id role.type = type role.is_admin = False if role.api_key is None: role.api_key = uuid4().hex role.name = name role.email = email if is_admin is not None: role.is_admin = is_admin db.session.add(role) db.session.flush() return role @classmethod def system(cls, foreign_id): if not hasattr(current_app, '_authz_roles'): current_app._authz_roles = {} if foreign_id not in current_app._authz_roles: role = cls.by_foreign_id(foreign_id) if role is None: return current_app._authz_roles[foreign_id] = role.id return current_app._authz_roles[foreign_id] def __repr__(self): return '<Role(%r,%r)>' % (self.id, self.foreign_id) def __unicode__(self): return self.name def to_dict(self): data = super(Role, self).to_dict() data['api_url'] = url_for('roles_api.view', id=self.id) data['foreign_id'] = self.foreign_id data['is_admin'] = self.is_admin data['email'] = self.email data['type'] = self.type return data
class EntitySetItem(db.Model, SoftDeleteModel): __tablename__ = "entityset_item" id = db.Column(db.Integer, primary_key=True) entityset_id = db.Column(db.String(ENTITY_ID_LEN), db.ForeignKey("entityset.id"), index=True) entity_id = db.Column(db.String(ENTITY_ID_LEN), index=True) collection_id = db.Column(db.Integer, db.ForeignKey("collection.id"), index=True) compared_to_entity_id = db.Column(db.String(ENTITY_ID_LEN)) added_by_id = db.Column(db.Integer, db.ForeignKey("role.id")) judgement = db.Column(db.Enum(Judgement)) entityset = db.relationship(EntitySet) collection = db.relationship(Collection) added_by = db.relationship(Role) @classmethod def by_entity_id(cls, entityset, entity_id): q = cls.all() q = q.filter(cls.entityset_id == entityset.id) q = q.filter(cls.entity_id == entity_id) q = q.order_by(cls.created_at.desc()) return q.first() @classmethod def save(cls, entityset, entity_id, judgement=None, collection_id=None, **data): if judgement is None: judgement = Judgement.POSITIVE else: judgement = Judgement(judgement) # Special case for profiles: an entity can only be part of one profile # in any given collection. An attempt to add it to a second profile must # result in a merging of both profiles. if entityset.type == EntitySet.PROFILE and judgement == Judgement.POSITIVE: for existing in EntitySet.by_entity_id( entity_id, collection_ids=[entityset.collection_id], judgements=[Judgement.POSITIVE], types=[EntitySet.PROFILE], ): entityset = entityset.merge(existing, entity_id) # Check if there is an existing relationship between the entity and the # entity set. If the judgement matches, no-op - otherwise delete the # previous relationship item. existing = cls.by_entity_id(entityset, entity_id) if existing is not None: if existing.judgement == judgement: return existing existing.delete() # There is no judgement information to be stored, so let's keep that out # of the database: if judgement == Judgement.NO_JUDGEMENT: return item = cls( entityset=entityset, entity_id=entity_id, judgement=judgement, compared_to_entity_id=data.get("compared_to_entity_id"), collection_id=collection_id or entityset.collection_id, added_by_id=data.get("added_by_id"), ) db.session.add(item) return item @classmethod def delete_by_collection(cls, collection_id): pq = db.session.query(cls) pq = pq.filter(cls.collection_id == collection_id) pq.delete(synchronize_session=False) pq = db.session.query(cls) pq = pq.filter(EntitySet.collection_id == collection_id) pq = pq.filter(EntitySet.id == cls.entityset_id) pq.delete(synchronize_session=False) @classmethod def delete_by_entity(cls, entity_id): pq = db.session.query(cls) pq = pq.filter(cls.entity_id == entity_id) pq.delete(synchronize_session=False) def to_dict(self, entityset=None): data = { "id": "$".join((self.entityset_id, self.entity_id)), "entity_id": self.entity_id, "collection_id": self.collection_id, "added_by_id": self.added_by_id, "judgement": self.judgement, "compared_to_entity_id": self.compared_to_entity_id, } entityset = entityset or self.entityset data["entityset_collection_id"] = entityset.collection_id data["entityset_id"] = entityset.id data.update(self.to_dict_dates()) return data def __repr__(self): return "<EntitySetItem(%r, %r)>" % (self.entityset_id, self.entity_id)
class Role(db.Model, IdModel, SoftDeleteModel, SchemaModel): """A user, group or other access control subject.""" _schema = 'role.json#' __tablename__ = 'role' USER = '******' GROUP = 'group' SYSTEM = 'system' TYPES = [USER, GROUP, SYSTEM] SYSTEM_GUEST = 'guest' SYSTEM_USER = '******' foreign_id = db.Column(db.Unicode(2048), nullable=False, unique=True) name = db.Column(db.Unicode, nullable=False) email = db.Column(db.Unicode, nullable=False, unique=True) api_key = db.Column(db.Unicode, nullable=True) is_admin = db.Column(db.Boolean, nullable=False, default=False) type = db.Column(db.Enum(*TYPES, name='role_type'), nullable=False) permissions = db.relationship("Permission", backref="role") confirmed_at = db.Column(db.DateTime()) password = db.Column(db.String(255), nullable=False, server_default='') reset_password_token = db.Column(db.String(100), nullable=False, server_default='') mailing_list = db.Column(db.Boolean, default=False) def update(self, data): print('---role update---') print(data) self.schema_update(data) def check_pw(self, pw): return pwd_context.verify(pw, self.password) @classmethod def notifiable(cls): return cls.all_ids().filter(cls.email != None) # noqa @classmethod def by_foreign_id(cls, foreign_id): if foreign_id is not None: return cls.all().filter_by(foreign_id=foreign_id).first() @classmethod def by_api_key(cls, api_key): if api_key is not None: return cls.all().filter_by(api_key=api_key).first() @classmethod def load_or_create(cls, foreign_id, type, name, email=None, is_admin=None, mailing_list=False): role = cls.by_foreign_id(foreign_id) if role is None: role = cls() role.foreign_id = foreign_id role.type = type role.is_admin = False role.mailing_list = mailing_list if role.api_key is None: role.api_key = uuid4().hex role.name = name role.email = email if is_admin is not None: role.is_admin = is_admin db.session.add(role) db.session.flush() return role @classmethod def system(cls, foreign_id): if not hasattr(current_app, '_authz_roles'): current_app._authz_roles = {} if foreign_id not in current_app._authz_roles: role = cls.by_foreign_id(foreign_id) if role is None: return current_app._authz_roles[foreign_id] = role.id return current_app._authz_roles[foreign_id] @classmethod def by_email(cls, email): q = db.session.query(cls).filter_by(email=email) return q.first() @classmethod def create_by_email(cls, email, pw, mailing_list=None): src = cls( email=email, name=email, # XXX ask for this in the registration form password=pwd_context.encrypt(pw), type=cls.USER, mailing_list=mailing_list, foreign_id="openoil:%s" % email) # XXX do we really need this? db.session.add(src) db.session.commit() send_welcome_mail(src) return src @classmethod def create(cls, data, user=None): src = cls() data = SourceCreateForm().deserialize(data) src.slug = data.get('slug') src.crawler = data.get('crawler') src.update_data(data, user) db.session.add(src) return src def __repr__(self): return '<Role(%r,%r)>' % (self.id, self.foreign_id) def __unicode__(self): return self.name def to_dict(self): data = super(Role, self).to_dict() data['api_url'] = url_for('roles_api.view', id=self.id) data['foreign_id'] = self.foreign_id data['is_admin'] = self.is_admin data['email'] = self.email data['mailing_list'] = self.mailing_list data['type'] = self.type return data
class Role(db.Model, IdModel, SoftDeleteModel): """A user, group or other access control subject.""" __tablename__ = "role" USER = "******" GROUP = "group" SYSTEM = "system" TYPES = [USER, GROUP, SYSTEM] SYSTEM_GUEST = "guest" SYSTEM_USER = "******" #: Generates URL-safe signatures for invitations. SIGNATURE = URLSafeTimedSerializer(settings.SECRET_KEY) #: Signature maximum age, defaults to 1 day SIGNATURE_MAX_AGE = 60 * 60 * 24 foreign_id = db.Column(db.Unicode(2048), nullable=False, unique=True) name = db.Column(db.Unicode, nullable=False) email = db.Column(db.Unicode, nullable=True) type = db.Column(db.Enum(*TYPES, name="role_type"), nullable=False) api_key = db.Column(db.Unicode, nullable=True) is_admin = db.Column(db.Boolean, nullable=False, default=False) is_muted = db.Column(db.Boolean, nullable=False, default=False) is_tester = db.Column(db.Boolean, nullable=False, default=False) is_blocked = db.Column(db.Boolean, nullable=False, default=False) password_digest = db.Column(db.Unicode, nullable=True) password = None reset_token = db.Column(db.Unicode, nullable=True) locale = db.Column(db.Unicode, nullable=True) permissions = db.relationship("Permission", backref="role") @property def has_password(self): return self.password_digest is not None @property def is_public(self): return self.id in self.public_roles() @property def is_actor(self): if self.type != self.USER: return False if self.is_blocked or self.deleted_at is not None: return False return True @property def is_alertable(self): if self.email is None or not self.is_actor: return False if self.is_muted: return False if self.updated_at < (datetime.utcnow() - settings.ROLE_INACTIVE): # Disable sending notifications to roles that haven't been # logged in for a set amount of time. return False return True @property def label(self): return anonymize_email(self.name, self.email) def update(self, data): self.name = data.get("name", self.name) if self.name is None: self.name = self.email or self.foreign_id self.is_muted = data.get("is_muted", self.is_muted) self.is_tester = data.get("is_tester", self.is_tester) if data.get("password"): self.set_password(data.get("password")) self.locale = stringify(data.get("locale", self.locale)) self.touch() def touch(self): self.updated_at = datetime.utcnow() db.session.add(self) def clear_roles(self): """Removes any existing roles from group membership.""" self.roles = [] self.touch() db.session.flush() def add_role(self, role): """Adds an existing role as a membership of a group.""" self.roles.append(role) db.session.add(role) db.session.add(self) self.updated_at = datetime.utcnow() def set_password(self, secret): """Hashes and sets the role password. :param str secret: The password to be set. """ self.password_digest = generate_password_hash(secret) def check_password(self, secret): """Checks the password if it matches the role password hash. :param str secret: The password to be checked. :rtype: bool """ digest = self.password_digest or "" return check_password_hash(digest, secret) def to_dict(self): data = self.to_dict_dates() data.update({ "id": stringify(self.id), "type": self.type, "name": self.name, "label": self.label, "email": self.email, "locale": self.locale, "api_key": self.api_key, "is_admin": self.is_admin, "is_muted": self.is_muted, "is_tester": self.is_tester, "has_password": self.has_password, # 'notified_at': self.notified_at }) return data @classmethod def by_foreign_id(cls, foreign_id, deleted=False): if foreign_id is not None: q = cls.all(deleted=deleted) q = q.filter(cls.foreign_id == foreign_id) return q.first() @classmethod def by_email(cls, email): if email is None: return None q = cls.all() q = q.filter(func.lower(cls.email) == email.lower()) q = q.filter(cls.type == cls.USER) return q.first() @classmethod def by_api_key(cls, api_key): if api_key is None: return None q = cls.all() q = q.filter_by(api_key=api_key) q = q.filter(cls.type == cls.USER) q = q.filter(cls.is_blocked == False) # noqa return q.first() @classmethod def load_or_create(cls, foreign_id, type_, name, email=None, is_admin=None): role = cls.by_foreign_id(foreign_id) if role is None: role = cls() role.foreign_id = foreign_id role.name = name or email role.type = type_ role.is_admin = False role.is_muted = False role.is_tester = False role.is_blocked = False role.notified_at = datetime.utcnow() if role.api_key is None: role.api_key = make_token() if email is not None: role.email = email if is_admin is not None: role.is_admin = is_admin # see: https://github.com/alephdata/aleph/issues/111 auto_admins = [a.lower() for a in settings.ADMINS] if email is not None and email.lower() in auto_admins: role.is_admin = True db.session.add(role) db.session.flush() return role @classmethod def load_cli_user(cls): return cls.load_or_create(settings.SYSTEM_USER, cls.USER, "Aleph", is_admin=True) @classmethod def load_id(cls, foreign_id): """Load a role and return the ID.""" if not hasattr(settings, "_roles"): settings._roles = {} if foreign_id not in settings._roles: role_id = cls.all_ids().filter_by(foreign_id=foreign_id).first() if role_id is not None: settings._roles[foreign_id] = role_id[0] return settings._roles.get(foreign_id) @classmethod def public_roles(cls): """Roles which make a collection to be considered public.""" return set( [cls.load_id(cls.SYSTEM_USER), cls.load_id(cls.SYSTEM_GUEST)]) @classmethod def by_prefix(cls, prefix, exclude=[]): """Load a list of roles matching a name, email address, or foreign_id. :param str pattern: Pattern to match. """ q = cls.all_users() if len(exclude): q = q.filter(not_(Role.id.in_(exclude))) q = q.filter( or_( func.lower(cls.email) == prefix.lower(), query_like(cls.name, prefix))) q = q.order_by(Role.id.asc()) return q @classmethod def all_groups(cls, authz): q = cls.all() q = q.filter(Role.type == Role.GROUP) q = q.order_by(Role.name.asc()) q = q.order_by(Role.foreign_id.asc()) if not authz.is_admin: q = q.filter(Role.id.in_(authz.roles)) return q @classmethod def all_users(cls): q = cls.all().filter(Role.type == Role.USER) q = q.filter(cls.is_blocked == False) # noqa return q @classmethod def all_system(cls): return cls.all().filter(Role.type == Role.SYSTEM) @classmethod def login(cls, email, password): """Attempt to log a user in via an email/password method.""" role = cls.by_email(email) if role is None or not role.is_actor or not role.has_password: return if role.check_password(password): return role def __repr__(self): return "<Role(%r,%r)>" % (self.id, self.foreign_id)
class Entity(db.Model): id = db.Column(db.Unicode(50), primary_key=True, default=make_textid) label = db.Column(db.Unicode) category = db.Column(db.Enum(*CATEGORIES, name='entity_categories'), nullable=False) creator_id = db.Column(db.Integer(), db.ForeignKey('user.id')) creator = db.relationship(User, backref=db.backref('entities', lazy='dynamic', cascade='all, delete-orphan')) list_id = db.Column(db.Integer(), db.ForeignKey('list.id')) list = db.relationship('List', backref=db.backref('entities', lazy='dynamic', cascade='all, delete-orphan')) created_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) def to_dict(self): return { 'id': self.id, 'api_url': url_for('entities.view', id=self.id), 'label': self.label, 'category': self.category, 'creator_id': self.creator_id, 'selectors': [s.text for s in self.selectors], 'list': self.list_id, 'created_at': self.created_at, 'updated_at': self.updated_at } def has_selector(self, text): normalized = Selector.normalize(text) for selector in self.selectors: if selector.normalized == normalized: return True return False def delete(self): db.session.delete(self) @classmethod def create(cls, data, user): ent = cls() ent.update(data) ent.creator = user db.session.add(ent) return ent def update(self, data): data = EntityForm().deserialize(data) self.label = data.get('label') self.list = data.get('list') self.category = data.get('category') selectors = set(data.get('selectors')) selectors.add(self.label) existing = list(self.selectors) for sel in list(existing): if sel.text in selectors: selectors.remove(sel.text) existing.remove(sel) for sel in existing: db.session.delete(sel) for text in selectors: sel = Selector() sel.entity = self sel.text = text db.session.add(sel) @classmethod def by_normalized_label(cls, label, lst): q = db.session.query(cls) q = q.filter_by(list=lst) q = q.filter(db_compare(cls.label, label)) return q.first() @classmethod def by_id(cls, id): q = db.session.query(cls).filter_by(id=id) return q.first() @classmethod def by_lists(cls, lists, prefix=None): q = db.session.query(cls) q = q.filter(cls.list_id.in_(lists)) if prefix is not None and len(prefix): q = q.join(Selector, cls.id == Selector.entity_id) q = cls.apply_filter(q, Selector.normalized, prefix) q = q.order_by(cls.label.asc()) return q @classmethod def by_id_set(cls, ids): if not len(ids): return {} q = db.session.query(cls) q = q.filter(cls.id.in_(ids)) entities = {} for ent in q: entities[ent.id] = ent return entities @classmethod def apply_filter(cls, q, col, prefix): prefix = Selector.normalize(prefix) return q.filter( or_(col.like('%s%%' % prefix), col.like('%% %s%%' % prefix))) @classmethod def suggest_prefix(cls, prefix, lists, limit=10): from aleph.model import EntityTag ent = aliased(Entity) sel = aliased(Selector) tag = aliased(EntityTag) q = db.session.query(ent.id, ent.label, ent.category) q = q.join(sel, ent.id == sel.entity_id) q = q.join(tag, ent.id == tag.entity_id) q = q.filter(ent.list_id.in_(lists)) if prefix is None or not len(prefix): return [] q = cls.apply_filter(q, sel.normalized, prefix) q = q.order_by(ent.label.asc()) q = q.limit(limit) q = q.distinct() suggestions = [] for entity_id, label, category in q.all(): suggestions.append({ 'id': entity_id, 'label': label, 'category': category }) return suggestions @property def terms(self): return set([s.normalized for s in self.selectors]) def __repr__(self): return '<Entity(%r, %r)>' % (self.id, self.label) def __unicode__(self): return self.label