642cdb8b6512c3f22609cffd74fe5dabb1d0efd5
[GroupUserFolder.git] / LDAPUserFolderAdapter.py
1 # -*- coding: utf-8 -*-
2 ## GroupUserFolder
3 ## Copyright (C)2006 Ingeniweb
4
5 ## This program is free software; you can redistribute it and/or modify
6 ## it under the terms of the GNU General Public License as published by
7 ## the Free Software Foundation; either version 2 of the License, or
8 ## (at your option) any later version.
9
10 ## This program is distributed in the hope that it will be useful,
11 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ## GNU General Public License for more details.
14
15 ## You should have received a copy of the GNU General Public License
16 ## along with this program; see the file COPYING. If not, write to the
17 ## Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 """
19
20 """
21 __version__ = "$Revision: $"
22 # $Source: $
23 # $Id: LDAPUserFolderAdapter.py 587 2008-07-31 09:20:06Z pin $
24 __docformat__ = 'restructuredtext'
25
26
27 from global_symbols import *
28 from Products.GroupUserFolder import postonly
29
30
31 # These mandatory attributes are required by LDAP schema.
32 # They will be filled with user name as a default value.
33 # You have to provide a gruf_ldap_required_fields python script
34 # in your Plone's skins if you want to override this.
35 MANDATORY_ATTRIBUTES = ("sn", "cn", )
36
37
38 def _doAddUser(self, name, password, roles, domains, **kw):
39 """
40 Special user adding method for use with LDAPUserFolder.
41 This will ensure parameters are correct for LDAP management
42 """
43 kwargs = {} # We will pass this dict
44 attrs = {}
45
46 # Get gruf_ldap_required_fields result and fill in mandatory stuff
47 if hasattr(self, "gruf_ldap_required_fields"):
48 attrs = self.gruf_ldap_required_fields(login = name)
49 else:
50 for attr in MANDATORY_ATTRIBUTES:
51 attrs[attr] = name
52 kwargs.update(attrs)
53
54 # We assume that name is rdn attribute
55 rdn_attr = self._rdnattr
56 kwargs[rdn_attr] = name
57
58 # Manage password(s)
59 kwargs['user_pw'] = password
60 kwargs['confirm_pw'] = password
61
62 # Mangle roles
63 kwargs['user_roles'] = self._mangleRoles(name, roles)
64
65 # Delegate to LDAPUF default method
66 msg = self.manage_addUser(kwargs = kwargs)
67 if msg:
68 raise RuntimeError, msg
69
70
71 def _doDelUsers(self, names):
72 """
73 Remove a bunch of users from LDAP.
74 We have to call manage_deleteUsers but, before, we need to find their dn.
75 """
76 dns = []
77 for name in names:
78 dns.append(self._find_user_dn(name))
79
80 self.manage_deleteUsers(dns)
81
82
83 def _find_user_dn(self, name):
84 """
85 Convert a name to an LDAP dn
86 """
87 # Search records matching name
88 login_attr = self._login_attr
89 v = self.findUser(search_param = login_attr, search_term = name)
90
91 # Filter to keep exact matches only
92 v = filter(lambda x: x[login_attr] == name, v)
93
94 # Now, decide what to do
95 l = len(v)
96 if not l:
97 # Invalid name
98 raise "Invalid user name: '%s'" % (name, )
99 elif l > 1:
100 # Several records... don't know how to handle
101 raise "Duplicate user name for '%s'" % (name, )
102 return v[0]['dn']
103
104
105 def _mangleRoles(self, name, roles):
106 """
107 Return role_dns for this user
108 """
109 # Local groups => the easiest part
110 if self._local_groups:
111 return roles
112
113 # We have to transform roles into group dns: transform them as a dict
114 role_dns = []
115 all_groups = self.getGroups()
116 all_roles = self.valid_roles()
117 groups = {}
118 for g in all_groups:
119 groups[g[0]] = g[1]
120
121 # LDAPUF does the mistake of adding possibly invalid roles to the user roles
122 # (for example, adding the cn of a group additionnaly to the mapped zope role).
123 # So we must remove from our 'roles' list all roles which are prefixed by group prefix
124 # but are not actually groups.
125 # See http://www.dataflake.org/tracker/issue_00376 for more information on that
126 # particular issue.
127 # If a group has the same name as a role, we assume that it should be a _role_.
128 # We should check against group/role mapping here, but... well... XXX TODO !
129 # See "HERE IT IS" comment below.
130
131 # Scan roles we are asking for to manage groups correctly
132 for role in roles:
133 if not role in all_roles:
134 continue # Do not allow propagation of invalid roles
135 if role.startswith(GROUP_PREFIX):
136 role = role[GROUP_PREFIX_LEN:] # Remove group prefix : groups are stored WITHOUT prefix in LDAP
137 if role in all_roles:
138 continue # HERE IT IS
139 r = groups.get(role, None)
140 if not r:
141 Log(LOG_WARNING, "LDAP Server doesn't provide a '%s' group (required for user '%s')." % (role, name, ))
142 else:
143 role_dns.append(r)
144
145 return role_dns
146
147
148 def _doChangeUser(self, name, password, roles, domains, **kw):
149 """
150 Update a user
151 """
152 # Find the dn at first
153 dn = self._find_user_dn(name)
154
155 # Change password
156 if password is not None:
157 if password == '':
158 raise ValueError, "Password must not be empty for LDAP users."
159 self.manage_editUserPassword(dn, password)
160
161 # Perform role change
162 self.manage_editUserRoles(dn, self._mangleRoles(name, roles))
163
164 # (No domain management with LDAP.)
165
166
167 def manage_editGroupRoles(self, user_dn, role_dns=[], REQUEST=None):
168 """ Edit the roles (groups) of a group """
169 from Products.LDAPUserFolder.utils import GROUP_MEMBER_MAP
170 try:
171 from Products.LDAPUserFolder.LDAPDelegate import ADD, DELETE
172 except ImportError:
173 # Support for LDAPUserFolder >= 2.6
174 ADD = self._delegate.ADD
175 DELETE = self._delegate.DELETE
176
177 msg = ""
178
179 ## Log(LOG_DEBUG, "assigning", role_dns, "to", user_dn)
180 all_groups = self.getGroups(attr='dn')
181 cur_groups = self.getGroups(dn=user_dn, attr='dn')
182 group_dns = []
183 for group in role_dns:
184 if group.find('=') == -1:
185 group_dns.append('cn=%s,%s' % (group, self.groups_base))
186 else:
187 group_dns.append(group)
188
189 if self._local_groups:
190 if len(role_dns) == 0:
191 del self._groups_store[user_dn]
192 else:
193 self._groups_store[user_dn] = role_dns
194
195 else:
196 for group in all_groups:
197 member_attr = GROUP_MEMBER_MAP.get(self.getGroupType(group))
198
199 if group in cur_groups and group not in group_dns:
200 action = DELETE
201 elif group in group_dns and group not in cur_groups:
202 action = ADD
203 else:
204 action = None
205 if action is not None:
206 msg = self._delegate.modify(
207 group
208 , action
209 , {member_attr : [user_dn]}
210 )
211 ## Log(LOG_DEBUG, "group", group, "subgroup", user_dn, "result", msg)
212
213 if msg:
214 raise RuntimeError, msg
215 manage_editGroupRoles = postonly(manage_editGroupRoles)