class ActivitySchema(colander.MappingSchema): """ Schema for CustomContent. """ engagement_id = colander.SchemaNode( colander.Integer(), title=_(u"Engagement"), widget=deferred_engagement_select_widget) start_dt = colander.SchemaNode( colander.DateTime( default_tzinfo=FixedOffset(8, 0, 'Asia/Kuala_Lumpur')), title=_(u'Start'), widget=DateTimeInputWidget(time_options=(('format', 'h:i A'), ('interval', 60))), default=deferred_default_dt) end_dt = colander.SchemaNode( colander.DateTime( default_tzinfo=FixedOffset(8, 0, 'Asia/Kuala_Lumpur')), title=_(u'End'), widget=DateTimeInputWidget(time_options=(('format', 'h:i A'), ('interval', 60))), default=deferred_default_dt) summary = colander.SchemaNode( colander.String(), title=_('Activity Summary'), widget=RichTextWidget(), missing=u"", ) issues = colander.SchemaNode( colander.String(), title=_('Issues Faced'), widget=RichTextWidget(), missing=u"", ) tags = colander.SchemaNode( ObjectType(), title=_('Tags'), widget=deferred_tag_it_widget, missing=[], )
class OptionalPendulumNodeUTC(SchemaNode): """ Colander node containing an optional :class:`Pendulum` date/time, in which the date/time is assumed to be UTC. """ @staticmethod def schema_type() -> SchemaType: return AllowNoneType(PendulumType(use_local_tz=False)) default = None missing = None widget = DateTimeInputWidget( date_options=DEFAULT_WIDGET_DATE_OPTIONS_FOR_PENDULUM, time_options=DEFAULT_WIDGET_TIME_OPTIONS_FOR_PENDULUM, )
class BlogEntrySchema(DocumentSchema): date = colander.SchemaNode( colander.DateTime(), title=_(u'Date'), description=_(u'Choose date of the blog entry. ' u'If you leave this empty the creation date is used.'), validator=colander.Range( min=datetime.datetime(2012, 1, 1, 0, 0, tzinfo=colander.iso8601.Utc()), min_err=_('${val} is earlier than earliest datetime ${min}')), widget=DateTimeInputWidget(), missing=deferred_date_missing, )
class User(Base): __tablename__ = "user" __table_args__ = {"schema": _schema} __colanderalchemy_config__ = {"title": _("User"), "plural": _("Users")} __c2cgeoform_config__ = {"duplicate": True} item_type = Column("type", String(10), nullable=False, info={"colanderalchemy": { "widget": HiddenWidget() }}) __mapper_args__ = { "polymorphic_on": item_type, "polymorphic_identity": "user" } id = Column(Integer, primary_key=True, info={"colanderalchemy": { "widget": HiddenWidget() }}) username = Column(Unicode, unique=True, nullable=False, info={"colanderalchemy": { "title": _("Username") }}) _password = Column("password", Unicode, nullable=False, info={"colanderalchemy": { "exclude": True }}) temp_password = Column("temp_password", Unicode, nullable=True, info={"colanderalchemy": { "exclude": True }}) tech_data = Column(MutableDict.as_mutable(HSTORE), info={"colanderalchemy": { "exclude": True }}) email = Column( Unicode, nullable=False, info={"colanderalchemy": { "title": _("Email"), "validator": Email() }}) is_password_changed = Column(Boolean, default=False, info={ "colanderalchemy": { "title": _("The user changed his password") } }) settings_role_id = Column( Integer, info={ "colanderalchemy": { "title": _("Settings from role"), "description": "Only used for settings not for permissions", "widget": RelationSelect2Widget(Role, "id", "name", order_by="name", default_value=("", _("- Select -"))), } }, ) settings_role = relationship( Role, foreign_keys="User.settings_role_id", primaryjoin="Role.id==User.settings_role_id", info={ "colanderalchemy": { "title": _("Settings role"), "exclude": True } }, ) roles = relationship( Role, secondary=user_role, secondaryjoin=Role.id == user_role.c.role_id, backref=backref("users", order_by="User.username", info={"colanderalchemy": { "exclude": True }}), info={"colanderalchemy": { "title": _("Roles"), "exclude": True }}, ) last_login = Column( DateTime(timezone=True), info={ "colanderalchemy": { "title": _("Last login"), "missing": drop, "widget": DateTimeInputWidget(readonly=True), } }, ) expire_on = Column( DateTime(timezone=True), info={"colanderalchemy": { "title": _("Expiration date") }}) deactivated = Column(Boolean, default=False, info={"colanderalchemy": { "title": _("Deactivated") }}) def __init__( self, username: str = "", password: str = "", email: str = "", is_password_changed: bool = False, settings_role: Role = None, roles: List[Role] = None, expire_on: datetime = None, deactivated: bool = False, ) -> None: self.username = username self.password = password self.tech_data = {} self.email = email self.is_password_changed = is_password_changed if settings_role: self.settings_role = settings_role self.roles = roles or [] self.expire_on = expire_on self.deactivated = deactivated @property def password(self) -> str: """returns password""" return self._password # type: ignore @password.setter def password(self, password: str) -> None: """encrypts password on the fly.""" self._password = self.__encrypt_password(password) def set_temp_password(self, password: str) -> None: """encrypts password on the fly.""" self.temp_password = self.__encrypt_password(password) @staticmethod def __encrypt_password_legacy(password: str) -> str: """Hash the given password with SHA1.""" return sha1(password.encode("utf8")).hexdigest() # nosec @staticmethod def __encrypt_password(password: str) -> str: return crypt.crypt(password, crypt.METHOD_SHA512) def validate_password(self, passwd: str) -> bool: """Check the password against existing credentials. this method _MUST_ return a boolean. @param passwd: the password that was provided by the user to try and authenticate. This is the clear text version that we will need to match against the (possibly) encrypted one in the database. """ if self._password.startswith("$"): # new encryption method if compare_hash(self._password, crypt.crypt(passwd, self._password)): return True else: # legacy encryption method if compare_hash(self._password, self.__encrypt_password_legacy(passwd)): # convert to the new encryption method self._password = self.__encrypt_password(passwd) return True if (self.temp_password is not None and self.temp_password != "" and compare_hash(self.temp_password, crypt.crypt(passwd, self.temp_password))): self._password = self.temp_password self.temp_password = None self.is_password_changed = False return True return False def expired(self) -> bool: return self.expire_on is not None and self.expire_on < datetime.now( pytz.utc) def update_last_login(self) -> None: self.last_login = datetime.now(pytz.utc) def __str__(self) -> str: return self.username or ""
class User(Base): __tablename__ = 'user' __table_args__ = {'schema': _schema} __colanderalchemy_config__ = {'title': _('User'), 'plural': _('Users')} __c2cgeoform_config__ = {'duplicate': True} item_type = Column('type', String(10), nullable=False, info={'colanderalchemy': { 'widget': HiddenWidget() }}) __mapper_args__ = { 'polymorphic_on': item_type, 'polymorphic_identity': 'user', } id = Column(Integer, primary_key=True, info={'colanderalchemy': { 'widget': HiddenWidget() }}) username = Column(Unicode, unique=True, nullable=False, info={'colanderalchemy': { 'title': _('Username') }}) _password = Column('password', Unicode, nullable=False, info={'colanderalchemy': { 'exclude': True }}) temp_password = Column('temp_password', Unicode, nullable=True, info={'colanderalchemy': { 'exclude': True }}) email = Column(Unicode, nullable=False, info={ 'colanderalchemy': { 'title': _('Email'), 'validator': colander.Email() } }) is_password_changed = Column(Boolean, default=False, info={'colanderalchemy': { 'exclude': True }}) role_name = Column(String, info={ 'colanderalchemy': { 'title': _('Role'), 'widget': deform_ext.RelationSelect2Widget( Role, 'name', 'name', order_by='name', default_value=('', _('- Select -'))) } }) _cached_role_name = None # type: str _cached_role = None # type: Optional[Role] last_login = Column(DateTime(timezone=True), info={ 'colanderalchemy': { 'title': _('Last login'), 'missing': colander.drop, 'widget': DateTimeInputWidget(readonly=True) } }) expire_on = Column( DateTime(timezone=True), info={'colanderalchemy': { 'title': _('Expiration date') }}) deactivated = Column(Boolean, default=False, info={'colanderalchemy': { 'title': _('Deactivated') }}) @property def role(self) -> Optional[Role]: if self._cached_role_name == self.role_name: return self._cached_role if self.role_name is None or self.role_name == '': # pragma: no cover self._cached_role_name = self.role_name self._cached_role = None return None result = self._sa_instance_state.session.query(Role).filter( Role.name == self.role_name).all() if len(result) == 0: # pragma: no cover self._cached_role = None else: self._cached_role = result[0] self._cached_role_name = self.role_name return self._cached_role def __init__(self, username: str = '', password: str = '', email: str = '', is_password_changed: bool = False, role: Role = None, expire_on: datetime = None, deactivated: bool = False) -> None: self.username = username self.password = password self.email = email self.is_password_changed = is_password_changed if role is not None: self.role_name = role.name self.expire_on = expire_on self.deactivated = deactivated @property def password(self) -> str: """returns password""" return self._password # pragma: no cover @password.setter def password(self, password: str) -> None: """encrypts password on the fly.""" self._password = self.__encrypt_password(password) def set_temp_password(self, password: str) -> None: """encrypts password on the fly.""" self.temp_password = self.__encrypt_password(password) @staticmethod def __encrypt_password(password: str) -> str: """Hash the given password with SHA1.""" return sha1(password.encode('utf8')).hexdigest() def validate_password(self, passwd: str) -> bool: """Check the password against existing credentials. this method _MUST_ return a boolean. @param passwd: the password that was provided by the user to try and authenticate. This is the clear text version that we will need to match against the (possibly) encrypted one in the database. @type password: string """ if self._password == self.__encrypt_password(passwd): return True if \ self.temp_password is not None and \ self.temp_password != '' and \ self.temp_password == self.__encrypt_password(passwd): self._password = self.temp_password self.temp_password = None self.is_password_changed = False return True return False def expired(self) -> bool: return self.expire_on is not None and self.expire_on < datetime.now( pytz.utc) def update_last_login(self) -> None: self.last_login = datetime.now(pytz.utc) def __unicode__(self) -> str: return self.username or '' # pragma: no cover
class User(Base): __tablename__ = 'user' __table_args__ = {'schema': _schema} __colanderalchemy_config__ = { 'title': _('User'), 'plural': _('Users') } __c2cgeoform_config__ = { 'duplicate': True } item_type = Column('type', String(10), nullable=False, info={ 'colanderalchemy': { 'widget': HiddenWidget() } }) __mapper_args__ = { 'polymorphic_on': item_type, 'polymorphic_identity': 'user', } id = Column(Integer, primary_key=True, info={ 'colanderalchemy': { 'widget': HiddenWidget() } }) username = Column(Unicode, unique=True, nullable=False, info={ 'colanderalchemy': { 'title': _('Username') } }) _password = Column('password', Unicode, nullable=False, info={'colanderalchemy': {'exclude': True}}) temp_password = Column('temp_password', Unicode, nullable=True, info={'colanderalchemy': {'exclude': True}}) email = Column(Unicode, nullable=False, info={ 'colanderalchemy': { 'title': _('Email'), 'validator': colander.Email() } }) is_password_changed = Column(Boolean, default=False, info={'colanderalchemy': {'exclude': True}}) settings_role_id = Column(Integer, info={ 'colanderalchemy': { 'title': _('Settings from role'), 'description': 'Only used for settings not for permissions', 'widget': deform_ext.RelationSelect2Widget( Role, 'id', 'name', order_by='name', default_value=('', _('- Select -')) ) } }) settings_role = relationship( Role, foreign_keys='User.settings_role_id', primaryjoin='Role.id==User.settings_role_id', info={ 'colanderalchemy': { 'title': _('Settings role'), 'exclude': True } }) roles = relationship( Role, secondary=user_role, secondaryjoin=Role.id == user_role.c.role_id, backref=backref('users', info={'colanderalchemy': {'exclude': True}}), info={ 'colanderalchemy': { 'title': _('Roles'), 'exclude': True } } ) last_login = Column(DateTime(timezone=True), info={ 'colanderalchemy': { 'title': _('Last login'), 'missing': colander.drop, 'widget': DateTimeInputWidget(readonly=True) } }) expire_on = Column(DateTime(timezone=True), info={ 'colanderalchemy': { 'title': _('Expiration date') } }) deactivated = Column(Boolean, default=False, info={ 'colanderalchemy': { 'title': _('Deactivated') } }) def __init__( self, username: str = '', password: str = '', email: str = '', is_password_changed: bool = False, settings_role: Role = None, roles: List[Role] = [], expire_on: datetime = None, deactivated: bool = False ) -> None: self.username = username self.password = password self.email = email self.is_password_changed = is_password_changed if settings_role: self.settings_role = settings_role self.roles = roles self.expire_on = expire_on self.deactivated = deactivated @property def password(self) -> str: """returns password""" return self._password # pragma: no cover @password.setter def password(self, password: str) -> None: """encrypts password on the fly.""" self._password = self.__encrypt_password(password) def set_temp_password(self, password: str) -> None: """encrypts password on the fly.""" self.temp_password = self.__encrypt_password(password) @staticmethod def __encrypt_password_legacy(password: str) -> str: """Hash the given password with SHA1.""" return sha1(password.encode('utf8')).hexdigest() @staticmethod def __encrypt_password(password: str) -> str: # TODO: remove pylint disable when https://github.com/PyCQA/pylint/issues/3047 is fixed return crypt.crypt(password, crypt.METHOD_SHA512) # pylint: disable=E1101 def validate_password(self, passwd: str) -> bool: """Check the password against existing credentials. this method _MUST_ return a boolean. @param passwd: the password that was provided by the user to try and authenticate. This is the clear text version that we will need to match against the (possibly) encrypted one in the database. """ if self._password.startswith('$'): # new encryption method if compare_hash(self._password, crypt.crypt(passwd, self._password)): return True else: # legacy encryption method if compare_hash(self._password, self.__encrypt_password_legacy(passwd)): # convert to the new encryption method self._password = self.__encrypt_password(passwd) return True if \ self.temp_password is not None and \ self.temp_password != '' and \ compare_hash(self.temp_password, crypt.crypt(passwd, self.temp_password)): self._password = self.temp_password self.temp_password = None self.is_password_changed = False return True return False def expired(self) -> bool: return self.expire_on is not None and self.expire_on < datetime.now(pytz.utc) def update_last_login(self) -> None: self.last_login = datetime.now(pytz.utc) def __unicode__(self) -> str: return self.username or '' # pragma: no cover
class SoftwareProjectSchema(DocumentSchema): choices = ( ('', '- Select -'), ('use_entered', 'Used entered description (can be blank)'), ('use_pypi_summary', 'Use summary in PyPI data'), ('use_pypi_description', 'Use description in PyPI data'), ('use_github_description', 'Use description in GitHub data'), ('use_bitbucket_description', 'Use description in Bitbucket data')) desc_handling_choice = colander.SchemaNode( colander.String(), default='use_entered', missing='use_entered', title=_(u'Description Handling'), widget=SelectWidget(values=choices)) pypi_url = colander.SchemaNode( colander.String(), title=_(u'PyPI URL'), description=_(u'Enter unless doing a manual entry.'), missing=_(''),) choices = ( ('', '- Select -'), ('use_entered', 'Use entered date'), ('use_pypi_date', 'Use date in PyPI data'), ('use_github_date', 'Use date in GitHub data'), ('use_bitbucket_date', 'Use date in Bitbucket data'), ('use_now', 'Use current date and time')) date_handling_choice = colander.SchemaNode( colander.String(), default='use_pypi_date', title=_(u'Date Handling'), widget=SelectWidget(values=choices)) date = colander.SchemaNode( colander.DateTime(), title=_(u'Date'), description=_(u'Enter date only if date handling = use_entered.'), validator=colander.Range( min=datetime.datetime(2012, 1, 1, 0, 0, tzinfo=colander.iso8601.Utc()), min_err=_('${val} is too early; min date now is ${min}')), widget=DateTimeInputWidget(), missing=deferred_date_missing,) home_page_url = colander.SchemaNode( colander.String(), title=_(u'Home Page URL'), description=_(u'Leave blank usually, and the URL will be fetched.'), missing=_(''),) overwrite_home_page_url = colander.SchemaNode( colander.Boolean(), description='Overwrite home page from PyPI', default=True, missing=True, widget=CheckboxWidget(), title='') docs_url = colander.SchemaNode( colander.String(), title=_(u'Docs URL'), description=_(u'Leave blank usually, and the URL will be fetched.'), missing=_(''),) overwrite_docs_url = colander.SchemaNode( colander.Boolean(), description='Overwrite docs URL from PyPI', default=True, missing=True, widget=CheckboxWidget(), title='') package_url = colander.SchemaNode( colander.String(), title=_(u'Download URL'), description=_(u'Leave blank usually, and the URL will be fetched.'), missing=_(''),) overwrite_package_url = colander.SchemaNode( colander.Boolean(), description='Overwrite package URL from PyPI', default=True, missing=True, widget=CheckboxWidget(), title='') bugtrack_url = colander.SchemaNode( colander.String(), title=_(u'Bugtracker URL'), description=_(u'Leave blank usually, and the URL will be fetched.'), missing=_(''),) overwrite_bugtrack_url = colander.SchemaNode( colander.Boolean(), description='Overwrite bugtracker URL from PyPI', default=True, missing=True, widget=CheckboxWidget(), title='') github_owner = colander.SchemaNode( colander.String(), title=_(u'GitHub Owner'), description=_(u'Name of the owner on GitHub for the project repo.'), missing=_(''),) github_repo = colander.SchemaNode( colander.String(), title=_(u'GitHub Repo'), description=_(u'Name of the repo on GitHub for the project.'), missing=_(''),) bitbucket_owner = colander.SchemaNode( colander.String(), title=_(u'Bitbucket Owner'), description=_(u'Name of the owner on Bitbucket for the project repo.'), missing=_(''),) bitbucket_repo = colander.SchemaNode( colander.String(), title=_(u'Bitbucket Repo'), description=_(u'Name of the repo on Bitbucket for the project.'), missing=_(''),)
def __init__(self, title, name, round=1, missing='', widget=None, mapinit=None, processing_form=None, desc=None, table_reduce=False, rank=0, required=False, missing_msg='champ requis', primary_key=False): # Form display self.title = title self.name = name self.required = required self.missing_msg = missing_msg self.type = str self.desc = desc self.round = round if widget is None: keyw = { 'key_date': '{}-date'.format(self.name), 'key_time': '{}-time'.format(self.name) } self.widget = DateTimeInputWidget(**keyw) self.mapinit = {'date': None, 'time': None} processing_form = lambda x: pd.Timestamp('{} {}'.format( x['date'], x['time'])) else: self.widget = widget self.mapinit = mapinit # Db column self.dbcol_ = (name, Colsql(Strsql(250), primary_key=primary_key)) # Table display self.table_reduce, self.rank, = table_reduce, rank self.processing_form = { 'form': processing_form, 'db': lambda x: pd.Timestamp(x) } self.processing_db = { 'upload': lambda x: str(pd.Timestamp(x)), 'download': lambda x: str(x) } self.sn = sn(DateTime(), title=self.title, name=name, widget=self.widget, description=desc, required=self.required, missing_msg=self.missing_msg) if not required: self.sn.missing = missing
def widget(self, kw: dict): widget = DateTimeInputWidget() schema = widget._pstruct_schema schema['date_submit'].missing = null # Fix readonly template bug schema['time_submit'].missing = null return widget
class User(Base): # type: ignore """The user table representation.""" __tablename__ = "user" __table_args__ = {"schema": _schema} __colanderalchemy_config__ = { "title": _("User"), "plural": _("Users"), "description": Literal( _(""" <div class="help-block"> <p>Each user may have from 1 to n roles, but each user has a default role from which are taken some settings. The default role (defined through the "Settings from role" selection) has an influence on the role extent and on some functionalities regarding their configuration.</p> <p>Role extents for users can only be set in one role, because the application is currently not able to check multiple extents for one user, thus it is the default role which defines this unique extent.</p> <p>Any functionality specified as <b>single</b> can be defined only once per user. Hence, these functionalities have to be defined in the default role.</p> <p>By default, functionalities are not specified as <b>single</b>. Currently, the following functionalities are of <b>single</b> type:</p> <ul> <li><code>default_basemap</code></li> <li><code>default_theme</code></li> <li><code>preset_layer_filter</code></li> <li><code>open_panel</code></li> </ul> <p>Any other functionality (with <b>single</b> not set or set to <code>false</code>) can be defined in any role linked to the user.</p> <hr> </div> """)), } __c2cgeoform_config__ = {"duplicate": True} item_type = Column("type", String(10), nullable=False, info={"colanderalchemy": { "widget": HiddenWidget() }}) __mapper_args__ = { "polymorphic_on": item_type, "polymorphic_identity": "user" } id = Column(Integer, primary_key=True, info={"colanderalchemy": { "widget": HiddenWidget() }}) username = Column( Unicode, unique=True, nullable=False, info={ "colanderalchemy": { "title": _("Username"), "description": _("Name used for authentication (must be unique)."), } }, ) _password = Column("password", Unicode, nullable=False, info={"colanderalchemy": { "exclude": True }}) temp_password = Column("temp_password", Unicode, nullable=True, info={"colanderalchemy": { "exclude": True }}) tech_data = Column(MutableDict.as_mutable(HSTORE), info={"colanderalchemy": { "exclude": True }}) email = Column( Unicode, nullable=False, info={ "colanderalchemy": { "title": _("Email"), "description": _("Used to send emails to the user, for example in case of password recovery." ), "validator": Email(), } }, ) is_password_changed = Column( Boolean, default=False, info={ "colanderalchemy": { "title": _("The user changed his password"), "description": _("Indicates if user has changed his password."), } }, ) settings_role_id = Column( Integer, info={ "colanderalchemy": { "title": _("Settings from role"), "description": _("Used to get some settings for the user (not for permissions)." ), "widget": RelationSelect2Widget(Role, "id", "name", order_by="name", default_value=("", _("- Select -"))), } }, ) settings_role = relationship( Role, foreign_keys="User.settings_role_id", primaryjoin="Role.id==User.settings_role_id", info={ "colanderalchemy": { "title": _("Settings role"), "exclude": True } }, ) roles = relationship( Role, secondary=user_role, secondaryjoin=Role.id == user_role.c.role_id, backref=backref( "users", order_by="User.username", info={ "colanderalchemy": { "title": _("Users"), "description": _("Users granted with this role."), "exclude": True, } }, ), info={ "colanderalchemy": { "title": _("Roles"), "description": _("Roles granted to the user."), "exclude": True, } }, ) last_login = Column( DateTime(timezone=True), info={ "colanderalchemy": { "title": _("Last login"), "description": _("Date of the user's last login."), "missing": drop, "widget": DateTimeInputWidget(readonly=True), } }, ) expire_on = Column( DateTime(timezone=True), info={ "colanderalchemy": { "title": _("Expiration date"), "description": _("After this date the user will not be able to login anymore." ), } }, ) deactivated = Column( Boolean, default=False, info={ "colanderalchemy": { "title": _("Deactivated"), "description": _("Deactivate a user without removing it completely."), } }, ) def __init__( # nosec self, username: str = "", password: str = "", email: str = "", is_password_changed: bool = False, settings_role: Optional[Role] = None, roles: Optional[List[Role]] = None, expire_on: Optional[datetime] = None, deactivated: bool = False, ) -> None: self.username = username self.password = password self.tech_data = {} self.email = email self.is_password_changed = is_password_changed if settings_role: self.settings_role = settings_role self.roles = roles or [] self.expire_on = expire_on self.deactivated = deactivated @property def password(self) -> str: """Get the password.""" return self._password # type: ignore @password.setter def password(self, password: str) -> None: """Encrypt password on the fly.""" self._password = self.__encrypt_password(password) def set_temp_password(self, password: str) -> None: """Encrypt password on the fly.""" self.temp_password = self.__encrypt_password(password) @staticmethod def __encrypt_password_legacy(password: str) -> str: """Hash the given password with SHA1.""" return sha1(password.encode("utf8")).hexdigest() # nosec @staticmethod def __encrypt_password(password: str) -> str: return crypt.crypt(password, crypt.METHOD_SHA512) def validate_password(self, passwd: str) -> bool: """ Check the password against existing credentials. this method _MUST_ return a boolean. @param passwd: the password that was provided by the user to try and authenticate. This is the clear text version that we will need to match against the (possibly) encrypted one in the database. """ if self._password.startswith("$"): # new encryption method if compare_hash(self._password, crypt.crypt(passwd, self._password)): return True else: # legacy encryption method if compare_hash(self._password, self.__encrypt_password_legacy(passwd)): # convert to the new encryption method self._password = self.__encrypt_password(passwd) return True if (self.temp_password is not None and self.temp_password != "" # nosec and compare_hash(self.temp_password, crypt.crypt(passwd, self.temp_password))): self._password = self.temp_password self.temp_password = None self.is_password_changed = False return True return False def expired(self) -> bool: return self.expire_on is not None and self.expire_on < datetime.now( pytz.utc) def update_last_login(self) -> None: self.last_login = datetime.now(pytz.utc) def __str__(self) -> str: return self.username or ""