@@ -1,3 +1,4 @@
|
||||
* Allow resetting password of user via RPC
|
||||
* Support cached_property on object with __slots__
|
||||
* Allow creating indexes concurrently
|
||||
* Allow skipping index creation
|
||||
|
||||
@@ -36,6 +36,7 @@ def rpc(request, database_name):
|
||||
methods = {
|
||||
'common.db.login': login,
|
||||
'common.db.logout': logout,
|
||||
'common.db.reset_password': reset_password,
|
||||
'system.listMethods': list_method,
|
||||
'system.methodHelp': help_method,
|
||||
'system.methodSignature': lambda *a: 'signatures not supported',
|
||||
@@ -72,6 +73,24 @@ def logout(request, database_name):
|
||||
context={'_request': request.context})
|
||||
|
||||
|
||||
def reset_password(request, database_name, user, language=None):
|
||||
authentications = config.get(
|
||||
'session', 'authentications', default='password').split(',')
|
||||
if not any('password' in m.split('+') for m in authentications):
|
||||
abort(HTTPStatus.FORBIDDEN)
|
||||
context = {
|
||||
'language': language,
|
||||
'_request': request.context,
|
||||
}
|
||||
try:
|
||||
security.reset_password(database_name, user, context=context)
|
||||
except backend.DatabaseOperationalError:
|
||||
logger.error('fail to connect to %s', database_name, exc_info=True)
|
||||
abort(HTTPStatus.NOT_FOUND)
|
||||
except RateLimitException:
|
||||
abort(HTTPStatus.TOO_MANY_REQUESTS)
|
||||
|
||||
|
||||
@app.route('/', methods=['POST'])
|
||||
def root(request, *args):
|
||||
methods = {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
import datetime as dt
|
||||
import logging
|
||||
import random
|
||||
import time
|
||||
|
||||
from trytond import backend
|
||||
from trytond.config import config
|
||||
@@ -76,6 +79,36 @@ def logout(dbname, user, session, context=None):
|
||||
user, _get_remote_addr(context), dbname)
|
||||
|
||||
|
||||
def reset_password(dbname, user, context=None):
|
||||
now = dt.datetime.now()
|
||||
# Prevent guessing code execution path
|
||||
time.sleep(random.random())
|
||||
for count in range(config.getint('database', 'retry'), -1, -1):
|
||||
with Transaction().start(dbname, 0, context=context):
|
||||
pool = _get_pool(dbname)
|
||||
User = pool.get('res.user')
|
||||
try:
|
||||
users = User.search([
|
||||
('login', '=', user),
|
||||
])
|
||||
if not users:
|
||||
logger.info("Reset password for unknown user: %s", user)
|
||||
break
|
||||
else:
|
||||
user, = users
|
||||
if user.password_reset and user.password_reset_expire > now:
|
||||
logger.info(
|
||||
"Password reset already exists for user: %s", user)
|
||||
else:
|
||||
user.reset_password()
|
||||
logger.info("Password reset for user: %s", user)
|
||||
break
|
||||
except backend.DatabaseOperationalError:
|
||||
if count:
|
||||
continue
|
||||
raise
|
||||
|
||||
|
||||
def check(dbname, user, session, context=None):
|
||||
for count in range(config.getint('database', 'retry'), -1, -1):
|
||||
with Transaction().start(dbname, user, context=context) as transaction:
|
||||
|
||||
Reference in New Issue
Block a user