class UserToUserGroup(DeclarativeBase, TimeStamp): __tablename__ = "user_to_usergroup" user_id = C(FK(User.id, ondelete="CASCADE"), primary_key=True) usergroup_id = C(FK(UserGroup.id, ondelete="CASCADE"), primary_key=True) managed_by_authenticator = C(Boolean, server_default="false") private = C(Boolean, server_default="false") user = rel(User, backref=bref("group_assocs", passive_deletes=True, cascade="all, delete-orphan")) usergroup = rel(UserGroup, backref=bref("user_assocs", passive_deletes=True, cascade="all, delete-orphan")) __table_args__ = ( # This exclude constraint is something like a unique constraint only for rows where private is true. # Postgres doesn't support WHERE for unique constraints (why?), so lets just use this. # Alternatively, we could use a unique partial index to enforce the constraint. ExcludeConstraint((user_id, "="), using="btree", where="private = true", name="only_one_private_group_per_user"), ExcludeConstraint((usergroup_id, "="), using="btree", where="private = true", name="only_one_user_per_private_group"), # XXX: missing constraint: groups cannot be used elsewhere if they are private )
class NodeToAccessRuleset(DeclarativeBase): __tablename__ = "node_to_access_ruleset" __versioned__ = {} nid = C(FK(Node.id, ondelete="CASCADE"), primary_key=True, nullable=False) ruleset_name = C(FK(AccessRuleset.name, ondelete="CASCADE", onupdate="CASCADE"), primary_key=True, nullable=False) ruletype = C(Text, index=True, primary_key=True, nullable=False) invert = C(Boolean, server_default="false", index=True) blocking = C(Boolean, server_default="false", index=True) private = C(Boolean, server_default="false") ruleset = rel(AccessRuleset, backref="node_assocs") __table_args__ = ( # This exclude constraint is something like a unique constraint only for rows where private is true. # Postgres doesn't support WHERE for unique constraints (why?), so lets just use this. # Alternatively, we could use a unique partial index to enforce the constraint. ExcludeConstraint( (nid, "="), (ruletype, "="), using="btree", where="private = true", name="only_one_private_ruleset_per_node_and_ruletype"), # XXX: missing constraint: private rulesets cannot be used elsewhere if they are private )
class OAuthUserCredentials(DeclarativeBase, TimeStamp): __tablename__ = "oauth_user_credentials" oauth_user = C(Unicode, primary_key=True) oauth_key = C(Unicode, nullable=False) user_id = integer_fk(User.id, nullable=False) user = rel(User)
class NodeToAccessRule(DeclarativeBase): __tablename__ = "node_to_access_rule" nid = C(FK(Node.id, ondelete="CASCADE"), primary_key=True) rule_id = C(FK(AccessRule.id, ondelete="CASCADE"), primary_key=True) ruletype = C(Text, index=True, primary_key=True) invert = C(Boolean, server_default="false", index=True, primary_key=True) inherited = C(Boolean, server_default="false", index=True) blocking = C(Boolean, server_default="false", index=True, primary_key=True) rule = rel(AccessRule, backref="node_assocs")
class NodeAlias(DeclarativeBase): """Alias name for a node that will be shown if the alias is requested in the frontend. A node can have multiple aliases.""" __tablename__ = "node_alias" alias = C(Unicode, primary_key=True) nid = integer_fk(Node.id) description = C(Unicode) node = rel(Node)
class AccessRulesetToRule(DeclarativeBase): __tablename__ = "access_ruleset_to_rule" __versioned__ = {} rule_id = C(FK(AccessRule.id, ondelete="CASCADE"), primary_key=True, nullable=False) ruleset_name = C(FK(AccessRuleset.name, ondelete="CASCADE", onupdate="CASCADE"), primary_key=True, nullable=False) invert = C(Boolean, server_default="false", index=True) blocking = C(Boolean, server_default="false", index=True) rule = rel(AccessRule, backref="ruleset_assocs")
"""Gets the special access ruleset for this node for the specified `ruletype`. Returns None, if node has no special ruleset. Use Node.get_or_add_special_access_ruleset() instead if you want to edit the special ruleset! :rtype: AccessRuleset """ ruleset_assoc = self.access_ruleset_assocs.filter_by( private=True, ruletype=ruletype).scalar() if ruleset_assoc is not None: return ruleset_assoc.ruleset Node.get_or_add_special_access_ruleset = get_or_add_special_access_ruleset Node.get_special_access_ruleset = get_special_access_ruleset EffectiveNodeToAccessRuleset = map_function_to_mapped_class( mediatumfunc.effective_access_rulesets, NodeToAccessRuleset, "node_id") def _effective_access_ruleset_assocs(self): return object_session(self).query(EffectiveNodeToAccessRuleset).params( node_id=self.id) Node.effective_access_ruleset_assocs = property( _effective_access_ruleset_assocs) AccessRuleset.rule_assocs = rel(AccessRulesetToRule, backref="ruleset", cascade="all, delete-orphan", passive_deletes=True)
class User(DeclarativeBase, TimeStamp, UserMixin): __tablename__ = "user" __versioned__ = {} id = integer_pk() login_name = C(Unicode, nullable=False) display_name = C(Unicode) lastname = C(Unicode) firstname = C(Unicode) telephone = C(Unicode) organisation = C(Unicode) comment = C(UnicodeText) email = C(EmailType) password_hash = C(String) salt = C(String) password = u'' # user activity last_login = C(DateTime) active = C(Boolean, server_default="true") # options can_edit_shoppingbag = C(Boolean, server_default="false") can_change_password = C(Boolean, server_default="false") home_dir_id = integer_fk("node.id") # relationships groups = association_proxy( "group_assocs", "usergroup", creator=lambda ug: UserToUserGroup(usergroup=ug)) home_dir = rel("Directory", foreign_keys=[home_dir_id]) authenticator_info = rel(AuthenticatorInfo) authenticator_id = integer_fk(AuthenticatorInfo.id, nullable=False) @property def group_ids(self): return [g.id for g in self.groups] @property def group_names(self): return [g.name for g in self.groups] @property def is_editor(self): return any(g.is_editor_group for g in self.groups) @property def is_admin(self): return any(g.is_admin_group for g in self.groups) @property def is_guest(self): return self.login_name == config.get_guest_name( ) and self.authenticator_id == 0 @property def is_workflow_editor(self): return any(g.is_workflow_editor_group for g in self.groups) @property def hidden_edit_functions(self): return [ f for group in self.groups for f in group.hidden_edit_functions or [] ] @property def upload_dir(self): from contenttypes import Directory if self.home_dir: return self.home_dir.children.filter( Directory.system_attrs[u"used_as"].astext == u"upload").one() @property def trash_dir(self): from contenttypes import Directory if self.home_dir: return self.home_dir.children.filter( Directory.system_attrs[u"used_as"].astext == u"trash").one() def get_or_add_private_group(self): """Gets the private group for this user. Creates the group if it's missing and adds it to the session. Always use this method and don't create private groups by yourself! :rtype: UserGroup """ maybe_group_assoc = [g for g in self.group_assocs if g.private == True] if not maybe_group_assoc: # the name doesn't really matter, but it must be unique group = UserGroup(name=u"_user_{}".format(unicode(self.id))) group_assoc = UserToUserGroup(usergroup=group, private=True) self.group_assocs.append(group_assoc) else: group = maybe_group_assoc[0].usergroup return group def change_password(self, password): from core.auth import create_password_hash self.password_hash, self.salt = create_password_hash(password) def create_home_dir(self): from contenttypes.container import Directory, Home from core.database.postgres.permission import AccessRulesetToRule from core.permission import get_or_add_access_rule s = object_session(self) home_root = s.query(Home).one() homedir_name = self.login_name home = Directory(homedir_name) home_root.container_children.append(home) home.children.extend(create_special_user_dirs()) # add access rules so only the user itself can access the home dir private_group = self.get_or_add_private_group() # we need the private group ID, it's set on flush by the DB s.flush() user_access_rule = get_or_add_access_rule(group_ids=[private_group.id]) for access_type in (u"read", u"write", u"data"): ruleset = home.get_or_add_special_access_ruleset(access_type) arr = AccessRulesetToRule(rule=user_access_rule) ruleset.rule_assocs.append(arr) self.home_dir = home logg.info("created home dir for user '%s (id: %s)'", self.login_name, self.id) return home # Flask-Login integration functions def is_authenticated(self): return not self.is_guest def is_active(self): return not self.is_guest @property def is_anonymous(self): return self.is_guest def __eq__(self, other): ''' Checks the equality of two `UserMixin` objects using `get_id`. ''' if isinstance(other, UserMixin): return self.get_id() == other.get_id() return NotImplemented def __ne__(self, other): ''' Checks the inequality of two `UserMixin` objects using `get_id`. ''' equal = self.__eq__(other) if equal is NotImplemented: return NotImplemented return not equal def get_id(self): return unicode(self.id) def __unicode__(self): return u"{} \"{}\" ({}:{})".format( self.login_name, self.display_name if self.display_name else "", self.authenticator_info.auth_type, self.authenticator_info.name) def __repr__(self): return u"User<{} '{}'> ({})".format( self.id, self.login_name, object.__repr__(self)).encode("utf8") __table_args__ = (UniqueConstraint(login_name, authenticator_id), )
def all_parents_rel(*args, **kwargs): extended_kwargs = _all_parents_rel_options.copy() extended_kwargs.update(kwargs) return rel(*args, **extended_kwargs)
def children_rel(*args, **kwargs): extended_kwargs = _children_rel_options.copy() extended_kwargs.update(kwargs) return rel(*args, **extended_kwargs)
self.access_ruleset_assocs.append(ruleset_assoc) ruleset = ruleset_assoc.ruleset return ruleset def get_special_access_ruleset(self, ruletype): """Gets the special access ruleset for this node for the specified `ruletype`. Returns None, if node has no special ruleset. Use Node.get_or_add_special_access_ruleset() instead if you want to edit the special ruleset! :rtype: AccessRuleset """ ruleset_assoc = self.access_ruleset_assocs.filter_by(private=True, ruletype=ruletype).scalar() if ruleset_assoc is not None: return ruleset_assoc.ruleset Node.get_or_add_special_access_ruleset = get_or_add_special_access_ruleset Node.get_special_access_ruleset = get_special_access_ruleset EffectiveNodeToAccessRuleset = map_function_to_mapped_class(mediatumfunc.effective_access_rulesets, NodeToAccessRuleset, "node_id") def _effective_access_ruleset_assocs(self): return object_session(self).query(EffectiveNodeToAccessRuleset).params(node_id=self.id) Node.effective_access_ruleset_assocs = property(_effective_access_ruleset_assocs) AccessRuleset.rule_assocs = rel(AccessRulesetToRule, backref="ruleset", cascade="all, delete-orphan", passive_deletes=True)
class File(DeclarativeBase, FileMixin): """Represents an item on the filesystem """ __versioned__ = {"base_classes": (FileVersionMixin, DeclarativeBase)} #: True means: physical file should be deleted when database object is deleted unlink_after_deletion = False def __init__(self, path, filetype, mimetype, node=None, **kwargs): # legacy stuff datadir = config.settings["paths.datadir"] if path.startswith(datadir): warn("file path starts with paths.datadir, should be relative", DeprecationWarning) path = path[len(datadir):] if "type" in kwargs: raise Exception( "type keyword arg is not allowed anymore, use filetype") if "filename" in kwargs: raise Exception( "name positional arg is not allowed anymore, use path") self.path = path self.filetype = filetype self.mimetype = mimetype if node is not None: self.node = node __tablename__ = "file" id = integer_pk() path = C(Unicode(4096)) filetype = C(Unicode(126)) mimetype = C(String(255)) _size = C('size', BigInteger) # Checksum/hash columns sha512 = C(String(128)) # LargeBinary could be an alternative sha512_created_at = C(DateTime()) sha512_checked_at = C(DateTime()) sha512_ok = C(Boolean()) nodes = rel(Node, secondary=NodeToFile.__table__, backref=bref("files", lazy="dynamic", query_class=AppenderQueryWithLen), lazy="dynamic") _node_objects = rel(Node, secondary=NodeToFile.__table__, backref=bref("file_objects", viewonly=True), viewonly=True) def unlink(self): if self.exists: os.unlink(self.abspath) else: logg.warn( "tried to unlink missing physical file %s at %s, ignored", self.id, self.path) def __repr__(self): return "File #{} ({}:{}|{}) at {}".format(self.id, self.path, self.filetype, self.mimetype, hex(id(self))) def __unicode__(self): return u"# {} {} {} in {}".format(self.id, self.filetype, self.mimetype, self.path) @property def size(self): """Return size of file in bytes""" if self._size is None: return get_filesize(self.path) return self._size @property def size_humanized(self): """Return string with the size in human-friendly format, e.g. '7.9 kB'""" return humanize.naturalsize(self.size) def calculate_sha512(self): """Calculate the hash from the file on disk.""" if not self.exists: return None return sha512_from_file(self.abspath) def update_sha512(self): """Overwrite the stored checksum value with the current checksum of the file on disk. Use with caution, should not be necessary under usual circumstances!""" if not self.exists: return None logg.info('Updating sha512 for file ID: %s.' % self.id) self.sha512 = self.calculate_sha512() self.sha512_ok = True self.sha512_created_at = self.sha512_checked_at = datetime.utcnow() self._size = get_filesize(self.path) return self.sha512 def get_or_create_sha512(self): """Return the stored hash. If there is none, create and store it.""" if not self.exists: return None, False created = False if not self.sha512: created = True logg.info('Checksum not in DB, creating it for file ID: %s.' % self.id) self.update_sha512() return self.sha512, created def verify_checksum(self): """Make sure the file exists and has the same checksum as before""" if not self.exists: #raise IOError() logg.warn('check_checksum: file %s does not exist at %s!' % (self.id, self.abspath)) self.sha512_ok = None return None self.sha512_checked_at = datetime.utcnow() sha_stored, created = self.get_or_create_sha512() if created: # checksum was just created, skip a second calculation of the hash return True else: sha_calculated = self.calculate_sha512() if sha_stored == sha_calculated and sha_calculated is not None: logg.debug('Matching checksums :) for file ID: %s.' % self.id) self.sha512_ok = True else: logg.warn('Checksum mismatch for file ID: %s.' % self.id) self.sha512_ok = False return self.sha512_ok