Merge lp:~fabien-morin/unifield-server/fm-us-1381-1802-1651-1763 into lp:unifield-server

Proposed by jftempo
Status: Merged
Merged at revision: 4079
Proposed branch: lp:~fabien-morin/unifield-server/fm-us-1381-1802-1651-1763
Merge into: lp:unifield-server
Diff against target: 1094 lines (+536/-174) (has conflicts)
12 files modified
bin/addons/base/base_update.xml (+8/-0)
bin/addons/base/res/res_user.py (+200/-94)
bin/addons/msf_profile/data/patches.xml (+7/-0)
bin/addons/msf_profile/i18n/fr_MF.po (+81/-0)
bin/addons/msf_profile/msf_profile.py (+18/-0)
bin/addons/msf_profile/user_access_configurator_view.xml (+1/-0)
bin/addons/msf_sync_data_server/data/sync_server.sync_rule.csv (+1/-0)
bin/addons/sync_client/rpc.py (+3/-0)
bin/openerp-server.py (+22/-19)
bin/service/security.py (+134/-39)
bin/service/web_services.py (+57/-22)
setup.py (+4/-0)
Text conflict in bin/addons/msf_profile/data/patches.xml
Text conflict in bin/addons/msf_profile/i18n/fr_MF.po
Text conflict in bin/addons/msf_profile/msf_profile.py
Text conflict in bin/addons/sync_client/rpc.py
Text conflict in bin/service/web_services.py
Text conflict in setup.py
To merge this branch: bzr merge lp:~fabien-morin/unifield-server/fm-us-1381-1802-1651-1763
Reviewer Review Type Date Requested Status
UniField Reviewer Team Pending
Review via email: mp+309126@code.launchpad.net
To post a comment you must log in.
3996. By Fabien MORIN

US-1381 [IMP] comment the synchronize checkbox because the US-1381 is
postponned.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/addons/base/base_update.xml'
2--- bin/addons/base/base_update.xml 2016-06-15 09:19:59 +0000
3+++ bin/addons/base/base_update.xml 2016-11-21 09:47:14 +0000
4@@ -111,6 +111,12 @@
5 <field name="login" select="1"/>
6 <field name="new_password" password="True"/>
7 <field name="force_password_change"/>
8+ <!-- this functionnality is linked to US-1381. As this
9+ ticket was postponed to UF2.1-5, this checkbox will not be displayed until this ticket is not released
10+ <field name="synchronize" attrs="{'invisible': ['|', '|', ('is_erp_manager', '=', True), ('is_sync_config', '=', True), ('instance_level', '!=', 'section')]}"/>-->
11+ <field name="is_erp_manager" invisible="1"/>
12+ <field name="is_sync_config" invisible="1"/>
13+ <field name="instance_level" invisible="1"/>
14 <newline/>
15 <notebook colspan="4">
16 <page string="User">
17@@ -164,6 +170,7 @@
18 <field name="login"/>
19 <field name="context_lang"/>
20 <field name="date"/>
21+ <field name="synchronize"/>
22 </tree>
23 </field>
24 </record>
25@@ -178,6 +185,7 @@
26 <field name="login"/>
27 <field name="address_id" string="Address"/>
28 <field name="company_ids" string="Company" groups="base.group_multi_company"/>
29+ <field name="synchronize"/>
30 </search>
31 </field>
32 </record>
33
34=== modified file 'bin/addons/base/res/res_user.py'
35--- bin/addons/base/res/res_user.py 2016-10-04 15:38:43 +0000
36+++ bin/addons/base/res/res_user.py 2016-11-21 09:47:14 +0000
37@@ -30,7 +30,7 @@
38 from service import security
39 import netsvc
40 import logging
41-import re
42+from passlib.hash import bcrypt
43
44 class groups(osv.osv):
45 _name = "res.groups"
46@@ -103,7 +103,6 @@
47 _name = "res.users"
48 _order = 'name'
49
50- PASSWORD_MIN_LENGHT = 6
51 WELCOME_MAIL_SUBJECT = u"Welcome to OpenERP"
52 WELCOME_MAIL_BODY = u"An OpenERP account has been created for you, "\
53 "\"%(name)s\".\n\nYour login is %(login)s, "\
54@@ -135,7 +134,7 @@
55
56 def send_welcome_email(self, cr, uid, id, context=None):
57 logger= netsvc.Logger()
58- user = self.pool.get('res.users').read(cr, uid, id, context=context)
59+ user = self.read(cr, uid, id, context=context)
60 if not tools.config.get('smtp_server'):
61 logger.notifyChannel('mails', netsvc.LOG_WARNING,
62 _('"smtp_server" needs to be set to send mails to users'))
63@@ -214,11 +213,69 @@
64 # so that the new password is immediately used for further RPC requests, otherwise the user
65 # will face unexpected 'Access Denied' exceptions.
66 raise osv.except_osv(_('Operation Canceled'), _('Please use the change password wizard (in User Preferences or User menu) to change your own password.'))
67- if not all(self.is_password_strong(value, login).values()):
68- raise osv.except_osv(_('Operation Canceled'), _('The new password is not strong enough. '\
69- 'Password must be different from the login, it must contain '\
70- 'at least one number and be at least %s characters.' % self.PASSWORD_MIN_LENGHT))
71- self.write(cr, uid, id, {'password': value})
72+ security.check_password_validity(self, cr, uid, None, value, value, login)
73+ encrypted_password = bcrypt.encrypt(tools.ustr(value))
74+ self.write(cr, uid, id, {'password': encrypted_password})
75+
76+ def _is_erp_manager(self, cr, uid, ids, name=None, arg=None, context=None):
77+ '''
78+ return True if the user is member of the group_erp_manager (usually,
79+ admin of the site).
80+ '''
81+ if isinstance(ids, (int, long)):
82+ ids = [ids]
83+ manager_group_id = None
84+ result = dict.fromkeys(ids, False)
85+ try:
86+ dataobj = self.pool.get('ir.model.data')
87+ dummy, manager_group_id = dataobj.get_object_reference(cr, 1, 'base',
88+ 'group_erp_manager')
89+ except ValueError:
90+ # If these groups does not exists anymore
91+ pass
92+ if manager_group_id:
93+ read_result = self.read(cr, uid, ids, ['groups_id'], context=context)
94+ for current_user in read_result:
95+ if manager_group_id in current_user['groups_id']:
96+ result[current_user['id']] = True
97+ return result
98+
99+ def _is_sync_config(self, cr, uid, ids, name=None, arg=None, context=None):
100+ '''
101+ return True if the user is member of the Sync_Config
102+ '''
103+ if isinstance(ids, (int, long)):
104+ ids = [ids]
105+ result = dict.fromkeys(ids, False)
106+ group_id = None
107+ res_group_obj = self.pool.get('res.groups')
108+ group_ids = res_group_obj.search(cr, uid,
109+ [('name', '=', 'Sync_Config')], context=context)
110+ if group_ids:
111+ group_id = group_ids[0]
112+ read_result = self.read(cr, uid, ids, ['groups_id'], context=context)
113+ for current_user in read_result:
114+ if group_id in current_user['groups_id']:
115+ result[current_user['id']] = True
116+ return result
117+
118+ def _get_instance_level(self, cr, uid, ids, name=None, arg=None, context=None):
119+ '''
120+ return the level of the instance related to the company of the user
121+ '''
122+ if isinstance(ids, (int, long)):
123+ ids = [ids]
124+ result = {}
125+ for user_id in ids:
126+ level = False
127+ company_id = self._get_company(cr, user_id, context=context)
128+ instance_id = self.pool.get('res.company').read(cr, uid, company_id,
129+ ['instance_id'], context=context)['instance_id']
130+ instance_id = instance_id and instance_id[0] or False
131+ if instance_id:
132+ level = self.pool.get('msf.instance').read(cr, uid, instance_id, ['level'], context=context)['level']
133+ result[user_id] = level
134+ return result
135
136 _columns = {
137 'name': fields.char('User Name', size=64, required=True, select=True,
138@@ -226,7 +283,7 @@
139 " and most listings"),
140 'login': fields.char('Login', size=64, required=True,
141 help="Used to log into the system"),
142- 'password': fields.char('Password', size=64, invisible=True, help="Keep empty if you don't want the user to be able to connect on the system."),
143+ 'password': fields.char('Password', size=128, invisible=True, help="Keep empty if you don't want the user to be able to connect on the system."),
144 'new_password': fields.function(lambda *a:'', method=True, type='char', size=64,
145 fnct_inv=_set_new_password,
146 string='Change password', help="Only specify a value if you want to change the user password. "
147@@ -265,6 +322,16 @@
148 'user_email': fields.function(_email_get, method=True, fnct_inv=_email_set, string='Email', type="char", size=240),
149 'menu_tips': fields.boolean('Menu Tips', help="Check out this box if you want to always display tips on each menu action"),
150 'date': fields.datetime('Last Connection', readonly=True),
151+ 'synchronize': fields.boolean('Synchronize', help="Synchronize down this user"),
152+ 'is_erp_manager': fields.function(_is_erp_manager, method=True,
153+ fnct_inv=lambda *a:'', string='Is ERP Manager ?',
154+ type="boolean"),
155+ 'is_sync_config': fields.function(_is_sync_config, method=True,
156+ fnct_inv=lambda *a:'', string='Is Sync Config ?',
157+ type="boolean"),
158+ 'instance_level': fields.function(_get_instance_level, method=True,
159+ fnct_inv=lambda *a:'', string='Instance level',
160+ type="char"),
161 }
162
163 def on_change_company_id(self, cr, uid, ids, company_id):
164@@ -277,17 +344,18 @@
165
166 def read(self,cr, uid, ids, fields=None, context=None, load='_classic_read'):
167 def override_password(o):
168- if 'password' in o and ( 'id' not in o or o['id'] != uid ):
169+ if 'id' not in o or o['id'] != uid:
170 o['password'] = '********'
171 return o
172
173 result = super(users, self).read(cr, uid, ids, fields, context, load)
174- canwrite = self.pool.get('ir.model.access').check(cr, uid, 'res.users', 'write', raise_exception=False)
175- if not canwrite:
176- if isinstance(ids, (int, float)):
177- result = override_password(result)
178- else:
179- result = map(override_password, result)
180+ if 'password' in result:
181+ canwrite = self.pool.get('ir.model.access').check(cr, uid, 'res.users', 'write', raise_exception=False)
182+ if not canwrite:
183+ if isinstance(ids, (int, float)):
184+ result = override_password(result)
185+ else:
186+ result = map(override_password, result)
187 return result
188
189
190@@ -321,7 +389,7 @@
191 def _get_company(self,cr, uid, context=None, uid2=False):
192 if not uid2:
193 uid2 = uid
194- user = self.pool.get('res.users').read(cr, uid, uid2, ['company_id'], context)
195+ user = self.read(cr, uid, uid2, ['company_id'], context)
196 company_id = user.get('company_id', False)
197 return company_id and company_id[0] or False
198
199@@ -398,6 +466,13 @@
200
201 res = super(users, self).write(cr, uid, ids, values, context=context)
202
203+ # uncheck synchronize checkbox if the user is manager or sync config
204+ if values.get('groups_id'):
205+ if any(self._is_sync_config(cr, uid, ids, context=context).values()) or\
206+ any(self._is_erp_manager(cr, uid, ids, context=context).values()):
207+ vals = {'synchronize': False}
208+ res = super(users, self).write(cr, uid, ids, vals, context=context)
209+
210 # clear caches linked to the users
211 self.company_get.clear_cache(cr.dbname)
212 self.pool.get('ir.model.access').call_cache_clearing_methods(cr)
213@@ -461,51 +536,81 @@
214 data_id = dataobj._get_id(cr, 1, 'base', 'action_res_users_my')
215 return dataobj.browse(cr, uid, data_id, context=context).res_id
216
217+ def get_user_database_password_from_uid(self, cr, uid):
218+ '''
219+ return encrypted password from the database using uid
220+ '''
221+ cr.execute("""SELECT password from res_users
222+ WHERE id=%s AND active""",
223+ (uid,))
224+ res = cr.fetchone()
225+ if res:
226+ return tools.ustr(res[0])
227+ return False
228+
229+ def get_user_database_password_from_login(self, cr, login):
230+ '''
231+ return encrypted password from the database using login
232+ '''
233+ login = tools.ustr(login).lower()
234+ cr.execute("""SELECT password from res_users
235+ WHERE login=%s AND active""",
236+ (login,))
237+ res = cr.fetchone()
238+ if res:
239+ return tools.ustr(res[0])
240+ return False
241+
242 def login(self, db, login, password):
243 if not password:
244 return False
245 login = tools.ustr(login).lower()
246 cr = pooler.get_db(db).cursor()
247 try:
248- # autocommit: our single request will be performed atomically.
249- # (In this way, there is no opportunity to have two transactions
250- # interleaving their cr.execute()..cr.commit() calls and have one
251- # of them rolled back due to a concurrent access.)
252- # We effectively unconditionally write the res_users line.
253- cr.autocommit(True)
254- # Even w/ autocommit there's a chance the user row will be locked,
255- # in which case we can't delay the login just for the purpose of
256- # update the last login date - hence we use FOR UPDATE NOWAIT to
257- # try to get the lock - fail-fast
258- cr.execute("""SELECT id from res_users
259- WHERE login=%s AND password=%s
260- AND active FOR UPDATE NOWAIT""",
261- (login, tools.ustr(password)), log_exceptions=False)
262- cr.execute('UPDATE res_users SET date=now() WHERE login=%s AND password=%s AND active RETURNING id',
263- (login, tools.ustr(password)))
264- except Exception:
265- # Failing to acquire the lock on the res_users row probably means
266- # another request is holding it - no big deal, we skip the update
267- # for this time, and let the user login anyway.
268- logging.getLogger('res.users').warn('Can\'t acquire lock on res users', exc_info=True)
269- cr.rollback()
270- cr.execute("""SELECT id from res_users
271- WHERE login=%s AND password=%s
272- AND active""",
273- (login, tools.ustr(password)))
274+ database_password = self.get_user_database_password_from_login(cr, login)
275+ # check the password is a bcrypt encrypted one
276+ database_password = tools.ustr(database_password)
277+ password = tools.ustr(password)
278+ if bcrypt.identify(database_password):
279+ if not bcrypt.verify(password, database_password):
280+ return False
281+ elif password != database_password:
282+ return False
283+ try:
284+ # autocommit: our single request will be performed atomically.
285+ # (In this way, there is no opportunity to have two transactions
286+ # interleaving their cr.execute()..cr.commit() calls and have one
287+ # of them rolled back due to a concurrent access.)
288+ # We effectively unconditionally write the res_users line.
289+ cr.autocommit(True)
290+ # Even w/ autocommit there's a chance the user row will be locked,
291+ # in which case we can't delay the login just for the purpose of
292+ # update the last login date - hence we use FOR UPDATE NOWAIT to
293+ # try to get the lock - fail-fast
294+ cr.execute("""SELECT id from res_users
295+ WHERE login=%s AND password=%s
296+ AND active FOR UPDATE NOWAIT""",
297+ (login, tools.ustr(database_password)), log_exceptions=False)
298+ cr.execute('UPDATE res_users SET date=now() WHERE login=%s AND password=%s AND active RETURNING id',
299+ (login, tools.ustr(database_password)))
300+ except Exception:
301+ # Failing to acquire the lock on the res_users row probably means
302+ # another request is holding it - no big deal, we skip the update
303+ # for this time, and let the user login anyway.
304+ logging.getLogger('res.users').warn('Can\'t acquire lock on res users', exc_info=True)
305+ cr.rollback()
306+ cr.execute("""SELECT id from res_users
307+ WHERE login=%s AND password=%s
308+ AND active""",
309+ (login, tools.ustr(database_password)))
310+ finally:
311+ res = cr.fetchone()
312+ if res:
313+ return res[0]
314 finally:
315- res = cr.fetchone()
316 cr.close()
317- if res:
318- return res[0]
319 return False
320
321- def check_super(self, passwd):
322- if passwd == tools.config['admin_passwd']:
323- return True
324- else:
325- raise security.ExceptionNoTb('AccessDenied')
326-
327 def check(self, db, uid, passwd):
328 """Verifies that the given (uid, password) pair is authorized for the database ``db`` and
329 raise an exception if it is not."""
330@@ -516,11 +621,16 @@
331 return
332 cr = pooler.get_db(db).cursor()
333 try:
334- cr.execute('SELECT COUNT(1) FROM res_users WHERE id=%s AND password=%s AND active=%s',
335- (int(uid), passwd, True))
336- res = cr.fetchone()[0]
337- if not res:
338+ database_password = self.get_user_database_password_from_uid(cr, uid)
339+ # check the password is a bcrypt encrypted one
340+ database_password = tools.ustr(database_password)
341+ passwd = tools.ustr(passwd)
342+ if bcrypt.identify(database_password):
343+ if not bcrypt.verify(passwd, database_password):
344+ raise security.ExceptionNoTb('AccessDenied')
345+ elif passwd != database_password:
346 raise security.ExceptionNoTb('AccessDenied')
347+
348 if self._uid_cache.has_key(db):
349 ulist = self._uid_cache[db]
350 ulist[uid] = passwd
351@@ -542,55 +652,51 @@
352 finally:
353 cr.close()
354
355- def is_password_strong(self, password, login):
356- """
357- Check that given password is strong enough.
358- In case it is, all values of the returned dict are True
359- """
360- result = {
361- 'has_digit': False,
362- 'long_enough': False,
363- 'login_not_equal_password': False,
364- }
365-
366- # check it contains at least one digit
367- if re.search(r'\d', password):
368- result['has_digit'] = True
369-
370- # check password lenght
371- if len(password) >= self.PASSWORD_MIN_LENGHT:
372- result['long_enough'] = True
373-
374- # check login != password:
375- if password != login:
376- result['login_not_equal_password'] = True
377-
378- return result
379-
380- def change_password(self, cr, uid, old_passwd, new_passwd, context=None):
381+ def pref_change_password(self, cr, uid, old_passwd, new_passwd,
382+ confirm_passwd, context=None):
383+ self.check(cr.dbname, uid, tools.ustr(old_passwd))
384+ login = self.read(cr, uid, uid, ['login'])['login']
385+ return self.change_password(cr.dbname, login, old_passwd, new_passwd,
386+ confirm_passwd, context=context)
387+
388+ def change_password(self, db_name, login, old_passwd, new_passwd,
389+ confirm_passwd, context=None):
390 """Change current user password. Old password must be provided explicitly
391 to prevent hijacking an existing user session, or for cases where the cleartext
392 password is not used to authenticate requests.
393
394- The write of the new password is done with uid=1 to prevent raise it
395+ The write of the new password is done with uid=1 to prevent raise if
396 the current logged user don't have permission on res_users.
397
398 :return: True
399 :raise: security.ExceptionNoTb when old password is wrong
400 :raise: except_osv when new password is not set or empty
401 """
402- self.check(cr.dbname, uid, old_passwd)
403 if new_passwd:
404- login = self.read(cr, uid, uid, ['login'])['login']
405- if not all(self.is_password_strong(new_passwd, login).values()):
406- raise osv.except_osv(_('Operation Canceled'), _('The new password is not strong enough. '\
407- 'Password must be diffrent from the login, it must contain '\
408- 'at least one number and be at least %s characters.' % self.PASSWORD_MIN_LENGHT))
409- vals = {
410- 'password': new_passwd,
411- 'force_password_change': False,
412- }
413- return self.write(cr, 1, uid, vals)
414+ cr = pooler.get_db(db_name).cursor()
415+ try:
416+ # get user_uid
417+ cr.execute("""SELECT id from res_users
418+ WHERE login=%s AND active=%s""",
419+ (login, True))
420+ res = cr.fetchone()
421+ uid = None
422+ if res:
423+ uid = res[0]
424+ if not uid:
425+ raise security.ExceptionNoTb('AccessDenied')
426+ security.check_password_validity(self, cr, uid, old_passwd, new_passwd, confirm_passwd, login)
427+ new_passwd = bcrypt.encrypt(tools.ustr(new_passwd))
428+ vals = {
429+ 'password': new_passwd,
430+ 'force_password_change': False,
431+ }
432+ self.check(db_name, uid, tools.ustr(old_passwd))
433+ result = self.write(cr, 1, uid, vals)
434+ cr.commit()
435+ finally:
436+ cr.close()
437+ return result
438 raise osv.except_osv(_('Warning!'), _("Setting empty passwords is not allowed for security reasons!"))
439
440 def get_admin_profile(self, cr, uid, context=None):
441
442=== modified file 'bin/addons/msf_profile/data/patches.xml'
443--- bin/addons/msf_profile/data/patches.xml 2016-11-08 15:50:26 +0000
444+++ bin/addons/msf_profile/data/patches.xml 2016-11-21 09:47:14 +0000
445@@ -1,10 +1,17 @@
446 <?xml version="1.0" encoding="utf-8" ?>
447 <openerp>
448 <data>
449+<<<<<<< TREE
450 <record id="us_1482" model="patch.scripts">
451 <field name="method">us_1482_fix_default_code_on_msf_lines</field>
452 </record>
453
454+=======
455+ <record id="us_1381" model="patch.scripts">
456+ <field name="method">us_1381_encrypt_passwords</field>
457+ </record>
458+
459+>>>>>>> MERGE-SOURCE
460 <record id="us_1388" model="patch.scripts">
461 <field name="method">us_1388_change_sequence_implementation</field>
462 </record>
463
464=== modified file 'bin/addons/msf_profile/i18n/fr_MF.po'
465--- bin/addons/msf_profile/i18n/fr_MF.po 2016-11-09 15:21:10 +0000
466+++ bin/addons/msf_profile/i18n/fr_MF.po 2016-11-21 09:47:14 +0000
467@@ -74686,6 +74686,7 @@
468 " Number of columns is not equal to %s"
469 msgstr "\n"
470 " Le nombre de colonnes n'est pas égal à %s"
471+<<<<<<< TREE
472
473 #. module: msf_partner
474 #: constraint:res.partner:0
475@@ -75092,3 +75093,83 @@
476 msgid "Attachment configuration"
477 msgstr "Configuration des pièces jointes"
478
479+=======
480+
481+#. module: base
482+#: code:addons/base/res/res_user.py:550
483+#, python-format
484+msgid "The new password is not strong enough. Password must contain at least one digit."
485+msgstr "Le nouveau mot de passe n'est pas assez robuste. Il doit contenir au moins un chiffre."
486+
487+#. module: base
488+#: code:addons/base/res/res_user.py:556
489+#, python-format
490+msgid "The new password is not strong enough. Password must be at least %s characters long."
491+msgstr "Le nouveau mot de passe n'est pas assez robuste. Il doit être d'au moins %s caractères."
492+
493+#. module: base
494+#: code:addons/base/res/res_user.py:561
495+#, python-format
496+msgid "The new password cannot be equal to the login."
497+msgstr "Le nouveau mot de passe ne peut pas être égal à l'identifiant."
498+
499+#. module: base
500+#: code:addons/base/res/res_user.py:566
501+#, python-format
502+msgid "The new password does not match the confirm password."
503+msgstr "Le nouveau mot de passe ne correspond pas à la confirmation."
504+
505+#. module: base
506+#: code:addons/base/res/res_user.py:571
507+#, python-format
508+msgid "The new password must be different from the actual one."
509+msgstr "Le nouveau mot de passe doit être différent de l'actuel."
510+
511+#. module: base
512+#: code:addons/base/res/res_user.py:537
513+#, python-format
514+msgid "Bad username or password"
515+msgstr "Mauvais nom d'utilisateur ou mot de passe"
516+
517+#. module: base
518+#: code:addons/base/res/res_user.py:623
519+#, python-format
520+msgid "Setting empty passwords is not allowed for security reasons!"
521+msgstr "La définition de mot de passe vides n'est pas autorisée pour des raisons de sécurité !"
522+
523+#. module: base
524+#: code:addons/base/res/res_user.py:229
525+#, python-format
526+msgid "Change password"
527+msgstr "Changer le mot de passe"
528+
529+#. module: base
530+#: code:addons/base/res/res_user.py:226
531+#, python-format
532+msgid "Password"
533+msgstr "Mot de passe"
534+
535+#. module: base
536+#: code:addons/base/res/res_user.py:224
537+#, python-format
538+msgid "Login"
539+msgstr "Identifiant"
540+
541+#. module: base
542+#: code:addons/base/res/res_user.py:238
543+#, python-format
544+msgid "Change password on next login"
545+msgstr "Changer le mot de passe à la prochaine connexion"
546+
547+#. module: base
548+#: code:addons/base/res/res_user.py:239
549+#, python-format
550+msgid "Check out this box to force this user to change his password on next login."
551+msgstr "Cocher cette case pour forcer l'utilisateur à changer son mot de passe à la prochaine connexion."
552+
553+#. module: base
554+#: code:addons/base/res/res_user.py:216
555+#, python-format
556+msgid "Please use the change password wizard (in User Preferences or User menu) to change your own password."
557+msgstr "Merci d'utiliser l'assistant (dans les préférences de l'utilisateur ou le menu de l'utilisateur) pour changer votre propre mot de passe."
558+>>>>>>> MERGE-SOURCE
559
560=== modified file 'bin/addons/msf_profile/msf_profile.py'
561--- bin/addons/msf_profile/msf_profile.py 2016-11-09 13:21:34 +0000
562+++ bin/addons/msf_profile/msf_profile.py 2016-11-21 09:47:14 +0000
563@@ -46,6 +46,7 @@
564 'model': lambda *a: 'patch.scripts',
565 }
566
567+<<<<<<< TREE
568 def us_1482_fix_default_code_on_msf_lines(self, cr, uid, *a, **b):
569 """
570 If the default code set on the MSR lines is different from the
571@@ -69,6 +70,23 @@
572 """
573 cr.execute(request)
574
575+=======
576+ def us_1381_encrypt_passwords(self, cr, uid, *a, **b):
577+ """
578+ encrypt all passwords
579+ """
580+ from passlib.hash import bcrypt
581+ users_obj = self.pool.get('res.users')
582+ user_ids = users_obj.search(cr, uid, [])
583+ for user in users_obj.read(cr, uid, user_ids, ['password']):
584+ original_password = tools.ustr(user['password'])
585+ # check the password is not already encrypted
586+ if not bcrypt.identify(original_password):
587+ encrypted_password = bcrypt.encrypt(original_password)
588+ users_obj.write(cr, uid, user['id'],
589+ {'password': encrypted_password})
590+
591+>>>>>>> MERGE-SOURCE
592 def us_1388_change_sequence_implementation(self, cr, uid, *a, **b):
593 """
594 change the implementation of the finance.ocb.export ir_sequence to be
595
596=== modified file 'bin/addons/msf_profile/user_access_configurator_view.xml'
597--- bin/addons/msf_profile/user_access_configurator_view.xml 2013-12-11 10:02:33 +0000
598+++ bin/addons/msf_profile/user_access_configurator_view.xml 2016-11-21 09:47:14 +0000
599@@ -395,6 +395,7 @@
600 <field name="arch" type="xml">
601 <search string="Users">
602 <filter name="show_inactive" string="Show inactive" icon="gtk-ok" domain="[('active', '=', False)]" />
603+ <filter name="show_synchronized" string="Show synchronized" icon="gtk-ok" domain="[('synchronize', '=', True)]"/>
604 <field name="name" />
605 <field name="login" />
606 <field name="address_id" string="Address" />
607
608=== modified file 'bin/addons/msf_sync_data_server/data/sync_server.sync_rule.csv'
609--- bin/addons/msf_sync_data_server/data/sync_server.sync_rule.csv 2016-11-03 15:58:00 +0000
610+++ bin/addons/msf_sync_data_server/data/sync_server.sync_rule.csv 2016-11-21 09:47:14 +0000
611@@ -252,3 +252,4 @@
612 msf_usb_sync_data_server.button_access_rules,FALSE,TRUE,FALSE,TRUE,cp_to_rw,Bidirectional,[],"['active', 'comment', 'group_ids/id', 'group_names', 'label', 'model_id/id', 'name', 'type', 'view_id/id']",USB,msf_button_access_rights.button_access_rule,,[USER RIGHTS] Button Access Rules,Valid,,4405
613 msf_usb_sync_data_server.button_access_rules2,TRUE,TRUE,FALSE,TRUE,cp_to_rw,Bidirectional,[],"['active', 'comment', 'group_ids/id', 'group_names', 'label', 'model_id/id', 'name', 'type', 'view_id/id', 'xmlname']",USB,msf_button_access_rights.button_access_rule,,[USER RIGHTS] Button Access Rules,Valid,,4415
614 msf_sync_data_server.ir_translation_USB,TRUE,TRUE,TRUE,TRUE,cp_to_rw,Down,"[('res_id', '!=', 0), ('src', '!=', False), ('xml_id', '!=', False), ('lang', '!=', 'en_US'), ('type', '=', 'model'), ('name', 'in', ['res.partner.category,name','product.uom,name','product.template,description_purchase','product.template,description','product.template,description_sale','product.uom.categ,name','product.pricelist,name','product.category,name','product.nomenclature,name','product.product,form_value','product.product,function_value','product.product,fit_value','res.currency,currency_name','res.country,name','product.ul,name','product.price.type,name','product.pricelist.type,name','product.pricelist.version,name','account.analytic.account,name','account.fiscal.position,note','hr.employee.marital.status,name','account.period,name','account.tax.code,name','account.journal,name','account.tax,name','account.analytic.journal,name','account.account,name','stock.reason.type,name','product.template,name','product.justification.code,code'])]","['lang', 'src', 'name', 'value', 'type', 'xml_id']",USB,ir.translation,,[OTHER] Translations,Valid,,4406
615+msf_sync_data_server.res_users,TRUE,TRUE,TRUE,TRUE,bidirectional,Down,"[('synchronize', '=', True)]","['action_id/id', 'active', 'address_id/id', 'email', 'force_password_change', 'groups_id/id', 'login', 'menu_id/id', 'menu_tips', 'name', 'password', 'signature', 'user_email', 'view']",OC,res.users,,Res Users,Valid,,1002
616
617=== modified file 'bin/addons/sync_client/rpc.py'
618--- bin/addons/sync_client/rpc.py 2016-10-27 13:23:52 +0000
619+++ bin/addons/sync_client/rpc.py 2016-11-21 09:47:14 +0000
620@@ -373,7 +373,10 @@
621 self.user_id = user_id
622 if user_id is None:
623 self.user_id = Common(self.connector).login(self.database, self.login, self.password)
624+<<<<<<< TREE
625
626+=======
627+>>>>>>> MERGE-SOURCE
628 if self.user_id is False:
629 raise osv.except_osv(_('Error!'), _('Unable to connect to the distant server with this user!'))
630 self._logger.debug(self.user_id)
631
632=== modified file 'bin/openerp-server.py'
633--- bin/openerp-server.py 2016-08-03 09:59:23 +0000
634+++ bin/openerp-server.py 2016-11-21 09:47:14 +0000
635@@ -125,15 +125,14 @@
636 for dbname in tools.config['db_name'].split(','):
637 db,pool = pooler.get_db_and_pool(dbname, update_module=tools.config['init'] or tools.config['update'], pooljobs=False)
638 cr = db.cursor()
639-
640- if tools.config["test_file"]:
641- logger.info('loading test file %s', tools.config["test_file"])
642- tools.convert_yaml_import(cr, 'base', file(tools.config["test_file"]), {}, 'test', True)
643- cr.rollback()
644-
645- pool.get('ir.cron')._poolJobs(db.dbname)
646-
647- cr.close()
648+ try:
649+ if tools.config["test_file"]:
650+ logger.info('loading test file %s', tools.config["test_file"])
651+ tools.convert_yaml_import(cr, 'base', file(tools.config["test_file"]), {}, 'test', True)
652+ cr.rollback()
653+ pool.get('ir.cron')._poolJobs(db.dbname)
654+ finally:
655+ cr.close()
656
657 #----------------------------------------------------------
658 # translation stuff
659@@ -151,9 +150,11 @@
660 buf = file(tools.config["translate_out"], "w")
661 dbname = tools.config['db_name']
662 cr = pooler.get_db(dbname).cursor()
663- tools.trans_export(tools.config["language"], tools.config["translate_modules"] or ["all"], buf, fileformat, cr)
664- cr.close()
665- buf.close()
666+ try:
667+ tools.trans_export(tools.config["language"], tools.config["translate_modules"] or ["all"], buf, fileformat, cr)
668+ finally:
669+ cr.close()
670+ buf.close()
671
672 logger.info('translation file written successfully')
673 sys.exit(0)
674@@ -162,13 +163,15 @@
675 context = {'overwrite': tools.config["overwrite_existing_translations"]}
676 dbname = tools.config['db_name']
677 cr = pooler.get_db(dbname).cursor()
678- tools.trans_load(cr,
679- tools.config["translate_in"],
680- tools.config["language"],
681- context=context)
682- tools.trans_update_res_ids(cr)
683- cr.commit()
684- cr.close()
685+ try:
686+ tools.trans_load(cr,
687+ tools.config["translate_in"],
688+ tools.config["language"],
689+ context=context)
690+ tools.trans_update_res_ids(cr)
691+ finally:
692+ cr.commit()
693+ cr.close()
694 sys.exit(0)
695
696 #----------------------------------------------------------------------------------
697
698=== modified file 'bin/service/security.py'
699--- bin/service/security.py 2016-07-19 13:25:46 +0000
700+++ bin/service/security.py 2016-11-21 09:47:14 +0000
701@@ -23,6 +23,11 @@
702 import tools
703 import threading
704 import updater
705+import re
706+from passlib.hash import bcrypt
707+from tools.translate import _
708+from osv import osv
709+PASSWORD_MIN_LENGHT = 6
710
711 # When rejecting a password, hide the traceback
712 class ExceptionNoTb(Exception):
713@@ -32,8 +37,10 @@
714
715 def number_update_modules(db):
716 cr = pooler.get_db_only(db).cursor()
717- n = _get_number_modules(cr)
718- cr.close()
719+ try:
720+ n = _get_number_modules(cr)
721+ finally:
722+ cr.close()
723 return n
724
725 def _get_number_modules(cr, testlogin=False):
726@@ -51,57 +58,145 @@
727 return True
728 return False
729
730-def login(db, login, password):
731- cr = pooler.get_db_only(db).cursor()
732- nb = _get_number_modules(cr, testlogin=True)
733- patch_failed = [0]
734- cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname='patch_scripts'")
735- if cr.rowcount:
736- cr.execute("SELECT count(id) FROM patch_scripts WHERE run = \'f\'")
737- patch_failed = cr.fetchone()
738- to_update = False
739- if not nb:
740- to_update = updater.test_do_upgrade(cr)
741- cr.close()
742- if nb or to_update:
743- s = threading.Thread(target=pooler.get_pool, args=(db,),
744- kwargs={'threaded': True})
745- s.start()
746- raise Exception("ServerUpdate: Server is updating modules ...")
747-
748-
749- pool = pooler.get_pool(db)
750+def change_password(db_name, login, password, new_password, confirm_password):
751+ '''
752+ Call the res.user change_password method
753+ '''
754+ db, pool = pooler.get_db_and_pool(db_name)
755+ cr = db.cursor()
756+ try:
757+ user_obj = pool.get('res.users')
758+ result = user_obj.change_password(db_name, login, password, new_password,
759+ confirm_password)
760+ finally:
761+ cr.close()
762+ return result
763+
764+def login(db_name, login, password):
765+ # it is required here not to get pool but only the db, if the pool it also
766+ # get, then the server will update the module at this step without display
767+ # the "Server is updating modules ..." message
768+ cr = pooler.get_db_only(db_name).cursor()
769+ try:
770+ nb = _get_number_modules(cr, testlogin=True)
771+ patch_failed = [0]
772+ cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname='patch_scripts'")
773+ if cr.rowcount:
774+ cr.execute("SELECT count(id) FROM patch_scripts WHERE run = \'f\'")
775+ patch_failed = cr.fetchone()
776+ to_update = False
777+ if not nb:
778+ to_update = updater.test_do_upgrade(cr)
779+ if nb or to_update:
780+ s = threading.Thread(target=pooler.get_pool, args=(db_name,),
781+ kwargs={'threaded': True})
782+ s.start()
783+ raise Exception("ServerUpdate: Server is updating modules ...")
784+
785+ pool = pooler.get_pool(db_name)
786+ # check if the user have to change his password
787+ cr.execute("""SELECT force_password_change
788+ FROM res_users
789+ WHERE login='%s'""" % (login))
790+ force_password = [x[0] for x in cr.fetchall()]
791+ if any(force_password):
792+ raise Exception("ForcePasswordChange: The admin requests your password change ...")
793+ finally:
794+ cr.close()
795+
796 user_obj = pool.get('res.users')
797- user_res = user_obj.login(db, login, password)
798+ user_res = user_obj.login(db_name, login, password)
799
800 if user_res != 1 and patch_failed[0]:
801 raise Exception("PatchFailed: A script during upgrade has failed. Login is forbidden. Please contact your administrator")
802
803 return user_res
804
805+def check_password_validity(self, cr, uid, old_password, new_password, confirm_password, login):
806+ '''
807+ Check password respect some conditions
808+ Raise is any of the condition is not respected
809+
810+ :param self: the caller of this method. It is needed for _get_lang to be
811+ able to know the language of the requested user
812+ :param cr: database cursor
813+ :param uid: the user id of the password to check
814+ :param old_password: the previous password
815+ :param new_password: the new password to setup
816+ :param confirm_password: the confirmation of the new password
817+ :param login: the login that request the password check
818+ :return: True if the password check pass
819+ :rtype: boolean
820+ :raise osv.except_osv: if the password is not ok
821+ '''
822+ # check it contains at least one digit
823+ if not re.search(r'\d', new_password):
824+ message = _('The new password is not strong enough. '\
825+ 'Password must contain at least one digit.')
826+ raise osv.except_osv(_('Operation Canceled'), message)
827+
828+ # check new_password lenght
829+ if len(new_password) < PASSWORD_MIN_LENGHT:
830+ message = _('The new password is not strong enough. '\
831+ 'Password must be at least %s characters long.' % PASSWORD_MIN_LENGHT)
832+ raise osv.except_osv(_('Operation Canceled'), message)
833+
834+ # check login != new_password:
835+ if new_password == login:
836+ message = _('The new password cannot be equal to the login.')
837+ raise osv.except_osv(_('Operation Canceled'), message)
838+
839+ # check confirm_password == new_password:
840+ if new_password != confirm_password:
841+ message = _('The new password does not match the confirm password.')
842+ raise osv.except_osv(_('Operation Canceled'), message)
843+
844+ # check new_password != old_password
845+ if new_password == old_password:
846+ message = _('The new password must be different from the actual one.')
847+ raise osv.except_osv(_('Operation Canceled'), message)
848+ return True
849+
850+def check_password(password, hashed_password):
851+ '''
852+ Check that the password match the hashed_password
853+
854+ :param password: a string containing the password to check
855+ :param hashed_password: a string containing the hash to check against, such
856+ as returned by encrypt()
857+ :return: True if the password match
858+ :rtype: boolean
859+ :raise ExceptionNoTb: if password don't match
860+ '''
861+ hashed_password = tools.ustr(hashed_password)
862+ password = tools.ustr(password)
863+
864+ # check the password is a bcrypt encrypted one
865+ if bcrypt.identify(hashed_password) and \
866+ bcrypt.verify(password, hashed_password):
867+ return True
868+ elif password == hashed_password:
869+ # this is a not encrypted password (we want to keep compatibility with
870+ # old way password
871+ return True
872+ else:
873+ raise ExceptionNoTb('AccessDenied: Invalid super administrator password.')
874+
875+def check_super_password_validity(password):
876+ check_password_validity(None, None, 1, None, password, password, 'admin')
877+ return True
878+
879 def check_super(passwd):
880- if passwd == tools.config['admin_passwd']:
881- return True
882- else:
883- raise ExceptionNoTb('AccessDenied: Invalid super administrator password.')
884+ return check_password(passwd, tools.config['admin_passwd'])
885
886 def check_super_dropdb(passwd):
887- if passwd == tools.config['admin_dropdb_passwd']:
888- return True
889- else:
890- raise ExceptionNoTb('AccessDenied: Invalid super administrator password.')
891+ return check_password(passwd, tools.config['admin_dropdb_passwd'])
892
893 def check_super_bkpdb(passwd):
894- if passwd == tools.config['admin_bkpdb_passwd']:
895- return True
896- else:
897- raise ExceptionNoTb('AccessDenied: Invalid super administrator password.')
898+ return check_password(passwd, tools.config['admin_bkpdb_passwd'])
899
900 def check_super_restoredb(passwd):
901- if passwd == tools.config['admin_restoredb_passwd']:
902- return True
903- else:
904- raise ExceptionNoTb('AccessDenied: Invalid super administrator password.')
905+ return check_password(passwd, tools.config['admin_restoredb_passwd'])
906
907 def check(db, uid, passwd):
908 pool = pooler.get_pool(db)
909
910=== modified file 'bin/service/web_services.py'
911--- bin/service/web_services.py 2016-11-09 09:47:06 +0000
912+++ bin/service/web_services.py 2016-11-21 09:47:14 +0000
913@@ -44,11 +44,15 @@
914 from cStringIO import StringIO
915 from tempfile import NamedTemporaryFile
916 from updater import get_server_version
917+<<<<<<< TREE
918 from tools.misc import file_open
919 from mako.template import Template
920 from mako import exceptions
921 from mako.runtime import Context
922 import codecs
923+=======
924+from passlib.hash import bcrypt
925+>>>>>>> MERGE-SOURCE
926
927 def export_csv(fields, result, result_file_path):
928 try:
929@@ -111,7 +115,12 @@
930 params = params[1:]
931 security.check_super(passwd)
932 elif method in [ 'db_exist', 'list', 'list_lang', 'server_version',
933+<<<<<<< TREE
934 'check_timezone', 'connected_to_prod_sync_server' ]:
935+=======
936+ 'check_timezone', 'connected_to_prod_sync_server',
937+ 'check_super_password_validity' ]:
938+>>>>>>> MERGE-SOURCE
939 # params = params
940 # No security check for these methods
941 pass
942@@ -137,11 +146,12 @@
943 self.id += 1
944 id = self.id
945 self.id_protect.release()
946-
947 self.actions[id] = {'clean': False}
948-
949 self._create_empty_database(db_name)
950
951+ # encrypt the db admin password
952+ user_password = bcrypt.encrypt(tools.ustr(user_password))
953+
954 class DBInitialize(object):
955 def __call__(self, serv, id, db_name, demo, lang, user_password='admin'):
956 cr = None
957@@ -171,7 +181,6 @@
958 serv.actions[id]['users'] = cr.dictfetchall()
959 serv.actions[id]['clean'] = True
960 cr.commit()
961- cr.close(True)
962 except Exception, e:
963 serv.actions[id]['clean'] = False
964 serv.actions[id]['exception'] = e
965@@ -182,6 +191,7 @@
966 e_str.close()
967 netsvc.Logger().notifyChannel('web-services', netsvc.LOG_ERROR, 'CREATE DATABASE\n%s' % (traceback_str))
968 serv.actions[id]['traceback'] = traceback_str
969+ finally:
970 if cr:
971 cr.close(True)
972 logger = netsvc.Logger()
973@@ -193,6 +203,13 @@
974 self.actions[id]['thread'] = create_thread
975 return id
976
977+ def exp_check_super_password_validity(self, password):
978+ try:
979+ security.check_super_password_validity(password)
980+ except Exception as e:
981+ return str(e)
982+ return True
983+
984 def exp_check_timezone(self):
985 return check_tz()
986
987@@ -380,10 +397,12 @@
988 return False
989
990 cr = connection.cursor()
991- cr.execute('''SELECT host, database
992- FROM sync_client_sync_server_connection''')
993- host, database = cr.fetchone()
994- cr.close()
995+ try:
996+ cr.execute('''SELECT host, database
997+ FROM sync_client_sync_server_connection''')
998+ host, database = cr.fetchone()
999+ finally:
1000+ cr.close()
1001 if host and database and database.strip() == 'SYNC_SERVER' and \
1002 ('sync.unifield.net' in host.lower() or '212.95.73.129' in host):
1003 return True
1004@@ -409,16 +428,18 @@
1005 db = sql_db.db_connect(dbname)
1006 cr = db.cursor()
1007
1008- # check sync_client_version table existance
1009- cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname='sync_client_version'")
1010- if not cr.fetchone():
1011- # the table sync_client_version doesn't exists, fallback on the
1012- # version from release.py file
1013- return release.version or 'UNKNOWN_VERSION'
1014+ try:
1015+ # check sync_client_version table existance
1016+ cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname='sync_client_version'")
1017+ if not cr.fetchone():
1018+ # the table sync_client_version doesn't exists, fallback on the
1019+ # version from release.py file
1020+ return release.version or 'UNKNOWN_VERSION'
1021
1022- cr.execute("SELECT name, sum FROM sync_client_version WHERE state='installed' ORDER BY applied DESC")
1023- res = cr.fetchone()
1024- cr.close(True)
1025+ cr.execute("SELECT name, sum FROM sync_client_version WHERE state='installed' ORDER BY applied DESC")
1026+ res = cr.fetchone()
1027+ finally:
1028+ cr.close(True)
1029 if res and res[0]:
1030 return res[0]
1031 elif res[1]:
1032@@ -459,10 +480,12 @@
1033 params = params[3:]
1034 security.check(db,uid,passwd)
1035 cr = pooler.get_db(db).cursor()
1036- fn = getattr(self, 'exp_'+method)
1037- res = fn(cr, uid, *params)
1038- cr.commit()
1039- cr.close()
1040+ try:
1041+ fn = getattr(self, 'exp_'+method)
1042+ res = fn(cr, uid, *params)
1043+ cr.commit()
1044+ finally:
1045+ cr.close()
1046 return res
1047
1048 class common(_ObjectService):
1049@@ -484,6 +507,17 @@
1050 return res or False
1051 elif method == 'number_update_modules':
1052 return security.number_update_modules(params[0])
1053+ elif method == 'change_password':
1054+ try:
1055+ security.change_password(params[0], params[1], params[2],
1056+ params[3], params[4])
1057+ except Exception as e:
1058+ if hasattr(e, 'value'):
1059+ msg = tools.ustr(e.value)
1060+ else:
1061+ msg = tools.ustr(e)
1062+ return msg
1063+ return True
1064 elif method == 'logout':
1065 if auth:
1066 auth.logout(params[1])
1067@@ -1009,8 +1043,9 @@
1068 else:
1069 self._reports[id]['exception'] = ExceptionWithTraceback(tools.exception_to_unicode(exception), tb)
1070 self._reports[id]['state'] = True
1071- cr.commit()
1072- cr.close()
1073+ finally:
1074+ cr.commit()
1075+ cr.close()
1076 return True
1077
1078 thread.start_new_thread(go, (id, uid, ids, datas, context))
1079
1080=== modified file 'setup.py'
1081--- setup.py 2016-11-09 13:21:34 +0000
1082+++ setup.py 2016-11-21 09:47:14 +0000
1083@@ -65,7 +65,11 @@
1084 "HTMLParser", "select", "mako", "poplib",
1085 "imaplib", "smtplib", "email", "yaml", "DAV",
1086 "uuid", "commands", "mx.DateTime", "json",
1087+<<<<<<< TREE
1088 "pylzma", "xlwt"
1089+=======
1090+ "pylzma", "passlib", "bcrypt", "six", "cffi",
1091+>>>>>>> MERGE-SOURCE
1092 ],
1093 "excludes" : ["Tkconstants","Tkinter","tcl"],
1094 }

Subscribers

People subscribed via source and target branches