# 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 logging import random import time from trytond.config import config from trytond.protocols.wrappers import ( abort, allow_null_origin, with_pool, with_transaction) from trytond.transaction import Transaction from trytond.wsgi import app logger = logging.getLogger(__name__) @app.route('//user/application/', methods=['POST', 'DELETE']) @allow_null_origin @with_pool @with_transaction(readonly=False) def user_application(request, pool): User = pool.get('res.user') UserApplication = pool.get('res.user.application') LoginAttempt = pool.get('res.user.login.attempt') data = request.parsed_data login = data.get('user') if request.method == 'POST': # Make time random to process and try to use the same path as much as # possible to prevent guessing between valid and invalid requests. Transaction().atexit(time.sleep, random.random()) users = User.search([ ('login', '=', login), ]) if not users: logger.info('User Application not found: %s', data.get('user')) user_id = None else: user, = users user_id = user.id if UserApplication.count(user_id): logger.info('User Application has already a request: %s', login) user_id = None data['user'] = user_id data.pop('key', None) data['state'] = 'requested' application, = UserApplication.create([data]) key = application.key UserApplication.delete(UserApplication.search([ ('user', '=', None), ])) return key elif request.method == 'DELETE': count = LoginAttempt.count(login) if count > config.getint('session', 'max_attempt', default=5): LoginAttempt.add(login) abort(429) Transaction().atexit(time.sleep, 2 ** count - 1) applications = UserApplication.search([ ('user.login', '=', login), ('key', '=', data.get('key')), ('application', '=', data.get('application')), ]) if applications: UserApplication.delete(applications) LoginAttempt.remove(login) else: LoginAttempt.add(login)