Projet

Général

Profil

dataproxy.py

Dataproxy version modifiée - Pascal MIETLICKI, 05/08/2015 06:29

Télécharger (13 ko)

 
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