示例#1
0
class Invoice(metaclass=PoolMeta):
    __name__ = 'account.invoice'
    numbered_at = fields.Timestamp("Numbered At")

    @classmethod
    def __register__(cls, module_name):
        super().__register__(module_name)
        table_h = cls.__table_handler__(module_name)
        table = cls.__table__()
        cursor = Transaction().connection.cursor()

        # Migration from 5.2: rename open_date into numbered_at
        if table_h.column_exist('open_date'):
            cursor.execute(
                *table.update(
                    [table.numbered_at],
                    [table.open_date]))
            table_h.drop_column('open_date')

    @classmethod
    def __setup__(cls):
        super(Invoice, cls).__setup__()
        cls._check_modify_exclude.append('numbered_at')
        cls.party.datetime_field = 'numbered_at'
        if 'numbered_at' not in cls.party.depends:
            cls.party.depends.append('numbered_at')
        cls.invoice_address.datetime_field = 'numbered_at'
        if 'numbered_at' not in cls.invoice_address.depends:
            cls.invoice_address.depends.append('numbered_at')
        cls.payment_term.datetime_field = 'numbered_at'
        if 'numbered_at' not in cls.payment_term.depends:
            cls.payment_term.depends.append('numbered_at')

    @classmethod
    def set_number(cls, invoices):
        numbered = [i for i in invoices if not i.number or not i.numbered_at]
        super(Invoice, cls).set_number(invoices)
        if numbered:
            cls.write(numbered, {
                    'numbered_at': CurrentTimestamp(),
                    })

    @classmethod
    @ModelView.button
    @Workflow.transition('draft')
    def draft(cls, invoices):
        super(Invoice, cls).draft(invoices)
        cls.write(invoices, {
                'numbered_at': None,
                })

    @classmethod
    def copy(cls, invoices, default=None):
        if default is None:
            default = {}
        else:
            default = default.copy()
        default.setdefault('numbered_at', None)
        return super(Invoice, cls).copy(invoices, default=default)
示例#2
0
 def get_fields(self):
     # TODO: Cache
     Data = Pool().get('lims.interface.data')
     table = Data.get_table()
     if not table:
         return Data._previous_fields
     res = {}
     groups = 0
     for field in table.fields_:
         if field.type == 'char':
             obj = fields.Char(field.string)
         elif field.type == 'multiline':
             obj = fields.Text(field.string)
         elif field.type == 'integer':
             obj = fields.Integer(field.string)
         elif field.type == 'float':
             obj = fields.Float(field.string)
         elif field.type == 'boolean':
             obj = fields.Boolean(field.string)
         elif field.type == 'numeric':
             obj = fields.Numeric(field.string)
         elif field.type == 'date':
             obj = fields.Date(field.string)
         elif field.type == 'datetime':
             obj = fields.DateTime(field.string)
         elif field.type == 'timestamp':
             obj = fields.Timestamp(field.string)
         elif field.type == 'many2one':
             obj = fields.Many2One(field.related_model.model, field.string)
         elif field.type in ('binary', 'icon'):
             obj = fields.Binary(field.string)
         elif field.type == 'selection':
             selection = [tuple(v.split(':', 1))
                 for v in field.selection.splitlines() if v]
             obj = fields.Selection(selection, field.string)
         obj.name = field.name
         res[field.name] = obj
         groups = max(groups, field.group or 0)
     obj = fields.Integer('ID')
     obj.name = 'id'
     res['id'] = obj
     obj = fields.Many2One('lims.interface.compilation', 'Compilation')
     obj.name = 'compilation'
     res['compilation'] = obj
     obj = fields.Boolean('Annulled')
     obj.name = 'annulled'
     res['annulled'] = obj
     obj = fields.Many2One('lims.notebook.line', 'Notebook Line')
     obj.name = 'notebook_line'
     obj.readonly = True
     res['notebook_line'] = obj
     for i in range(0, groups):
         obj = fields.One2Many(
             'lims.interface.grouped_data', 'data', 'Group %s' % (i + 1, ))
         obj.name = 'group_%s' % (i + 1, )
         res[obj.name] = obj
     return res
示例#3
0
class TestHistory(ModelSQL):
    'Test History'
    __name__ = 'test.history'
    _history = True
    value = fields.Integer('Value')
    lines = fields.One2Many('test.history.line', 'history', 'Lines')
    lines_at_stamp = fields.One2Many('test.history.line',
                                     'history',
                                     'Lines at Stamp',
                                     datetime_field='stamp')
    stamp = fields.Timestamp('Stamp')
示例#4
0
 def get_fields(self):
     GroupedData = Pool().get('lims.interface.grouped_data')
     table = GroupedData.get_table()
     if not table:
         return GroupedData._previous_fields
     res = {}
     for field in table.grouped_fields_:
         if field.type == 'char':
             obj = fields.Char(field.string)
         elif field.type == 'multiline':
             obj = fields.Text(field.string)
         elif field.type == 'integer':
             obj = fields.Integer(field.string)
         elif field.type == 'float':
             obj = fields.Float(field.string)
         elif field.type == 'boolean':
             obj = fields.Boolean(field.string)
         elif field.type == 'numeric':
             obj = fields.Numeric(field.string)
         elif field.type == 'date':
             obj = fields.Date(field.string)
         elif field.type == 'datetime':
             obj = fields.DateTime(field.string)
         elif field.type == 'timestamp':
             obj = fields.Timestamp(field.string)
         elif field.type == 'many2one':
             obj = fields.Many2One(field.related_model.model, field.string)
         elif field.type in ('binary', 'icon'):
             obj = fields.Binary(field.string)
         elif field.type == 'selection':
             selection = [tuple(v.split(':', 1))
                 for v in field.selection.splitlines() if v]
             obj = fields.Selection(selection, field.string)
         obj.name = field.name
         res[field.name] = obj
     obj = fields.Integer('ID')
     obj.name = 'id'
     res['id'] = obj
     obj = fields.Many2One('lims.notebook.line', 'Notebook Line')
     obj.name = 'notebook_line'
     obj.readonly = True
     res['notebook_line'] = obj
     obj = fields.Many2One('lims.interface.data', 'Data')
     obj.name = 'data'
     obj.readonly = True
     res['data'] = obj
     obj = fields.Integer('Iteration')
     obj.name = 'iteration'
     obj.readonly = True
     res['iteration'] = obj
     return res
示例#5
0
    def get_fields(self):
        # TODO: Cache
        Data = Pool().get('shine.data')
        table = Data.get_table()
        if not table:
            return Data._previous_fields
        res = {}
        for field in table.fields:
            if field.type == 'char':
                obj = fields.Char(field.string)
            elif field.type == 'multiline':
                obj = fields.Text(field.string)
            elif field.type == 'integer':
                obj = fields.Integer(field.string)
            elif field.type == 'float':
                obj = fields.Float(field.string)
            elif field.type == 'boolean':
                obj = fields.Boolean(field.string)
            elif field.type == 'numeric':
                obj = fields.Numeric(field.string)
            elif field.type == 'date':
                obj = fields.Date(field.string)
            elif field.type == 'datetime':
                obj = fields.DateTime(field.string)
            elif field.type == 'timestamp':
                obj = fields.Timestamp(field.string)
            elif field.type == 'many2one':
                obj = fields.Many2One(field.related_model.model, field.string)
            elif field.type in ('binary', 'icon'):
                obj = fields.Binary(field.string)
            obj.name = field.name
            res[field.name] = obj
        if not 'id' in res:
            obj = fields.Integer('ID')
            obj.name = 'id'
            res[field.name] = obj

        return res
示例#6
0
class Queue(ModelSQL):
    "Queue"
    __name__ = 'ir.queue'
    name = fields.Char("Name", required=True)

    data = fields.Dict(None, "Data")

    enqueued_at = fields.Timestamp("Enqueued at", required=True)
    dequeued_at = fields.Timestamp("Dequeued at")
    finished_at = fields.Timestamp("Finished at")

    scheduled_at = fields.Timestamp("Scheduled at",
        help="When the task can start.")
    expected_at = fields.Timestamp("Expected at",
        help="When the task should be done.")

    @classmethod
    def __register__(cls, module_name):
        queue = cls.__table__()
        super().__register__(module_name)
        table_h = cls.__table_handler__(module_name)

        # Add index for candidates
        table_h.index_action([
                queue.scheduled_at.nulls_first,
                queue.expected_at.nulls_first,
                queue.dequeued_at,
                queue.name,
                ], action='add')

    @classmethod
    def default_enqueued_at(cls):
        return datetime.datetime.now()

    @classmethod
    def copy(cls, records, default=None):
        if default is None:
            default = {}
        else:
            default = default.copy()
        default.setdefault('enqueued_at')
        default.setdefault('dequeued_at')
        default.setdefault('finished_at')
        return super(Queue, cls).copy(records, default=default)

    @classmethod
    def push(cls, name, data, scheduled_at=None, expected_at=None):
        transaction = Transaction()
        database = transaction.database
        cursor = transaction.connection.cursor()
        with transaction.set_user(0):
            record, = cls.create([{
                        'name': name,
                        'data': data,
                        'scheduled_at': scheduled_at,
                        'expected_at': expected_at,
                        }])
        if database.has_channel():
            cursor.execute('NOTIFY "%s"', (cls.__name__,))
        if not has_worker:
            transaction.tasks.append(record.id)
        return record.id

    @classmethod
    def pull(cls, database, connection, name=None):
        cursor = connection.cursor()
        queue = cls.__table__()

        candidates = With('id', 'scheduled_at', 'expected_at',
            query=queue.select(
                queue.id,
                queue.scheduled_at,
                queue.expected_at,
                where=((queue.name == name) if name else Literal(True))
                & (queue.dequeued_at == Null),
                order_by=[
                    queue.scheduled_at.nulls_first,
                    queue.expected_at.nulls_first]))
        selected = With('id', query=candidates.select(
                candidates.id,
                where=((candidates.scheduled_at <= CurrentTimestamp())
                    | (candidates.scheduled_at == Null))
                & database.lock_id(candidates.id),
                order_by=[
                    candidates.scheduled_at.nulls_first,
                    candidates.expected_at.nulls_first],
                limit=1))
        next_timeout = With('seconds', query=candidates.select(
                Min(Extract('second',
                        candidates.scheduled_at - CurrentTimestamp())
                    ),
                where=candidates.scheduled_at >= CurrentTimestamp()))

        task_id, seconds = None, None
        if database.has_returning():
            query = queue.update([queue.dequeued_at], [CurrentTimestamp()],
                where=queue.id == selected.select(selected.id),
                with_=[candidates, selected, next_timeout],
                returning=[
                    queue.id, next_timeout.select(next_timeout.seconds)])
            cursor.execute(*query)
            row = cursor.fetchone()
            if row:
                task_id, seconds = row
        else:
            query = queue.select(queue.id,
                where=queue.id == selected.select(selected.id),
                with_=[candidates, selected])
            cursor.execute(*query)
            row = cursor.fetchone()
            if row:
                task_id, = row
                query = queue.update([queue.dequeued_at], [CurrentTimestamp()],
                    where=queue.id == task_id)
                cursor.execute(*query)
            query = next_timeout.select(next_timeout.seconds)
            cursor.execute(*query)
            row = cursor.fetchone()
            if row:
                seconds, = row

        if not task_id and database.has_channel():
            cursor.execute('LISTEN "%s"', (cls.__name__,))
        return task_id, seconds

    def run(self):
        transaction = Transaction()
        Model = Pool().get(self.data['model'])
        with transaction.set_user(self.data['user']), \
                transaction.set_context(self.data['context']):
            instances = self.data['instances']
            # Ensure record ids still exist
            if isinstance(instances, int):
                with transaction.set_context(active_test=False):
                    if Model.search([('id', '=', instances)]):
                        instances = Model(instances)
                    else:
                        instances = None
            else:
                ids = set()
                with transaction.set_context(active_test=False):
                    for sub_ids in grouped_slice(instances):
                        records = Model.search([('id', 'in', list(sub_ids))])
                        ids.update(map(int, records))
                if ids:
                    instances = Model.browse(
                        [i for i in instances if i in ids])
                else:
                    instances = None
            if instances is not None:
                getattr(Model, self.data['method'])(
                    instances, *self.data['args'], **self.data['kwargs'])
        if not self.dequeued_at:
            self.dequeued_at = datetime.datetime.now()
        self.finished_at = datetime.datetime.now()
        self.save()

    @classmethod
    def caller(cls, model):
        return _Model(cls, model)
示例#7
0
文件: user.py 项目: tryton/web_user
class User(avatar_mixin(100), DeactivableMixin, ModelSQL, ModelView):
    'Web User'
    __name__ = 'web.user'
    _rec_name = 'email'

    email = fields.Char('E-mail', select=True,
        states={
            'required': Eval('active', True),
            })
    email_valid = fields.Boolean('E-mail Valid')
    email_token = fields.Char('E-mail Token', select=True)
    password_hash = fields.Char('Password Hash')
    password = fields.Function(
        fields.Char('Password'), 'get_password', setter='set_password')
    reset_password_token = fields.Char('Reset Password Token', select=True)
    reset_password_token_expire = fields.Timestamp(
        'Reset Password Token Expire')
    party = fields.Many2One('party.party', 'Party', ondelete='RESTRICT')
    secondary_parties = fields.Many2Many(
        'web.user-party.party.secondary', 'user', 'party', "Secondary Parties")

    @classmethod
    def __setup__(cls):
        super(User, cls).__setup__()
        table = cls.__table__()
        cls._sql_constraints += [
            ('email_exclude',
                Exclude(table, (table.email, Equal),
                    where=table.active == Literal(True)),
                'web_user.msg_user_email_unique'),
            ]
        cls._buttons.update({
                'validate_email': {
                    'readonly': Eval('email_valid', False),
                    'depends': ['email_valid'],
                    },
                'reset_password': {
                    'readonly': ~Eval('email_valid', False),
                    'depends': ['email_valid'],
                    },
                })

    @classmethod
    def __register__(cls, module_name):
        super(User, cls).__register__(module_name)

        table_h = cls.__table_handler__(module_name)

        # Migration from 4.6
        table_h.not_null_action('email', 'remove')

        # Migration from 4.6: replace unique by exclude
        table_h.drop_constraint('email_unique')

    @classmethod
    def default_email_valid(cls):
        return False

    def get_password(self, name):
        return 'x' * 10

    @classmethod
    def set_password(cls, users, name, value):
        pool = Pool()
        User = pool.get('res.user')

        if value == 'x' * 10:
            return

        if Transaction().user and value:
            User.validate_password(value, users)

        to_write = []
        for user in users:
            to_write.extend([[user], {
                        'password_hash': cls.hash_password(value),
                        }])
        cls.write(*to_write)

    @fields.depends('party', 'email')
    def on_change_party(self):
        if not self.email and self.party:
            self.email = self.party.email

    @classmethod
    def _format_email(cls, users):
        for user in users:
            email = user.email.lower()
            if email != user.email:
                user.email = email
        cls.save(users)

    @classmethod
    def create(cls, vlist):
        users = super(User, cls).create(vlist)
        cls._format_email(users)
        return users

    @classmethod
    def write(cls, *args):
        super(User, cls).write(*args)
        users = sum(args[0:None:2], [])
        cls._format_email(users)

    @classmethod
    def authenticate(cls, email, password):
        pool = Pool()
        Attempt = pool.get('web.user.authenticate.attempt')
        email = email.lower()

        count_ip = Attempt.count_ip()
        if count_ip > config.getint(
                'session', 'max_attempt_ip_network', default=300):
            # Do not add attempt as the goal is to prevent flooding
            raise RateLimitException()
        count = Attempt.count(email)
        if count > config.getint('session', 'max_attempt', default=5):
            Attempt.add(email)
            raise RateLimitException()
        # Prevent brute force attack
        Transaction().atexit(time.sleep, 2 ** count - 1)

        users = cls.search([('email', '=', email)])
        if users:
            user, = users
            valid, new_hash = cls.check_password(password, user.password_hash)
            if valid:
                if new_hash:
                    logger.info("Update password hash for %s", user.id)
                    with Transaction().new_transaction() as transaction:
                        with transaction.set_user(0):
                            cls.write([cls(user.id)], {
                                    'password_hash': new_hash,
                                    })
                Attempt.remove(email)
                return user
        Attempt.add(email)

    @classmethod
    def hash_password(cls, password):
        '''Hash given password in the form
        <hash_method>$<password>$<salt>...'''
        if not password:
            return ''
        return CRYPT_CONTEXT.hash(password)

    @classmethod
    def check_password(cls, password, hash_):
        if not hash_:
            return False, None
        try:
            return CRYPT_CONTEXT.verify_and_update(password, hash_)
        except ValueError:
            hash_method = hash_.split('$', 1)[0]
            warnings.warn(
                "Use deprecated hash method %s" % hash_method,
                DeprecationWarning)
            valid = getattr(cls, 'check_' + hash_method)(password, hash_)
            if valid:
                new_hash = CRYPT_CONTEXT.hash(password)
            else:
                new_hash = None
            return valid, new_hash

    @classmethod
    def hash_sha1(cls, password):
        salt = ''.join(random.sample(string.ascii_letters + string.digits, 8))
        salted_password = password + salt
        if isinstance(salted_password, str):
            salted_password = salted_password.encode('utf-8')
        hash_ = hashlib.sha1(salted_password).hexdigest()
        return '$'.join(['sha1', hash_, salt])

    @classmethod
    def check_sha1(cls, password, hash_):
        if isinstance(password, str):
            password = password.encode('utf-8')
        hash_method, hash_, salt = hash_.split('$', 2)
        salt = salt or ''
        if isinstance(salt, str):
            salt = salt.encode('utf-8')
        assert hash_method == 'sha1'
        return hash_ == hashlib.sha1(password + salt).hexdigest()

    @classmethod
    def hash_bcrypt(cls, password):
        if isinstance(password, str):
            password = password.encode('utf-8')
        hash_ = bcrypt.hashpw(password, bcrypt.gensalt()).decode('utf-8')
        return '$'.join(['bcrypt', hash_])

    @classmethod
    def check_bcrypt(cls, password, hash_):
        if isinstance(password, str):
            password = password.encode('utf-8')
        hash_method, hash_ = hash_.split('$', 1)
        if isinstance(hash_, str):
            hash_ = hash_.encode('utf-8')
        assert hash_method == 'bcrypt'
        return hash_ == bcrypt.hashpw(password, hash_)

    def new_session(self):
        pool = Pool()
        Session = pool.get('web.user.session')
        return Session.add(self)

    @classmethod
    def get_user(cls, session):
        pool = Pool()
        Session = pool.get('web.user.session')
        return Session.get_user(session)

    @classmethod
    @ModelView.button
    def validate_email(cls, users, from_=None):
        for user in users:
            user.set_email_token()
        cls.save(users)
        _send_email(from_, users, cls.get_email_validation)

    def set_email_token(self, nbytes=None):
        self.email_token = token_hex(nbytes)

    def get_email_validation(self):
        return get_email(
            'web.user.email_validation', self, self.languages)

    def get_email_validation_url(self, url=None):
        if url is None:
            url = config.get('web', 'email_validation_url')
        return _add_params(url, token=self.email_token)

    @classmethod
    def validate_email_url(cls, url):
        parts = urllib.parse.urlsplit(url)
        tokens = filter(
            None, urllib.parse.parse_qs(parts.query).get('token', [None]))
        return cls.validate_email_token(list(tokens))

    @classmethod
    def validate_email_token(cls, tokens):
        users = cls.search([
                ('email_token', 'in', tokens),
                ])
        cls.write(users, {
                'email_valid': True,
                'email_token': None,
                })
        return users

    @classmethod
    @ModelView.button
    def reset_password(cls, users, from_=None):
        now = datetime.datetime.now()

        # Prevent abusive reset
        def reset(user):
            return not (user.reset_password_token_expire
                and user.reset_password_token_expire > now)
        users = list(filter(reset, users))

        for user in users:
            user.set_reset_password_token()
        cls.save(users)
        _send_email(from_, users, cls.get_email_reset_password)

    def set_reset_password_token(self, nbytes=None):
        self.reset_password_token = token_hex(nbytes)
        self.reset_password_token_expire = (
            datetime.datetime.now() + datetime.timedelta(
                seconds=config.getint(
                    'session', 'web_timeout_reset', default=24 * 60 * 60)))

    def clear_reset_password_token(self):
        self.reset_password_token = None
        self.reset_password_token_expire = None

    def get_email_reset_password(self):
        return get_email(
            'web.user.email_reset_password', self, self.languages)

    def get_email_reset_password_url(self, url=None):
        if url is None:
            url = config.get('web', 'reset_password_url')
        return _add_params(
            url, token=self.reset_password_token, email=self.email)

    @classmethod
    def set_password_url(cls, url, password):
        parts = urllib.parse.urlsplit(url)
        query = urllib.parse.parse_qs(parts.query)
        email = query.get('email', [None])[0]
        token = query.get('token', [None])[0]
        return cls.set_password_token(email, token, password)

    @classmethod
    def set_password_token(cls, email, token, password):
        pool = Pool()
        Attempt = pool.get('web.user.authenticate.attempt')
        email = email.lower()

        # Prevent brute force attack
        Transaction().atexit(
            time.sleep, random.randint(0, 2 ** Attempt.count(email) - 1))

        users = cls.search([
                ('email', '=', email),
                ])
        if users:
            user, = users
            if user.reset_password_token == token:
                now = datetime.datetime.now()
                expire = user.reset_password_token_expire
                user.clear_reset_password_token()
                if expire > now:
                    user.password = password
                    user.save()
                    Attempt.remove(email)
                    return True
        Attempt.add(email)
        return False

    @property
    def languages(self):
        pool = Pool()
        Language = pool.get('ir.lang')
        if self.party and self.party.lang:
            languages = [self.party.lang]
        else:
            languages = Language.search([
                    ('code', '=', Transaction().language),
                    ])
        return languages
示例#8
0
文件: user.py 项目: zarumaru/trytond
class User(avatar_mixin(100, 'login'), DeactivableMixin, ModelSQL, ModelView):
    "User"
    __name__ = "res.user"
    name = fields.Char('Name', select=True)
    login = fields.Char('Login', required=True)
    password_hash = fields.Char('Password Hash')
    password = fields.Function(fields.Char("Password",
                                           states={
                                               'invisible': not _has_password,
                                           }),
                               getter='get_password',
                               setter='set_password')
    password_reset = fields.Char("Reset Password",
                                 states={
                                     'invisible': not _has_password,
                                 })
    password_reset_expire = fields.Timestamp("Reset Password Expire",
                                             states={
                                                 'required':
                                                 Bool(Eval('password_reset')),
                                                 'invisible':
                                                 not _has_password,
                                             },
                                             depends=['password_reset'])
    signature = fields.Text('Signature')
    menu = fields.Many2One('ir.action',
                           'Menu Action',
                           domain=[('usage', '=', 'menu')],
                           required=True)
    pyson_menu = fields.Function(fields.Char('PySON Menu'), 'get_pyson_menu')
    actions = fields.Many2Many('res.user-ir.action',
                               'user',
                               'action',
                               'Actions',
                               help='Actions that will be run at login.',
                               size=5)
    groups = fields.Many2Many('res.user-res.group', 'user', 'group', 'Groups')
    applications = fields.One2Many('res.user.application', 'user',
                                   "Applications")
    language = fields.Many2One('ir.lang',
                               'Language',
                               domain=[
                                   'OR',
                                   ('translatable', '=', True),
                               ])
    language_direction = fields.Function(fields.Char('Language Direction'),
                                         'get_language_direction')
    email = fields.Char('Email')
    status_bar = fields.Function(fields.Char('Status Bar'), 'get_status_bar')
    avatar_badge_url = fields.Function(fields.Char("Avatar Badge URL"),
                                       'get_avatar_badge_url')
    warnings = fields.One2Many('res.user.warning', 'user', 'Warnings')
    sessions = fields.Function(fields.Integer('Sessions'), 'get_sessions')
    _get_preferences_cache = Cache('res_user.get_preferences')
    _get_groups_cache = Cache('res_user.get_groups', context=False)
    _get_login_cache = Cache('res_user._get_login', context=False)

    @classmethod
    def __setup__(cls):
        super(User, cls).__setup__()
        cls.__rpc__.update({
            'get_preferences':
            RPC(check_access=False),
            'set_preferences':
            RPC(readonly=False, check_access=False, fresh_session=True),
            'get_preferences_fields_view':
            RPC(check_access=False),
        })
        table = cls.__table__()
        cls._sql_constraints += [
            ('login_key', Unique(table, table.login),
             'You can not have two users with the same login!')
        ]
        cls._buttons.update({
            'reset_password': {
                'invisible': ~Eval('email', True) | (not _has_password),
            },
        })
        cls._preferences_fields = [
            'name',
            'password',
            'email',
            'signature',
            'menu',
            'pyson_menu',
            'actions',
            'status_bar',
            'avatar',
            'avatar_url',
            'avatar_badge_url',
            'warnings',
            'applications',
        ]
        cls._context_fields = [
            'language',
            'language_direction',
            'groups',
        ]
        cls._order.insert(0, ('name', 'ASC'))

    @classmethod
    def __register__(cls, module_name):
        pool = Pool()
        ModelData = pool.get('ir.model.data')
        model_data = ModelData.__table__()
        cursor = Transaction().connection.cursor()
        super(User, cls).__register__(module_name)
        table = cls.__table_handler__(module_name)

        # Migration from 3.0
        if table.column_exist('password') and table.column_exist('salt'):
            sqltable = cls.__table__()
            password_hash_new = Concat(
                'sha1$',
                Concat(sqltable.password,
                       Concat('$', Coalesce(sqltable.salt, ''))))
            cursor.execute(*sqltable.update(columns=[sqltable.password_hash],
                                            values=[password_hash_new]))
            table.drop_column('password')
            table.drop_column('salt')

        # Migration from 4.2: Remove required on name
        table.not_null_action('name', action='remove')

        # Migration from 5.6: Set noupdate to admin
        cursor.execute(
            *model_data.update([model_data.noupdate], [True],
                               where=(model_data.model == cls.__name__)
                               & (model_data.module == 'res')
                               & (model_data.fs_id == 'user_admin')))

    @staticmethod
    def default_menu():
        pool = Pool()
        Action = pool.get('ir.action')
        actions = Action.search([
            ('usage', '=', 'menu'),
        ], limit=1)
        if actions:
            return actions[0].id
        return None

    def get_pyson_menu(self, name):
        encoder = PYSONEncoder()
        return encoder.encode(self.menu.get_action_value())

    def get_language_direction(self, name):
        pool = Pool()
        Lang = pool.get('ir.lang')
        if self.language:
            return self.language.direction
        else:
            return Lang.default_direction()

    def get_status_bar(self, name):
        return self.name

    def get_avatar_badge_url(self, name):
        pass

    def get_password(self, name):
        return 'x' * 10

    @classmethod
    def set_password(cls, users, name, value):
        if value == 'x' * 10:
            return

        if Transaction().user and value:
            cls.validate_password(value, users)

        to_write = []
        for user in users:
            to_write.extend([[user], {
                'password_hash': cls.hash_password(value),
            }])
        cls.write(*to_write)

    @classmethod
    def validate_password(cls, password, users):
        password_b = password
        if isinstance(password, str):
            password_b = password.encode('utf-8')
        length = config.getint('password', 'length', default=0)
        if length > 0:
            if len(password_b) < length:
                raise PasswordError(
                    gettext(
                        'res.msg_password_length',
                        length=length,
                    ))
        path = config.get('password', 'forbidden', default=None)
        if path:
            with open(path, 'r') as f:
                forbidden = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
                if forbidden.find(password_b) >= 0:
                    raise PasswordError(gettext('res.msg_password_forbidden'))
        entropy = config.getfloat('password', 'entropy', default=0)
        if entropy:
            if len(set(password)) / len(password) < entropy:
                raise PasswordError(gettext('res.msg_password_entropy'))
        for user in users:
            # Use getattr to allow to use non User instances
            for test, message in [
                (getattr(user, 'name', ''), 'res.msg_password_name'),
                (getattr(user, 'login', ''), 'res.msg_password_login'),
                (getattr(user, 'email', ''), 'res.msg_password_email'),
            ]:
                if test and password.lower() == test.lower():
                    raise PasswordError(gettext(message))

    @classmethod
    @ModelView.button
    def reset_password(cls, users, length=8, from_=None):
        for user in users:
            user.password_reset = gen_password(length=length)
            user.password_reset_expire = (
                datetime.datetime.now() + datetime.timedelta(
                    seconds=config.getint('password', 'reset_timeout')))
            user.password = None
        cls.save(users)
        _send_email(from_, users, cls.get_email_reset_password)

    def get_email_reset_password(self):
        return get_email('res.user.email_reset_password', self, self.languages)

    @property
    def languages(self):
        pool = Pool()
        Lang = pool.get('ir.lang')
        if self.language:
            languages = [self.language]
        else:
            languages = Lang.search([
                ('code', '=', Transaction().language),
            ])
        return languages

    @staticmethod
    def get_sessions(users, name):
        Session = Pool().get('ir.session')
        now = datetime.datetime.now()
        timeout = datetime.timedelta(
            seconds=config.getint('session', 'max_age'))
        result = dict((u.id, 0) for u in users)
        with Transaction().set_user(0):
            for sub_ids in grouped_slice(users):
                sessions = Session.search([
                    ('create_uid', 'in', sub_ids),
                ],
                                          order=[('create_uid', 'ASC')])

                def filter_(session):
                    timestamp = session.write_date or session.create_date
                    return abs(timestamp - now) < timeout

                result.update(
                    dict((i, len(list(g)))
                         for i, g in groupby(filter(filter_, sessions),
                                             attrgetter('create_uid.id'))))
        return result

    @staticmethod
    def _convert_vals(vals):
        vals = vals.copy()
        pool = Pool()
        Action = pool.get('ir.action')
        if 'menu' in vals:
            vals['menu'] = Action.get_action_id(vals['menu'])
        return vals

    @classmethod
    def read(cls, ids, fields_names):
        result = super(User, cls).read(ids, fields_names)
        if not fields_names or 'password_hash' in fields_names:
            for values in result:
                values['password_hash'] = None
        return result

    @classmethod
    def create(cls, vlist):
        vlist = [cls._convert_vals(vals) for vals in vlist]
        res = super(User, cls).create(vlist)
        # Restart the cache for _get_login
        cls._get_login_cache.clear()
        return res

    @classmethod
    def write(cls, users, values, *args):
        pool = Pool()
        Session = pool.get('ir.session')
        UserDevice = pool.get('res.user.device')

        actions = iter((users, values) + args)
        all_users = []
        session_to_clear = []
        users_to_clear = []
        args = []
        for users, values in zip(actions, actions):
            all_users += users
            args.extend((users, cls._convert_vals(values)))

            if 'password' in values:
                session_to_clear += users
                users_to_clear += [u.login for u in users]

        super(User, cls).write(*args)

        Session.clear(session_to_clear)
        UserDevice.clear(users_to_clear)

        # Clean cursor cache as it could be filled by domain_get
        for cache in Transaction().cache.values():
            if cls.__name__ in cache:
                for user in all_users:
                    cache[cls.__name__].pop(user.id, None)
        # Restart the cache for domain_get method
        pool = Pool()
        pool.get('ir.rule')._domain_get_cache.clear()
        # Restart the cache for get_groups
        cls._get_groups_cache.clear()
        # Restart the cache for _get_login
        cls._get_login_cache.clear()
        # Restart the cache for get_preferences
        cls._get_preferences_cache.clear()
        # Restart the cache of check
        pool.get('ir.model.access')._get_access_cache.clear()
        # Restart the cache of check
        pool.get('ir.model.field.access')._get_access_cache.clear()
        # Restart the cache
        ModelView._fields_view_get_cache.clear()

    @classmethod
    def delete(cls, users):
        raise DeleteError(gettext('res.msg_user_delete_forbidden'))

    def get_rec_name(self, name):
        return self.name if self.name else self.login

    @classmethod
    def search_rec_name(cls, name, clause):
        if clause[1].startswith('!') or clause[1].startswith('not '):
            bool_op = 'AND'
        else:
            bool_op = 'OR'
        return [
            bool_op,
            ('login', ) + tuple(clause[1:]),
            ('name', ) + tuple(clause[1:]),
        ]

    @classmethod
    def copy(cls, users, default=None):
        if default is None:
            default = {}
        default = default.copy()

        default['password'] = ''
        default.setdefault('warnings')
        default.setdefault('applications')

        new_users = []
        for user in users:
            default['login'] = user.login + ' (copy)'
            new_user, = super(User, cls).copy([user], default)
            new_users.append(new_user)
        return new_users

    @classmethod
    def _get_preferences(cls, user, context_only=False):
        pool = Pool()
        ModelData = pool.get('ir.model.data')
        Action = pool.get('ir.action')
        Config = pool.get('ir.configuration')
        ConfigItem = pool.get('ir.module.config_wizard.item')
        Lang = pool.get('ir.lang')

        res = {}
        if context_only:
            fields = cls._context_fields
        else:
            fields = cls._preferences_fields + cls._context_fields
        for field in fields:
            if cls._fields[field]._type in ('many2one', ):
                if field == 'language':
                    if user.language:
                        res['language'] = user.language.code
                    else:
                        res['language'] = Config.get_language()
                else:
                    if getattr(user, field):
                        res[field] = getattr(user, field).id
                        res[field + '.rec_name'] = \
                            getattr(user, field).rec_name
            elif cls._fields[field]._type in ('one2many', 'many2many'):
                res[field] = [x.id for x in getattr(user, field)]
                admin_id = ModelData.get_id('res.user_admin')
                if field == 'actions' and user.id == admin_id:
                    config_wizard_id = ModelData.get_id(
                        'ir', 'act_module_config_wizard')
                    action_id = Action.get_action_id(config_wizard_id)
                    if action_id in res[field]:
                        res[field].remove(action_id)
                    if ConfigItem.search([
                        ('state', '=', 'open'),
                    ]):
                        res[field].insert(0, action_id)
            else:
                res[field] = getattr(user, field)

        if user.language:
            language = user.language
        else:
            try:
                language = Lang.get(Config.get_language())
            except ValueError:
                language = None
        if language:
            date = language.date
            for i, j in [('%a', ''), ('%A', ''), ('%b', '%m'), ('%B', '%m'),
                         ('%j', ''), ('%U', ''), ('%w', ''), ('%W', '')]:
                date = date.replace(i, j)
            res['locale'] = {
                'date': date,
                'grouping': literal_eval(language.grouping),
                'decimal_point': language.decimal_point,
                'thousands_sep': language.thousands_sep,
                'mon_grouping': literal_eval(language.mon_grouping),
                'mon_decimal_point': language.mon_decimal_point,
                'mon_thousands_sep': language.mon_thousands_sep,
                'p_sign_posn': language.p_sign_posn,
                'n_sign_posn': language.n_sign_posn,
                'positive_sign': language.positive_sign,
                'negative_sign': language.negative_sign,
                'p_cs_precedes': language.p_cs_precedes,
                'n_cs_precedes': language.n_cs_precedes,
                'p_sep_by_space': language.p_sep_by_space,
                'n_sep_by_space': language.n_sep_by_space,
            }
        return res

    @classmethod
    def get_preferences(cls, context_only=False):
        key = (Transaction().user, context_only)
        preferences = cls._get_preferences_cache.get(key)
        if preferences is not None:
            return preferences.copy()
        user = Transaction().user
        user = cls(user)
        preferences = cls._get_preferences(user, context_only=context_only)
        cls._get_preferences_cache.set(key, preferences)
        return preferences.copy()

    @classmethod
    def set_preferences(cls, values):
        '''
        Set user preferences
        '''
        pool = Pool()
        Lang = pool.get('ir.lang')
        values_clean = values.copy()
        fields = cls._preferences_fields + cls._context_fields
        user_id = Transaction().user
        user = cls(user_id)
        for field in values:
            if field not in fields or field == 'groups':
                del values_clean[field]
            if field == 'language':
                langs = Lang.search([
                    ('code', '=', values['language']),
                ])
                if langs:
                    values_clean['language'] = langs[0].id
                else:
                    del values_clean['language']
        # Set new context to write as validation could depend on it
        context = {}
        for name in cls._context_fields:
            if name in values:
                context[name] = values[name]
        with Transaction().set_context(context):
            cls.write([user], values_clean)

    @classmethod
    def get_preferences_fields_view(cls):
        pool = Pool()
        ModelData = pool.get('ir.model.data')
        Lang = pool.get('ir.lang')
        Action = pool.get('ir.action')

        view_id = ModelData.get_id('res', 'user_view_form_preferences')
        res = cls.fields_view_get(view_id=view_id)
        res = copy.deepcopy(res)
        for field in res['fields']:
            if field not in ('groups', 'language_direction'):
                res['fields'][field]['readonly'] = False
            else:
                res['fields'][field]['readonly'] = True

        def convert2selection(definition, name):
            del definition[name]['relation']
            del definition[name]['domain']
            definition[name]['type'] = 'selection'
            selection = []
            definition[name]['selection'] = selection
            return selection

        if 'language' in res['fields']:
            selection = convert2selection(res['fields'], 'language')
            langs = Lang.search(cls.language.domain)
            lang_ids = [l.id for l in langs]
            with Transaction().set_context(translate_name=True):
                for lang in Lang.browse(lang_ids):
                    selection.append((lang.code, lang.name))
        if 'action' in res['fields']:
            selection = convert2selection(res['fields'], 'action')
            selection.append((None, ''))
            actions = Action.search([])
            for action in actions:
                selection.append((action.id, action.rec_name))
        if 'menu' in res['fields']:
            selection = convert2selection(res['fields'], 'menu')
            actions = Action.search([
                ('usage', '=', 'menu'),
            ])
            for action in actions:
                selection.append((action.id, action.rec_name))
        return res

    @classmethod
    def get_groups(cls, name=None):
        '''
        Return a list of all group ids for the user
        '''
        user = Transaction().user
        groups = cls._get_groups_cache.get(user)
        if groups is not None:
            return groups
        pool = Pool()
        UserGroup = pool.get('res.user-res.group')
        cursor = Transaction().connection.cursor()
        user_group = UserGroup.user_group_all_table()
        cursor.execute(*user_group.select(user_group.group,
                                          where=user_group.user == user))
        groups = [g for g, in cursor]
        cls._get_groups_cache.set(user, groups)
        return groups

    @classmethod
    def _get_login(cls, login):
        result = cls._get_login_cache.get(login)
        if result:
            return result
        cursor = Transaction().connection.cursor()
        table = cls.__table__()
        cursor.execute(
            *table.select(table.id,
                          table.password_hash,
                          Case((
                              table.password_reset_expire > CurrentTimestamp(),
                              table.password_reset),
                               else_=None),
                          where=(table.login == login)
                          & (table.active == Literal(True))))
        result = cursor.fetchone() or (None, None, None)
        cls._get_login_cache.set(login, result)
        return result

    @classmethod
    def get_login(cls, login, parameters):
        '''
        Return user id if password matches
        '''
        pool = Pool()
        LoginAttempt = pool.get('res.user.login.attempt')
        UserDevice = pool.get('res.user.device')

        count_ip = LoginAttempt.count_ip()
        if count_ip > config.getint(
                'session', 'max_attempt_ip_network', default=300):
            # Do not add attempt as the goal is to prevent flooding
            raise RateLimitException()
        device_cookie = UserDevice.get_valid_cookie(
            login, parameters.get('device_cookie'))
        count = LoginAttempt.count(login, device_cookie)
        if count > config.getint('session', 'max_attempt', default=5):
            LoginAttempt.add(login, device_cookie)
            raise RateLimitException()
        Transaction().atexit(time.sleep, random.randint(0, 2**count - 1))
        for methods in config.get('session',
                                  'authentications',
                                  default='password').split(','):
            user_ids = set()
            for method in methods.split('+'):
                try:
                    func = getattr(cls, '_login_%s' % method)
                except AttributeError:
                    logger.info('Missing login method: %s', method)
                    break
                user_ids.add(func(login, parameters))
                if len(user_ids) != 1 or not all(user_ids):
                    break
            if len(user_ids) == 1 and all(user_ids):
                LoginAttempt.remove(login, device_cookie)
                return user_ids.pop()
        LoginAttempt.add(login, device_cookie)

    @classmethod
    def _login_password(cls, login, parameters):
        if 'password' not in parameters:
            msg = gettext('res.msg_user_password', login=login)
            raise LoginException('password', msg, type='password')
        user_id, password_hash, password_reset = cls._get_login(login)
        if user_id and password_hash:
            password = parameters['password']
            valid, new_hash = cls.check_password(password, password_hash)
            if valid:
                if new_hash:
                    logger.info("Update password hash for %s", user_id)
                    with Transaction().new_transaction() as transaction:
                        with transaction.set_user(0):
                            cls.write([cls(user_id)], {
                                'password_hash': new_hash,
                            })
                return user_id
        elif user_id and password_reset:
            if password_reset == parameters['password']:
                return user_id

    @classmethod
    def hash_password(cls, password):
        '''Hash given password in the form
        <hash_method>$<password>$<salt>...'''
        if not password:
            return None
        return CRYPT_CONTEXT.hash(password)

    @classmethod
    def check_password(cls, password, hash_):
        if not hash_:
            return False
        try:
            return CRYPT_CONTEXT.verify_and_update(password, hash_)
        except ValueError:
            hash_method = hash_.split('$', 1)[0]
            warnings.warn("Use deprecated hash method %s" % hash_method,
                          DeprecationWarning)
            valid = getattr(cls, 'check_' + hash_method)(password, hash_)
            if valid:
                new_hash = CRYPT_CONTEXT.hash(password)
            else:
                new_hash = None
            return valid, new_hash

    @classmethod
    def hash_sha1(cls, password):
        salt = gen_password()
        salted_password = password + salt
        if isinstance(salted_password, str):
            salted_password = salted_password.encode('utf-8')
        hash_ = hashlib.sha1(salted_password).hexdigest()
        return '$'.join(['sha1', hash_, salt])

    @classmethod
    def check_sha1(cls, password, hash_):
        if isinstance(password, str):
            password = password.encode('utf-8')
        hash_method, hash_, salt = hash_.split('$', 2)
        salt = salt or ''
        if isinstance(salt, str):
            salt = salt.encode('utf-8')
        assert hash_method == 'sha1'
        return hash_ == hashlib.sha1(password + salt).hexdigest()

    @classmethod
    def hash_bcrypt(cls, password):
        if isinstance(password, str):
            password = password.encode('utf-8')
        hash_ = bcrypt.hashpw(password, bcrypt.gensalt()).decode('utf-8')
        return '$'.join(['bcrypt', hash_])

    @classmethod
    def check_bcrypt(cls, password, hash_):
        if isinstance(password, str):
            password = password.encode('utf-8')
        hash_method, hash_ = hash_.split('$', 1)
        if isinstance(hash_, str):
            hash_ = hash_.encode('utf-8')
        assert hash_method == 'bcrypt'
        return hash_ == bcrypt.hashpw(password, hash_)
示例#9
0
文件: cache.py 项目: tryton/trytond
class Cache(ModelSQL):
    "Cache"
    __name__ = 'ir.cache'
    name = fields.Char('Name', required=True)
    timestamp = fields.Timestamp("Timestamp")
示例#10
0
class User(ModelSQL, ModelView):
    'Web User'
    __name__ = 'web.user'
    _rec_name = 'email'

    email = fields.Char('E-mail', required=True, select=True)
    email_valid = fields.Boolean('E-mail Valid')
    email_token = fields.Char('E-mail Token', select=True)
    password_hash = fields.Char('Password Hash')
    password = fields.Function(fields.Char('Password'),
                               'get_password',
                               setter='set_password')
    reset_password_token = fields.Char('Reset Password Token', select=True)
    reset_password_token_expire = fields.Timestamp(
        'Reset Password Token Expire')
    active = fields.Boolean('Active')
    party = fields.Many2One('party.party', 'Party')

    @classmethod
    def __setup__(cls):
        super(User, cls).__setup__()
        table = cls.__table__()
        cls._sql_constraints += [
            ('email_unique', Unique(table,
                                    table.email), 'E-mail must be unique'),
        ]
        cls._buttons.update({
            'validate_email': {
                'readonly': Eval('email_valid', False),
            },
            'reset_password': {
                'readonly': ~Eval('email_valid', False),
            },
        })

    @staticmethod
    def default_active():
        return True

    @classmethod
    def default_email_valid(cls):
        return False

    def get_password(self, name):
        return 'x' * 10

    @classmethod
    def set_password(cls, users, name, value):
        pool = Pool()
        User = pool.get('res.user')

        if value == 'x' * 10:
            return

        if Transaction().user and value:
            User.validate_password(value, users)

        to_write = []
        for user in users:
            to_write.extend([[user], {
                'password_hash': cls.hash_password(value),
            }])
        cls.write(*to_write)

    @classmethod
    def _format_email(cls, users):
        for user in users:
            email = user.email.lower()
            if email != user.email:
                user.email = email
        cls.save(users)

    @classmethod
    def create(cls, vlist):
        users = super(User, cls).create(vlist)
        cls._format_email(users)
        return users

    @classmethod
    def write(cls, *args):
        super(User, cls).write(*args)
        users = sum(args[0:None:2], [])
        cls._format_email(users)

    @classmethod
    def authenticate(cls, email, password):
        pool = Pool()
        Attempt = pool.get('web.user.authenticate.attempt')
        email = email.lower()

        # Prevent brute force attack
        Transaction().atexit(time.sleep, 2**Attempt.count(email) - 1)

        users = cls.search([('email', '=', email)])
        if users:
            user, = users
            if cls.check_password(password, user.password_hash):
                Attempt.remove(email)
                return user
        Attempt.add(email)

    @staticmethod
    def hash_method():
        return 'bcrypt' if bcrypt else 'sha1'

    @classmethod
    def hash_password(cls, password):
        '''Hash given password in the form
        <hash_method>$<password>$<salt>...'''
        if not password:
            return ''
        return getattr(cls, 'hash_' + cls.hash_method())(password)

    @classmethod
    def check_password(cls, password, hash_):
        if not hash_:
            return False
        hash_method = hash_.split('$', 1)[0]
        return getattr(cls, 'check_' + hash_method)(password, hash_)

    @classmethod
    def hash_sha1(cls, password):
        salt = ''.join(random.sample(string.ascii_letters + string.digits, 8))
        salted_password = password + salt
        if isinstance(salted_password, unicode):
            salted_password = salted_password.encode('utf-8')
        hash_ = hashlib.sha1(salted_password).hexdigest()
        return '$'.join(['sha1', hash_, salt])

    @classmethod
    def check_sha1(cls, password, hash_):
        if isinstance(password, unicode):
            password = password.encode('utf-8')
        hash_method, hash_, salt = hash_.split('$', 2)
        salt = salt or ''
        if isinstance(salt, unicode):
            salt = salt.encode('utf-8')
        assert hash_method == 'sha1'
        return hash_ == hashlib.sha1(password + salt).hexdigest()

    @classmethod
    def hash_bcrypt(cls, password):
        if isinstance(password, unicode):
            password = password.encode('utf-8')
        hash_ = bcrypt.hashpw(password, bcrypt.gensalt()).decode('utf-8')
        return '$'.join(['bcrypt', hash_])

    @classmethod
    def check_bcrypt(cls, password, hash_):
        if isinstance(password, unicode):
            password = password.encode('utf-8')
        hash_method, hash_ = hash_.split('$', 1)
        if isinstance(hash_, unicode):
            hash_ = hash_.encode('utf-8')
        assert hash_method == 'bcrypt'
        return hash_ == bcrypt.hashpw(password, hash_)

    def new_session(self):
        pool = Pool()
        Session = pool.get('web.user.session')
        return Session.add(self)

    @classmethod
    def get_user(cls, session):
        pool = Pool()
        Session = pool.get('web.user.session')
        return Session.get_user(session)

    @classmethod
    @ModelView.button
    def validate_email(cls, users, from_=None):
        for user in users:
            user.set_email_token()
        cls.save(users)
        _send_email(from_, users, cls.get_email_validation)

    def set_email_token(self, nbytes=None):
        self.email_token = token_hex(nbytes)

    def get_email_validation(self):
        return get_email('web.user.email_validation', self, self.languages)

    def get_email_validation_url(self, url=None):
        if url is None:
            url = config.get('web', 'email_validation_url')
        return _add_params(url, token=self.email_token)

    @classmethod
    def validate_email_url(cls, url):
        parts = urlparse.urlsplit(url)
        tokens = filter(None,
                        urlparse.parse_qs(parts.query).get('token', [None]))
        return cls.validate_email_token(tokens)

    @classmethod
    def validate_email_token(cls, tokens):
        users = cls.search([
            ('email_token', 'in', tokens),
        ])
        cls.write(users, {
            'email_valid': True,
            'email_token': None,
        })
        return bool(users)

    @classmethod
    @ModelView.button
    def reset_password(cls, users, from_=None):
        now = datetime.datetime.now()

        # Prevent abusive reset
        def reset(user):
            return not (user.reset_password_token_expire
                        and user.reset_password_token_expire > now)

        users = filter(reset, users)

        for user in users:
            user.set_reset_password_token()
        cls.save(users)
        _send_email(from_, users, cls.get_email_reset_password)

    def set_reset_password_token(self, nbytes=None):
        self.reset_password_token = token_hex(nbytes)
        self.reset_password_token_expire = (
            datetime.datetime.now() + datetime.timedelta(seconds=config.getint(
                'session', 'web_timeout_reset', default=24 * 60 * 60)))

    def clear_reset_password_token(self):
        self.reset_password_token = None
        self.reset_password_token_expire = None

    def get_email_reset_password(self):
        return get_email('web.user.email_reset_password', self, self.languages)

    def get_email_reset_password_url(self, url=None):
        if url is None:
            url = config.get('web', 'reset_password_url')
        return _add_params(url,
                           token=self.reset_password_token,
                           email=self.email)

    @classmethod
    def set_password_url(cls, url, password):
        parts = urlparse.urlsplit(url)
        query = urlparse.parse_qs(parts.query)
        email = query.get('email', [None])[0]
        token = query.get('token', [None])[0]
        return cls.set_password_token(email, token, password)

    @classmethod
    def set_password_token(cls, email, token, password):
        pool = Pool()
        Attempt = pool.get('web.user.authenticate.attempt')
        email = email.lower()

        # Prevent brute force attack
        Transaction().atexit(time.sleep, 2**Attempt.count(email) - 1)

        users = cls.search([
            ('email', '=', email),
        ])
        if users:
            user, = users
            if user.reset_password_token == token:
                now = datetime.datetime.now()
                expire = user.reset_password_token_expire
                user.clear_reset_password_token()
                if expire > now:
                    user.password = password
                    user.save()
                    Attempt.remove(email)
                    return True
        Attempt.add(email)
        return False

    @property
    def languages(self):
        pool = Pool()
        Language = pool.get('ir.lang')
        if self.party and self.party.lang:
            languages = [self.party.lang]
        else:
            languages = Language.search([
                ('code', '=', Transaction().language),
            ])
        return languages
示例#11
0
class Invoice(metaclass=PoolMeta):
    __name__ = 'account.invoice'
    numbered_at = fields.Timestamp("Numbered At")
    history_datetime = fields.Function(fields.Timestamp("History DateTime"),
                                       'get_history_datetime')

    @classmethod
    def __register__(cls, module_name):
        super().__register__(module_name)
        table_h = cls.__table_handler__(module_name)
        table = cls.__table__()
        cursor = Transaction().connection.cursor()

        # Migration from 5.2: rename open_date into numbered_at
        if table_h.column_exist('open_date'):
            cursor.execute(
                *table.update([table.numbered_at], [table.open_date]))
            table_h.drop_column('open_date')

    @classmethod
    def __setup__(cls):
        super(Invoice, cls).__setup__()
        cls._check_modify_exclude.add('numbered_at')
        cls.party.datetime_field = 'history_datetime'
        cls.invoice_address.datetime_field = 'history_datetime'
        cls.payment_term.datetime_field = 'history_datetime'

    @classmethod
    def get_history_datetime(cls, invoices, name):
        pool = Pool()
        Party = pool.get('party.party')
        Address = pool.get('party.address')
        PaymentTerm = pool.get('account.invoice.payment_term')
        table = cls.__table__()
        party = Party.__table__()
        address = Address.__table__()
        payment_term = PaymentTerm.__table__()
        cursor = Transaction().connection.cursor()

        invoice_ids = [i.id for i in invoices]
        datetimes = dict.fromkeys(invoice_ids)
        for ids in grouped_slice(invoice_ids):
            cursor.execute(
                *table.join(party, condition=table.party == party.id).join(
                    address, condition=table.invoice_address == address.id
                ).join(payment_term,
                       'LEFT',
                       condition=table.payment_term == payment_term.id).
                select(table.id,
                       Greatest(table.numbered_at, party.create_date,
                                address.create_date, payment_term.create_date),
                       where=reduce_ids(table.id, ids)
                       & (table.numbered_at != Null)))
            datetimes.update(cursor)
        return datetimes

    @classmethod
    def set_number(cls, invoices):
        numbered = [i for i in invoices if not i.number or not i.numbered_at]
        super(Invoice, cls).set_number(invoices)
        if numbered:
            cls.write(numbered, {
                'numbered_at': CurrentTimestamp(),
            })

    @classmethod
    @ModelView.button
    @Workflow.transition('draft')
    def draft(cls, invoices):
        super(Invoice, cls).draft(invoices)
        cls.write(invoices, {
            'numbered_at': None,
        })

    @classmethod
    def copy(cls, invoices, default=None):
        if default is None:
            default = {}
        else:
            default = default.copy()
        default.setdefault('numbered_at', None)
        return super(Invoice, cls).copy(invoices, default=default)