dataproxy.py
1 |
# -*- coding: UTF-8 -*-
|
---|---|
2 |
###########################################################################
|
3 |
# Eole NG - 2007
|
4 |
# Copyright Pole de Competence Eole (Ministere Education - Academie Dijon)
|
5 |
# Licence CeCill cf /root/LicenceEole.txt
|
6 |
# eole@ac-dijon.fr
|
7 |
#
|
8 |
# dataproxy.py
|
9 |
#
|
10 |
# classes permettant de vérifier l'authentification sur différents supports
|
11 |
# (actuellement seulement annuaire LDAP)
|
12 |
#
|
13 |
###########################################################################
|
14 |
|
15 |
# utile ?
|
16 |
class IAuthProxy: |
17 |
"""Interface pour un système d'authentification et de récupération des infos utilisateurs.
|
18 |
Mettre les éventuels commandes de connextion, ... dans __init__.py"""
|
19 |
|
20 |
def get_data(self, identifier, identifier_field): |
21 |
"""Récupère les données correspondant à identifier
|
22 |
|
23 |
@param identifier: à priori l'identifiant d'un utilisateur dans le système de données
|
24 |
"""
|
25 |
pass
|
26 |
|
27 |
def authenticate(self, login, password): |
28 |
"""Vérifie la validité d'un couple login/password
|
29 |
|
30 |
@param login: identifiant de l'utilisateur dans ce système
|
31 |
@param password: mot de passe
|
32 |
|
33 |
retourne True ou False
|
34 |
"""
|
35 |
pass
|
36 |
|
37 |
from eoleldaptor import eoleldapproxy |
38 |
from twisted.python import log, failure |
39 |
from util import get_replication_branches |
40 |
from scribe.eoleldap import Ldap |
41 |
from scribe.linker import _user_factory |
42 |
import os, socket |
43 |
|
44 |
class LDAPProxy: |
45 |
"""récupère les infos d'un utilisateur dans un annuaire LDAP"""
|
46 |
__implements__ = (IAuthProxy,) |
47 |
|
48 |
ignored_attrs = ['userPassword', 'sambaLMPassword', 'sambaNTPassword'] |
49 |
service_name = 'ldap'
|
50 |
group_attrs = ['sambaGroupType', 'displayName', 'cn', 'objectClass', 'gidNumber', 'mail', 'description', 'niveau'] |
51 |
|
52 |
def __init__(self, hosts, ports, bases, ldap_labels, ldap_infos=[], readers=[], pass_files=[], login_otp=[], static_data={}, match_attributes=[], strict_base=True): |
53 |
"""
|
54 |
hosts : liste de serveurs ldap (ou un seul)
|
55 |
ports : liste des ports correspondants
|
56 |
"""
|
57 |
# on attend des listes de valeur (de même taille) pour hosts/ports/bases/readers/pass_files
|
58 |
self.ldap_servers = []
|
59 |
self.otp_config = {}
|
60 |
if type(hosts) is not list: |
61 |
hosts = [hosts] |
62 |
if type(ports) is not list: |
63 |
ports = [ports] |
64 |
if type(bases) is not list: |
65 |
bases = [bases] |
66 |
if type(ldap_infos) is not list: |
67 |
ldap_infos = [ldap_infos] |
68 |
if type(ldap_labels) is not list: |
69 |
ldap_labels = [ldap_labels] |
70 |
if type(readers) is not list: |
71 |
readers = [readers] |
72 |
if type(pass_files) is not list: |
73 |
pass_files = [pass_files] |
74 |
if type(login_otp) is not list: |
75 |
login_otp = [login_otp] |
76 |
if type(match_attributes) is not list: |
77 |
match_attributes = [match_attributes] |
78 |
# si ancienne configuration, on met les valeurs uniques dans une liste
|
79 |
self.static_data = static_data
|
80 |
self.strict_base = strict_base
|
81 |
# initialisation des proxies ldap
|
82 |
self.use_branches = False |
83 |
self.search_branches = {}
|
84 |
self.ldap_infos = {}
|
85 |
nb_branches = 0
|
86 |
for host, port, base, ldap_label, ldap_infos, reader, pass_file, match_attribute, login_otp \
|
87 |
in zip(hosts, ports, bases, ldap_labels, ldap_infos, readers, pass_files, match_attributes, login_otp): |
88 |
search_branches = {} |
89 |
# recherche des branches de recherche des utilisateurs dans les annuaires disponibles
|
90 |
# la branche par défaut est la base de recherche renseignée dans la configuration
|
91 |
if host in ['localhost', '127.0.0.1']: |
92 |
# branches de recherche dans le cas d'un annuaire local répliqué
|
93 |
repl_branches = get_replication_branches() |
94 |
if repl_branches:
|
95 |
for dn, label in repl_branches.items(): |
96 |
search_branches[dn] = label |
97 |
nb_branches +=1
|
98 |
else:
|
99 |
search_branches[base] = ldap_label |
100 |
nb_branches +=1
|
101 |
else:
|
102 |
# XXX FIXME: conserver la branche de base même en cas d'annuaires répliqués ?
|
103 |
# si libellé non rempli, on en crée un automatiquement
|
104 |
if ldap_label == "": |
105 |
try:
|
106 |
ldap_label = "Annuaire de %s" % socket.gethostbyaddr(host)[0] |
107 |
except:
|
108 |
ldap_label = "Annuaire de %s" % host
|
109 |
#search_branches['default'] = ldap_label
|
110 |
search_branches[base] = ldap_label |
111 |
nb_branches +=1
|
112 |
if reader != "" and os.path.isfile(pass_file): |
113 |
reader_dn = reader |
114 |
reader_pass = passwd = open(pass_file).read().strip()
|
115 |
else:
|
116 |
reader_dn = reader_pass = None
|
117 |
eole_proxy = eoleldapproxy.EoleLdapProxy(base, host, port, reader_dn, reader_pass, match_attribute) |
118 |
self.ldap_servers.append((host, eole_proxy, (reader, pass_file), match_attribute))
|
119 |
self.search_branches[host] = search_branches
|
120 |
self.ldap_infos[host] = ldap_infos
|
121 |
self.otp_config[host] = login_otp
|
122 |
#log.msg("Eole proxy %s" % eole_proxy)
|
123 |
if nb_branches > 1: |
124 |
self.use_branches = True |
125 |
|
126 |
def check_otp_config(self, search_branch): |
127 |
"""retourne le mode gestion de l'identifiant OTP pour un annuaire particulier
|
128 |
inactif : OTP non géré pour cet annuaire
|
129 |
identique : login OTP identique au login LDAP
|
130 |
configurable : login OTP déclaré par l'utilisateur
|
131 |
"""
|
132 |
if search_branch == "default": |
133 |
host = self.ldap_servers[0][0] |
134 |
else:
|
135 |
host = search_branch.split(':', 1)[0] |
136 |
return self.otp_config.get(host, 'inactifs') |
137 |
|
138 |
def get_search_branch(self, attributes, attr_set): |
139 |
# on regarde si les attributs permettent de limiter la recherche à une branche
|
140 |
# (peut résoudre les problèmes de doublons dans le cas d'un annuaire multi établissements)
|
141 |
# lancement de l'authentification sur le premier proxy de la liste
|
142 |
search_exprs = [] |
143 |
search_base_attrs = attr_set['branch_attrs']
|
144 |
# search_expr correspond aux expressions à rechercher dans le dn des branches connues
|
145 |
for attr_name, attr_value in attributes.items(): |
146 |
if attr_name in search_base_attrs: |
147 |
search_exprs.append('%s=%s' % (search_base_attrs[attr_name], attr_value[0])) |
148 |
if search_exprs:
|
149 |
for proxy_data in self.ldap_servers: |
150 |
s_branches = self.search_branches[proxy_data[0]] |
151 |
for s_branch in s_branches.keys(): |
152 |
branch_ok = True
|
153 |
for expr in search_exprs: |
154 |
if expr not in s_branch: |
155 |
branch_ok = False
|
156 |
break
|
157 |
if branch_ok:
|
158 |
# toutes les valeurs demandées correspondent
|
159 |
return '%s:%s' % (proxy_data[0], s_branch) |
160 |
if self.strict_base: |
161 |
# mode strict : si au moins un attribut de recherche de
|
162 |
# branche est donné, on n'utilise pas la branche de
|
163 |
# recherche par défaut si aucune ne correspond
|
164 |
return None |
165 |
return 'default' |
166 |
# del(attributes[attr_name])
|
167 |
# return s_branch, attributes
|
168 |
#return 'default', attributes
|
169 |
|
170 |
def get_user_branches(self, user_id, branches=None, servers=None): |
171 |
"""
|
172 |
retourne les branches et libellés d'établissement dans lesquels un login donné est présent
|
173 |
"""
|
174 |
# on fait une recherche anonyme sur la racine pour récupérer
|
175 |
# tous les dn correspondants à user_id
|
176 |
if servers is None: |
177 |
servers = self.ldap_servers
|
178 |
if branches is None: |
179 |
branches = [] |
180 |
host = servers[0][0] |
181 |
proxy = servers[0][1] |
182 |
defer_branches = proxy.get_user_dn(user_id, unicity=False)
|
183 |
return defer_branches.addBoth(self.callb_branches, user_id, branches, host, servers[1:]) |
184 |
|
185 |
def callb_branches(self, result_dn, user_id, branches, host, servers): |
186 |
if isinstance(result_dn, failure.Failure): |
187 |
log.msg(result_dn.getErrorMessage()) |
188 |
else:
|
189 |
# recherche des libellé etab correspondant à ces dn dans l'annuaire
|
190 |
for user_dn in result_dn: |
191 |
# on recherche la branche d'annuaire à laquelle ce dn appartient
|
192 |
for branche in self.search_branches[host]: |
193 |
# on force la casse en minuscule pour la recherche
|
194 |
# des branches correspondant au dn
|
195 |
if branche.lower() in str(user_dn).lower(): |
196 |
# pour chaque branche, on renvoie : index du serveur ldap, dn de la branche, libellé
|
197 |
branches.append(('%s:%s' % (host, branche), self.search_branches[host][branche])) |
198 |
break
|
199 |
if len(servers) > 0: |
200 |
return self.get_user_branches(user_id, branches, servers) |
201 |
return branches
|
202 |
|
203 |
def authenticate(self, user_id, passwd, search_branch='default', servers=None): |
204 |
"""
|
205 |
lancement de l'authentification sur le premier proxy de la liste
|
206 |
branch_attrs : attributs pour déterminer une branche de recherche dans l'annuaire (ex: rne)
|
207 |
servers : liste des serveurs LDAP sur lesquels essayer l'authentification
|
208 |
"""
|
209 |
if search_branch == 'default': |
210 |
branch = None
|
211 |
if servers is None: |
212 |
servers = self.ldap_servers
|
213 |
proxy = servers[0][1] |
214 |
else:
|
215 |
# on utilise directement le serveur spécifié avec la branche
|
216 |
host, branch = search_branch.split(':', 1) |
217 |
for server in self.ldap_servers: |
218 |
if server[0] == host: |
219 |
proxy = server[1]
|
220 |
servers = [] |
221 |
break
|
222 |
defer_auth = proxy.authenticate(user_id, passwd, branch) |
223 |
return defer_auth.addCallbacks(self.callb_auth, self.errb_auth, callbackArgs=[user_id, passwd, search_branch, servers[1:]]) |
224 |
|
225 |
def callb_auth(self, result_auth, user_id, passwd, search_branch, servers): |
226 |
success, user_data = result_auth |
227 |
|
228 |
if success == True and "localhost" not in servers: |
229 |
#log.msg("MAJ pwd vers localhost")
|
230 |
#recupere de ead2 backend/actions/scribe/userpwd.py
|
231 |
try:
|
232 |
conn = Ldap() |
233 |
conn.connect() |
234 |
ldapuser = _user_factory(user_id, conn.connexion) |
235 |
ldapuser.c_mod_password(user_id, passwd) |
236 |
conn.close() |
237 |
except:
|
238 |
pass
|
239 |
|
240 |
# echec de l'authentification sur ce serveur
|
241 |
if len(servers) > 0: |
242 |
# test sur le serveur suivant
|
243 |
return self.authenticate(user_id, passwd, search_branch, servers) |
244 |
|
245 |
if success == False : |
246 |
# on a essayé sur tous les serveurs
|
247 |
return False, {} |
248 |
|
249 |
# on supprime certains champs (mot de passe)
|
250 |
for user_attr in self.ignored_attrs: |
251 |
if user_attr in user_data: |
252 |
del(user_data[user_attr])
|
253 |
return success, user_data
|
254 |
|
255 |
def errb_auth(self, failure): |
256 |
log.msg(failure.getErrorMessage()) |
257 |
return False, {} |
258 |
|
259 |
def get_user_data(self, filter_attrs, search_branch='default', servers=None): |
260 |
"""recherche des données utilisateur locales depuis un ensemble d'attributs/valeurs à matcher.
|
261 |
utilisé dans le cadre de la fédération pour retrouver l'utilisateur local
|
262 |
"""
|
263 |
if search_branch == 'default': |
264 |
branch = None
|
265 |
if servers is None: |
266 |
servers = self.ldap_servers
|
267 |
proxy = servers[0][1] |
268 |
else:
|
269 |
# on utilise directement le serveur spécifié avec la branche
|
270 |
host, branch = search_branch.split(':', 1) |
271 |
for server in self.ldap_servers: |
272 |
if server[0] == host: |
273 |
proxy = server[1]
|
274 |
servers = [] |
275 |
break
|
276 |
defer_auth = proxy.get_user_data(filter_attrs, branch) |
277 |
return defer_auth.addCallbacks(self.callb_get_data, self.errb_auth, callbackArgs=[filter_attrs, search_branch, servers[1:]]) |
278 |
|
279 |
def callb_get_data(self, result_auth, filter_attrs, search_branch, servers): |
280 |
success, user_data = result_auth |
281 |
# vérification du résultat de l'authentification sur le serveur précédent
|
282 |
if success == False: |
283 |
# echec de l'authentification sur ce serveur
|
284 |
if len(servers) > 0: |
285 |
# test sur le serveur suivant
|
286 |
return self.get_user_data(filter_attrs, search_branch, servers) |
287 |
else:
|
288 |
# on a essayé sur tous les serveurs
|
289 |
return False, {} |
290 |
# on supprime certains champs (mot de passe)
|
291 |
for user_attr in self.ignored_attrs: |
292 |
if user_attr in user_data: |
293 |
del(user_data[user_attr])
|
294 |
return success, user_data
|