2012-09-21 10:42:37 +00:00
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2012-today OpenERP SA (<http://www.openerp.com>)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
#
##############################################################################
2012-12-03 14:44:24 +00:00
from datetime import datetime , timedelta
2012-10-11 00:05:34 +00:00
import random
2012-12-14 10:43:29 +00:00
from urllib import urlencode
from urlparse import urljoin
2012-09-21 10:42:37 +00:00
from openerp . osv import osv , fields
from openerp . tools . misc import DEFAULT_SERVER_DATETIME_FORMAT
2012-09-25 12:55:22 +00:00
from openerp . tools . safe_eval import safe_eval
2012-12-03 14:44:24 +00:00
from openerp . tools . translate import _
2012-09-21 10:42:37 +00:00
2012-11-03 14:42:37 +00:00
class SignupError ( Exception ) :
pass
2012-10-23 19:18:05 +00:00
2012-09-21 10:42:37 +00:00
def random_token ( ) :
2012-09-25 10:40:13 +00:00
# the token has an entropy of about 120 bits (6 bits/char * 20 chars)
chars = ' ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 '
2012-12-03 14:44:24 +00:00
return ' ' . join ( random . choice ( chars ) for i in xrange ( 20 ) )
def now ( * * kwargs ) :
dt = datetime . now ( ) + timedelta ( * * kwargs )
return dt . strftime ( DEFAULT_SERVER_DATETIME_FORMAT )
2012-09-21 10:42:37 +00:00
class res_partner ( osv . Model ) :
_inherit = ' res.partner '
2012-09-25 10:40:13 +00:00
2012-09-28 08:58:43 +00:00
def _get_signup_valid ( self , cr , uid , ids , name , arg , context = None ) :
dt = now ( )
res = { }
for partner in self . browse ( cr , uid , ids , context ) :
2012-10-01 15:03:33 +00:00
res [ partner . id ] = bool ( partner . signup_token ) and \
( not partner . signup_expiration or dt < = partner . signup_expiration )
2012-09-28 08:58:43 +00:00
return res
2013-01-04 11:32:41 +00:00
def _get_signup_url_for_action ( self , cr , uid , ids , action = ' login ' , view_type = None , menu_id = None , res_id = None , model = None , context = None ) :
2012-11-16 16:15:54 +00:00
""" generate a signup url for the given partner ids and action, possibly overriding
the url state components ( menu_id , id , view_type ) """
2012-09-28 08:58:43 +00:00
res = dict . fromkeys ( ids , False )
2012-11-16 16:15:54 +00:00
base_url = self . pool . get ( ' ir.config_parameter ' ) . get_param ( cr , uid , ' web.base.url ' )
2012-09-28 08:58:43 +00:00
for partner in self . browse ( cr , uid , ids , context ) :
2012-11-16 16:15:54 +00:00
# when required, make sure the partner has a valid signup token
if context and context . get ( ' signup_valid ' ) and not partner . user_ids :
self . signup_prepare ( cr , uid , [ partner . id ] , context = context )
2013-01-04 11:32:41 +00:00
partner . refresh ( )
2012-12-03 14:44:24 +00:00
2012-12-14 10:43:29 +00:00
# the parameters to encode for the query and fragment part of url
query = { ' db ' : cr . dbname }
2012-12-18 15:32:55 +00:00
fragment = { ' action ' : action , ' type ' : partner . signup_type }
2012-12-14 10:43:29 +00:00
2012-09-28 08:58:43 +00:00
if partner . signup_token :
2012-12-14 10:43:29 +00:00
fragment [ ' token ' ] = partner . signup_token
2012-10-01 09:14:41 +00:00
elif partner . user_ids :
2012-12-14 10:43:29 +00:00
fragment [ ' db ' ] = cr . dbname
fragment [ ' login ' ] = partner . user_ids [ 0 ] . login
else :
continue # no signup token, no user, thus no signup url!
if view_type :
fragment [ ' view_type ' ] = view_type
if menu_id :
fragment [ ' menu_id ' ] = menu_id
2013-01-04 11:32:41 +00:00
if model :
fragment [ ' model ' ] = model
2012-12-14 10:43:29 +00:00
if res_id :
fragment [ ' id ' ] = res_id
res [ partner . id ] = urljoin ( base_url , " ? %s # %s " % ( urlencode ( query ) , urlencode ( fragment ) ) )
2012-09-28 08:58:43 +00:00
return res
2012-11-16 16:15:54 +00:00
def _get_signup_url ( self , cr , uid , ids , name , arg , context = None ) :
""" proxy for function field towards actual implementation """
return self . _get_signup_url_for_action ( cr , uid , ids , context = context )
2012-09-21 10:42:37 +00:00
_columns = {
2012-11-16 16:15:54 +00:00
' signup_token ' : fields . char ( ' Signup Token ' ) ,
2012-12-17 16:33:57 +00:00
' signup_type ' : fields . char ( ' Signup Token Type ' ) ,
2012-11-16 16:15:54 +00:00
' signup_expiration ' : fields . datetime ( ' Signup Expiration ' ) ,
2012-09-28 08:58:43 +00:00
' signup_valid ' : fields . function ( _get_signup_valid , type = ' boolean ' , string = ' Signup Token is Valid ' ) ,
' signup_url ' : fields . function ( _get_signup_url , type = ' char ' , string = ' Signup URL ' ) ,
2012-09-21 10:42:37 +00:00
}
2012-09-28 08:58:43 +00:00
def action_signup_prepare ( self , cr , uid , ids , context = None ) :
return self . signup_prepare ( cr , uid , ids , context = context )
2012-09-27 08:27:04 +00:00
2012-12-17 16:33:57 +00:00
def signup_prepare ( self , cr , uid , ids , signup_type = " signup " , expiration = False , context = None ) :
2012-09-28 08:58:43 +00:00
""" generate a new token for the partners with the given validity, if necessary
2012-09-21 10:42:37 +00:00
: param expiration : the expiration datetime of the token ( string , optional )
"""
2012-09-28 08:58:43 +00:00
for partner in self . browse ( cr , uid , ids , context ) :
if expiration or not partner . signup_valid :
token = random_token ( )
while self . _signup_retrieve_partner ( cr , uid , token , context = context ) :
token = random_token ( )
2012-12-17 16:33:57 +00:00
partner . write ( { ' signup_token ' : token , ' signup_type ' : signup_type , ' signup_expiration ' : expiration } )
2012-09-28 08:58:43 +00:00
return True
2012-09-21 10:42:37 +00:00
2012-09-28 14:21:03 +00:00
def _signup_retrieve_partner ( self , cr , uid , token ,
check_validity = False , raise_exception = False , context = None ) :
""" find the partner corresponding to a token, and possibly check its validity
: param token : the token to resolve
: param check_validity : if True , also check validity
: param raise_exception : if True , raise exception instead of returning False
2012-09-25 10:40:13 +00:00
: return : partner ( browse record ) or False ( if raise_exception is False )
"""
2012-09-21 10:42:37 +00:00
partner_ids = self . search ( cr , uid , [ ( ' signup_token ' , ' = ' , token ) ] , context = context )
2012-09-25 10:40:13 +00:00
if not partner_ids :
if raise_exception :
2012-10-23 19:18:05 +00:00
raise SignupError ( " Signup token ' %s ' is not valid " % token )
2012-09-25 10:40:13 +00:00
return False
partner = self . browse ( cr , uid , partner_ids [ 0 ] , context )
2012-09-28 14:21:03 +00:00
if check_validity and not partner . signup_valid :
2012-09-25 10:40:13 +00:00
if raise_exception :
2012-10-23 19:18:05 +00:00
raise SignupError ( " Signup token ' %s ' is no longer valid " % token )
2012-09-25 10:40:13 +00:00
return False
return partner
def signup_retrieve_info ( self , cr , uid , token , context = None ) :
""" retrieve the user info about the token
2012-09-28 14:21:03 +00:00
: return : a dictionary with the user information :
- ' db ' : the name of the database
- ' token ' : the token , if token is valid
- ' name ' : the name of the partner , if token is valid
- ' login ' : the user login , if the user already exists
- ' email ' : the partner email , if the user does not exist
2012-09-25 10:40:13 +00:00
"""
partner = self . _signup_retrieve_partner ( cr , uid , token , raise_exception = True , context = None )
2012-09-28 14:21:03 +00:00
res = { ' db ' : cr . dbname }
if partner . signup_valid :
res [ ' token ' ] = token
res [ ' name ' ] = partner . name
2012-09-25 10:40:13 +00:00
if partner . user_ids :
2012-09-28 14:21:03 +00:00
res [ ' login ' ] = partner . user_ids [ 0 ] . login
2012-09-25 10:40:13 +00:00
else :
2012-09-28 14:21:03 +00:00
res [ ' email ' ] = partner . email or ' '
return res
2012-09-21 10:42:37 +00:00
2012-09-25 10:40:13 +00:00
class res_users ( osv . Model ) :
_inherit = ' res.users '
2012-10-02 14:57:57 +00:00
def _get_state ( self , cr , uid , ids , name , arg , context = None ) :
2012-11-28 09:03:35 +00:00
res = { }
for user in self . browse ( cr , uid , ids , context ) :
2013-03-14 16:01:41 +00:00
res [ user . id ] = ( ' active ' if user . login_date else ' new ' )
2012-11-28 09:03:35 +00:00
return res
2012-10-02 14:57:57 +00:00
_columns = {
2012-10-12 11:42:58 +00:00
' state ' : fields . function ( _get_state , string = ' Status ' , type = ' selection ' ,
2013-03-14 16:01:41 +00:00
selection = [ ( ' new ' , ' Never Connected ' ) , ( ' active ' , ' Activated ' ) ] ) ,
2012-10-02 14:57:57 +00:00
}
2012-09-25 10:40:13 +00:00
def signup ( self , cr , uid , values , token = None , context = None ) :
2012-09-21 10:42:37 +00:00
""" signup a user, to either:
- create a new user ( no token ) , or
- create a user for a partner ( with token , but no user for partner ) , or
- change the password of a user ( with token , and existing user ) .
2012-11-23 15:35:38 +00:00
: param values : a dictionary with field values that are written on user
2012-09-21 10:42:37 +00:00
: param token : signup token ( optional )
2012-09-24 08:26:09 +00:00
: return : ( dbname , login , password ) for the signed up user
2012-09-21 10:42:37 +00:00
"""
if token :
# signup with a token: find the corresponding partner id
2012-09-25 10:40:13 +00:00
res_partner = self . pool . get ( ' res.partner ' )
2012-11-23 15:35:38 +00:00
partner = res_partner . _signup_retrieve_partner (
cr , uid , token , check_validity = True , raise_exception = True , context = None )
2012-09-26 15:04:59 +00:00
# invalidate signup token
2012-12-17 16:33:57 +00:00
partner . write ( { ' signup_token ' : False , ' signup_type ' : False , ' signup_expiration ' : False } )
2012-11-23 15:35:38 +00:00
partner_user = partner . user_ids and partner . user_ids [ 0 ] or False
if partner_user :
# user exists, modify it according to values
values . pop ( ' login ' , None )
values . pop ( ' name ' , None )
partner_user . write ( values )
return ( cr . dbname , partner_user . login , values . get ( ' password ' ) )
2012-09-24 08:26:09 +00:00
else :
# user does not exist: sign up invited user
2012-11-23 15:35:38 +00:00
values . update ( {
2012-09-28 12:09:44 +00:00
' name ' : partner . name ,
2012-09-24 08:26:09 +00:00
' partner_id ' : partner . id ,
2012-11-23 15:35:38 +00:00
' email ' : values . get ( ' email ' ) or values . get ( ' login ' ) ,
} )
2013-04-15 17:39:01 +00:00
if partner . company_id :
values [ ' company_id ' ] = partner . company_id . id
values [ ' company_ids ' ] = [ ( 6 , 0 , [ partner . company_id . id ] ) ]
2012-11-23 15:35:38 +00:00
self . _signup_create_user ( cr , uid , values , context = context )
else :
# no token, sign up an external user
values [ ' email ' ] = values . get ( ' email ' ) or values . get ( ' login ' )
self . _signup_create_user ( cr , uid , values , context = context )
return ( cr . dbname , values . get ( ' login ' ) , values . get ( ' password ' ) )
2012-09-24 08:26:09 +00:00
2012-09-26 15:04:59 +00:00
def _signup_create_user ( self , cr , uid , values , context = None ) :
2012-09-24 08:26:09 +00:00
""" create a new user from the template user """
ir_config_parameter = self . pool . get ( ' ir.config_parameter ' )
2012-09-26 15:04:59 +00:00
template_user_id = safe_eval ( ir_config_parameter . get_param ( cr , uid , ' auth_signup.template_user_id ' , ' False ' ) )
assert template_user_id and self . exists ( cr , uid , template_user_id , context = context ) , ' Signup: invalid template user '
2012-09-28 12:09:44 +00:00
# check that uninvited users may sign up
if ' partner_id ' not in values :
2012-09-25 12:55:22 +00:00
if not safe_eval ( ir_config_parameter . get_param ( cr , uid , ' auth_signup.allow_uninvited ' , ' False ' ) ) :
2012-10-23 19:18:05 +00:00
raise SignupError ( ' Signup is not allowed for uninvited users ' )
2012-09-24 08:26:09 +00:00
2012-11-23 15:35:38 +00:00
assert values . get ( ' login ' ) , " Signup: no login given for new user "
assert values . get ( ' partner_id ' ) or values . get ( ' name ' ) , " Signup: no name or partner given for new user "
2012-09-28 12:09:44 +00:00
# create a copy of the template user (attached to a specific partner_id if given)
values [ ' active ' ] = True
return self . copy ( cr , uid , template_user_id , values , context = context )
2012-12-03 14:44:24 +00:00
def reset_password ( self , cr , uid , login , context = None ) :
""" retrieve the user corresponding to login (login or email),
and reset their password
"""
user_ids = self . search ( cr , uid , [ ( ' login ' , ' = ' , login ) ] , context = context )
if not user_ids :
user_ids = self . search ( cr , uid , [ ( ' email ' , ' = ' , login ) ] , context = context )
if len ( user_ids ) != 1 :
raise Exception ( ' Reset password: invalid username or email ' )
return self . action_reset_password ( cr , uid , user_ids , context = context )
def action_reset_password ( self , cr , uid , ids , context = None ) :
""" create signup token for each user, and send their signup url by email """
# prepare reset password signup
res_partner = self . pool . get ( ' res.partner ' )
partner_ids = [ user . partner_id . id for user in self . browse ( cr , uid , ids , context ) ]
2012-12-17 16:33:57 +00:00
res_partner . signup_prepare ( cr , uid , partner_ids , signup_type = " reset " , expiration = now ( days = + 1 ) , context = context )
2012-12-03 14:44:24 +00:00
2013-01-31 12:08:34 +00:00
if not context :
context = { }
2012-12-03 14:44:24 +00:00
# send email to users with their signup url
2013-01-22 14:59:25 +00:00
template = False
if context . get ( ' create_user ' ) :
try :
template = self . pool . get ( ' ir.model.data ' ) . get_object ( cr , uid , ' auth_signup ' , ' set_password_email ' )
except ValueError :
pass
if not bool ( template ) :
template = self . pool . get ( ' ir.model.data ' ) . get_object ( cr , uid , ' auth_signup ' , ' reset_password_email ' )
2012-12-20 14:11:51 +00:00
mail_obj = self . pool . get ( ' mail.mail ' )
2012-12-03 14:44:24 +00:00
assert template . _name == ' email.template '
2013-02-14 17:31:56 +00:00
2012-12-03 14:44:24 +00:00
for user in self . browse ( cr , uid , ids , context ) :
if not user . email :
2013-01-02 10:18:05 +00:00
raise osv . except_osv ( _ ( " Cannot send email: user has no email address. " ) , user . name )
2012-12-20 14:11:51 +00:00
mail_id = self . pool . get ( ' email.template ' ) . send_mail ( cr , uid , template . id , user . id , True , context = context )
mail_state = mail_obj . read ( cr , uid , mail_id , [ ' state ' ] , context = context )
2013-02-14 17:31:56 +00:00
2013-01-02 10:18:05 +00:00
if mail_state and mail_state [ ' state ' ] == ' exception ' :
2013-02-14 17:34:13 +00:00
raise self . pool . get ( ' res.config.settings ' ) . get_config_warning ( cr , _ ( " Cannot send email: no outgoing email server configured. \n You can configure it under % (menu:base_setup.menu_general_configuration)s. " ) , context )
2012-12-20 14:11:51 +00:00
else :
2013-03-13 16:26:51 +00:00
return True
2012-12-03 15:57:57 +00:00
def create ( self , cr , uid , values , context = None ) :
# overridden to automatically invite user to sign up
user_id = super ( res_users , self ) . create ( cr , uid , values , context = context )
user = self . browse ( cr , uid , user_id , context = context )
2012-12-06 09:36:03 +00:00
if context and context . get ( ' reset_password ' ) and user . email :
2013-01-22 14:59:25 +00:00
ctx = dict ( context , create_user = True )
self . action_reset_password ( cr , uid , [ user . id ] , context = ctx )
2012-12-31 15:38:50 +00:00
return user_id