From 8f373af1f65679b181def7d75da59153b49e6acf Mon Sep 17 00:00:00 2001 From: Daniel Reis Date: Tue, 23 Jul 2013 12:21:53 +0100 Subject: [PATCH 1/2] Therp's modules to sync OpenERP with LDAP directories --- users_ldap_groups/__init__.py | 22 +++++ users_ldap_groups/__openerp__.py | 62 ++++++++++++ .../security/ir.model.access.csv | 2 + users_ldap_groups/users_ldap_groups.py | 98 +++++++++++++++++++ users_ldap_groups/users_ldap_groups.xml | 28 ++++++ .../users_ldap_groups_operators.py | 44 +++++++++ users_ldap_populate/__init__.py | 1 + users_ldap_populate/__openerp__.py | 49 ++++++++++ users_ldap_populate/i18n/nl.po | 56 +++++++++++ users_ldap_populate/model/__init__.py | 2 + users_ldap_populate/model/populate_wizard.py | 42 ++++++++ users_ldap_populate/model/users_ldap.py | 96 ++++++++++++++++++ users_ldap_populate/view/populate_wizard.xml | 18 ++++ users_ldap_populate/view/users_ldap.xml | 20 ++++ 14 files changed, 540 insertions(+) create mode 100644 users_ldap_groups/__init__.py create mode 100644 users_ldap_groups/__openerp__.py create mode 100644 users_ldap_groups/security/ir.model.access.csv create mode 100644 users_ldap_groups/users_ldap_groups.py create mode 100644 users_ldap_groups/users_ldap_groups.xml create mode 100644 users_ldap_groups/users_ldap_groups_operators.py create mode 100644 users_ldap_populate/__init__.py create mode 100644 users_ldap_populate/__openerp__.py create mode 100644 users_ldap_populate/i18n/nl.po create mode 100644 users_ldap_populate/model/__init__.py create mode 100644 users_ldap_populate/model/populate_wizard.py create mode 100644 users_ldap_populate/model/users_ldap.py create mode 100644 users_ldap_populate/view/populate_wizard.xml create mode 100644 users_ldap_populate/view/users_ldap.xml diff --git a/users_ldap_groups/__init__.py b/users_ldap_groups/__init__.py new file mode 100644 index 000000000..d359182a2 --- /dev/null +++ b/users_ldap_groups/__init__.py @@ -0,0 +1,22 @@ +# -*- 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 users_ldap_groups diff --git a/users_ldap_groups/__openerp__.py b/users_ldap_groups/__openerp__.py new file mode 100644 index 000000000..b5755fb75 --- /dev/null +++ b/users_ldap_groups/__openerp__.py @@ -0,0 +1,62 @@ +# -*- 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 . +# +############################################################################## + +{ +"name" : "Groups assignment", +"version" : "1.0", +"depends" : ["auth_ldap"], +"author" : "Therp BV", +"description": """ +Adds user accounts to groups based on rules defined by the administrator. + +Usage: + +Define mappings in Settings->Companies->[your company]->tab configuration->[your +ldap server]. + +Decide whether you want only groups mapped from ldap (Only ldap groups=y) or a +mix of manually set groups and ldap groups (Only ldap groups=n). Setting this to +'no' will result in users never losing privileges when you remove them from a +ldap group, so that's a potential security issue. It is still the default to +prevent losing group information by accident. + +For active directory, use LDAP attribute 'memberOf' and operator 'contains'. +Fill in the DN of the windows group as value and choose an OpenERP group users +with this windows group are to be assigned to. + +For posix accounts, use operator 'query' and a value like +(&(cn=bzr)(objectClass=posixGroup)(memberUid=$uid)) + +The operator query matches if the filter in value returns something, and value +can contain $[attribute] which will be replaced by the first value of the +user's ldap record's attribute named [attribute]. +""", +"category" : "Tools", +"data" : [ + 'users_ldap_groups.xml', + 'security/ir.model.access.csv', +], +"installable": True, +"external_dependencies" : { + 'python' : ['ldap'], +}, +} +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/users_ldap_groups/security/ir.model.access.csv b/users_ldap_groups/security/ir.model.access.csv new file mode 100644 index 000000000..7144dac1f --- /dev/null +++ b/users_ldap_groups/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_res_company_ldap_groups,res_company_ldap_groups,model_res_company_ldap_group_mapping,base.group_system,1,1,1,1 diff --git a/users_ldap_groups/users_ldap_groups.py b/users_ldap_groups/users_ldap_groups.py new file mode 100644 index 000000000..59421f924 --- /dev/null +++ b/users_ldap_groups/users_ldap_groups.py @@ -0,0 +1,98 @@ +# -*- 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 fields, orm +import logging +import users_ldap_groups_operators +import inspect +import sys + +class CompanyLDAPGroupMapping(orm.Model): + _name='res.company.ldap.group_mapping' + _rec_name='ldap_attribute' + _order='ldap_attribute' + + def _get_operators(self, cr, uid, context=None): + operators=[] + for name, operator in inspect.getmembers(users_ldap_groups_operators, + lambda cls: inspect.isclass(cls) + and cls!=users_ldap_groups_operators.LDAPOperator): + operators.append((name, name)) + return tuple(operators) + + _columns={ + 'ldap_id': fields.many2one('res.company.ldap', 'LDAP server', + required=True), + 'ldap_attribute': fields.char('LDAP attribute', size=64, + help='The LDAP attribute to check.\n' + 'For active directory, use memberOf.'), + 'operator': fields.selection(_get_operators, 'Operator', + help='The operator to check the attribute against the value\n' + 'For active directory, use \'contains\'', required=True), + 'value': fields.char('Value', size=1024, + help='The value to check the attribute against.\n' + 'For active directory, use the dn of the desired group', + required=True), + 'group': fields.many2one('res.groups', 'OpenERP group', + help='The OpenERP group to assign', required=True), + } + +class CompanyLDAP(orm.Model): + _inherit='res.company.ldap' + + _columns={ + 'group_mappings': fields.one2many('res.company.ldap.group_mapping', + 'ldap_id', 'Group mappings', + help='Define how OpenERP groups are assigned to ldap users'), + 'only_ldap_groups': fields.boolean('Only ldap groups', + help='If this is checked, manual changes to group membership are ' + 'undone on every login (so OpenERP groups are always synchronous ' + 'with LDAP groups). If not, manually added groups are preserved.') + } + + _default={ + 'only_ldap_groups': False + } + + def get_or_create_user(self, cr, uid, conf, login, ldap_entry, context=None): + user_id=super(CompanyLDAP, self).get_or_create_user(cr, uid, conf, login, + ldap_entry, context) + if not user_id: + return user_id + logger=logging.getLogger('users_ldap_groups') + mappingobj=self.pool.get('res.company.ldap.group_mapping') + userobj=self.pool.get('res.users') + conf_all=self.read(cr, uid, conf['id'], ['only_ldap_groups']) + if(conf_all['only_ldap_groups']): + logger.debug('deleting all groups from user %d' % user_id) + userobj.write(cr, uid, user_id, {'groups_id': [(5, )]}) + + for mapping in mappingobj.read(cr, uid, mappingobj.search(cr, uid, + [('ldap_id', '=', conf['id'])]), []): + operator=getattr(users_ldap_groups_operators, mapping['operator'])() + logger.debug('checking mapping %s' % mapping) + if operator.check_value(ldap_entry, mapping['ldap_attribute'], + mapping['value'], conf, self, logger): + logger.debug('adding user %d to group %s' % + (user_id, mapping['group'][1])) + userobj.write(cr, uid, user_id, + {'groups_id': [(4, mapping['group'][0])]}) + return user_id diff --git a/users_ldap_groups/users_ldap_groups.xml b/users_ldap_groups/users_ldap_groups.xml new file mode 100644 index 000000000..3a9dce390 --- /dev/null +++ b/users_ldap_groups/users_ldap_groups.xml @@ -0,0 +1,28 @@ + + + + + res.company.form.inherit.users_ldap_groups + res.company + form + + + + + + + + + + + + + + + + + + + + + diff --git a/users_ldap_groups/users_ldap_groups_operators.py b/users_ldap_groups/users_ldap_groups_operators.py new file mode 100644 index 000000000..9706459e9 --- /dev/null +++ b/users_ldap_groups/users_ldap_groups_operators.py @@ -0,0 +1,44 @@ +# -*- 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 string import Template + +class LDAPOperator: + pass + +class contains(LDAPOperator): + def check_value(self, ldap_entry, attribute, value, ldap_config, company, + logger): + return (attribute in ldap_entry[1]) and (value in ldap_entry[1][attribute]) + +class equals(LDAPOperator): + def check_value(self, ldap_entry, attribute, value, ldap_config, company, + logger): + return (attribute in ldap_entry[1]) and (str(value)==str(ldap_entry[1][attribute])) + +class query(LDAPOperator): + def check_value(self, ldap_entry, attribute, value, ldap_config, company, + logger): + query_string=Template(value).safe_substitute(dict([(attribute, + ldap_entry[1][attribute][0]) for attribute in ldap_entry[1]])) + logger.debug('evaluating query group mapping, filter: %s'%query_string) + results=company.query(ldap_config, query_string) + logger.debug(results) + return bool(results) diff --git a/users_ldap_populate/__init__.py b/users_ldap_populate/__init__.py new file mode 100644 index 000000000..16e8b082f --- /dev/null +++ b/users_ldap_populate/__init__.py @@ -0,0 +1 @@ +import model diff --git a/users_ldap_populate/__openerp__.py b/users_ldap_populate/__openerp__.py new file mode 100644 index 000000000..e73d47c87 --- /dev/null +++ b/users_ldap_populate/__openerp__.py @@ -0,0 +1,49 @@ +# -*- 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 . +# +############################################################################## +{ + "name": "LDAP Populate", + "version": "1.0", + "author": "Therp BV", + "category": 'Tools', + "description": """ +This module allows to prepopulate the user database with all entries in the LDAP +database. + +In order to schedule the population of the user database on a regular basis, +create a new scheduled action with the following properties: + +- Object: res.company.ldap +- Function: action_populate +- Arguments: [res.company.ldap.id] + +Substitute res.company.ldap.id with the actual id of the res.company.ldap object you want to query. + +""", + "depends": [ + 'auth_ldap', + ], + "data": [ + 'view/users_ldap.xml', + 'view/populate_wizard.xml', + ], + 'installable': True, +} +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/users_ldap_populate/i18n/nl.po b/users_ldap_populate/i18n/nl.po new file mode 100644 index 000000000..ea0bf5db9 --- /dev/null +++ b/users_ldap_populate/i18n/nl.po @@ -0,0 +1,56 @@ +# Translation of OpenERP Server. +# This file contains the translation of the following modules: +# * users_ldap_populate +# +msgid "" +msgstr "" +"Project-Id-Version: OpenERP Server 6.1\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-07-31 13:08+0000\n" +"PO-Revision-Date: 2012-07-31 13:08+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: users_ldap_populate +#: field:res.company.ldap.populate_wizard,ldap_id:0 +msgid "LDAP Configuration" +msgstr "LDAP configuratie" + +#. module: users_ldap_populate +#: view:res.company:0 +msgid "Populate user database" +msgstr "Gebruikersbestand bevolken" + +#. module: users_ldap_populate +#: field:res.company.ldap.populate_wizard,name:0 +msgid "Name" +msgstr "Naam" + +#. module: users_ldap_populate +#: view:res.company:0 +msgid "Populate" +msgstr "Bevolken" + +#. module: users_ldap_populate +#: model:ir.model,name:users_ldap_populate.model_res_company_ldap_populate_wizard +msgid "Populate users from LDAP" +msgstr "Gebruikersbestand bevolken op basis van LDAP" + +#. module: users_ldap_populate +#: field:res.company.ldap.populate_wizard,users_created:0 +msgid "Number of users created" +msgstr "Aantal aangemaakte gebruikers" + +#. module: users_ldap_populate +#: model:ir.model,name:users_ldap_populate.model_res_company_ldap +msgid "res.company.ldap" +msgstr "res.company.ldap" + +#. module: users_ldap_populate +#: view:res.company.ldap.populate_wizard:0 +msgid "OK" +msgstr "OK" diff --git a/users_ldap_populate/model/__init__.py b/users_ldap_populate/model/__init__.py new file mode 100644 index 000000000..1fa6a8664 --- /dev/null +++ b/users_ldap_populate/model/__init__.py @@ -0,0 +1,2 @@ +import users_ldap +import populate_wizard diff --git a/users_ldap_populate/model/populate_wizard.py b/users_ldap_populate/model/populate_wizard.py new file mode 100644 index 000000000..6677e2245 --- /dev/null +++ b/users_ldap_populate/model/populate_wizard.py @@ -0,0 +1,42 @@ +# -*- 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 osv import osv, fields + +class CompanyLDAPPopulateWizard(osv.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), + } + + def create(self, cr, uid, vals, context=None): + ldap_pool = self.pool.get('res.company.ldap') + users_pool = self.pool.get('res.users') + if 'ldap_id' in vals: + vals['users_created'] = 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 new file mode 100644 index 000000000..f58388e8a --- /dev/null +++ b/users_ldap_populate/model/users_ldap.py @@ -0,0 +1,96 @@ +# -*- 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 ldap.filter import filter_format +from openerp.osv import orm, fields +import openerp.exceptions +import logging + +class CompanyLDAP(orm.Model): + _inherit = 'res.company.ldap' + + 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) + + for conf in self.get_ldap_dicts(cr, ids): + if not conf['create_user']: + continue + attribute_match = re.search( + '([a-zA-Z_]+)=\%s', conf['ldap_filter']) + if attribute_match: + login_attr = attribute_match.group(1) + else: + raise osv.except_osv( + "No login attribute found", + "Could not extract login attribute from filter %s" % + conf['ldap_filter']) + ldap_filter = filter_format(conf['ldap_filter'] % '*', ()) + for result in self.query(conf, ldap_filter): + self.get_or_create_user( + cr, uid, conf, result[1][login_attr][0], result) + + users_no_after = users_pool.search( + cr, uid, [], context=context, count=True) + users_created = users_no_after - users_no_before + logger.debug("%d users created", users_created) + return users_created + + 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/view/populate_wizard.xml b/users_ldap_populate/view/populate_wizard.xml new file mode 100644 index 000000000..5b80f7214 --- /dev/null +++ b/users_ldap_populate/view/populate_wizard.xml @@ -0,0 +1,18 @@ + + + + + Add populate button to ldap view + res.company.ldap.populate_wizard + form + +
+ + +