Example #1
0
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
    )
Example #2
0
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
    )
Example #3
0
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)
Example #4
0
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")
Example #5
0
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)
Example #6
0
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")
Example #7
0
    """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)
Example #8
0
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), )
Example #9
0
def all_parents_rel(*args, **kwargs):
    extended_kwargs = _all_parents_rel_options.copy()
    extended_kwargs.update(kwargs)
    return rel(*args, **extended_kwargs)
Example #10
0
def children_rel(*args, **kwargs):
    extended_kwargs = _children_rel_options.copy()
    extended_kwargs.update(kwargs)
    return rel(*args, **extended_kwargs)
Example #11
0
        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)
Example #12
0
def all_parents_rel(*args, **kwargs):
    extended_kwargs = _all_parents_rel_options.copy()
    extended_kwargs.update(kwargs)
    return rel(*args, **extended_kwargs)
Example #13
0
def children_rel(*args, **kwargs):
    extended_kwargs = _children_rel_options.copy()
    extended_kwargs.update(kwargs)
    return rel(*args, **extended_kwargs)
Example #14
0
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