Ejemplo n.º 1
0
Archivo: clin.py Proyecto: v1psta/atst
class CLIN(Base, mixins.TimestampsMixin):
    __tablename__ = "clins"

    id = types.Id()

    task_order_id = Column(ForeignKey("task_orders.id"), nullable=False)
    task_order = relationship("TaskOrder")

    number = Column(String, nullable=False)
    start_date = Column(Date, nullable=False)
    end_date = Column(Date, nullable=False)
    total_amount = Column(Numeric(scale=2), nullable=False)
    obligated_amount = Column(Numeric(scale=2), nullable=False)
    jedi_clin_type = Column(SQLAEnum(JEDICLINType, native_enum=False),
                            nullable=False)

    #
    # NOTE: For now obligated CLINS are CLIN 1 + CLIN 3
    #
    def is_obligated(self):
        return self.jedi_clin_type in [
            JEDICLINType.JEDI_CLIN_1,
            JEDICLINType.JEDI_CLIN_3,
        ]

    @property
    def type(self):
        return "Base" if self.number[0] == "0" else "Option"

    @property
    def is_completed(self):
        return all([
            self.number,
            self.start_date,
            self.end_date,
            self.total_amount,
            self.obligated_amount,
            self.jedi_clin_type,
        ])

    def to_dictionary(self):
        return {
            c.name: getattr(self, c.name)
            for c in self.__table__.columns if c.name not in ["id"]
        }

    @property
    def is_active(self):
        return (self.start_date <= date.today() <=
                self.end_date) and self.task_order.signed_at
Ejemplo n.º 2
0
class Attachment(Base, mixins.TimestampsMixin):
    __tablename__ = "attachments"

    id = types.Id()
    filename = Column(String, nullable=False)
    object_name = Column(String, unique=True, nullable=False)
    resource = Column(String)
    resource_id = Column(UUID(as_uuid=True), index=True)

    @classmethod
    def get_or_create(cls, object_name, params):
        try:
            return db.session.query(Attachment).filter_by(object_name=object_name).one()
        except NoResultFound:
            new_attachment = cls(**params)
            db.session.add(new_attachment)
            db.session.commit()
            return new_attachment

    @classmethod
    def get(cls, id_):
        try:
            return db.session.query(Attachment).filter_by(id=id_).one()
        except NoResultFound:
            raise NotFoundError("attachment")

    @classmethod
    def get_for_resource(cls, resource, resource_id):
        try:
            return (
                db.session.query(Attachment)
                .filter_by(resource=resource, resource_id=resource_id)
                .one()
            )
        except NoResultFound:
            raise NotFoundError("attachment")

    @classmethod
    def delete_for_resource(cls, resource, resource_id):
        try:
            return (
                db.session.query(Attachment)
                .filter_by(resource=resource, resource_id=resource_id)
                .update({"resource_id": None})
            )
        except NoResultFound:
            raise NotFoundError("attachment")

    def __repr__(self):
        return "<Attachment(name='{}', id='{}')>".format(self.filename, self.id)
Ejemplo n.º 3
0
class PermissionSet(Base, mixins.TimestampsMixin):
    __tablename__ = "permission_sets"

    id = types.Id()
    name = Column(String, index=True, unique=True, nullable=False)
    display_name = Column(String, nullable=False)
    description = Column(String, nullable=False)
    permissions = Column(ARRAY(String),
                         index=True,
                         server_default="{}",
                         nullable=False)

    def __repr__(self):
        return "<PermissionSet(name='{}', description='{}', permissions='{}', id='{}')>".format(
            self.name, self.description, self.permissions, self.id)
Ejemplo n.º 4
0
class AuditEvent(Base, TimestampsMixin):
    __tablename__ = "audit_events"

    id = types.Id()

    user_id = Column(UUID(as_uuid=True), ForeignKey("users.id"), index=True)
    user = relationship("User", backref="audit_events")

    portfolio_id = Column(UUID(as_uuid=True),
                          ForeignKey("portfolios.id"),
                          index=True)
    portfolio = relationship("Portfolio", backref="audit_events")

    application_id = Column(UUID(as_uuid=True),
                            ForeignKey("applications.id"),
                            index=True)
    application = relationship("Application", backref="audit_events")

    changed_state = Column(JSONB())
    event_details = Column(JSONB())

    resource_type = Column(String(), nullable=False)
    resource_id = Column(UUID(as_uuid=True), index=True, nullable=False)

    display_name = Column(String())
    action = Column(String(), nullable=False)

    @property
    def log(self):
        return {
            "portfolio_id": str(self.portfolio_id),
            "application_id": str(self.application_id),
            "changed_state": self.changed_state,
            "event_details": self.event_details,
            "resource_type": self.resource_type,
            "resource_id": str(self.resource_id),
            "display_name": self.display_name,
            "action": self.action,
        }

    def save(self, connection):
        attrs = inspect(self).dict

        connection.execute(self.__table__.insert(), **attrs)

    def __repr__(self):  # pragma: no cover
        return "<AuditEvent(name='{}', action='{}', id='{}')>".format(
            self.display_name, self.action, self.id)
Ejemplo n.º 5
0
class User(Base, mixins.TimestampsMixin, mixins.AuditableMixin,
           mixins.PermissionsMixin):
    __tablename__ = "users"

    id = types.Id()
    username = Column(String)

    permission_sets = relationship("PermissionSet",
                                   secondary=users_permission_sets)

    portfolio_roles = relationship("PortfolioRole", backref="user")
    application_roles = relationship(
        "ApplicationRole",
        backref="user",
        primaryjoin=
        "and_(ApplicationRole.user_id == User.id, ApplicationRole.deleted == False)",
    )

    portfolio_invitations = relationship(
        "PortfolioInvitation", foreign_keys=PortfolioInvitation.user_id)
    sent_portfolio_invitations = relationship(
        "PortfolioInvitation", foreign_keys=PortfolioInvitation.inviter_id)

    application_invitations = relationship(
        "ApplicationInvitation", foreign_keys=ApplicationInvitation.user_id)
    sent_application_invitations = relationship(
        "ApplicationInvitation", foreign_keys=ApplicationInvitation.inviter_id)

    email = Column(String)
    dod_id = Column(String, unique=True, nullable=False)
    first_name = Column(String, nullable=False)
    last_name = Column(String, nullable=False)
    phone_number = Column(String)
    phone_ext = Column(String)
    service_branch = Column(String)
    citizenship = Column(String)
    designation = Column(String)
    date_latest_training = Column(Date)
    last_login = Column(TIMESTAMP(timezone=True), nullable=True)
    last_session_id = Column(UUID(as_uuid=True), nullable=True)

    provisional = Column(Boolean)

    cloud_id = Column(String)

    REQUIRED_FIELDS = [
        "email",
        "dod_id",
        "first_name",
        "last_name",
        "phone_number",
        "service_branch",
        "citizenship",
        "designation",
        "date_latest_training",
    ]

    @property
    def profile_complete(self):
        return all([
            getattr(self, field_name) is not None
            for field_name in self.REQUIRED_FIELDS
        ])

    @property
    def full_name(self):
        return "{} {}".format(self.first_name, self.last_name)

    @property
    def displayname(self):
        return self.full_name

    @property
    def portfolio_id(self):
        return None

    @property
    def application_id(self):
        return None

    def __repr__(self):
        return "<User(name='{}', dod_id='{}', email='{}', id='{}')>".format(
            self.full_name, self.dod_id, self.email, self.id)

    def to_dictionary(self):
        return {
            c.name: getattr(self, c.name)
            for c in self.__table__.columns if c.name not in ["id"]
        }

    @staticmethod
    def audit_update(mapper, connection, target):
        changes = AuditableMixin.get_changes(target)
        if changes and not "last_login" in changes:
            target.create_audit_event(connection, target, ACTION_UPDATE)
Ejemplo n.º 6
0
class EnvironmentRole(
    Base, mixins.TimestampsMixin, mixins.AuditableMixin, mixins.DeletableMixin
):
    __tablename__ = "environment_roles"

    id = types.Id()
    environment_id = Column(
        UUID(as_uuid=True), ForeignKey("environments.id"), nullable=False
    )
    environment = relationship("Environment", backref="roles")

    role = Column(String())

    application_role_id = Column(
        UUID(as_uuid=True), ForeignKey("application_roles.id"), nullable=False
    )
    application_role = relationship("ApplicationRole")

    job_failures = relationship("EnvironmentRoleJobFailure")

    csp_user_id = Column(String())
    claimed_until = Column(TIMESTAMP(timezone=True))

    class Status(Enum):
        PENDING = "pending"
        COMPLETED = "completed"
        PENDING_DELETE = "pending_delete"

    status = Column(SQLAEnum(Status, native_enum=False), default=Status.PENDING)

    def __repr__(self):
        return "<EnvironmentRole(role='{}', user='******', environment='{}', id='{}')>".format(
            self.role, self.application_role.user_name, self.environment.name, self.id
        )

    @property
    def history(self):
        return self.get_changes()

    @property
    def portfolio_id(self):
        return self.environment.application.portfolio_id

    @property
    def application_id(self):
        return self.environment.application_id

    @property
    def displayname(self):
        return self.role

    @property
    def event_details(self):
        return {
            "updated_user_name": self.application_role.user_name,
            "updated_application_role_id": str(self.application_role_id),
            "role": self.role,
            "environment": self.environment.displayname,
            "environment_id": str(self.environment_id),
            "application": self.environment.application.name,
            "application_id": str(self.environment.application_id),
            "portfolio": self.environment.application.portfolio.name,
            "portfolio_id": str(self.environment.application.portfolio.id),
        }
Ejemplo n.º 7
0
class NotificationRecipient(Base, mixins.TimestampsMixin):
    __tablename__ = "notification_recipients"

    id = types.Id()
    email = Column(String, nullable=False)
Ejemplo n.º 8
0
class TaskOrder(Base, mixins.TimestampsMixin):
    __tablename__ = "task_orders"

    id = types.Id()

    portfolio_id = Column(ForeignKey("portfolios.id"), nullable=False)
    portfolio = relationship("Portfolio")

    pdf_attachment_id = Column(ForeignKey("attachments.id"))
    _pdf = relationship("Attachment", foreign_keys=[pdf_attachment_id])
    number = Column(String, unique=True,)  # Task Order Number
    signer_dod_id = Column(String)
    signed_at = Column(DateTime)

    clins = relationship(
        "CLIN", back_populates="task_order", cascade="all, delete-orphan"
    )

    @property
    def sorted_clins(self):
        return sorted(self.clins, key=lambda clin: (clin.number[1:], clin.number[0]))

    @hybrid_property
    def pdf(self):
        return self._pdf

    @pdf.setter
    def pdf(self, new_pdf):
        self._pdf = self._set_attachment(new_pdf, "_pdf")

    def _set_attachment(self, new_attachment, attribute):
        if isinstance(new_attachment, Attachment):
            return new_attachment
        elif isinstance(new_attachment, dict):
            if new_attachment["filename"] and new_attachment["object_name"]:
                attachment = Attachment.get_or_create(
                    new_attachment["object_name"], new_attachment
                )
                return attachment
            else:
                return None
        elif not new_attachment and hasattr(self, attribute):
            return None
        else:
            raise TypeError("Could not set attachment with invalid type")

    @property
    def is_draft(self):
        return self.status == Status.DRAFT

    @property
    def is_active(self):
        return self.status == Status.ACTIVE

    @property
    def is_expired(self):
        return self.status == Status.EXPIRED

    @property
    def clins_are_completed(self):
        return all([len(self.clins), (clin.is_completed for clin in self.clins)])

    @property
    def is_completed(self):
        return all([self.pdf, self.number, self.clins_are_completed])

    @property
    def is_signed(self):
        return self.signed_at is not None

    @property
    def status(self):
        todays_date = today(tz="UTC").date()

        if not self.is_completed and not self.is_signed:
            return Status.DRAFT
        elif self.is_completed and not self.is_signed:
            return Status.UNSIGNED
        elif todays_date < self.start_date:
            return Status.UPCOMING
        elif todays_date > self.end_date:
            return Status.EXPIRED
        elif self.start_date <= todays_date <= self.end_date:
            return Status.ACTIVE

    @property
    def start_date(self):
        return min((c.start_date for c in self.clins), default=None)

    @property
    def end_date(self):
        return max((c.end_date for c in self.clins), default=None)

    @property
    def days_to_expiration(self):
        if self.end_date:
            return (self.end_date - today(tz="UTC").date()).days

    @property
    def total_obligated_funds(self):
        return sum(
            (clin.obligated_amount for clin in self.clins if clin.obligated_amount)
        )

    @property
    def total_contract_amount(self):
        return sum((clin.total_amount for clin in self.clins if clin.total_amount))

    @property
    def invoiced_funds(self):
        # TODO: implement this using reporting data from the CSP
        if self.is_active:
            return self.total_obligated_funds * Decimal(0.75)
        else:
            return 0

    @property
    def display_status(self):
        return self.status.value

    @property
    def portfolio_name(self):
        return self.portfolio.name

    def to_dictionary(self):
        return {
            "portfolio_name": self.portfolio_name,
            "pdf": self.pdf,
            "clins": [clin.to_dictionary() for clin in self.clins],
            **{
                c.name: getattr(self, c.name)
                for c in self.__table__.columns
                if c.name not in ["id"]
            },
        }

    def __repr__(self):
        return "<TaskOrder(number='{}', id='{}')>".format(self.number, self.id)
Ejemplo n.º 9
0
class Portfolio(Base, mixins.TimestampsMixin, mixins.AuditableMixin,
                mixins.DeletableMixin):
    __tablename__ = "portfolios"

    id = types.Id()
    name = Column(String)
    defense_component = Column(String)  # Department of Defense Component

    app_migration = Column(String)  # App Migration
    complexity = Column(ARRAY(String))  # Application Complexity
    complexity_other = Column(String)
    description = Column(String)
    dev_team = Column(ARRAY(String))  # Development Team
    dev_team_other = Column(String)
    native_apps = Column(String)  # Native Apps
    team_experience = Column(String)  # Team Experience

    applications = relationship(
        "Application",
        back_populates="portfolio",
        primaryjoin=
        "and_(Application.portfolio_id == Portfolio.id, Application.deleted == False)",
    )
    roles = relationship("PortfolioRole")

    task_orders = relationship("TaskOrder")

    @property
    def owner_role(self):
        def _is_portfolio_owner(portfolio_role):
            return PermissionSets.PORTFOLIO_POC in [
                perms_set.name for perms_set in portfolio_role.permission_sets
            ]

        return first_or_none(_is_portfolio_owner, self.roles)

    @property
    def owner(self):
        owner_role = self.owner_role
        return owner_role.user if owner_role else None

    @property
    def users(self):
        return set(role.user for role in self.roles)

    @property
    def user_count(self):
        return len(self.members)

    @property
    def num_task_orders(self):
        return len(self.task_orders)

    @property
    def active_clins(self):
        return [
            clin for task_order in self.task_orders
            for clin in task_order.clins if clin.is_active
        ]

    @property
    def active_task_orders(self):
        return [
            task_order for task_order in self.task_orders
            if task_order.is_active
        ]

    @property
    def funding_duration(self):
        """
        Return the earliest period of performance start date and latest period
        of performance end date for all active task orders in a portfolio.
        @return: (datetime.date or None, datetime.date or None)  
        """
        start_dates = (task_order.start_date for task_order in self.task_orders
                       if task_order.is_active)

        end_dates = (task_order.end_date for task_order in self.task_orders
                     if task_order.is_active)

        earliest_pop_start_date = min(start_dates, default=None)
        latest_pop_end_date = max(end_dates, default=None)

        return (earliest_pop_start_date, latest_pop_end_date)

    @property
    def days_to_funding_expiration(self):
        """
        Returns the number of days between today and the lastest period performance
        end date of all active Task Orders
        """
        return max(
            (task_order.days_to_expiration
             for task_order in self.task_orders if task_order.is_active),
            default=0,
        )

    @property
    def members(self):
        return (db.session.query(PortfolioRole).filter(
            PortfolioRole.portfolio_id == self.id).filter(
                PortfolioRole.status != PortfolioRoleStatus.DISABLED).all())

    @property
    def displayname(self):
        return self.name

    @property
    def all_environments(self):
        return list(
            chain.from_iterable(p.environments for p in self.applications))

    @property
    def portfolio_id(self):
        return self.id

    @property
    def application_id(self):
        return None

    def __repr__(self):
        return "<Portfolio(name='{}', user_count='{}', id='{}')>".format(
            self.name, self.user_count, self.id)
Ejemplo n.º 10
0
class TaskOrder(Base, mixins.TimestampsMixin):
    __tablename__ = "task_orders"

    id = types.Id()

    portfolio_id = Column(ForeignKey("portfolios.id"))
    portfolio = relationship("Portfolio")

    user_id = Column(ForeignKey("users.id"))
    creator = relationship("User", foreign_keys="TaskOrder.user_id")

    pdf_attachment_id = Column(ForeignKey("attachments.id"))
    _pdf = relationship("Attachment", foreign_keys=[pdf_attachment_id])
    number = Column(String)  # Task Order Number
    signer_dod_id = Column(String)
    signed_at = Column(DateTime)

    clins = relationship("CLIN",
                         back_populates="task_order",
                         cascade="all, delete-orphan")

    @property
    def sorted_clins(self):
        return sorted(self.clins,
                      key=lambda clin: (clin.number[1:], clin.number[0]))

    @hybrid_property
    def pdf(self):
        return self._pdf

    @pdf.setter
    def pdf(self, new_pdf):
        self._pdf = self._set_attachment(new_pdf, "_pdf")

    def _set_attachment(self, new_attachment, attribute):
        if isinstance(new_attachment, Attachment):
            return new_attachment
        elif isinstance(new_attachment, dict):
            if new_attachment["filename"] and new_attachment["object_name"]:
                attachment = Attachment.get_or_create(
                    new_attachment["object_name"], new_attachment)
                return attachment
            else:
                return None
        elif not new_attachment and hasattr(self, attribute):
            return None
        else:
            raise TypeError("Could not set attachment with invalid type")

    @property
    def is_draft(self):
        return self.status == Status.DRAFT

    @property
    def is_active(self):
        return self.status == Status.ACTIVE

    @property
    def is_upcoming(self):
        return self.status == Status.UPCOMING

    @property
    def is_expired(self):
        return self.status == Status.EXPIRED

    @property
    def is_unsigned(self):
        return self.status == Status.UNSIGNED

    @property
    def has_begun(self):
        return self.start_date is not None and Clock.today() >= self.start_date

    @property
    def has_ended(self):
        return self.start_date is not None and Clock.today() >= self.end_date

    @property
    def clins_are_completed(self):
        return all(
            [len(self.clins), (clin.is_completed for clin in self.clins)])

    @property
    def is_completed(self):
        return all([self.pdf, self.number, self.clins_are_completed])

    @property
    def is_signed(self):
        return self.signed_at is not None

    @property
    def status(self):
        today = Clock.today()

        if not self.is_completed and not self.is_signed:
            return Status.DRAFT
        elif self.is_completed and not self.is_signed:
            return Status.UNSIGNED
        elif today < self.start_date:
            return Status.UPCOMING
        elif today >= self.end_date:
            return Status.EXPIRED
        elif self.start_date <= today < self.end_date:
            return Status.ACTIVE

    @property
    def start_date(self):
        return min((c.start_date for c in self.clins),
                   default=self.time_created.date())

    @property
    def end_date(self):
        default_end_date = self.start_date + timedelta(days=1)
        return max((c.end_date for c in self.clins), default=default_end_date)

    @property
    def days_to_expiration(self):
        if self.end_date:
            return (self.end_date - Clock.today()).days

    @property
    def total_obligated_funds(self):
        total = 0
        for clin in self.clins:
            if clin.obligated_amount is not None:
                total += clin.obligated_amount
        return total

    @property
    def total_contract_amount(self):
        total = 0
        for clin in self.clins:
            if clin.total_amount is not None:
                total += clin.total_amount
        return total

    @property
    # TODO delete when we delete task_order_review flow
    def budget(self):
        return 100000

    @property
    def balance(self):
        # TODO: fix task order -- reimplement using CLINs
        # Faked for display purposes
        return 50

    @property
    def display_status(self):
        return self.status.value

    @property
    def portfolio_name(self):
        return self.portfolio.name

    def to_dictionary(self):
        return {
            "portfolio_name": self.portfolio_name,
            "pdf": self.pdf,
            "clins": [clin.to_dictionary() for clin in self.clins],
            **{
                c.name: getattr(self, c.name)
                for c in self.__table__.columns if c.name not in ["id"]
            },
        }

    def __repr__(self):
        return "<TaskOrder(number='{}', id='{}')>".format(self.number, self.id)
Ejemplo n.º 11
0
class Portfolio(
    Base, mixins.TimestampsMixin, mixins.AuditableMixin, mixins.DeletableMixin
):
    __tablename__ = "portfolios"

    id = types.Id()
    name = Column(String)
    defense_component = Column(String)  # Department of Defense Component

    app_migration = Column(String)  # App Migration
    complexity = Column(ARRAY(String))  # Application Complexity
    complexity_other = Column(String)
    description = Column(String)
    dev_team = Column(ARRAY(String))  # Development Team
    dev_team_other = Column(String)
    native_apps = Column(String)  # Native Apps
    team_experience = Column(String)  # Team Experience

    applications = relationship(
        "Application",
        back_populates="portfolio",
        primaryjoin=and_(Application.portfolio_id == id, Application.deleted == False),
    )
    roles = relationship("PortfolioRole")

    task_orders = relationship("TaskOrder")

    @property
    def owner_role(self):
        def _is_portfolio_owner(portfolio_role):
            return PermissionSets.PORTFOLIO_POC in [
                perms_set.name for perms_set in portfolio_role.permission_sets
            ]

        return first_or_none(_is_portfolio_owner, self.roles)

    @property
    def owner(self):
        owner_role = self.owner_role
        return owner_role.user if owner_role else None

    @property
    def users(self):
        return set(role.user for role in self.roles)

    @property
    def user_count(self):
        return len(self.members)

    @property
    def num_task_orders(self):
        return len(self.task_orders)

    @property
    def members(self):
        return (
            db.session.query(PortfolioRole)
            .filter(PortfolioRole.portfolio_id == self.id)
            .filter(PortfolioRole.status != PortfolioRoleStatus.DISABLED)
            .all()
        )

    @property
    def displayname(self):
        return self.name

    @property
    def all_environments(self):
        return list(chain.from_iterable(p.environments for p in self.applications))

    @property
    def portfolio_id(self):
        return self.id

    @property
    def application_id(self):
        return None

    def __repr__(self):
        return "<Portfolio(name='{}', user_count='{}', id='{}')>".format(
            self.name, self.user_count, self.id
        )
Ejemplo n.º 12
0
class InvitesMixin(object):
    id = types.Id()

    @declared_attr
    def user_id(cls):
        return Column(UUID(as_uuid=True), ForeignKey("users.id"), index=True)

    @declared_attr
    def user(cls):
        return relationship("User", foreign_keys=[cls.user_id])

    @declared_attr
    def inviter_id(cls):
        return Column(UUID(as_uuid=True),
                      ForeignKey("users.id"),
                      index=True,
                      nullable=False)

    @declared_attr
    def inviter(cls):
        return relationship("User", foreign_keys=[cls.inviter_id])

    status = Column(
        SQLAEnum(Status,
                 native_enum=False,
                 default=Status.PENDING,
                 nullable=False))

    expiration_time = Column(TIMESTAMP(timezone=True), nullable=False)

    token = Column(String,
                   index=True,
                   default=lambda: secrets.token_urlsafe(),
                   nullable=False)

    email = Column(String, nullable=False)

    dod_id = Column(String, nullable=False)
    first_name = Column(String, nullable=False)
    last_name = Column(String, nullable=False)
    phone_number = Column(String)
    phone_ext = Column(String)

    def __repr__(self):
        role_id = self.role.id if self.role else None
        return "<{}(user='******', role='{}', id='{}', email='{}')>".format(
            self.__class__.__name__, self.user_id, role_id, self.id,
            self.email)

    @property
    def is_accepted(self):
        return self.status == Status.ACCEPTED

    @property
    def is_revoked(self):
        return self.status == Status.REVOKED

    @property
    def is_pending(self):
        return self.status == Status.PENDING

    @property
    def is_rejected(self):
        return self.status in [
            Status.REJECTED_WRONG_USER, Status.REJECTED_EXPIRED
        ]

    @property
    def is_rejected_expired(self):
        return self.status == Status.REJECTED_EXPIRED

    @property
    def is_rejected_wrong_user(self):
        return self.status == Status.REJECTED_WRONG_USER

    @property
    def is_expired(self):
        return (datetime.datetime.now(self.expiration_time.tzinfo) >
                self.expiration_time and not self.status == Status.ACCEPTED)

    @property
    def is_inactive(self):
        return self.is_expired or self.status in [
            Status.REJECTED_WRONG_USER,
            Status.REJECTED_EXPIRED,
            Status.REVOKED,
        ]

    @property
    def user_name(self):
        return "{} {}".format(self.first_name, self.last_name)

    @property
    def is_revokable(self):
        return self.is_pending and not self.is_expired

    @property
    def can_resend(self):
        return self.is_pending or self.is_expired

    @property
    def user_dod_id(self):
        return self.user.dod_id if self.user is not None else None

    @property
    def event_details(self):
        """Overrides the same property in AuditableMixin.
        Provides the event details for an invite that are required for the audit log
        """
        return {"email": self.email, "dod_id": self.user_dod_id}

    @property
    def history(self):
        """Overrides the same property in AuditableMixin
        Determines whether or not invite status has been updated
        """
        changes = self.get_changes()
        change_set = {}

        if "status" in changes:
            change_set["status"] = [s.name for s in changes["status"]]

        return change_set
Ejemplo n.º 13
0
class ApplicationRole(
    Base,
    mixins.TimestampsMixin,
    mixins.AuditableMixin,
    mixins.PermissionsMixin,
    mixins.DeletableMixin,
):
    __tablename__ = "application_roles"

    id = types.Id()
    application_id = Column(
        UUID(as_uuid=True), ForeignKey("applications.id"), index=True, nullable=False
    )
    application = relationship("Application", back_populates="roles")

    user_id = Column(
        UUID(as_uuid=True), ForeignKey("users.id"), index=True, nullable=True
    )

    status = Column(
        SQLAEnum(Status, native_enum=False), default=Status.PENDING, nullable=False
    )

    permission_sets = relationship(
        "PermissionSet", secondary=application_roles_permission_sets
    )

    environment_roles = relationship(
        "EnvironmentRole",
        primaryjoin="and_(EnvironmentRole.application_role_id == ApplicationRole.id, EnvironmentRole.deleted == False)",
    )

    @property
    def latest_invitation(self):
        if self.invitations:
            return self.invitations[-1]

    @property
    def user_name(self):
        if self.user:
            return self.user.full_name
        elif self.latest_invitation:
            return self.latest_invitation.user_name

    def __repr__(self):
        return "<ApplicationRole(application='{}', user_id='{}', id='{}', permissions={})>".format(
            self.application.name, self.user_id, self.id, self.permissions
        )

    @property
    def history(self):
        previous_state = self.get_changes()
        change_set = {}
        if "status" in previous_state:
            from_status = previous_state["status"][0].value
            to_status = self.status.value
            change_set["status"] = [from_status, to_status]
        return change_set

    def has_permission_set(self, perm_set_name):
        return first_or_none(
            lambda prms: prms.name == perm_set_name, self.permission_sets
        )

    @property
    def portfolio_id(self):
        return self.application.portfolio_id

    @property
    def event_details(self):
        return {
            "updated_user_name": self.user_name,
            "updated_user_id": str(self.user_id),
            "application": self.application.name,
            "portfolio": self.application.portfolio.name,
        }

    @property
    def is_pending(self):
        return self.status == Status.PENDING

    @property
    def is_active(self):
        return self.status == Status.ACTIVE

    @property
    def display_status(self):
        if (
            self.is_pending
            and self.latest_invitation
            and self.latest_invitation.is_expired
        ):
            return "invite_expired"
        elif (
            self.is_pending
            and self.latest_invitation
            and self.latest_invitation.is_pending
        ):
            return "invite_pending"
        elif self.is_active and any(
            env_role.is_pending for env_role in self.environment_roles
        ):
            return "changes_pending"

        return None
Ejemplo n.º 14
0
class PortfolioRole(Base, mixins.TimestampsMixin, mixins.AuditableMixin,
                    mixins.PermissionsMixin):
    __tablename__ = "portfolio_roles"

    id = types.Id()
    portfolio_id = Column(UUID(as_uuid=True),
                          ForeignKey("portfolios.id"),
                          index=True,
                          nullable=False)
    portfolio = relationship("Portfolio", back_populates="roles")

    user_id = Column(UUID(as_uuid=True),
                     ForeignKey("users.id"),
                     index=True,
                     nullable=True)

    status = Column(SQLAEnum(Status, native_enum=False),
                    default=Status.PENDING,
                    nullable=False)

    permission_sets = relationship("PermissionSet",
                                   secondary=portfolio_roles_permission_sets)

    def __repr__(self):
        return "<PortfolioRole(portfolio='{}', user_id='{}', id='{}', permissions={})>".format(
            self.portfolio.name, self.user_id, self.id, self.permissions)

    @property
    def history(self):
        previous_state = self.get_changes()
        change_set = {}
        if "status" in previous_state:
            from_status = previous_state["status"][0].value
            to_status = self.status.value
            change_set["status"] = [from_status, to_status]
        return change_set

    @property
    def event_details(self):
        return {
            "updated_user_name": self.user_name,
            "updated_user_id": str(self.user_id),
        }

    @property
    def latest_invitation(self):
        if self.invitations:
            return self.invitations[-1]

    @property
    def display_status(self):
        if self.status == Status.ACTIVE:
            return "active"
        elif self.status == Status.DISABLED:
            return "disabled"
        elif self.latest_invitation:
            if self.latest_invitation.is_revoked:
                return "invite_revoked"
            elif self.latest_invitation.is_rejected_wrong_user:
                return "invite_error"
            elif (self.latest_invitation.is_rejected_expired
                  or self.latest_invitation.is_expired):
                return "invite_expired"
            else:
                return "invite_pending"
        else:
            return "unknown"

    def has_permission_set(self, perm_set_name):
        return first_or_none(lambda prms: prms.name == perm_set_name,
                             self.permission_sets)

    @property
    def has_dod_id_error(self):
        return self.latest_invitation and self.latest_invitation.is_rejected_wrong_user

    @property
    def user_name(self):
        if self.user:
            return self.user.full_name
        else:
            return self.latest_invitation.user_name

    @property
    def full_name(self):
        return self.user_name

    @property
    def is_active(self):
        return self.status == Status.ACTIVE

    @property
    def can_resend_invitation(self):
        return not self.is_active and (self.latest_invitation
                                       and self.latest_invitation.is_inactive)

    @property
    def application_id(self):
        return None
Ejemplo n.º 15
0
class Environment(Base, mixins.TimestampsMixin, mixins.AuditableMixin,
                  mixins.DeletableMixin):
    __tablename__ = "environments"

    id = types.Id()
    name = Column(String, nullable=False)

    application_id = Column(ForeignKey("applications.id"), nullable=False)
    application = relationship("Application")

    # User user.id as the foreign key here beacuse the Environment creator may
    # not have an application role. We may need to revisit this if we receive any
    # requirements around tracking an environment's custodian.
    creator_id = Column(ForeignKey("users.id"), nullable=False)
    creator = relationship("User")

    cloud_id = Column(String)
    root_user_info = Column(JSONB(none_as_null=True))

    claimed_until = Column(TIMESTAMP(timezone=True))

    job_failures = relationship("EnvironmentJobFailure")

    roles = relationship(
        "EnvironmentRole",
        back_populates="environment",
        primaryjoin=
        "and_(EnvironmentRole.environment_id == Environment.id, EnvironmentRole.deleted == False)",
    )

    class ProvisioningStatus(Enum):
        PENDING = "pending"
        COMPLETED = "completed"

    @property
    def users(self):
        return {r.application_role.user for r in self.roles}

    @property
    def num_users(self):
        return len(self.users)

    @property
    def displayname(self):
        return self.name

    @property
    def portfolio(self):
        return self.application.portfolio

    @property
    def portfolio_id(self):
        return self.application.portfolio_id

    @property
    def provisioning_status(self) -> ProvisioningStatus:
        if self.cloud_id is None or self.root_user_info is None:
            return self.ProvisioningStatus.PENDING
        else:
            return self.ProvisioningStatus.COMPLETED

    @property
    def is_pending(self):
        return self.provisioning_status == self.ProvisioningStatus.PENDING

    def __repr__(self):
        return "<Environment(name='{}', num_users='{}', application='{}', portfolio='{}', id='{}')>".format(
            self.name,
            self.num_users,
            self.application.name,
            self.application.portfolio.name,
            self.id,
        )

    @property
    def history(self):
        return self.get_changes()

    @property
    def csp_credentials(self):
        return (self.root_user_info.get("credentials")
                if self.root_user_info is not None else None)