Browse Source
Merge pull request #1144 from hbrunn/10.0-users_ldap_groups
Merge pull request #1144 from hbrunn/10.0-users_ldap_groups
[10.0][MIG] users_ldap_groupspull/1231/head
Daniel Reis
7 years ago
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 279 additions and 224 deletions
-
36users_ldap_groups/README.rst
-
24users_ldap_groups/__init__.py
-
38users_ldap_groups/__manifest__.py
-
7users_ldap_groups/models/__init__.py
-
47users_ldap_groups/models/res_company_ldap.py
-
33users_ldap_groups/models/res_company_ldap_group_mapping.py
-
43users_ldap_groups/models/res_company_ldap_operator.py
-
27users_ldap_groups/models/res_users.py
-
4users_ldap_groups/tests/__init__.py
-
65users_ldap_groups/tests/test_users_ldap_groups.py
-
128users_ldap_groups/users_ldap_groups.py
-
27users_ldap_groups/users_ldap_groups.xml
-
24users_ldap_groups/views/base_config_settings.xml
@ -1,22 +1,4 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################## |
|||
# |
|||
# OpenERP, Open Source Management Solution |
|||
# This module copyright (C) 2012 Therp BV (<http://therp.nl>). |
|||
# |
|||
# 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/>. |
|||
# |
|||
############################################################################## |
|||
|
|||
from . import users_ldap_groups |
|||
# Copyright 2012-2018 Therp BV <https://therp.nl> |
|||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). |
|||
from . import models |
@ -1,39 +1,19 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################## |
|||
# |
|||
# OpenERP, Open Source Management Solution |
|||
# This module copyright (C) 2012 Therp BV (<http://therp.nl>). |
|||
# |
|||
# 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/>. |
|||
# |
|||
############################################################################## |
|||
|
|||
# Copyright 2012-2018 Therp BV <https://therp.nl> |
|||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). |
|||
{ |
|||
"name": "Groups assignment", |
|||
"version": "8.0.1.2.0", |
|||
"name": "LDAP groups assignment", |
|||
"version": "10.0.0.0.0", |
|||
"depends": ["auth_ldap"], |
|||
"author": "Therp BV,Odoo Community Association (OCA)", |
|||
"author": "Therp BV, Odoo Community Association (OCA)", |
|||
"license": "AGPL-3", |
|||
"summary": """ |
|||
Adds user accounts to groups based on rules defined by the administrator. |
|||
""", |
|||
"category": "Tools", |
|||
"summary": "Adds user accounts to groups based on rules defined " |
|||
"by the administrator.", |
|||
"category": "Authentication", |
|||
"data": [ |
|||
'users_ldap_groups.xml', |
|||
'views/base_config_settings.xml', |
|||
'security/ir.model.access.csv', |
|||
], |
|||
'installable': False, |
|||
"external_dependencies": { |
|||
'python': ['ldap'], |
|||
}, |
|||
|
@ -0,0 +1,7 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Copyright 2012-2018 Therp BV <https://therp.nl> |
|||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). |
|||
from . import res_company_ldap |
|||
from . import res_company_ldap_operator |
|||
from . import res_company_ldap_group_mapping |
|||
from . import res_users |
@ -0,0 +1,47 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Copyright 2012-2018 Therp BV <https://therp.nl> |
|||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). |
|||
from logging import getLogger |
|||
from odoo import api, fields, models |
|||
_logger = getLogger(__name__) |
|||
|
|||
|
|||
class ResCompanyLdap(models.Model): |
|||
_inherit = 'res.company.ldap' |
|||
|
|||
group_mapping_ids = fields.One2many( |
|||
'res.company.ldap.group_mapping', |
|||
'ldap_id', 'Group mappings', |
|||
help='Define how Odoo groups are assigned to ldap users', |
|||
) |
|||
only_ldap_groups = fields.Boolean( |
|||
'Only ldap groups', default=False, |
|||
help='If this is checked, manual changes to group membership are ' |
|||
'undone on every login (so Odoo groups are always synchronous ' |
|||
'with LDAP groups). If not, manually added groups are preserved.', |
|||
) |
|||
|
|||
@api.model |
|||
def get_or_create_user(self, conf, login, ldap_entry): |
|||
op_obj = self.env['res.company.ldap.operator'] |
|||
user_id = super(ResCompanyLdap, self).get_or_create_user( |
|||
conf, login, ldap_entry |
|||
) |
|||
if not user_id: |
|||
return user_id |
|||
this = self.browse(conf['id']) |
|||
user = self.env['res.users'].browse(user_id) |
|||
if this.only_ldap_groups: |
|||
_logger.debug('deleting all groups from user %d', user_id) |
|||
user.write({'groups_id': [(5, False, False)]}) |
|||
|
|||
for mapping in this.group_mapping_ids: |
|||
operator = getattr(op_obj, mapping.operator) |
|||
_logger.debug('checking mapping %s', mapping) |
|||
|
|||
if operator(ldap_entry, mapping): |
|||
_logger.debug( |
|||
'adding user %d to group %s', user, mapping.group_id.name, |
|||
) |
|||
user.write({'groups_id': [(4, mapping.group_id.id)]}) |
|||
return user_id |
@ -0,0 +1,33 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2012-2018 Therp BV <https://therp.nl> |
|||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). |
|||
from odoo import fields, models |
|||
|
|||
|
|||
class ResCompanyLdapGroupMapping(models.Model): |
|||
_name = 'res.company.ldap.group_mapping' |
|||
_rec_name = 'ldap_attribute' |
|||
_order = 'ldap_attribute' |
|||
|
|||
ldap_id = fields.Many2one( |
|||
'res.company.ldap', 'LDAP server', required=True, ondelete='cascade', |
|||
) |
|||
ldap_attribute = fields.Char( |
|||
'LDAP attribute', |
|||
help='The LDAP attribute to check.\n' |
|||
'For active directory, use memberOf.') |
|||
operator = fields.Selection( |
|||
lambda self: [ |
|||
(o, o) for o in self.env['res.company.ldap.operator'].operators() |
|||
], |
|||
'Operator', |
|||
help='The operator to check the attribute against the value\n' |
|||
'For active directory, use \'contains\'', required=True) |
|||
value = fields.Char( |
|||
'Value', |
|||
help='The value to check the attribute against.\n' |
|||
'For active directory, use the dn of the desired group', |
|||
required=True) |
|||
group_id = fields.Many2one( |
|||
'res.groups', 'Odoo group', oldname='group', |
|||
help='The Odoo group to assign', required=True) |
@ -0,0 +1,43 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2012-2018 Therp BV <https://therp.nl> |
|||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). |
|||
from logging import getLogger |
|||
from odoo import api, models |
|||
from string import Template |
|||
_logger = getLogger(__name__) |
|||
|
|||
|
|||
class ResCompanyLdapOperator(models.AbstractModel): |
|||
"""Define operators for group mappings""" |
|||
|
|||
_name = "res.company.ldap.operator" |
|||
_description = "Definition op LDAP operations" |
|||
|
|||
@api.model |
|||
def operators(self): |
|||
"""Return names of function to call on this model as operator""" |
|||
return ('contains', 'equals', 'query') |
|||
|
|||
@api.model |
|||
def contains(self, ldap_entry, mapping): |
|||
return mapping.ldap_attribute in ldap_entry[1] and \ |
|||
mapping.value in ldap_entry[1][mapping.ldap_attribute] |
|||
|
|||
def equals(self, ldap_entry, mapping): |
|||
return mapping.ldap_attribute in ldap_entry[1] and \ |
|||
unicode(mapping.value) == unicode( |
|||
ldap_entry[1][mapping.ldap_attribute] |
|||
) |
|||
|
|||
def query(self, ldap_entry, mapping): |
|||
query_string = Template(mapping.value).safe_substitute({ |
|||
attr: ldap_entry[1][attr][0] for attr in ldap_entry[1] |
|||
}) |
|||
_logger.debug( |
|||
'evaluating query group mapping, filter: %s' % query_string |
|||
) |
|||
results = mapping.ldap_id.query( |
|||
mapping.ldap_id.read()[0], query_string |
|||
) |
|||
_logger.debug(results) |
|||
return bool(results) |
@ -0,0 +1,27 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2018 Therp BV <https://therp.nl> |
|||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). |
|||
from odoo import SUPERUSER_ID, api, models, registry |
|||
|
|||
|
|||
class ResUsers(models.Model): |
|||
_inherit = 'res.users' |
|||
|
|||
@classmethod |
|||
def _login(cls, db, login, password): |
|||
user_id = super(ResUsers, cls)._login(db, login, password) |
|||
if not user_id: |
|||
return user_id |
|||
with registry(db).cursor() as cr: |
|||
env = api.Environment(cr, SUPERUSER_ID, {}) |
|||
user = env['res.users'].browse(user_id) |
|||
# check if this user came from ldap, rerun get_or_create_user in |
|||
# this case to apply ldap groups if necessary |
|||
ldaps = user.company_id.ldaps |
|||
if user.active and any(ldaps.mapped('only_ldap_groups')): |
|||
for conf in ldaps.get_ldap_dicts(): |
|||
entry = ldaps.authenticate(conf, login, password) |
|||
if entry: |
|||
ldaps.get_or_create_user(conf, login, entry) |
|||
break |
|||
return user_id |
@ -0,0 +1,4 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Copyright 2018 Therp BV <https://therp.nl> |
|||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). |
|||
from . import test_users_ldap_groups |
@ -0,0 +1,65 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Copyright 2018 Therp BV <https://therp.nl> |
|||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). |
|||
from mock import Mock, patch |
|||
from odoo.tests.common import TransactionCase |
|||
|
|||
|
|||
@patch('ldap.initialize', return_value=Mock( |
|||
search_st=Mock(return_value=[ |
|||
('cn=hello', {'name': ['hello', 'hello2']}) |
|||
]), |
|||
)) |
|||
class TestUsersLdapGroups(TransactionCase): |
|||
def test_users_ldap_groups(self, ldap_initialize): |
|||
# _login does its work in a new cursor, so we need one too |
|||
with self.env.registry.cursor() as cr: |
|||
env = self.env(cr=cr) |
|||
group_contains = env['res.groups'].create({'name': 'contains'}) |
|||
group_equals = env['res.groups'].create({'name': 'equals'}) |
|||
group_query = env['res.groups'].create({'name': 'query'}) |
|||
env.ref('base.main_company').write({'ldaps': [(0, 0, { |
|||
'ldap_server': 'localhost', |
|||
'ldap_filter': '(&(objectClass=*),(uid=%s))', |
|||
'ldap_base': 'base', |
|||
'only_ldap_groups': True, |
|||
'group_mapping_ids': [ |
|||
(0, 0, { |
|||
'ldap_attribute': 'name', |
|||
'operator': 'contains', |
|||
'value': 'hello3', |
|||
'group_id': env.ref('base.group_system').id, |
|||
}), |
|||
(0, 0, { |
|||
'ldap_attribute': 'name', |
|||
'operator': 'contains', |
|||
'value': 'hello2', |
|||
'group_id': group_contains.id, |
|||
}), |
|||
(0, 0, { |
|||
'ldap_attribute': 'name', |
|||
'operator': 'equals', |
|||
'value': 'hello', |
|||
'group_id': group_equals.id, |
|||
}), |
|||
(0, 0, { |
|||
'ldap_attribute': '', |
|||
'operator': 'query', |
|||
'value': 'is not run because of patching', |
|||
'group_id': group_query.id, |
|||
}), |
|||
], |
|||
})]}) |
|||
|
|||
self.env['res.users']._login(self.env.cr.dbname, 'demo', 'wrong') |
|||
with self.env.registry.cursor() as cr: |
|||
env = self.env(cr=cr) |
|||
demo_user = env.ref('base.user_demo') |
|||
# this asserts group mappings from demo data |
|||
groups = demo_user.groups_id |
|||
self.assertIn(group_contains, groups) |
|||
self.assertNotIn(group_equals, groups) |
|||
self.assertIn(group_query, groups) |
|||
self.assertNotIn(env.ref('base.group_system'), groups) |
|||
# clean up |
|||
env.ref('base.main_company').write({'ldaps': [(6, False, [])]}) |
@ -1,128 +0,0 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################## |
|||
# |
|||
# OpenERP, Open Source Management Solution |
|||
# This module copyright (C) 2012 Therp BV (<http://therp.nl>). |
|||
# |
|||
# 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/>. |
|||
# |
|||
############################################################################## |
|||
|
|||
from openerp import models |
|||
from openerp import fields |
|||
from openerp import api |
|||
import logging |
|||
from string import Template |
|||
|
|||
|
|||
class LDAPOperator(models.AbstractModel): |
|||
_name = "res.company.ldap.operator" |
|||
|
|||
def operators(self): |
|||
return ('contains', 'equals', 'query') |
|||
|
|||
def contains(self, ldap_entry, attribute, value, ldap_config, company, |
|||
logger): |
|||
return (attribute in ldap_entry[1]) and \ |
|||
(value in ldap_entry[1][attribute]) |
|||
|
|||
def equals(self, ldap_entry, attribute, value, ldap_config, company, |
|||
logger): |
|||
return attribute in ldap_entry[1] and \ |
|||
unicode(value) == unicode(ldap_entry[1][attribute]) |
|||
|
|||
def query(self, ldap_entry, attribute, value, ldap_config, company, |
|||
logger): |
|||
query_string = Template(value).safe_substitute(dict( |
|||
[(attr, ldap_entry[1][attribute][0]) for attr 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) |
|||
|
|||
|
|||
class CompanyLDAPGroupMapping(models.Model): |
|||
_name = 'res.company.ldap.group_mapping' |
|||
_rec_name = 'ldap_attribute' |
|||
_order = 'ldap_attribute' |
|||
|
|||
def _get_operators(self): |
|||
op_obj = self.env['res.company.ldap.operator'] |
|||
operators = [(op, op) for op in op_obj.operators()] |
|||
return tuple(operators) |
|||
|
|||
ldap_id = fields.Many2one('res.company.ldap', 'LDAP server', required=True) |
|||
ldap_attribute = fields.Char( |
|||
'LDAP attribute', |
|||
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', |
|||
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(models.Model): |
|||
_inherit = 'res.company.ldap' |
|||
|
|||
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, |
|||
} |
|||
|
|||
@api.model |
|||
def get_or_create_user(self, conf, login, ldap_entry): |
|||
op_obj = self.env['res.company.ldap.operator'] |
|||
id_ = conf['id'] |
|||
this = self.browse(id_) |
|||
user_id = super(CompanyLDAP, self).get_or_create_user( |
|||
conf, login, ldap_entry) |
|||
if not user_id: |
|||
return user_id |
|||
userobj = self.env['res.users'] |
|||
user = userobj.browse(user_id) |
|||
logger = logging.getLogger('users_ldap_groups') |
|||
if self.only_ldap_groups: |
|||
logger.debug('deleting all groups from user %d' % user_id) |
|||
user.write({'groups_id': [(5, )]}) |
|||
|
|||
for mapping in this.group_mappings: |
|||
operator = getattr(op_obj, mapping.operator) |
|||
logger.debug('checking mapping %s' % mapping) |
|||
if operator(ldap_entry, mapping['ldap_attribute'], |
|||
mapping['value'], conf, self, logger): |
|||
logger.debug('adding user %d to group %s' % |
|||
(user_id, mapping.group.name)) |
|||
user.write({'groups_id': [(4, mapping.group.id)]}) |
|||
return user_id |
@ -1,27 +0,0 @@ |
|||
<?xml version="1.0"?> |
|||
<openerp> |
|||
<data> |
|||
<record model="ir.ui.view" id="company_form_view"> |
|||
<field name="name">res.company.form.inherit.users_ldap_groups</field> |
|||
<field name="model">res.company</field> |
|||
<field name="inherit_id" ref="auth_ldap.company_form_view"/> |
|||
<field name="arch" type="xml"> |
|||
|
|||
<xpath expr="//form[@string='LDAP Configuration']" position="inside"> |
|||
<group string="Map User Groups" > |
|||
<field name="only_ldap_groups" /> |
|||
<field name="group_mappings" colspan="4" nolabel="1"> |
|||
<tree editable="top"> |
|||
<field name="ldap_attribute" attrs="{'required': [('operator','not in',['query'])], 'readonly': [('operator','in',['query'])]}" /> |
|||
<field name="operator" /> |
|||
<field name="value" /> |
|||
<field name="group" /> |
|||
</tree> |
|||
</field> |
|||
</group> |
|||
</xpath> |
|||
|
|||
</field> |
|||
</record> |
|||
</data> |
|||
</openerp> |
@ -0,0 +1,24 @@ |
|||
<?xml version="1.0"?> |
|||
<odoo> |
|||
<record model="ir.ui.view" id="company_form_view"> |
|||
<field name="model">base.config.settings</field> |
|||
<field name="inherit_id" ref="auth_ldap.view_general_configuration_form_inherit_auth_ldap"/> |
|||
<field name="arch" type="xml"> |
|||
<xpath expr="//field[@name='ldaps']/form/group" position="after"> |
|||
<group string="Map User Groups" > |
|||
<field name="only_ldap_groups" /> |
|||
<label for="group_mapping_ids" /> |
|||
<field name="group_mapping_ids" nolabel="1"> |
|||
<tree editable="top"> |
|||
<field name="ldap_attribute" attrs="{'required': [('operator','not in',['query'])], 'readonly': [('operator','in',['query'])]}" /> |
|||
<field name="operator" /> |
|||
<field name="value" /> |
|||
<field name="group_id" /> |
|||
</tree> |
|||
</field> |
|||
</group> |
|||
</xpath> |
|||
|
|||
</field> |
|||
</record> |
|||
</odoo> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue