[FIX] account: incorrect usage of browse_record in fix for accounting reports merged...
[odoo/odoo.git] / addons / auth_signup / res_users.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2012-today OpenERP SA (<http://www.openerp.com>)
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>
19 #
20 ##############################################################################
21 import random
22 import time
23 import urllib
24 import urlparse
25
26 import openerp
27 from openerp.osv import osv, fields
28 from openerp import SUPERUSER_ID
29 from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
30 from openerp.tools.safe_eval import safe_eval
31
32 class SignupError(Exception):
33     pass
34
35 def random_token():
36     # the token has an entropy of about 120 bits (6 bits/char * 20 chars)
37     chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
38     return ''.join(random.choice(chars) for i in xrange(20))
39
40 def now():
41     return time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
42
43 class res_partner(osv.Model):
44     _inherit = 'res.partner'
45
46     def _get_signup_valid(self, cr, uid, ids, name, arg, context=None):
47         dt = now()
48         res = {}
49         for partner in self.browse(cr, uid, ids, context):
50             res[partner.id] = bool(partner.signup_token) and \
51                                 (not partner.signup_expiration or dt <= partner.signup_expiration)
52         return res
53
54     def _get_signup_url(self, cr, uid, ids, name, arg, context=None):
55         """ determine a signup url for a given partner """
56         base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
57
58         # if required, make sure that every partner without user has a valid signup token
59         if context and context.get('signup_valid'):
60             unsigned_ids = [p.id for p in self.browse(cr, uid, ids, context) if not p.user_ids]
61             self.signup_prepare(cr, uid, unsigned_ids, context=context)
62
63         res = dict.fromkeys(ids, False)
64         for partner in self.browse(cr, uid, ids, context):
65             if partner.signup_token:
66                 params = (urllib.quote(cr.dbname), urllib.quote(partner.signup_token))
67                 res[partner.id] = urlparse.urljoin(base_url, "#action=login&db=%s&token=%s" % params)
68             elif partner.user_ids:
69                 user = partner.user_ids[0]
70                 params = (urllib.quote(cr.dbname), urllib.quote(user.login))
71                 res[partner.id] = urlparse.urljoin(base_url, "#action=login&db=%s&login=%s" % params)
72         return res
73
74     _columns = {
75         'signup_token': fields.char(size=24, string='Signup Token'),
76         'signup_expiration': fields.datetime(string='Signup Expiration'),
77         'signup_valid': fields.function(_get_signup_valid, type='boolean', string='Signup Token is Valid'),
78         'signup_url': fields.function(_get_signup_url, type='char', string='Signup URL'),
79     }
80
81     def action_signup_prepare(self, cr, uid, ids, context=None):
82         return self.signup_prepare(cr, uid, ids, context=context)
83
84     def signup_prepare(self, cr, uid, ids, expiration=False, context=None):
85         """ generate a new token for the partners with the given validity, if necessary
86             :param expiration: the expiration datetime of the token (string, optional)
87         """
88         for partner in self.browse(cr, uid, ids, context):
89             if expiration or not partner.signup_valid:
90                 token = random_token()
91                 while self._signup_retrieve_partner(cr, uid, token, context=context):
92                     token = random_token()
93                 partner.write({'signup_token': token, 'signup_expiration': expiration})
94         return True
95
96     def _signup_retrieve_partner(self, cr, uid, token,
97             check_validity=False, raise_exception=False, context=None):
98         """ find the partner corresponding to a token, and possibly check its validity
99             :param token: the token to resolve
100             :param check_validity: if True, also check validity
101             :param raise_exception: if True, raise exception instead of returning False
102             :return: partner (browse record) or False (if raise_exception is False)
103         """
104         partner_ids = self.search(cr, uid, [('signup_token', '=', token)], context=context)
105         if not partner_ids:
106             if raise_exception:
107                 raise SignupError("Signup token '%s' is not valid" % token)
108             return False
109         partner = self.browse(cr, uid, partner_ids[0], context)
110         if check_validity and not partner.signup_valid:
111             if raise_exception:
112                 raise SignupError("Signup token '%s' is no longer valid" % token)
113             return False
114         return partner
115
116     def signup_retrieve_info(self, cr, uid, token, context=None):
117         """ retrieve the user info about the token
118             :return: a dictionary with the user information:
119                 - 'db': the name of the database
120                 - 'token': the token, if token is valid
121                 - 'name': the name of the partner, if token is valid
122                 - 'login': the user login, if the user already exists
123                 - 'email': the partner email, if the user does not exist
124         """
125         partner = self._signup_retrieve_partner(cr, uid, token, raise_exception=True, context=None)
126         res = {'db': cr.dbname}
127         if partner.signup_valid:
128             res['token'] = token
129             res['name'] = partner.name
130         if partner.user_ids:
131             res['login'] = partner.user_ids[0].login
132         else:
133             res['email'] = partner.email or ''
134         return res
135
136 class res_users(osv.Model):
137     _inherit = 'res.users'
138
139     def _get_state(self, cr, uid, ids, name, arg, context=None):
140         return dict((user.id, 'new' if not user.login_date else 'reset' if user.signup_token else 'active')
141                     for user in self.browse(cr, uid, ids, context))
142
143     _columns = {
144         'state': fields.function(_get_state, string='Status', type='selection',
145                     selection=[('new', 'New'), ('active', 'Active'), ('reset', 'Resetting Password')]),
146     }
147
148     def signup(self, cr, uid, values, token=None, context=None):
149         """ signup a user, to either:
150             - create a new user (no token), or
151             - create a user for a partner (with token, but no user for partner), or
152             - change the password of a user (with token, and existing user).
153             :param values: a dictionary with field values
154             :param token: signup token (optional)
155             :return: (dbname, login, password) for the signed up user
156         """
157         assert values.get('login') and values.get('password')
158         result = (cr.dbname, values['login'], values['password'])
159
160         if token:
161             # signup with a token: find the corresponding partner id
162             res_partner = self.pool.get('res.partner')
163             partner = res_partner._signup_retrieve_partner(cr, uid, token,
164                                         check_validity=True, raise_exception=True, context=None)
165             # invalidate signup token
166             partner.write({'signup_token': False, 'signup_expiration': False})
167             if partner.user_ids:
168                 # user exists, modify its password
169                 partner.user_ids[0].write({'password': values['password']})
170             else:
171                 # user does not exist: sign up invited user
172                 self._signup_create_user(cr, uid, {
173                     'name': partner.name,
174                     'login': values['login'],
175                     'password': values['password'],
176                     'email': values['login'],
177                     'partner_id': partner.id,
178                 }, context=context)
179             return result
180
181         # sign up an external user
182         assert values.get('name'), 'Signup: no name given for new user'
183         self._signup_create_user(cr, uid, {
184             'name': values['name'],
185             'login': values['login'],
186             'password': values['password'],
187             'email': values['login'],
188         }, context=context)
189         return result
190
191     def _signup_create_user(self, cr, uid, values, context=None):
192         """ create a new user from the template user """
193         ir_config_parameter = self.pool.get('ir.config_parameter')
194         template_user_id = safe_eval(ir_config_parameter.get_param(cr, uid, 'auth_signup.template_user_id', 'False'))
195         assert template_user_id and self.exists(cr, uid, template_user_id, context=context), 'Signup: invalid template user'
196
197         # check that uninvited users may sign up
198         if 'partner_id' not in values:
199             if not safe_eval(ir_config_parameter.get_param(cr, uid, 'auth_signup.allow_uninvited', 'False')):
200                 raise SignupError('Signup is not allowed for uninvited users')
201
202         # create a copy of the template user (attached to a specific partner_id if given)
203         values['active'] = True
204         return self.copy(cr, uid, template_user_id, values, context=context)