Browse Source
Merge pull request #931 from simahawk/users_ldap_populate-port-v8-feat-to-v9
Merge pull request #931 from simahawk/users_ldap_populate-port-v8-feat-to-v9
[mig] Users ldap populate port v8 feat to v9pull/1208/head
Moises Lopez - https://www.vauxoo.com/
6 years ago
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 330 additions and 197 deletions
-
13users_ldap_populate/README.rst
-
2users_ldap_populate/__init__.py
-
18users_ldap_populate/__openerp__.py
-
45users_ldap_populate/model/populate_wizard.py
-
98users_ldap_populate/model/users_ldap.py
-
2users_ldap_populate/models/__init__.py
-
33users_ldap_populate/models/populate_wizard.py
-
171users_ldap_populate/models/users_ldap.py
-
1users_ldap_populate/tests/__init__.py
-
71users_ldap_populate/tests/test_users_ldap_populate.py
-
17users_ldap_populate/view/populate_wizard.xml
-
19users_ldap_populate/view/users_ldap.xml
-
15users_ldap_populate/views/populate_wizard.xml
-
22users_ldap_populate/views/users_ldap.xml
@ -0,0 +1,13 @@ |
|||||
|
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. |
||||
|
|
@ -1 +1 @@ |
|||||
from . import model |
|
||||
|
from . import models |
@ -1,45 +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, 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 |
|
||||
) |
|
||||
|
|
||||
@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'] = ldap.action_populate() |
|
||||
return super(CompanyLDAPPopulateWizard, self).create(vals) |
|
@ -1,98 +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/>. |
|
||||
# |
|
||||
############################################################################## |
|
||||
|
|
||||
import re |
|
||||
|
|
||||
from openerp import models, api, _ |
|
||||
from openerp.exceptions import UserError |
|
||||
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(models.Model): |
|
||||
_inherit = 'res.company.ldap' |
|
||||
|
|
||||
@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). |
|
||||
""" |
|
||||
users_pool = self.env['res.users'] |
|
||||
users_no_before = users_pool.search_count([]) |
|
||||
logger = logging.getLogger('orm.ldap') |
|
||||
logger.debug("action_populate called on res.company.ldap ids %s", |
|
||||
self.ids) |
|
||||
|
|
||||
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']) |
|
||||
ldap_filter = filter_format(conf['ldap_filter'] % '*', ()) |
|
||||
for result in self.query(conf, ldap_filter.encode('utf-8')): |
|
||||
self.get_or_create_user(conf, result[1][login_attr][0], result) |
|
||||
|
|
||||
users_no_after = users_pool.search_count([]) |
|
||||
users_created = users_no_after - users_no_before |
|
||||
logger.debug("%d users created", users_created) |
|
||||
return users_created |
|
||||
|
|
||||
@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, |
|
||||
} |
|
@ -1,2 +1,2 @@ |
|||||
from . import users_ldap |
|
||||
from . import populate_wizard |
from . import populate_wizard |
||||
|
from . import users_ldap |
@ -0,0 +1,33 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# © 2012 Therp BV (<http://therp.nl>) |
||||
|
# 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) |
@ -0,0 +1,171 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# © 2012 Therp BV (<http://therp.nl>) |
||||
|
# 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: |
||||
|
login = result[1][login_attr][0].lower().strip() |
||||
|
user_id = self.with_context( |
||||
|
no_reset_password=True |
||||
|
).get_or_create_user(conf, login, result) |
||||
|
if not user_id: |
||||
|
# this happens if the user exists but is active = False |
||||
|
# -> fetch the user again and reactivate it |
||||
|
self.env.cr.execute( |
||||
|
"SELECT id FROM res_users " |
||||
|
"WHERE lower(login)=%s", |
||||
|
(login,)) |
||||
|
res = self.env.cr.fetchone() |
||||
|
if res: |
||||
|
self.env['res.users'].sudo().browse( |
||||
|
res[0] |
||||
|
).write( |
||||
|
{'active': True} |
||||
|
) |
||||
|
else: |
||||
|
raise UserError( |
||||
|
_('Unable to process user with login %s' % login) |
||||
|
) |
||||
|
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, |
||||
|
} |
@ -0,0 +1 @@ |
|||||
|
from . import test_users_ldap_populate |
@ -0,0 +1,71 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# © 2016 Therp BV <http://therp.nl> |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
from openerp.tests.common import TransactionCase |
||||
|
from contextlib import contextmanager |
||||
|
|
||||
|
|
||||
|
class PatchLdapConnection(object): |
||||
|
def __init__(self, results): |
||||
|
self.results = results |
||||
|
|
||||
|
def simple_bind_s(self, user, password): |
||||
|
return True |
||||
|
|
||||
|
def search_st(self, base, scope, ldap_filter, attributes, timeout=None): |
||||
|
if ldap_filter == '(uid=*)': |
||||
|
return self.results |
||||
|
else: |
||||
|
return [] |
||||
|
|
||||
|
def unbind(self): |
||||
|
return True |
||||
|
|
||||
|
|
||||
|
@contextmanager |
||||
|
def patch_ldap(self, results): |
||||
|
""" defuse ldap functions to return fake entries instead of talking to a |
||||
|
server. Use this in your own ldap related tests """ |
||||
|
import ldap |
||||
|
original_initialize = ldap.initialize |
||||
|
|
||||
|
def initialize(uri): |
||||
|
return PatchLdapConnection(results) |
||||
|
ldap.initialize = initialize |
||||
|
yield |
||||
|
ldap.initialize = original_initialize |
||||
|
|
||||
|
|
||||
|
def get_fake_ldap(self): |
||||
|
company = self.env.ref('base.main_company') |
||||
|
company.write({ |
||||
|
'ldaps': [(0, 0, { |
||||
|
'ldap_server': 'fake', |
||||
|
'ldap_server_port': 1234, |
||||
|
'ldap_filter': '(uid=%s)', |
||||
|
'ldap_base': 'fake', |
||||
|
'deactivate_unknown_users': True, |
||||
|
'no_deactivate_user_ids': [(6, 0, [ |
||||
|
self.env.ref('base.user_root').id, |
||||
|
])], |
||||
|
})], |
||||
|
}) |
||||
|
return company.ldaps.filtered( |
||||
|
lambda x: x.ldap_server == 'fake' |
||||
|
) |
||||
|
|
||||
|
|
||||
|
class TestUsersLdapPopulate(TransactionCase): |
||||
|
|
||||
|
def test_users_ldap_populate(self): |
||||
|
with patch_ldap(self, [('DN=fake', { |
||||
|
'cn': ['fake'], |
||||
|
'uid': ['fake'], |
||||
|
'mail': ['fake@fakery.com'], |
||||
|
})]): |
||||
|
get_fake_ldap(self).populate_wizard() |
||||
|
self.assertFalse(self.env.ref('base.user_demo').active) |
||||
|
self.assertTrue(self.env.ref('base.user_root').active) |
||||
|
self.assertTrue(self.env['res.users'].search([ |
||||
|
('login', '=', 'fake') |
||||
|
])) |
@ -1,17 +0,0 @@ |
|||||
<?xml version="1.0"?> |
|
||||
<openerp> |
|
||||
<data> |
|
||||
<record model="ir.ui.view" id="populate_wizard_view"> |
|
||||
<field name="name">Add populate button to ldap view</field> |
|
||||
<field name="model">res.company.ldap.populate_wizard</field> |
|
||||
<field name="arch" type="xml"> |
|
||||
<form string="Add populate button to ldap view"> |
|
||||
<group> |
|
||||
<field name="users_created"/> |
|
||||
<button icon="gtk-ok" string="OK" special="cancel"/> |
|
||||
</group> |
|
||||
</form> |
|
||||
</field> |
|
||||
</record> |
|
||||
</data> |
|
||||
</openerp> |
|
@ -1,19 +0,0 @@ |
|||||
<?xml version="1.0"?> |
|
||||
<openerp> |
|
||||
<data> |
|
||||
<record model="ir.ui.view" id="company_form_view"> |
|
||||
<field name="name">Add populate button to ldap view</field> |
|
||||
<field name="model">res.company</field> |
|
||||
<field name="inherit_id" ref="auth_ldap.company_form_view"/> |
|
||||
<field name="arch" type="xml"> |
|
||||
<xpath name="populate_ldap" expr="//field[@name='ldaps']/form" position="inside"> |
|
||||
<separator string="Populate user database" colspan="4"/> |
|
||||
<button name="populate_wizard" |
|
||||
string="Populate" |
|
||||
type="object" |
|
||||
colspan="2"/> |
|
||||
</xpath> |
|
||||
</field> |
|
||||
</record> |
|
||||
</data> |
|
||||
</openerp> |
|
@ -0,0 +1,15 @@ |
|||||
|
<?xml version="1.0"?> |
||||
|
<odoo> |
||||
|
<record model="ir.ui.view" id="populate_wizard_view"> |
||||
|
<field name="model">res.company.ldap.populate_wizard</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<form string="Add populate button to ldap view"> |
||||
|
<group> |
||||
|
<field name="users_created"/> |
||||
|
<field name="users_deactivated"/> |
||||
|
<button icon="gtk-ok" string="OK" special="cancel"/> |
||||
|
</group> |
||||
|
</form> |
||||
|
</field> |
||||
|
</record> |
||||
|
</odoo> |
@ -0,0 +1,22 @@ |
|||||
|
<?xml version="1.0"?> |
||||
|
<odoo> |
||||
|
<record model="ir.ui.view" id="company_form_view"> |
||||
|
<field name="name">Add populate button to ldap view</field> |
||||
|
<field name="model">res.company</field> |
||||
|
<field name="inherit_id" ref="auth_ldap.company_form_view"/> |
||||
|
<field name="arch" type="xml"> |
||||
|
<xpath name="populate_ldap" expr="//field[@name='ldaps']/form" position="inside"> |
||||
|
<group string="Populate user database"> |
||||
|
<field name="deactivate_unknown_users"/> |
||||
|
<field name="no_deactivate_user_ids" |
||||
|
attrs="{'invisible': [('deactivate_unknown_users', '=', False)]}" |
||||
|
widget="many2many_tags" /> |
||||
|
</group> |
||||
|
<button name="populate_wizard" |
||||
|
string="Populate" |
||||
|
type="object" |
||||
|
colspan="2"/> |
||||
|
</xpath> |
||||
|
</field> |
||||
|
</record> |
||||
|
</odoo> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue