Ejemplo n.º 1
0
class UserUsergroup(db.Model):
    """Represent a UserUsergroup record."""

    USER_STATUS = {
        'ADMIN': 'A',
        'MEMBER': 'M',
        'PENDING': 'P',
    }

    def __str__(self):
        """Return string representation."""
        return "%s:%s" % (self.user.nickname, self.usergroup.name)

    __tablename__ = 'user_usergroup'

    id_user = db.Column(db.Integer(15, unsigned=True),
                        db.ForeignKey(User.id),
                        nullable=False,
                        server_default='0',
                        primary_key=True)
    id_usergroup = db.Column(db.Integer(15, unsigned=True),
                             db.ForeignKey(Usergroup.id),
                             nullable=False,
                             server_default='0',
                             primary_key=True)
    user_status = db.Column(db.CHAR(1), nullable=False, server_default='')
    user_status_date = db.Column(db.DateTime,
                                 nullable=False,
                                 default=datetime.now,
                                 onupdate=datetime.now)
    user = db.relationship(User, backref=db.backref('usergroups'))
    usergroup = db.relationship(Usergroup,
                                backref=db.backref(
                                    'users', cascade="all, delete-orphan"))

    def is_admin(self):
        """Return True if user is a admin."""
        return self.user_status == self.USER_STATUS['ADMIN']
Ejemplo n.º 2
0
class PersistentIdentifier(db.Model):

    """Store and register persistent identifiers.

    Assumptions:
      * Persistent identifiers can be represented as a string of max 255 chars.
      * An object has many persistent identifiers.
      * A persistent identifier has one and only one object.
    """

    __tablename__ = 'pidSTORE'
    __table_args__ = (
        db.Index('uidx_type_pid', 'pid_type', 'pid_value', unique=True),
        db.Index('idx_status', 'status'),
        db.Index('idx_object', 'object_type', 'object_value'),
    )

    id = db.Column(db.Integer(15, unsigned=True), primary_key=True)
    """Id of persistent identifier entry."""

    pid_type = db.Column(db.String(6), nullable=False)
    """Persistent Identifier Schema."""

    pid_value = db.Column(db.String(length=255), nullable=False)
    """Persistent Identifier."""

    pid_provider = db.Column(db.String(length=255), nullable=False)
    """Persistent Identifier Provider"""

    status = db.Column(db.CHAR(length=1), nullable=False)
    """Status of persistent identifier, e.g. registered, reserved, deleted."""

    object_type = db.Column(db.String(3), nullable=True)
    """Object Type - e.g. rec for record."""

    object_value = db.Column(db.String(length=255), nullable=True)
    """Object ID - e.g. a record id."""

    created = db.Column(db.DateTime(), nullable=False, default=datetime.now)
    """Creation datetime of entry."""

    last_modified = db.Column(
        db.DateTime(), nullable=False, default=datetime.now,
        onupdate=datetime.now
    )
    """Last modification datetime of entry."""

    #
    # Class methods
    #
    @classmethod
    def create(cls, pid_type, pid_value, pid_provider='', provider=None):
        """Internally reserve a new persistent identifier.

        A provider for the given persistent identifier type must exists. By
        default the system will choose a provider according to the pid
        type. If desired, the default system provider can be overridden via
        the provider keyword argument.

        Return PID object if successful otherwise None.
        """
        # Ensure provider exists
        if provider is None:
            provider = PidProvider.create(pid_type, pid_value, pid_provider)
            if not provider:
                raise Exception(
                    "No provider found for %s:%s (%s)" % (
                        pid_type, pid_value, pid_provider)
                )

        try:
            obj = cls(pid_type=provider.pid_type,
                      pid_value=provider.create_new_pid(pid_value),
                      pid_provider=pid_provider,
                      status=cfg['PIDSTORE_STATUS_NEW'])
            obj._provider = provider
            db.session.add(obj)
            db.session.commit()
            obj.log("CREATE", "Created")
            return obj
        except SQLAlchemyError:
            db.session.rollback()
            obj.log("CREATE", "Failed to created. Already exists.")
            return None

    @classmethod
    def get(cls, pid_type, pid_value, pid_provider='', provider=None):
        """Get persistent identifier.

        Return None if not found.
        """
        pid_value = to_unicode(pid_value)
        obj = cls.query.filter_by(
            pid_type=pid_type, pid_value=pid_value, pid_provider=pid_provider
        ).first()
        if obj:
            obj._provider = provider
            return obj
        else:
            return None

    #
    # Instance methods
    #
    def has_object(self, object_type, object_value):
        """Determine if this PID is assigned to a specific object."""
        if object_type not in cfg['PIDSTORE_OBJECT_TYPES']:
            raise Exception("Invalid object type %s." % object_type)

        object_value = to_unicode(object_value)

        return self.object_type == object_type and \
            self.object_value == object_value

    def get_provider(self):
        """Get the provider for this type of persistent identifier."""
        if self._provider is None:
            self._provider = PidProvider.create(
                self.pid_type, self.pid_value, self.pid_provider
            )
        return self._provider

    def assign(self, object_type, object_value, overwrite=False):
        """Assign this persistent identifier to a given object.

        Note, the persistent identifier must first have been reserved. Also,
        if an exsiting object is already assigned to the pid, it will raise an
        exception unless overwrite=True.
        """
        if object_type not in cfg['PIDSTORE_OBJECT_TYPES']:
            raise Exception("Invalid object type %s." % object_type)
        object_value = to_unicode(object_value)

        if not self.id:
            raise Exception(
                "You must first create the persistent identifier before you "
                "can assign objects to it."
            )

        if self.is_deleted():
            raise Exception(
                "You cannot assign objects to a deleted persistent identifier."
            )

        # Check for an existing object assigned to this pid
        existing_obj_id = self.get_assigned_object(object_type)

        if existing_obj_id and existing_obj_id != object_value:
            if not overwrite:
                raise Exception(
                    "Persistent identifier is already assigned to another "
                    "object"
                )
            else:
                self.log(
                    "ASSIGN",
                    "Unassigned object %s:%s (overwrite requested)" % (
                        self.object_type, self.object_value)
                )
                self.object_type = None
                self.object_value = None
        elif existing_obj_id and existing_obj_id == object_value:
            # The object is already assigned to this pid.
            return True

        self.object_type = object_type
        self.object_value = object_value
        db.session.commit()
        self.log("ASSIGN", "Assigned object %s:%s" % (self.object_type,
                                                      self.object_value))
        return True

    def update(self, with_deleted=False, *args, **kwargs):
        """Update the persistent identifier with the provider.."""
        if self.is_new() or self.is_reserved():
            raise Exception(
                "Persistent identifier has not yet been registered."
            )

        if not with_deleted and self.is_deleted():
            raise Exception("Persistent identifier has been deleted.")

        provider = self.get_provider()
        if provider is None:
            self.log("UPDATE", "No provider found.")
            raise Exception("No provider found.")

        if provider.update(self, *args, **kwargs):
            if with_deleted and self.is_deleted():
                self.status = cfg['PIDSTORE_STATUS_REGISTERED']
                db.session.commit()
            return True
        return False

    def reserve(self, *args, **kwargs):
        """Reserve the persistent identifier with the provider.

        Note, the reserve method may be called multiple times, even if it was
        already reserved.
        """
        if not (self.is_new() or self.is_reserved()):
            raise Exception(
                "Persistent identifier has already been registered."
            )

        provider = self.get_provider()
        if provider is None:
            self.log("RESERVE", "No provider found.")
            raise Exception("No provider found.")

        if provider.reserve(self, *args, **kwargs):
            self.status = cfg['PIDSTORE_STATUS_RESERVED']
            db.session.commit()
            return True
        return False

    def register(self, *args, **kwargs):
        """Register the persistent identifier with the provider."""
        if self.is_registered() or self.is_deleted():
            raise Exception(
                "Persistent identifier has already been registered."
            )

        provider = self.get_provider()
        if provider is None:
            self.log("REGISTER", "No provider found.")
            raise Exception("No provider found.")

        if provider.register(self, *args, **kwargs):
            self.status = cfg['PIDSTORE_STATUS_REGISTERED']
            db.session.commit()
            return True
        return False

    def delete(self, *args, **kwargs):
        """Delete the persistent identifier."""
        if self.is_new():
            # New persistent identifier which haven't been registered yet. Just
            #  delete it completely but keep log)
            # Remove links to log entries (but otherwise leave the log entries)
            PidLog.query.filter_by(id_pid=self.id).update({'id_pid': None})
            db.session.delete(self)
            self.log("DELETE", "Unregistered PID successfully deleted")
        else:
            provider = self.get_provider()
            if not provider.delete(self, *args, **kwargs):
                return False
            self.status = cfg['PIDSTORE_STATUS_DELETED']
            db.session.commit()
        return True

    def sync_status(self, *args, **kwargs):
        """Synchronize persistent identifier status.

        Used when the provider uses an external service, which might have been
        modified outside of our system.
        """
        provider = self.get_provider()
        result = provider.sync_status(self, *args, **kwargs)
        db.session.commit()
        return result

    def get_assigned_object(self, object_type=None):
        """Return an assigned object."""
        if object_type is not None and self.object_type == object_type:
            return self.object_value
        return None

    def is_registered(self):
        """Return true if the persistent identifier has been registered."""
        return self.status == cfg['PIDSTORE_STATUS_REGISTERED']

    def is_deleted(self):
        """Return true if the persistent identifier has been deleted."""
        return self.status == cfg['PIDSTORE_STATUS_DELETED']

    def is_new(self):
        """Return true if the PIDhas not yet been registered or reserved."""
        return self.status == cfg['PIDSTORE_STATUS_NEW']

    def is_reserved(self):
        """Return true if the PID has not yet been reserved."""
        return self.status == cfg['PIDSTORE_STATUS_RESERVED']

    def log(self, action, message):
        """Store action and message in log."""
        if self.pid_type and self.pid_value:
            message = "[%s:%s] %s" % (self.pid_type, self.pid_value, message)
        p = PidLog(id_pid=self.id, action=action, message=message)
        db.session.add(p)
        db.session.commit()
Ejemplo n.º 3
0
class Usergroup(db.Model):
    """Represent a Usergroup record."""
    def __str__(self):
        """Return string representation."""
        return "%s <%s>" % (self.name, self.description)

    __tablename__ = 'usergroup'

    JOIN_POLICIES = {
        'VISIBLEOPEN': 'VO',
        'VISIBLEMAIL': 'VM',
        'INVISIBLEOPEN': 'IO',
        'INVISIBLEMAIL': 'IM',
        'VISIBLEEXTERNAL': 'VE',
    }

    LOGIN_METHODS = {
        'INTERNAL': 'INTERNAL',
        'EXTERNAL': 'EXTERNAL',
    }

    id = db.Column(db.Integer(15, unsigned=True),
                   nullable=False,
                   primary_key=True,
                   autoincrement=True)
    name = db.Column(db.String(255),
                     nullable=False,
                     server_default='',
                     unique=True,
                     index=True)
    description = db.Column(db.Text, nullable=True)
    join_policy = db.Column(ChoiceType(map(lambda (k, v): (v, k),
                                           JOIN_POLICIES.items()),
                                       impl=db.CHAR(2)),
                            nullable=False,
                            server_default='')
    login_method = db.Column(ChoiceType(
        map(lambda (k, v): (v, k), LOGIN_METHODS.items())),
                             nullable=False,
                             server_default='INTERNAL')

    # FIXME Unique(login_method(70), name)
    __table_args__ = (db.Index('login_method_name',
                               'login_method',
                               'name',
                               mysql_length={
                                   'login_method': 60,
                                   'name': 255
                               }), db.Model.__table_args__)

    @classmethod
    def filter_visible(cls):
        """Return query object with filtered out invisible groups."""
        visible = filter(lambda k: k[0] == 0, cls.JOIN_POLICIES.values())
        assert len(visible) > 1  # if implementation chage use == instead of in
        return cls.query.filter(cls.join_policy.in_(visible))

    @property
    def login_method_is_external(self):
        """Return True if the group is external."""
        return self.login_method == Usergroup.LOGIN_METHODS['EXTERNAL']

    def join(self, user, status=None):
        """Join user to group.

        :param user: User to add into the group.
        :param status: status of user
        """
        # if I want to join another user from the group
        if (user.id != current_user.get_id() and
                # I need to be an admin of the group
                not self.is_admin(current_user.get_id())):
            raise AccountSecurityError(
                'Not enough right to '
                'add user "{0}" from group "{1}"'.format(
                    user.nickname, self.name))

        # join group
        self.users.append(
            UserUsergroup(
                id_user=user.id,
                user_status=status or self.new_user_status,
            ))
        try:
            db.session.commit()
        except Exception:
            db.session.rollback()
            raise

    def leave(self, user):
        """Remove user from group.

        :param user: User to remove from the group.
        """
        # if I want to remove another user from the group
        if (user.id != current_user.get_id() and
                # I need to be an admin of the group
                not self.is_admin(current_user.get_id())):
            raise AccountSecurityError(
                'Not enough right to '
                'remove user "{0}" from group "{1}"'.format(
                    user.nickname, self.name))

        # check that I'm not the last admin before leaving the group.
        if self.is_admin(user.id) and self.admins.count() == 1:
            raise IntegrityUsergroupError('User can leave the group '
                                          'without admins, please delete the '
                                          'group if you want to leave.')

        # leave the group
        UserUsergroup.query.filter_by(
            id_usergroup=self.id,
            id_user=user.id,
        ).delete()
        try:
            db.session.commit()
        except Exception:
            db.session.rollback()
            raise

    def is_admin(self, id_user):
        """Return True if the user is an admin of the group."""
        return db.session.query(
            self.admins.filter(
                UserUsergroup.id_user == id_user).exists()).scalar()

    def get_users_not_in_this_group(self,
                                    nickname=None,
                                    email=None,
                                    limit=None):
        """Return users that not joined this group."""
        # base query
        query = User.query.outerjoin(User.usergroups).filter(
            User.id.notin_(
                db.select([UserUsergroup.id_user],
                          UserUsergroup.id_usergroup == self.id)))
        # additional optional filters
        if nickname:
            query = query.filter(User.nickname.like(nickname))
        if email:
            query = query.filter(User.email.like(email))
        if limit:
            query = query.limit(limit)
        # return results
        return query

    @property
    def new_user_status(self):
        """Return user status for new user."""
        if not self.join_policy.code.endswith('O'):
            return UserUsergroup.USER_STATUS['PENDING']
        return UserUsergroup.USER_STATUS['MEMBER']
Ejemplo n.º 4
0
class AccessRequest(db.Model):
    """Represent an request for access to restricted files in a record."""

    __tablename__ = 'accreqREQUEST'

    STATUS_CODES = {
        RequestStatus.EMAIL_VALIDATION: _(u'Email validation'),
        RequestStatus.PENDING: _(u'Pending'),
        RequestStatus.ACCEPTED: _(u'Accepted'),
        RequestStatus.REJECTED: _(u'Rejected'),
    }

    id = db.Column(db.Integer(unsigned=True),
                   primary_key=True,
                   autoincrement=True)
    """Access request ID."""

    status = db.Column(ChoiceType(STATUS_CODES.items(), impl=db.CHAR(1)),
                       nullable=False,
                       index=True)
    """Status of request."""

    receiver_user_id = db.Column(db.Integer(unsigned=True),
                                 db.ForeignKey(User.id),
                                 nullable=False,
                                 default=None)
    """Receiver's user id."""

    receiver = db.relationship(User, foreign_keys=[receiver_user_id])
    """Relationship to user"""

    sender_user_id = db.Column(db.Integer(unsigned=True),
                               db.ForeignKey(User.id),
                               nullable=True,
                               default=None)
    """Sender's user id (for authenticated users)."""

    sender = db.relationship(User, foreign_keys=[sender_user_id])
    """Relationship to user for a sender"""

    sender_full_name = db.Column(db.String(length=255),
                                 nullable=False,
                                 default='')
    """Sender's full name."""

    sender_email = db.Column(db.String(length=255), nullable=False, default='')
    """Sender's email address."""

    recid = db.Column(db.Integer(unsigned=True), nullable=False, index=True)
    """Record concerned for the request."""

    created = db.Column(db.DateTime,
                        nullable=False,
                        default=datetime.now,
                        index=True)
    """Creation timestamp."""

    modified = db.Column(db.DateTime,
                         nullable=False,
                         default=datetime.now,
                         onupdate=datetime.now)
    """Last modification timestamp."""

    justification = db.Column(db.Text, default='', nullable=False)
    """Sender's justification for how they fulfill conditions."""

    message = db.Column(db.Text, default='', nullable=False)
    """Receivers message to the sender."""

    link_id = db.Column(db.Integer(unsigned=True),
                        db.ForeignKey(SecretLink.id),
                        nullable=True,
                        default=None)
    """Relation to secret link if request was accepted."""

    link = db.relationship(SecretLink, foreign_keys=[link_id])
    """Relationship to secret link."""
    @classmethod
    def create(cls,
               recid=None,
               receiver=None,
               sender_full_name=None,
               sender_email=None,
               justification=None,
               sender=None):
        """Create a new access request.

        :param recid: Record id (required).
        :param receiver: User object of receiver (required).
        :param sender_full_name: Full name of sender (required).
        :param sender_email: Email address of sender (required).
        :param justification: Justification message (required).
        :param sender: User object of sender (optional).
        """
        sender_user_id = None if sender is None else sender.get_id()

        assert recid
        assert receiver
        assert sender_full_name
        assert sender_email
        assert justification

        # Determine status
        status = RequestStatus.EMAIL_VALIDATION
        if sender and sender.is_confirmed():
            status = RequestStatus.PENDING

        # Create object
        obj = cls(status=status,
                  recid=recid,
                  receiver_user_id=receiver.get_id(),
                  sender_user_id=sender_user_id,
                  sender_full_name=sender_full_name,
                  sender_email=sender_email,
                  justification=justification)

        db.session.add(obj)
        db.session.commit()

        # Send signal
        if obj.status == RequestStatus.EMAIL_VALIDATION:
            request_created.send(obj)
        else:
            request_confirmed.send(obj)
        return obj

    @classmethod
    def query_by_receiver(cls, user):
        """Get access requests for a specific receiver."""
        return cls.query.filter_by(receiver_user_id=user.get_id())

    @classmethod
    def get_by_receiver(cls, request_id, user):
        """Get access request for a specific receiver."""
        return cls.query.filter_by(id=request_id,
                                   receiver_user_id=user.get_id()).first()

    def confirm_email(self):
        """Confirm that senders email is valid."""
        if self.status != RequestStatus.EMAIL_VALIDATION:
            raise InvalidRequestStateError(RequestStatus.EMAIL_VALIDATION)
        self.status = RequestStatus.PENDING
        db.session.commit()
        request_confirmed.send(self)

    def accept(self, message=None, expires_at=None):
        """Accept request."""
        if self.status != RequestStatus.PENDING:
            raise InvalidRequestStateError(RequestStatus.PENDING)
        self.status = RequestStatus.ACCEPTED
        db.session.commit()
        request_accepted.send(self, message=message, expires_at=expires_at)

    def reject(self, message=None):
        """Reject request."""
        if self.status != RequestStatus.PENDING:
            raise InvalidRequestStateError(RequestStatus.PENDING)
        self.status = RequestStatus.REJECTED
        db.session.commit()
        request_rejected.send(self, message=message)

    def create_secret_link(self, title, description=None, expires_at=None):
        """Create a secret link from request."""
        self.link = SecretLink.create(
            title,
            self.receiver,
            extra_data=dict(recid=self.recid),
            description=description,
            expires_at=expires_at,
        )
        db.session.commit()
        return self.link