Exemple #1
0
class UserRole(Base, db.Model):
    __tablename__ = 'user_roles'

    # Override default from `ContextRBAC` to provide backref
    context = db.relationship('Context', backref='user_roles')

    role_id = db.Column(db.Integer(),
                        db.ForeignKey('roles.id'),
                        nullable=False)
    role = db.relationship('Role',
                           backref=backref('user_roles',
                                           cascade='all, delete-orphan'))
    person_id = db.Column(db.Integer(),
                          db.ForeignKey('people.id'),
                          nullable=False)
    person = db.relationship('Person',
                             backref=backref('user_roles',
                                             cascade='all, delete-orphan'))

    @staticmethod
    def _extra_table_args(cls):
        return (db.Index('ix_user_roles_person', 'person_id'), )

    _publish_attrs = ['role', 'person']

    @classmethod
    def role_assignments_for(cls, context):
        context_id = context.id if type(context) is Context else context
        all_assignments = db.session.query(UserRole)\
            .filter(UserRole.context_id == context_id)
        assignments_by_user = {}
        for assignment in all_assignments:
            assignments_by_user.setdefault(assignment.person.email, [])\
                .append(assignment.role)
        return assignments_by_user

    @classmethod
    def eager_query(cls):
        from sqlalchemy import orm

        query = super(UserRole, cls).eager_query()
        return query.options(orm.subqueryload('role'),
                             orm.subqueryload('person'),
                             orm.subqueryload('context'))

    def _display_name(self):
        if self.context and self.context.related_object_type and \
           self.context.related_object:
            context_related = ' in ' + self.context.related_object.display_name
        elif hasattr(self, '_display_related_title'):
            context_related = ' in ' + self._display_related_title
        elif self.context:
            logger.warning('Unable to identify context.related for UserRole')
            context_related = ''
        else:
            context_related = ''
        return u'{0} <-> {1}{2}'.format(self.person.display_name,
                                        self.role.display_name,
                                        context_related)
Exemple #2
0
class CalendarEvent(Relatable, Base, db.Model):
    """Model representing events for Calendar API."""
    __tablename__ = 'calendar_events'

    external_event_id = db.Column(db.String)
    title = db.Column(db.String)
    description = db.Column(db.String)
    due_date = db.Column(db.Date)
    last_synced_at = db.Column(db.DateTime)
    attendee_id = db.Column(db.Integer(),
                            db.ForeignKey('people.id'),
                            nullable=False)

    @declared_attr
    def attendee(cls):  # pylint: disable=no-self-argument
        """Relationship to user referenced by attendee_id."""
        return db.relationship(
            'Person',
            primaryjoin='{0}.attendee_id == Person.id'.format(cls.__name__),
            foreign_keys='{0}.attendee_id'.format(cls.__name__),
            remote_side='Person.id',
            uselist=False,
        )

    @validates("due_date")
    def validate_due_date(self, _, value):
        """Validator for due_date"""
        # pylint: disable=no-self-use
        return value.date() if isinstance(value, datetime.datetime) else value

    @property
    def calendar_end_date(self):
        """Returns end date for all-day event in calendar api."""
        return (self.due_date +
                datetime.timedelta(days=1)).strftime("%Y-%m-%d")

    @property
    def calendar_start_date(self):
        """Returns start date for all-day event in calendar api."""
        return self.due_date.strftime("%Y-%m-%d")

    @property
    def is_synced(self):
        """Indicates whether the event was synced or not."""
        return self.last_synced_at is not None

    @property
    def needs_sync(self):
        """Indicates should we send this event to user or not."""
        return self.attendee.profile.send_calendar_events

    def json_equals(self, event_response):
        """Checks if event is equal to json representation."""
        return (event_response['description'] == self.description
                and event_response['summary'] == self.title
                and event_response['end']['date'] == self.calendar_end_date and
                event_response['start']['date'] == self.calendar_start_date)
Exemple #3
0
class ContextImplication(Base, db.Model):
    '''A roles implication between two contexts. An implication may be scoped
  with additional scoping properties on the target and source contexts. The
  meaning of the scoping properties is determined by the module that
  contributed the implication. For example, an implication may be scoped based
  on the related objects of the contexts such as from a Program context to
  an Audit context.
  '''
    __tablename__ = 'context_implications'

    context_id = db.Column(db.Integer(),
                           db.ForeignKey('contexts.id'),
                           nullable=True)
    source_context_id = db.Column(db.Integer(),
                                  db.ForeignKey('contexts.id'),
                                  nullable=True)
    context_scope = db.Column(db.String, nullable=True)
    source_context_scope = db.Column(db.String, nullable=True)

    context = db.relationship(
        'Context',
        uselist=False,
        foreign_keys=[context_id],
    )
    source_context = db.relationship(
        'Context',
        uselist=False,
        foreign_keys=[source_context_id],
    )

    def _display_name(self):
        if self.source_context:
            source_context_display_name = self.source_context.display_name
        else:
            source_context_display_name = 'Default Context'
        if self.context:
            context_display_name = self.context.display_name
        else:
            context_display_name = 'Default Context'
        return u'{source_context} -> {context}'.format(
            source_context=source_context_display_name,
            context=context_display_name,
        )
Exemple #4
0
class Role(base.ContextRBAC, Base, Described, db.Model):
    """A user role. All roles have a unique name. This name could be a simple
  string, an email address, or some other form of string identifier.

  Example:

  ..  code-block:: python

      {
        'create': ['Program', 'Control'],
        'read': ['Program', 'Control'],
        'update': ['Program', 'Control'],
        'delete': ['Program'],
      }

  """
    __tablename__ = 'roles'

    name = db.Column(db.String(128), nullable=False)
    permissions_json = db.Column(db.Text(), nullable=False)
    scope = db.Column(db.String(64), nullable=True)
    role_order = db.Column(db.Integer(), nullable=True)

    @simple_property
    def permissions(self):
        if self.permissions_json == DECLARED_ROLE:
            declared_role = get_declared_role(self.name)
            permissions = declared_role.permissions
        else:
            permissions = json.loads(self.permissions_json) or {}
        # make sure not to omit actions
        for action in ['create', 'read', 'update', 'delete']:
            if action not in permissions:
                permissions[action] = []
        return permissions

    @permissions.setter
    def permissions(self, value):
        self.permissions_json = json.dumps(value)

    _api_attrs = reflection.ApiAttributes(
        'name',
        'permissions',
        'scope',
        'role_order',
    )

    def _display_name(self):
        return self.name
Exemple #5
0
class Context(Base, db.Model):
  """Context class. Sign permissions object for specific user."""
  __tablename__ = 'contexts'

  # This block and the 'description' attrs are taken from the Described mixin
  #  which we do not use for Context because indexing Context descriptions
  #  for fulltext search leads to undesirable results
  @declared_attr
  def description(cls):  # pylint: disable=no-self-argument
    return deferred(db.Column(db.Text, nullable=False, default=u""),
                    cls.__name__)

  name = deferred(db.Column(db.String(128), nullable=True), 'Context')
  related_object_id = deferred(
      db.Column(db.Integer(), nullable=True), 'Context')
  related_object_type = deferred(
      db.Column(db.String(128), nullable=True), 'Context')

  @property
  def related_object_attr(self):
    return '{0}_related_object'.format(self.related_object_type)

  @property
  def related_object(self):
    return getattr(self, self.related_object_attr)

  @related_object.setter
  def related_object(self, value):
    self.related_object_id = value.id if value is not None else None
    self.related_object_type = value.__class__.__name__ if value is not None \
        else None
    return setattr(self, self.related_object_attr, value)

  @staticmethod
  def _extra_table_args(_):
    return (
        db.Index(
            'ix_context_related_object',
            'related_object_type', 'related_object_id'),
    )

  _api_attrs = reflection.ApiAttributes('name',
                                        'related_object',
                                        'description')

  _sanitize_html = ['name', 'description']
  _include_links = []
Exemple #6
0
class Context(Base, db.Model):
  __tablename__ = 'contexts'

  # This block and the 'description' attrs are taken from the Described mixin
  #  which we do not use for Context because indexing Context descriptions
  #  for fulltext search leads to undesirable results
  @declared_attr
  def description(cls):
    return deferred(db.Column(db.Text), cls.__name__)

  name = deferred(db.Column(db.String(128), nullable=True), 'Context')
  related_object_id = deferred(
      db.Column(db.Integer(), nullable=True), 'Context')
  related_object_type = deferred(
      db.Column(db.String(128), nullable=True), 'Context')

  @property
  def related_object_attr(self):
    return '{0}_related_object'.format(self.related_object_type)

  @property
  def related_object(self):
    return getattr(self, self.related_object_attr)

  @related_object.setter
  def related_object(self, value):
    self.related_object_id = value.id if value is not None else None
    self.related_object_type = value.__class__.__name__ if value is not None \
        else None
    return setattr(self, self.related_object_attr, value)

  @staticmethod
  def _extra_table_args(cls):
    return (
        db.Index(
            'ix_context_related_object',
            'related_object_type', 'related_object_id'),
    )

  _publish_attrs = ['name', 'related_object', 'description']
  _sanitize_html = ['name', 'description']
  _include_links = []
Exemple #7
0
class UserRole(rest_handable.WithDeleteHandable,
               rest_handable.WithPostHandable, base.ContextRBAC, Base,
               db.Model):
    """`UserRole` model represents mapping between `User` and `Role` models."""

    __tablename__ = 'user_roles'

    # Override default from `ContextRBAC` to provide backref
    context = db.relationship('Context', backref='user_roles')

    role_id = db.Column(db.Integer(),
                        db.ForeignKey('roles.id'),
                        nullable=False)
    role = db.relationship('Role',
                           backref=backref('user_roles',
                                           cascade='all, delete-orphan'))
    person_id = db.Column(db.Integer(),
                          db.ForeignKey('people.id'),
                          nullable=False)
    person = db.relationship('Person',
                             backref=backref('user_roles',
                                             cascade='all, delete-orphan'))

    @staticmethod
    def _extra_table_args(model):
        return (db.UniqueConstraint('person_id',
                                    name='uq_{}'.format(model.__tablename__)),
                db.Index('ix_user_roles_person', 'person_id'))

    _api_attrs = reflection.ApiAttributes('role', 'person')

    @classmethod
    def role_assignments_for(cls, context):
        context_id = context.id if type(context) is Context else context
        all_assignments = db.session.query(UserRole)\
            .filter(UserRole.context_id == context_id)
        assignments_by_user = {}
        for assignment in all_assignments:
            assignments_by_user.setdefault(assignment.person.email, [])\
                .append(assignment.role)
        return assignments_by_user

    @classmethod
    def eager_query(cls, **kwargs):
        from sqlalchemy import orm

        query = super(UserRole, cls).eager_query(**kwargs)
        return query.options(orm.joinedload('role'),
                             orm.subqueryload('person'),
                             orm.subqueryload('context'))

    def _display_name(self):
        if self.context and self.context.related_object_type and \
           self.context.related_object:
            context_related = ' in ' + self.context.related_object.display_name
        elif hasattr(self, '_display_related_title'):
            context_related = ' in ' + self._display_related_title
        elif self.context:
            logger.warning('Unable to identify context.related for UserRole')
            context_related = ''
        else:
            context_related = ''
        return u'{0} <-> {1}{2}'.format(self.person.display_name,
                                        self.role.display_name,
                                        context_related)

    def _recalculate_permissions_cache(self):
        """Recalculate permissions cache for user `UserRole` relates to."""
        with utils.benchmark(
                "Invalidate permissions cache for user in UserRole"):
            from ggrc_basic_permissions import load_permissions_for
            load_permissions_for(self.person, expire_old=True)

    def handle_delete(self):
        """Handle `model_deleted` signals invoked on `UserRole` instance.

    HTTP DELTE method on `UserRole` model triggers following actions:
      - Recalculate permissions cache for user the `UserRole` object is
        related to.
    """
        self._recalculate_permissions_cache()

    def handle_post(self):
        """Handle `model_posted` signals invoked on `UserRole` instance.

    HTTP POST method on `UserRole` model triggers following actions:
      - Recalculate permissions cache for user the `UserRole` object is
        related to.
    """
        self._recalculate_permissions_cache()