Launchpad automatic translations update.
[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 from openerp.osv import osv, fields
27 from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
28 from openerp.tools.safe_eval import safe_eval
29
30 class SignupError(Exception):
31     pass
32
33 def random_token():
34     # the token has an entropy of about 120 bits (6 bits/char * 20 chars)
35     chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
36     return ''.join(random.choice(chars) for _ in xrange(20))
37
38 def now():
39     return time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
40
41 class res_partner(osv.Model):
42     _inherit = 'res.partner'
43
44     def _get_signup_valid(self, cr, uid, ids, name, arg, context=None):
45         dt = now()
46         res = {}
47         for partner in self.browse(cr, uid, ids, context):
48             res[partner.id] = bool(partner.signup_token) and \
49                                 (not partner.signup_expiration or dt <= partner.signup_expiration)
50         return res
51
52     def _get_signup_url_for_action(self, cr, uid, ids, action='login', view_type=None, menu_id=None, res_id=None, context=None):
53         """ generate a signup url for the given partner ids and action, possibly overriding
54             the url state components (menu_id, id, view_type) """
55         res = dict.fromkeys(ids, False)
56         base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
57         for partner in self.browse(cr, uid, ids, context):
58             # when required, make sure the partner has a valid signup token
59             if context and context.get('signup_valid') and not partner.user_ids:
60                 self.signup_prepare(cr, uid, [partner.id], context=context)
61     
62             action_template = None
63             params = {
64                 'action': urllib.quote(action),
65                 'db': urllib.quote(cr.dbname),
66             }
67             if partner.signup_token:
68                 action_template = "?db=%(db)s#action=%(action)s&token=%(token)s"
69                 params['token'] = urllib.quote(partner.signup_token)
70             elif partner.user_ids:
71                 action_template = "?db=%(db)s#action=%(action)s&db=%(db)s&login=%(login)s"
72                 params['login'] = urllib.quote(partner.user_ids[0].login)
73             if action_template:
74                 if view_type:
75                     action_template += '&view_type=%s' % urllib.quote(view_type)
76                 if menu_id:
77                     action_template += '&menu_id=%s' % urllib.quote(str(menu_id))
78                 if res_id:
79                     action_template += '&id=%s' % urllib.quote(str(res_id))
80                 res[partner.id] = urlparse.urljoin(base_url, action_template % params)
81         return res
82
83     def _get_signup_url(self, cr, uid, ids, name, arg, context=None):
84         """ proxy for function field towards actual implementation """
85         return self._get_signup_url_for_action(cr, uid, ids, context=context)
86
87     _columns = {
88         'signup_token': fields.char('Signup Token'),
89         'signup_expiration': fields.datetime('Signup Expiration'),
90         'signup_valid': fields.function(_get_signup_valid, type='boolean', string='Signup Token is Valid'),
91         'signup_url': fields.function(_get_signup_url, type='char', string='Signup URL'),
92     }
93
94     def action_signup_prepare(self, cr, uid, ids, context=None):
95         return self.signup_prepare(cr, uid, ids, context=context)
96
97     def signup_prepare(self, cr, uid, ids, expiration=False, context=None):
98         """ generate a new token for the partners with the given validity, if necessary
99             :param expiration: the expiration datetime of the token (string, optional)
100         """
101         for partner in self.browse(cr, uid, ids, context):
102             if expiration or not partner.signup_valid:
103                 token = random_token()
104                 while self._signup_retrieve_partner(cr, uid, token, context=context):
105                     token = random_token()
106                 partner.write({'signup_token': token, 'signup_expiration': expiration})
107         return True
108
109     def _signup_retrieve_partner(self, cr, uid, token,
110             check_validity=False, raise_exception=False, context=None):
111         """ find the partner corresponding to a token, and possibly check its validity
112             :param token: the token to resolve
113             :param check_validity: if True, also check validity
114             :param raise_exception: if True, raise exception instead of returning False
115             :return: partner (browse record) or False (if raise_exception is False)
116         """
117         partner_ids = self.search(cr, uid, [('signup_token', '=', token)], context=context)
118         if not partner_ids:
119             if raise_exception:
120                 raise SignupError("Signup token '%s' is not valid" % token)
121             return False
122         partner = self.browse(cr, uid, partner_ids[0], context)
123         if check_validity and not partner.signup_valid:
124             if raise_exception:
125                 raise SignupError("Signup token '%s' is no longer valid" % token)
126             return False
127         return partner
128
129     def signup_retrieve_info(self, cr, uid, token, context=None):
130         """ retrieve the user info about the token
131             :return: a dictionary with the user information:
132                 - 'db': the name of the database
133                 - 'token': the token, if token is valid
134                 - 'name': the name of the partner, if token is valid
135                 - 'login': the user login, if the user already exists
136                 - 'email': the partner email, if the user does not exist
137         """
138         partner = self._signup_retrieve_partner(cr, uid, token, raise_exception=True, context=None)
139         res = {'db': cr.dbname}
140         if partner.signup_valid:
141             res['token'] = token
142             res['name'] = partner.name
143         if partner.user_ids:
144             res['login'] = partner.user_ids[0].login
145         else:
146             res['email'] = partner.email or ''
147         return res
148
149 class res_users(osv.Model):
150     _inherit = 'res.users'
151
152     def _get_state(self, cr, uid, ids, name, arg, context=None):
153         res = {}
154         for user in self.browse(cr, uid, ids, context):
155             res[user.id] = ('reset' if user.signup_valid else
156                             'active' if user.login_date else
157                             'new')
158         return res
159
160     _columns = {
161         'state': fields.function(_get_state, string='Status', type='selection',
162                     selection=[('new', 'New'), ('active', 'Active'), ('reset', 'Resetting Password')]),
163     }
164
165     def signup(self, cr, uid, values, token=None, context=None):
166         """ signup a user, to either:
167             - create a new user (no token), or
168             - create a user for a partner (with token, but no user for partner), or
169             - change the password of a user (with token, and existing user).
170             :param values: a dictionary with field values that are written on user
171             :param token: signup token (optional)
172             :return: (dbname, login, password) for the signed up user
173         """
174         if token:
175             # signup with a token: find the corresponding partner id
176             res_partner = self.pool.get('res.partner')
177             partner = res_partner._signup_retrieve_partner(
178                             cr, uid, token, check_validity=True, raise_exception=True, context=None)
179             # invalidate signup token
180             partner.write({'signup_token': False, 'signup_expiration': False})
181
182             partner_user = partner.user_ids and partner.user_ids[0] or False
183             if partner_user:
184                 # user exists, modify it according to values
185                 values.pop('login', None)
186                 values.pop('name', None)
187                 partner_user.write(values)
188                 return (cr.dbname, partner_user.login, values.get('password'))
189             else:
190                 # user does not exist: sign up invited user
191                 values.update({
192                     'name': partner.name,
193                     'partner_id': partner.id,
194                     'email': values.get('email') or values.get('login'),
195                 })
196                 self._signup_create_user(cr, uid, values, context=context)
197         else:
198             # no token, sign up an external user
199             values['email'] = values.get('email') or values.get('login')
200             self._signup_create_user(cr, uid, values, context=context)
201
202         return (cr.dbname, values.get('login'), values.get('password'))
203
204     def _signup_create_user(self, cr, uid, values, context=None):
205         """ create a new user from the template user """
206         ir_config_parameter = self.pool.get('ir.config_parameter')
207         template_user_id = safe_eval(ir_config_parameter.get_param(cr, uid, 'auth_signup.template_user_id', 'False'))
208         assert template_user_id and self.exists(cr, uid, template_user_id, context=context), 'Signup: invalid template user'
209
210         # check that uninvited users may sign up
211         if 'partner_id' not in values:
212             if not safe_eval(ir_config_parameter.get_param(cr, uid, 'auth_signup.allow_uninvited', 'False')):
213                 raise SignupError('Signup is not allowed for uninvited users')
214
215         assert values.get('login'), "Signup: no login given for new user"
216         assert values.get('partner_id') or values.get('name'), "Signup: no name or partner given for new user"
217
218         # create a copy of the template user (attached to a specific partner_id if given)
219         values['active'] = True
220         return self.copy(cr, uid, template_user_id, values, context=context)