diff --git a/auth_user_case_insensitive/README.rst b/auth_user_case_insensitive/README.rst new file mode 100644 index 000000000..87e17ec39 --- /dev/null +++ b/auth_user_case_insensitive/README.rst @@ -0,0 +1,57 @@ +.. image:: https://img.shields.io/badge/licence-LGPL--3-blue.svg + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 + +======================= +Case Insensitive Logins +======================= + +This module makes user logins case insensitive. It also overwrites the +search method to allow these case insensitive logins to work on a database +that previously had case sensitive logins. + +Installation +============ + +Install this module as you would any other. No further configuration is +required. + +Note that upon installation the existing logins will all be converted to lowercase. + + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smash it by providing detailed and welcomed feedback. + +Credits +======= + +Images +------ + +* Odoo Community Association: `Icon `_. + +Contributors +------------ + +* Dave Lasley +* Ted Salmon + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/auth_user_case_insensitive/__init__.py b/auth_user_case_insensitive/__init__.py new file mode 100644 index 000000000..1fc495145 --- /dev/null +++ b/auth_user_case_insensitive/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +# Copyright 2015-2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from . import models +from .hooks import pre_init_hook_login_check +from .hooks import post_init_hook_login_convert diff --git a/auth_user_case_insensitive/__manifest__.py b/auth_user_case_insensitive/__manifest__.py new file mode 100644 index 000000000..be2d7a0b7 --- /dev/null +++ b/auth_user_case_insensitive/__manifest__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Copyright 2015-2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). +{ + "name": "Case Insensitive Logins", + "summary": "Makes the user login field case insensitive", + "version": "10.0.1.0.0", + "category": "Authentication", + "website": "https://laslabs.com/", + "author": "LasLabs, Odoo Community Association (OCA)", + "license": "AGPL-3", + "application": False, + 'installable': True, + "depends": [ + "mail", + ], + 'pre_init_hook': 'pre_init_hook_login_check', + 'post_init_hook': 'post_init_hook_login_convert', +} diff --git a/auth_user_case_insensitive/hooks.py b/auth_user_case_insensitive/hooks.py new file mode 100644 index 000000000..330ecac88 --- /dev/null +++ b/auth_user_case_insensitive/hooks.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo import _ +from odoo.exceptions import ValidationError + + +def pre_init_hook_login_check(cr): + """This hook will look to see if any conflicting logins exist before + the module is installed + :param openerp.sql_db.Cursor cr: + Database cursor. + """ + with cr.savepoint(): + users = [] + cr.execute("SELECT login FROM res_users") + for user in cr.fetchall(): + login = user[0].lower() + if login not in users: + users.append(login) + else: + raise ValidationError( + _('Conflicting user logins exist for `%s`' % login) + ) + + +def post_init_hook_login_convert(cr, registry): + """After the module is installed, set all logins to lowercase + :param openerp.sql_db.Cursor cr: + Database cursor. + :param openerp.modules.registry.RegistryManager registry: + Database registry, using v7 api. + """ + with cr.savepoint(): + cr.execute("UPDATE res_users SET login=lower(login)") diff --git a/auth_user_case_insensitive/models/__init__.py b/auth_user_case_insensitive/models/__init__.py new file mode 100644 index 000000000..ee30f654a --- /dev/null +++ b/auth_user_case_insensitive/models/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Copyright 2015-2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from . import res_users diff --git a/auth_user_case_insensitive/models/res_users.py b/auth_user_case_insensitive/models/res_users.py new file mode 100644 index 000000000..501a098aa --- /dev/null +++ b/auth_user_case_insensitive/models/res_users.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Copyright 2015-2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import api, fields, models + + +class ResUsers(models.Model): + + _inherit = 'res.users' + + login = fields.Char( + help='Used to log into the system. Case insensitive.', + ) + + @classmethod + def _login(cls, db, login, password): + """ Overload _login to lowercase the `login` before passing to the + super """ + login = login.lower() + return super(ResUsers, cls)._login(db, login, password) + + @api.model + def create(self, vals): + """ Overload create to lowercase login """ + vals['login'] = vals.get('login', '').lower() + return super(ResUsers, self).create(vals) + + @api.multi + def write(self, vals): + """ Overload write to lowercase login """ + if vals.get('login'): + vals['login'] = vals['login'].lower() + return super(ResUsers, self).write(vals) diff --git a/auth_user_case_insensitive/tests/__init__.py b/auth_user_case_insensitive/tests/__init__.py new file mode 100644 index 000000000..ea6bf27c9 --- /dev/null +++ b/auth_user_case_insensitive/tests/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Copyright 2015-2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from . import test_res_users diff --git a/auth_user_case_insensitive/tests/test_res_users.py b/auth_user_case_insensitive/tests/test_res_users.py new file mode 100644 index 000000000..98eb3e05a --- /dev/null +++ b/auth_user_case_insensitive/tests/test_res_users.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +# Copyright 2015-2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import api, registry +from odoo.tests.common import TransactionCase + + +class TestResUsers(TransactionCase): + + def setUp(self): + super(TestResUsers, self).setUp() + self.login = 'LasLabs@ExAmPlE.CoM' + self.partner_vals = { + 'name': 'Partner', + 'is_company': False, + 'email': self.login, + } + self.vals = { + 'name': 'User', + 'login': self.login, + 'password': 'password', + } + self.model_obj = self.env['res.users'] + + def _new_record(self): + """ It should enerate a new record to test with """ + partner_id = self.env['res.partner'].create(self.partner_vals) + self.vals['partner_id'] = partner_id.id + return self.model_obj.create(self.vals) + + def test_login_is_lowercased_on_create(self): + """ It should verify the login is set to lowercase on create """ + rec_id = self._new_record() + self.assertEqual( + self.login.lower(), rec_id.login, + 'Login was not lowercased when saved to db.', + ) + + def test_login_is_lowercased_on_write(self): + """ It should verify the login is set to lowercase on write """ + rec_id = self._new_record() + rec_id.write({'login': self.login}) + self.assertEqual( + self.login.lower(), rec_id.login, + 'Login was not lowercased when saved to db.', + ) + + def test_login_login_is_lowercased(self): + """ It should verify the login is set to lowercase on login """ + rec_id = self._new_record() + # We have to commit this cursor, because `_login` uses a fresh cursor + self.env.cr.commit() + res_id = self.model_obj._login( + self.env.registry.db_name, self.login.upper(), 'password' + ) + # Now clean up our mess to preserve idempotence + with api.Environment.manage(): + with registry(self.env.registry.db_name).cursor() as new_cr: + new_cr.execute( + "DELETE FROM res_users WHERE login='%s'" % + self.login.lower() + ) + new_cr.commit() + self.assertEqual( + rec_id.id, res_id, + 'Login with with uppercase chars was not successful', + )