server/sources/ldapuser.py
changeset 2699 1025300249d2
parent 2633 bc9386c3b2c9
child 2707 15ffc3c8923c
equal deleted inserted replaced
2698:a6ef9bec755e 2699:1025300249d2
    39 # search scopes
    39 # search scopes
    40 BASE = ldap.SCOPE_BASE
    40 BASE = ldap.SCOPE_BASE
    41 ONELEVEL = ldap.SCOPE_ONELEVEL
    41 ONELEVEL = ldap.SCOPE_ONELEVEL
    42 SUBTREE = ldap.SCOPE_SUBTREE
    42 SUBTREE = ldap.SCOPE_SUBTREE
    43 
    43 
    44 # XXX only for edition ??
    44 # map ldap protocol to their standard port
    45 ## password encryption possibilities
    45 PROTO_PORT = {'ldap': 389,
    46 #ENCRYPTIONS = ('SHA', 'CRYPT', 'MD5', 'CLEAR') # , 'SSHA'
    46               'ldaps': 636,
    47 
    47               'ldapi': None,
    48 # mode identifier : (port, protocol)
    48               }
    49 MODES = {
       
    50     0: (389, 'ldap'),
       
    51     1: (636, 'ldaps'),
       
    52     2: (0,   'ldapi'),
       
    53     }
       
    54 
    49 
    55 
    50 
    56 class LDAPUserSource(AbstractSource):
    51 class LDAPUserSource(AbstractSource):
    57     """LDAP read-only CWUser source"""
    52     """LDAP read-only CWUser source"""
    58     support_entities = {'CWUser': False}
    53     support_entities = {'CWUser': False}
    59 
       
    60     port = None
       
    61 
       
    62     cnx_mode = 0
       
    63     cnx_dn = ''
       
    64     cnx_pwd = ''
       
    65 
    54 
    66     options = (
    55     options = (
    67         ('host',
    56         ('host',
    68          {'type' : 'string',
    57          {'type' : 'string',
    69           'default': 'ldap',
    58           'default': 'ldap',
    70           'help': 'ldap host',
    59           'help': 'ldap host. It may contains port information using \
    71           'group': 'ldap-source', 'inputlevel': 1,
    60 <host>:<port> notation.',
    72           }),
    61           'group': 'ldap-source', 'inputlevel': 1,
       
    62           }),
       
    63         ('protocol',
       
    64          {'type' : 'choice',
       
    65           'default': 'ldap',
       
    66           'choices': ('ldap', 'ldaps', 'ldapi'),
       
    67           'help': 'ldap protocol',
       
    68           'group': 'ldap-source', 'inputlevel': 1,
       
    69           }),
       
    70 
       
    71         ('auth-mode',
       
    72          {'type' : 'choice',
       
    73           'default': 'simple',
       
    74           'choices': ('simple', 'cram_md5', 'digest_md5', 'gssapi'),
       
    75           'help': 'authentication mode used to authenticate user to the ldap.',
       
    76           'group': 'ldap-source', 'inputlevel': 1,
       
    77           }),
       
    78         ('auth-realm',
       
    79          {'type' : 'string',
       
    80           'default': None,
       
    81           'help': 'realm to use when using gssapp/kerberos authentication.',
       
    82           'group': 'ldap-source', 'inputlevel': 1,
       
    83           }),
       
    84 
       
    85         ('data-cnx-dn',
       
    86          {'type' : 'string',
       
    87           'default': '',
       
    88           'help': 'user dn to use to open data connection to the ldap (eg used \
       
    89 to respond to rql queries).',
       
    90           'group': 'ldap-source', 'inputlevel': 1,
       
    91           }),
       
    92         ('data-cnx-password',
       
    93          {'type' : 'string',
       
    94           'default': '',
       
    95           'help': 'password to use to open data connection to the ldap (eg used to respond to rql queries).',
       
    96           'group': 'ldap-source', 'inputlevel': 1,
       
    97           }),
       
    98 
    73         ('user-base-dn',
    99         ('user-base-dn',
    74          {'type' : 'string',
   100          {'type' : 'string',
    75           'default': 'ou=People,dc=logilab,dc=fr',
   101           'default': 'ou=People,dc=logilab,dc=fr',
    76           'help': 'base DN to lookup for users',
   102           'help': 'base DN to lookup for users',
    77           'group': 'ldap-source', 'inputlevel': 0,
   103           'group': 'ldap-source', 'inputlevel': 0,
   127 
   153 
   128     def __init__(self, repo, appschema, source_config, *args, **kwargs):
   154     def __init__(self, repo, appschema, source_config, *args, **kwargs):
   129         AbstractSource.__init__(self, repo, appschema, source_config,
   155         AbstractSource.__init__(self, repo, appschema, source_config,
   130                                 *args, **kwargs)
   156                                 *args, **kwargs)
   131         self.host = source_config['host']
   157         self.host = source_config['host']
       
   158         self.protocol = source_config.get('protocol', 'ldap')
       
   159         self.authmode = source_config.get('auth-mode', 'simple')
       
   160         self._authenticate = getattr(self, '_auth_%s' % self.authmode)
       
   161         self.cnx_dn = source_config.get('data-cnx-dn') or ''
       
   162         self.cnx_pwd = source_config.get('data-cnx-password') or ''
       
   163         self.user_base_scope = globals()[source_config['user-scope']]
   132         self.user_base_dn = source_config['user-base-dn']
   164         self.user_base_dn = source_config['user-base-dn']
   133         self.user_base_scope = globals()[source_config['user-scope']]
   165         self.user_base_scope = globals()[source_config['user-scope']]
   134         self.user_classes = splitstrip(source_config['user-classes'])
   166         self.user_classes = splitstrip(source_config['user-classes'])
   135         self.user_login_attr = source_config['user-login-attr']
   167         self.user_login_attr = source_config['user-login-attr']
   136         self.user_default_groups = splitstrip(source_config['user-default-group'])
   168         self.user_default_groups = splitstrip(source_config['user-default-group'])
   223         except IndexError:
   255         except IndexError:
   224             # no such user
   256             # no such user
   225             raise AuthenticationError()
   257             raise AuthenticationError()
   226         # check password by establishing a (unused) connection
   258         # check password by establishing a (unused) connection
   227         try:
   259         try:
   228             self._connect(user['dn'], password)
   260             self._connect(user, password)
   229         except:
   261         except Exception, ex:
       
   262             self.info('while trying to authenticate %s: %s', user, ex)
   230             # Something went wrong, most likely bad credentials
   263             # Something went wrong, most likely bad credentials
   231             raise AuthenticationError()
   264             raise AuthenticationError()
   232         return self.extid2eid(user['dn'], 'CWUser', session)
   265         return self.extid2eid(user['dn'], 'CWUser', session)
   233 
   266 
   234     def ldap_name(self, var):
   267     def ldap_name(self, var):
   366             result = trfunc.apply(result)
   399             result = trfunc.apply(result)
   367         #print '--> ldap result', result
   400         #print '--> ldap result', result
   368         return result
   401         return result
   369 
   402 
   370 
   403 
   371     def _connect(self, userdn=None, userpwd=None):
   404     def _connect(self, user=None, userpwd=None):
   372         port, protocol = MODES[self.cnx_mode]
   405         if self.protocol == 'ldapi':
   373         if protocol == 'ldapi':
       
   374             hostport = self.host
   406             hostport = self.host
       
   407         elif not ':' in self.host:
       
   408             hostport = '%s:%s' % (self.host, PROTO_PORT[self.protocol])
   375         else:
   409         else:
   376             hostport = '%s:%s' % (self.host, self.port or port)
   410             hostport = self.host
   377         self.info('connecting %s://%s as %s', protocol, hostport,
   411         self.info('connecting %s://%s as %s', self.protocol, hostport,
   378                   userdn or 'anonymous')
   412                   user and user['dn'] or 'anonymous')
   379         url = LDAPUrl(urlscheme=protocol, hostport=hostport)
   413         url = LDAPUrl(urlscheme=self.protocol, hostport=hostport)
   380         conn = ReconnectLDAPObject(url.initializeUrl())
   414         conn = ReconnectLDAPObject(url.initializeUrl())
   381         # Set the protocol version - version 3 is preferred
   415         # Set the protocol version - version 3 is preferred
   382         try:
   416         try:
   383             conn.set_option(ldap.OPT_PROTOCOL_VERSION, ldap.VERSION3)
   417             conn.set_option(ldap.OPT_PROTOCOL_VERSION, ldap.VERSION3)
   384         except ldap.LDAPError: # Invalid protocol version, fall back safely
   418         except ldap.LDAPError: # Invalid protocol version, fall back safely
   389         #except ldap.LDAPError: # Cannot set referrals, so do nothing
   423         #except ldap.LDAPError: # Cannot set referrals, so do nothing
   390         #    pass
   424         #    pass
   391         #conn.set_option(ldap.OPT_NETWORK_TIMEOUT, conn_timeout)
   425         #conn.set_option(ldap.OPT_NETWORK_TIMEOUT, conn_timeout)
   392         #conn.timeout = op_timeout
   426         #conn.timeout = op_timeout
   393         # Now bind with the credentials given. Let exceptions propagate out.
   427         # Now bind with the credentials given. Let exceptions propagate out.
   394         if userdn is None:
   428         if user is None:
       
   429             # no user specified, we want to initialize the 'data' connection,
   395             assert self._conn is None
   430             assert self._conn is None
   396             self._conn = conn
   431             self._conn = conn
   397             userdn = self.cnx_dn
   432             # XXX always use simple bind for data connection
   398             userpwd = self.cnx_pwd
   433             if not self.cnx_dn:
   399         conn.simple_bind_s(userdn, userpwd)
   434                 conn.simple_bind_s(self.cnx_dn, self.cnx_pwd)
       
   435             else:
       
   436                 self._authenticate(conn, {'dn': self.cnx_dn}, self.cnx_pwd)
       
   437         else:
       
   438             # user specified, we want to check user/password, no need to return
       
   439             # the connection which will be thrown out
       
   440             self._authenticate(conn, user, userpwd)
   400         return conn
   441         return conn
       
   442 
       
   443     def _auth_simple(self, conn, user, userpwd):
       
   444         conn.simple_bind_s(user['dn'], userpwd)
       
   445 
       
   446     def _auth_cram_md5(self, conn, user, userpwd):
       
   447         from ldap import sasl
       
   448         auth_token = sasl.cram_md5(user['dn'], userpwd)
       
   449         conn.sasl_interactive_bind_s("", auth_tokens)
       
   450 
       
   451     def _auth_digest_md5(self, conn, user, userpwd):
       
   452         from ldap import sasl
       
   453         auth_token = sasl.digest_md5(user['dn'], userpwd)
       
   454         conn.sasl_interactive_bind_s("", auth_tokens)
       
   455 
       
   456     def _auth_gssapi(self, conn, user, userpwd):
       
   457         # print XXX not proper sasl/gssapi
       
   458         from ldap import sasl
       
   459         import kerberos
       
   460         if not kerberos.checkPassword(user[self.user_login_attr], userpwd):
       
   461             raise Exception('BAD login / mdp')
       
   462         #conn.sasl_interactive_bind_s("", auth_tokens)
   401 
   463 
   402     def _search(self, session, base, scope,
   464     def _search(self, session, base, scope,
   403                 searchstr='(objectClass=*)', attrs=()):
   465                 searchstr='(objectClass=*)', attrs=()):
   404         """make an ldap query"""
   466         """make an ldap query"""
   405         cnx = session.pool.connection(self.uri).cnx
   467         cnx = session.pool.connection(self.uri).cnx