diff --git a/users_ldap_populate/__init__.py b/users_ldap_populate/__init__.py index 9186ee3ad..0650744f6 100644 --- a/users_ldap_populate/__init__.py +++ b/users_ldap_populate/__init__.py @@ -1 +1 @@ -from . import model +from . import models diff --git a/users_ldap_populate/__openerp__.py b/users_ldap_populate/__openerp__.py index a24a78c1a..f6732e33f 100644 --- a/users_ldap_populate/__openerp__.py +++ b/users_ldap_populate/__openerp__.py @@ -45,8 +45,8 @@ object you want to query. 'python': ['ldap'], }, "data": [ - 'view/users_ldap.xml', - 'view/populate_wizard.xml', + 'views/users_ldap.xml', + 'views/populate_wizard.xml', ], 'installable': True, } diff --git a/users_ldap_populate/model/populate_wizard.py b/users_ldap_populate/model/populate_wizard.py deleted file mode 100644 index b82476bf5..000000000 --- a/users_ldap_populate/model/populate_wizard.py +++ /dev/null @@ -1,44 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2012 Therp BV (). -# -# 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 . -# -############################################################################## -from openerp.osv import orm, fields # pylint: disable=W0402 - - -class CompanyLDAPPopulateWizard(orm.TransientModel): - _name = 'res.company.ldap.populate_wizard' - _description = 'Populate users from LDAP' - _columns = { - 'name': fields.char('Name', size=16), - 'ldap_id': fields.many2one( - 'res.company.ldap', 'LDAP Configuration'), - 'users_created': fields.integer( - 'Number of users created', readonly=True), - 'users_deactivated': fields.integer( - 'Number of users deactivated', readonly=True), - } - - def create(self, cr, uid, vals, context=None): - ldap_pool = self.pool.get('res.company.ldap') - if 'ldap_id' in vals: - vals['users_created'], vals['users_deactivated'] =\ - ldap_pool.action_populate( - cr, uid, vals['ldap_id'], context=context) - return super(CompanyLDAPPopulateWizard, self).create( - cr, uid, vals, context=None) diff --git a/users_ldap_populate/model/users_ldap.py b/users_ldap_populate/model/users_ldap.py deleted file mode 100644 index c20ae0503..000000000 --- a/users_ldap_populate/model/users_ldap.py +++ /dev/null @@ -1,192 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2012 Therp BV (). -# -# 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 . -# -############################################################################## - -import re -from openerp.osv import orm, fields # pylint: disable=W0402 -import ldap -from openerp import SUPERUSER_ID -import logging - -_logger = logging.getLogger(__name__) - -try: - from ldap.filter import filter_format -except ImportError: - _logger.debug('Can not `from ldap.filter import filter_format`.') - - -class CompanyLDAP(orm.Model): - _inherit = 'res.company.ldap' - - _columns = { - 'no_deactivate_user_ids': fields.many2many( - 'res.users', 'res_company_ldap_no_deactivate_user_rel', - 'ldap_id', 'user_id', - 'Users never to deactivate', - help='List users who never should be deactivated by' - ' the deactivation wizard'), - 'deactivate_unknown_users': fields.boolean( - 'Deactivate unknown users'), - } - - _defaults = { - 'no_deactivate_user_ids': [(6, 0, [SUPERUSER_ID])], - 'deactivate_unknown_users': False, - } - - def action_populate(self, cr, uid, ids, context=None): - """ - Prepopulate the user table from one or more LDAP resources. - - Obviously, the option to create users must be toggled in - the LDAP configuration. - - Return the number of users created (as far as we can tell). - """ - if isinstance(ids, (int, float)): - ids = [ids] - - users_pool = self.pool.get('res.users') - users_no_before = users_pool.search( - cr, uid, [], context=context, count=True) - logger = logging.getLogger('orm.ldap') - logger.debug("action_populate called on res.company.ldap ids %s", ids) - - deactivate_unknown = None - known_user_ids = [uid] - for this in self.read(cr, uid, ids, - [ - 'no_deactivate_user_ids', - 'deactivate_unknown_users', - ], - context=context, load='_classic_write'): - if deactivate_unknown is None: - deactivate_unknown = True - known_user_ids.extend(this['no_deactivate_user_ids']) - deactivate_unknown &= this['deactivate_unknown_users'] - - if deactivate_unknown: - logger.debug("will deactivate unknown users") - - for conf in self.get_ldap_dicts(cr, ids): - if not conf['create_user']: - continue - attribute_match = re.search( - r'([a-zA-Z_]+)=\%s', conf['ldap_filter']) - if attribute_match: - login_attr = attribute_match.group(1) - else: - raise orm.except_orm( - "No login attribute found", - "Could not extract login attribute from filter %s" % - conf['ldap_filter']) - results = self.get_ldap_entry_dicts(conf) - for result in results: - user_id = self.get_or_create_user( - cr, uid, conf, result[1][login_attr][0], result) - # this happens if something goes wrong while creating the user - # or fetching information from ldap - if not user_id: - deactivate_unknown = False - known_user_ids.append(user_id) - - users_no_after = users_pool.search( - cr, uid, [], context=context, count=True) - users_created = users_no_after - users_no_before - - deactivated_users_count = 0 - if deactivate_unknown: - deactivated_users_count = self.do_deactivate_unknown_users( - cr, uid, ids, known_user_ids, context=context) - - logger.debug("%d users created", users_created) - logger.debug("%d users deactivated", deactivated_users_count) - return users_created, deactivated_users_count - - def do_deactivate_unknown_users( - self, cr, uid, ids, known_user_ids, context=None): - """ - Deactivate users not found in last populate run - """ - res_users = self.pool.get('res.users') - unknown_user_ids = [] - for unknown_user in res_users.read( - cr, uid, - res_users.search( - cr, uid, - [('id', 'not in', known_user_ids)], - context=context), - ['login'], - context=context): - present_in_ldap = False - for conf in self.get_ldap_dicts(cr, ids): - present_in_ldap |= bool(self.get_ldap_entry_dicts( - conf, user_name=unknown_user['login'])) - if not present_in_ldap: - res_users.write( - cr, uid, unknown_user['id'], {'active': False}, - context=context) - unknown_user_ids.append(unknown_user['id']) - - return len(unknown_user_ids) - - def get_ldap_entry_dicts(self, conf, user_name='*'): - """ - Execute ldap query as defined in conf - - Don't call self.query because it supresses possible exceptions - """ - ldap_filter = filter_format(conf['ldap_filter'] % user_name, ()) - conn = self.connect(conf) - conn.simple_bind_s(conf['ldap_binddn'] or '', - conf['ldap_password'] or '') - results = conn.search_st(conf['ldap_base'], ldap.SCOPE_SUBTREE, - ldap_filter.encode('utf8'), None, - timeout=60) - conn.unbind() - - return results - - def populate_wizard(self, cr, uid, ids, context=None): - """ - GUI wrapper for the populate method that reports back - the number of users created. - """ - if not ids: - return - if isinstance(ids, (int, float)): - ids = [ids] - wizard_obj = self.pool.get('res.company.ldap.populate_wizard') - res_id = wizard_obj.create( - cr, uid, {'ldap_id': ids[0]}, context=context) - - return { - 'name': wizard_obj._description, - 'view_type': 'form', - 'view_mode': 'form', - 'res_model': wizard_obj._name, - 'domain': [], - 'context': context, - 'type': 'ir.actions.act_window', - 'target': 'new', - 'res_id': res_id, - 'nodestroy': True, - } diff --git a/users_ldap_populate/model/__init__.py b/users_ldap_populate/models/__init__.py similarity index 100% rename from users_ldap_populate/model/__init__.py rename to users_ldap_populate/models/__init__.py index a925c6082..25aba15b5 100644 --- a/users_ldap_populate/model/__init__.py +++ b/users_ldap_populate/models/__init__.py @@ -1,2 +1,2 @@ -from . import users_ldap from . import populate_wizard +from . import users_ldap diff --git a/users_ldap_populate/models/populate_wizard.py b/users_ldap_populate/models/populate_wizard.py new file mode 100644 index 000000000..7ec3fb59c --- /dev/null +++ b/users_ldap_populate/models/populate_wizard.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# © 2012 Therp BV () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/gpl.html). + +from openerp import models, fields, api + + +class CompanyLDAPPopulateWizard(models.TransientModel): + _name = 'res.company.ldap.populate_wizard' + _description = 'Populate users from LDAP' + + name = fields.Char('Name', size=16) + ldap_id = fields.Many2one( + 'res.company.ldap', + 'LDAP Configuration' + ) + users_created = fields.Integer( + 'Number of users created', + readonly=True + ) + users_deactivated = fields.Integer( + 'Number of users deactivated', + readonly=True + ) + + @api.model + @api.returns('self', lambda value: value.id) + def create(self, vals): + if 'ldap_id' in vals: + ldap = self.env['res.company.ldap'].browse(vals['ldap_id']) + vals['users_created'], vals['users_deactivated'] =\ + ldap.action_populate() + return super(CompanyLDAPPopulateWizard, self).create(vals) diff --git a/users_ldap_populate/models/users_ldap.py b/users_ldap_populate/models/users_ldap.py new file mode 100644 index 000000000..a98754385 --- /dev/null +++ b/users_ldap_populate/models/users_ldap.py @@ -0,0 +1,155 @@ +# -*- coding: utf-8 -*- +# © 2012 Therp BV () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/gpl.html). + +import re + +from openerp import models, fields, api, _, SUPERUSER_ID +from openerp.exceptions import UserError +import logging +import ldap + +_logger = logging.getLogger(__name__) + +try: + from ldap.filter import filter_format +except ImportError: + _logger.debug('Can not `from ldap.filter import filter_format`.') + + +class CompanyLDAP(models.Model): + _inherit = 'res.company.ldap' + + no_deactivate_user_ids = fields.Many2many( + comodel_name='res.users', + relation='res_company_ldap_no_deactivate_user_rel', + column1='ldap_id', + column2='user_id', + string='Users never to deactivate', + help='List users who never should be deactivated by' + ' the deactivation wizard', + default=lambda self: [(6, 0, [SUPERUSER_ID])], + ) + deactivate_unknown_users = fields.Boolean( + string='Deactivate unknown users', + default=False, + ) + + @api.multi + def action_populate(self): + """ + Prepopulate the user table from one or more LDAP resources. + + Obviously, the option to create users must be toggled in + the LDAP configuration. + + Return the number of users created (as far as we can tell). + """ + logger = logging.getLogger('orm.ldap') + logger.debug( + "action_populate called on res.company.ldap ids %s", self.ids) + + users_model = self.env['res.users'] + users_count_before = users_model.search_count([]) + + deactivate_unknown, known_user_ids = self._check_users() + if deactivate_unknown: + logger.debug("will deactivate unknown users") + for conf in self.get_ldap_dicts(): + if not conf['create_user']: + continue + attribute_match = re.search( + r'([a-zA-Z_]+)=\%s', conf['ldap_filter']) + if attribute_match: + login_attr = attribute_match.group(1) + else: + raise UserError( + _("No login attribute found: " + "Could not extract login attribute from filter %s") % + conf['ldap_filter']) + results = self.get_ldap_entry_dicts(conf) + for result in results: + user_id = self.get_or_create_user( + conf, result[1][login_attr][0], result) + # this happens if something goes wrong while creating the user + # or fetching information from ldap + if not user_id: + deactivate_unknown = False + known_user_ids.append(user_id) + + users_created = users_model.search_count([]) - users_count_before + + deactivated_users_count = 0 + if deactivate_unknown: + deactivated_users_count = \ + self.do_deactivate_unknown_users(known_user_ids) + + logger.debug("%d users created", users_created) + logger.debug("%d users deactivated", deactivated_users_count) + return users_created, deactivated_users_count + + def _check_users(self): + deactivate_unknown = None + known_user_ids = [self.env.user.id] + for item in self.read(['no_deactivate_user_ids', + 'deactivate_unknown_users'], + load='_classic_write'): + if deactivate_unknown is None: + deactivate_unknown = True + known_user_ids.extend(item['no_deactivate_user_ids']) + deactivate_unknown &= item['deactivate_unknown_users'] + return deactivate_unknown, known_user_ids + + def get_ldap_entry_dicts(self, conf, user_name='*'): + """Execute ldap query as defined in conf. + + Don't call self.query because it supresses possible exceptions + """ + ldap_filter = filter_format(conf['ldap_filter'] % user_name, ()) + conn = self.connect(conf) + conn.simple_bind_s(conf['ldap_binddn'] or '', + conf['ldap_password'] or '') + results = conn.search_st(conf['ldap_base'], ldap.SCOPE_SUBTREE, + ldap_filter.encode('utf8'), None, + timeout=60) + conn.unbind() + return results + + def do_deactivate_unknown_users(self, known_user_ids): + """Deactivate users not found in last populate run.""" + unknown_user_ids = [] + users = self.env['res.users'].search( + [('id', 'not in', known_user_ids)]) + for unknown_user in users: + present_in_ldap = False + for conf in self.get_ldap_dicts(): + present_in_ldap |= bool(self.get_ldap_entry_dicts( + conf, user_name=unknown_user.login)) + if not present_in_ldap: + unknown_user.active = False + unknown_user_ids.append(unknown_user.id) + return len(unknown_user_ids) + + @api.multi + def populate_wizard(self): + """ + GUI wrapper for the populate method that reports back + the number of users created. + """ + if not self: + return + wizard_obj = self.env['res.company.ldap.populate_wizard'] + res_id = wizard_obj.create({'ldap_id': self.id}).id + + return { + 'name': wizard_obj._description, + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': wizard_obj._name, + 'domain': [], + 'context': self.env.context, + 'type': 'ir.actions.act_window', + 'target': 'new', + 'res_id': res_id, + 'nodestroy': True, + } diff --git a/users_ldap_populate/tests/__init__.py b/users_ldap_populate/tests/__init__.py index 1da05de48..6b833dd97 100644 --- a/users_ldap_populate/tests/__init__.py +++ b/users_ldap_populate/tests/__init__.py @@ -1,4 +1 @@ -# -*- coding: utf-8 -*- -# © 2016 Therp BV -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from . import test_users_ldap_populate diff --git a/users_ldap_populate/tests/test_users_ldap_populate.py b/users_ldap_populate/tests/test_users_ldap_populate.py index befbdf6c1..55d5a8441 100644 --- a/users_ldap_populate/tests/test_users_ldap_populate.py +++ b/users_ldap_populate/tests/test_users_ldap_populate.py @@ -56,6 +56,7 @@ def get_fake_ldap(self): class TestUsersLdapPopulate(TransactionCase): + def test_users_ldap_populate(self): with patch_ldap(self, [('DN=fake', { 'cn': ['fake'], diff --git a/users_ldap_populate/view/populate_wizard.xml b/users_ldap_populate/view/populate_wizard.xml deleted file mode 100644 index 083ec3b0e..000000000 --- a/users_ldap_populate/view/populate_wizard.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - res.company.ldap.populate_wizard - -
- - - -