Exemplo n.º 1
0
class LocationEntry(EventBaseModel):
    backref_base_name = "location_entries"

    mission_id = db.Column(db.Integer,
                           db.ForeignKey("mission.id"),
                           index=True,
                           nullable=False)
    mission = db.relationship("Mission", backref=backref("location_entries"))

    address_id = db.Column(db.Integer,
                           db.ForeignKey("address.id"),
                           index=True,
                           nullable=False)
    _address = db.relationship("Address")

    company_known_address_id = db.Column(
        db.Integer, db.ForeignKey("company_known_address.id"), nullable=True)
    _company_known_address = db.relationship("CompanyKnownAddress")

    kilometer_reading = db.Column(db.Integer, nullable=True)

    kilometer_reading_received_at = db.Column(DateTimeStoredAsUTC,
                                              nullable=True)

    type = enum_column(LocationEntryType, nullable=False)

    __table_args__ = (db.UniqueConstraint(
        "mission_id",
        "type",
        name="only_one_location_entry_per_mission_and_type",
    ), )

    @property
    def address(self):
        return (self._company_known_address.address
                if self._company_known_address else self._address)

    def register_kilometer_reading(self, km, reception_time=None):
        if not km:
            return
        time = reception_time or datetime.now()
        is_location_at_mission_end = True
        if self.type == LocationEntryType.MISSION_START_LOCATION:
            other_location = self.mission.end_location
            is_location_at_mission_end = False
        else:
            other_location = self.mission.start_location
        if other_location and other_location.kilometer_reading:
            start_kilometer_reading = (other_location.kilometer_reading
                                       if is_location_at_mission_end else km)
            end_kilometer_reading = (km if is_location_at_mission_end else
                                     other_location.kilometer_reading)
            if not start_kilometer_reading <= end_kilometer_reading:
                raise InvalidParamsError(
                    "Kilometer reading at end of mission should be higher than kilometer reading at start"
                )

        self.kilometer_reading = km
        self.kilometer_reading_received_at = time
Exemplo n.º 2
0
class Expenditure(UserEventBaseModel, Dismissable):
    backref_base_name = "expenditures"

    mission_id = db.Column(db.Integer,
                           db.ForeignKey("mission.id"),
                           index=True,
                           nullable=False)
    mission = db.relationship("Mission", backref=backref("expenditures"))

    type = enum_column(ExpenditureType, nullable=False)

    __table_args__ = (
        db.Constraint("no_duplicate_expenditures_per_user_and_mission"), )

    def __repr__(self):
        return f"<Expenditure [{self.id}] : {self.type.value}>"
Exemplo n.º 3
0
class Email(BaseModel):
    mailjet_id = db.Column(db.TEXT, unique=True, nullable=False)

    address = db.Column(db.TEXT, nullable=False)

    type = enum_column(EmailType, nullable=False)

    user_id = db.Column(db.Integer,
                        db.ForeignKey("user.id"),
                        nullable=True,
                        index=True)
    user = db.relationship("User", backref="emails")

    employment_id = db.Column(db.Integer,
                              db.ForeignKey("employment.id"),
                              nullable=True,
                              index=True)
    employment = db.relationship("Employment", backref="invite_emails")
Exemplo n.º 4
0
class Activity(UserEventBaseModel, Dismissable, Period):
    backref_base_name = "activities"

    mission_id = db.Column(db.Integer,
                           db.ForeignKey("mission.id"),
                           index=True,
                           nullable=False)
    mission = db.relationship("Mission", backref=backref("activities"))

    type = enum_column(ActivityType, nullable=False)

    last_update_time = db.Column(DateTimeStoredAsUTC, nullable=False)

    editable_fields = {"start_time", "end_time"}

    __table_args__ = (
        db.Constraint(name="no_overlapping_acknowledged_activities"),
        db.Constraint(name="activity_start_time_before_end_time"),
        db.Constraint(name="activity_start_time_before_update_time"),
        db.Constraint(name="activity_end_time_before_update_time"),
        db.Constraint(name="no_successive_activities_with_same_type"),
    )

    # TODO : add (maybe)
    # - validator
    # - version (each version represents a set of changes to the day activities)
    # OR revises (indicates which prior activity the current one revises)

    def __repr__(self):
        return f"<Activity [{self.id}] : {self.type.value}>"

    def latest_version_number(self):
        return (max([r.version
                     for r in self.revisions]) if self.revisions else None)

    def version_at(self, at_time):
        if self.reception_time > at_time:
            return None
        if self.dismissed_at and self.dismissed_at <= at_time:
            return None
        return max(
            [r for r in self.revisions if r.reception_time <= at_time],
            key=lambda r: r.version,
        )

    def revise(
        self,
        revision_time,
        revision_context=None,
        bypass_check=False,
        **updated_props,
    ):
        from app.domain.log_activities import handle_activities_update

        if self.is_dismissed:
            raise ResourceAlreadyDismissedError("Activity already dismissed")

        if not set(updated_props.keys()) <= Activity.editable_fields:
            raise ValueError("Bad arguments to revise method")

        new = {
            field: updated_props.get(field, getattr(self, field))
            for field in Activity.editable_fields
        }
        old = {
            field: getattr(self, field)
            for field in Activity.editable_fields
        }

        if new == old:
            app.logger.warning(
                "No changes detected for the activity",
                extra={"to_secondary_slack_channel": True},
            )
            return None

        with handle_activities_update(
                submitter=current_user,
                user=self.user,
                mission=self.mission,
                reception_time=revision_time,
                start_time=new["start_time"],
                end_time=new["end_time"],
                bypass_check=bypass_check,
        ):
            revision = ActivityVersion(
                activity=self,
                reception_time=revision_time,
                start_time=new["start_time"],
                end_time=new["end_time"],
                context=revision_context,
                version=(self.latest_version_number() or 0) + 1,
                submitter=current_user,
            )
            db.session.add(revision)

            for field, value in updated_props.items():
                setattr(self, field, value)

            self.last_update_time = revision_time
            db.session.add(self)

            return revision

    def dismiss(self, dismiss_time=None, context=None):
        from app.domain.log_activities import handle_activities_update

        if not dismiss_time:
            dismiss_time = datetime.now()

        with handle_activities_update(
                submitter=current_user,
                user=self.user,
                mission=self.mission,
                reception_time=dismiss_time,
                start_time=self.start_time,
                end_time=None,
                bypass_check=True,
                reopen_mission_if_needed=False,
        ):
            super().dismiss(dismiss_time, context)
            self.last_update_time = self.dismissed_at
Exemplo n.º 5
0
class C1BSigningKey(BaseModel, RSAKey):
    __tablename__ = "c1b_signing_key"

    owner_type = enum_column(SigningKeyOwnerType, nullable=False)

    _modulus = db.Column(BYTEA, nullable=False)
    _private_exp = db.Column(BYTEA, nullable=False)
    _public_exp = db.Column(BYTEA, nullable=False)

    modulus_length = db.Column(db.Integer, nullable=False, default=128)

    serial_number = db.Column(db.Integer, nullable=False)

    @classmethod
    @cache_at_request_scope
    def get_current_root_key(cls):
        return (cls.query.filter(
            cls.owner_type == SigningKeyOwnerType.ROOT).order_by(
                desc(cls.serial_number)).limit(1).one_or_none())

    @classmethod
    @cache_at_request_scope
    def get_or_create_current_member_state_key(cls):
        current_ms_key = (cls.query.filter(
            cls.owner_type == SigningKeyOwnerType.MEMBER_STATE).order_by(
                desc(cls.serial_number)).limit(1).one_or_none())
        if not current_ms_key:
            return cls.generate_new_key(SigningKeyOwnerType.MEMBER_STATE)
        return current_ms_key

    @classmethod
    @cache_at_request_scope
    def get_or_create_current_card_key(cls):
        current_card_key = (cls.query.filter(
            cls.owner_type == SigningKeyOwnerType.CARD).order_by(
                desc(cls.serial_number)).limit(1).one_or_none())
        if not current_card_key:
            return cls.generate_new_key(SigningKeyOwnerType.CARD)
        return current_card_key

    @cached_property
    def modulus(self):
        return int.from_bytes(self._modulus, "big")

    @cached_property
    def private_exponent(self):
        return int.from_bytes(self._private_exp, "big")

    @cached_property
    def public_exponent(self):
        return int.from_bytes(self._public_exp, "big")

    @classmethod
    def generate_new_key(cls, type, modulus_length=128):
        current_key = (cls.query.filter(cls.owner_type == type).order_by(
            desc(cls.serial_number)).limit(1).one_or_none())
        new_serial_number = current_key.serial_number + 1 if current_key else 1

        pub_exp = 65537
        new_key = rsa.generate_private_key(public_exponent=pub_exp,
                                           key_size=modulus_length * 8)
        numbers = new_key.private_numbers()
        priv_exp = numbers.d
        mod = numbers.public_numbers.n

        key = cls(
            owner_type=type,
            _modulus=mod.to_bytes(modulus_length, "big"),
            _public_exp=pub_exp.to_bytes(modulus_length, "big"),
            _private_exp=priv_exp.to_bytes(modulus_length, "big"),
            modulus_length=modulus_length,
            serial_number=new_serial_number,
        )
        db.session.add(key)
        db.session.commit()
        return key

    @property
    def reference(self):
        from app.helpers.tachograph import _int_string_to_bcd

        if self.owner_type == SigningKeyOwnerType.ROOT:
            return (b"\xfcMLR" + self.serial_number.to_bytes(1, "big") +
                    b"\xff\xff\x01")
        if self.owner_type == SigningKeyOwnerType.MEMBER_STATE:
            return (b"\xfcMLM" + self.serial_number.to_bytes(1, "big") +
                    b"\xff\xff\x01")
        serial_number = self.serial_number.to_bytes(4, "big")
        cert_date = _int_string_to_bcd(self.creation_time.strftime("%m%y"))
        return serial_number + cert_date + b"\xff\x01"

    # https://eur-lex.europa.eu/legal-content/FR/TXT/PDF/?uri=CELEX:02016R0799-20200226&from=EN#page=367
    @cached(
        cache=LRUCache(maxsize=10),
        key=lambda s, a: hash(
            (s.__class__, s.id or s, a.__class__, a.id or a)),
    )
    def certificate(self, authority):
        if not authority:
            raise EnvironmentError("Signing service is unavailable")

        certificate = bytearray()
        certificate.extend(b"\x01")
        certificate.extend(authority.reference)
        certificate.extend(b"\xffMBLIC\x01\xff\xff\xff\xff")

        certificate.extend(self.reference)

        certificate.extend(self.modulus.to_bytes(self.modulus_length, "big"))
        certificate.extend(self.public_exponent.to_bytes(8, "big"))

        certificate_hash = sha1(certificate).digest()
        cert_part_to_sign = certificate[:106]
        cert_part_to_add_in_clear = certificate[106:]

        to_sign = b"\x6A" + cert_part_to_sign + certificate_hash + b"\xBC"

        signed = authority.sign(to_sign)

        return signed + cert_part_to_add_in_clear + authority.reference
Exemplo n.º 6
0
class Employment(UserEventBaseModel, Dismissable):
    backref_base_name = "employments"

    is_primary = db.Column(db.Boolean, nullable=True)

    validation_time = db.Column(DateTimeStoredAsUTC, nullable=True)

    validation_status = enum_column(EmploymentRequestValidationStatus,
                                    nullable=False)

    start_date = db.Column(db.Date, nullable=False)
    end_date = db.Column(db.Date, nullable=True)

    company_id = db.Column(db.Integer,
                           db.ForeignKey("company.id"),
                           index=True,
                           nullable=False)
    company = db.relationship("Company", backref="employments")

    has_admin_rights = db.Column(db.Boolean, nullable=True)

    user_id = db.Column(db.Integer,
                        db.ForeignKey("user.id"),
                        index=True,
                        nullable=True)
    email = db.Column(db.String(255), nullable=True)
    invite_token = db.Column(db.String(255), nullable=True, unique=True)

    __table_args__ = (
        db.Constraint(name="only_one_current_primary_enrollment_per_user"),
        db.Constraint(name="no_simultaneous_enrollments_for_the_same_company"),
        db.Constraint(name="no_undefined_employment_type_for_user"),
    )

    def __repr__(self):
        return f"<Employment [{self.id}] of User {self.user_id} in Company {self.company_id}>"

    @property
    def is_not_rejected(self):
        return (self.validation_status !=
                EmploymentRequestValidationStatus.REJECTED)

    @property
    def is_acknowledged(self):
        return (self.validation_status
                == EmploymentRequestValidationStatus.APPROVED
                and not self.is_dismissed)

    def bind(self, user):
        self.user_id = user.id
        for email in self.invite_emails:
            email.user_id = user.id

    def validate_by(self, user, time=None, reject=False):
        if not self.user_id == user.id:
            raise AuthorizationError(
                "Actor is not authorized to review the employment")

        if (not self.validation_status
                == EmploymentRequestValidationStatus.PENDING
                or self.is_dismissed):
            raise InvalidResourceError(
                f"Employment is already {'validated' if self.is_acknowledged else 'dismissed' if self.is_dismissed else 'rejected'}"
            )

        self.validation_status = (EmploymentRequestValidationStatus.APPROVED
                                  if not reject else
                                  EmploymentRequestValidationStatus.REJECTED)
        self.validation_time = time if time else datetime.now()