merge
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Wed, 05 Aug 2009 17:33:40 +0200
changeset 2704 09516a696636
parent 2700 ecf888c8a250 (diff)
parent 2703 27c04321fc81 (current diff)
child 2705 30bcdbd92820
merge
--- a/misc/migration/bootstrapmigration_repository.py	Wed Aug 05 12:15:48 2009 +0200
+++ b/misc/migration/bootstrapmigration_repository.py	Wed Aug 05 17:33:40 2009 +0200
@@ -21,11 +21,10 @@
     # use an internal session since some entity might forbid modifications to admin
     isession = repo.internal_session()
     for eid, in rql('Any X', ask_confirm=False):
-        try:
+        type, source, extid = session.describe(eid)
+        if source == 'system':
             isession.execute('SET X cwuri %(u)s WHERE X eid %(x)s',
                              {'x': eid, 'u': base_url + u'eid/%s' % eid})
-        except RepositoryError:
-            print 'unable to set cwuri for', eid, session.describe(eid)
     isession.commit()
     repo.hm.register_hook(uniquecstrcheck_before_modification, 'before_add_entity', '')
     repo.hm.register_hook(uniquecstrcheck_before_modification, 'before_update_entity', '')
--- a/server/hooks.py	Wed Aug 05 12:15:48 2009 +0200
+++ b/server/hooks.py	Wed Aug 05 17:33:40 2009 +0200
@@ -536,6 +536,9 @@
 
 
 def after_update_eproperty(session, entity):
+    if not ('pkey' in entity.edited_attributes or
+            'value' in entity.edited_attributes):
+        return
     key, value = entity.pkey, entity.value
     try:
         value = session.vreg.typed_value(key, value)
--- a/server/migractions.py	Wed Aug 05 12:15:48 2009 +0200
+++ b/server/migractions.py	Wed Aug 05 17:33:40 2009 +0200
@@ -694,6 +694,22 @@
             self.commit()
             self.rqlexecall(ss.rdef2rql(rschema),
                             ask_confirm=self.verbosity>=2)
+            if rtype in META_RTYPES:
+                # if the relation is in META_RTYPES, ensure we're adding it for
+                # all entity types *in the persistent schema*, not only those in
+                # the fs schema
+                for etype in self.repo.schema.entities():
+                    if not etype in self.fs_schema:
+                        # get sample object type and rproperties
+                        objtypes = rschema.objects()
+                        assert len(objtypes) == 1
+                        objtype = objtypes[0]
+                        props = rschema.rproperties(
+                            rschema.subjects(objtype)[0], objtype)
+                        assert props
+                        self.rqlexecall(ss.rdef2rql(rschema, etype, objtype, props),
+                                        ask_confirm=self.verbosity>=2)
+
         if commit:
             self.commit()
 
--- a/server/schemahooks.py	Wed Aug 05 12:15:48 2009 +0200
+++ b/server/schemahooks.py	Wed Aug 05 17:33:40 2009 +0200
@@ -446,26 +446,26 @@
             return
         subjtype, rtype, objtype = session.schema.schema_by_eid(rdef.eid)
         cstrtype = self.entity.type
-        cstr = rtype.constraint_by_type(subjtype, objtype, cstrtype)
-        prevcstr = CONSTRAINTS[cstrtype].deserialize(self.entity.value)
+        oldcstr = rtype.constraint_by_type(subjtype, objtype, cstrtype)
+        newcstr = CONSTRAINTS[cstrtype].deserialize(self.entity.value)
         table = SQL_PREFIX + str(subjtype)
         column = SQL_PREFIX + str(rtype)
         # alter the physical schema on size constraint changes
-        if prevcstr.type() == 'SizeConstraint' and (
-            cstr is None or cstr.max != prevcstr.max):
+        if newcstr.type() == 'SizeConstraint' and (
+            oldcstr is None or oldcstr.max != newcstr.max):
             adbh = self.session.pool.source('system').dbhelper
             card = rtype.rproperty(subjtype, objtype, 'cardinality')
-            coltype = type_from_constraints(adbh, objtype, [prevcstr],
+            coltype = type_from_constraints(adbh, objtype, [newcstr],
                                             creating=False)
             sql = adbh.sql_change_col_type(table, column, coltype, card != '1')
             try:
                 session.system_sql(sql, rollback_on_failure=False)
                 self.info('altered column %s of table %s: now VARCHAR(%s)',
-                          column, table, prevcstr.max)
+                          column, table, newcstr.max)
             except Exception, ex:
                 # not supported by sqlite for instance
                 self.error('error while altering table %s: %s', table, ex)
-        elif cstrtype == 'UniqueConstraint':
+        elif cstrtype == 'UniqueConstraint' and oldcstr is None:
             session.pool.source('system').create_index(
                 self.session, table, column, unique=True)
 
@@ -598,8 +598,8 @@
         self.prepare_constraints(subjtype, rtype, objtype)
         cstrtype = self.entity.type
         self.cstr = rtype.constraint_by_type(subjtype, objtype, cstrtype)
-        self.prevcstr = CONSTRAINTS[cstrtype].deserialize(self.entity.value)
-        self.prevcstr.eid = self.entity.eid
+        self.newcstr = CONSTRAINTS[cstrtype].deserialize(self.entity.value)
+        self.newcstr.eid = self.entity.eid
 
     def commit_event(self):
         if self.cancelled:
@@ -607,7 +607,7 @@
         # in-place modification
         if not self.cstr is None:
             self.constraints.remove(self.cstr)
-        self.constraints.append(self.prevcstr)
+        self.constraints.append(self.newcstr)
 
 
 class MemSchemaCWConstraintDel(MemSchemaOperation):
--- a/server/sources/ldapuser.py	Wed Aug 05 12:15:48 2009 +0200
+++ b/server/sources/ldapuser.py	Wed Aug 05 17:33:40 2009 +0200
@@ -41,35 +41,61 @@
 ONELEVEL = ldap.SCOPE_ONELEVEL
 SUBTREE = ldap.SCOPE_SUBTREE
 
-# XXX only for edition ??
-## password encryption possibilities
-#ENCRYPTIONS = ('SHA', 'CRYPT', 'MD5', 'CLEAR') # , 'SSHA'
-
-# mode identifier : (port, protocol)
-MODES = {
-    0: (389, 'ldap'),
-    1: (636, 'ldaps'),
-    2: (0,   'ldapi'),
-    }
+# map ldap protocol to their standard port
+PROTO_PORT = {'ldap': 389,
+              'ldaps': 636,
+              'ldapi': None,
+              }
 
 
 class LDAPUserSource(AbstractSource):
     """LDAP read-only CWUser source"""
     support_entities = {'CWUser': False}
 
-    port = None
-
-    cnx_mode = 0
-    cnx_dn = ''
-    cnx_pwd = ''
-
     options = (
         ('host',
          {'type' : 'string',
           'default': 'ldap',
-          'help': 'ldap host',
+          'help': 'ldap host. It may contains port information using \
+<host>:<port> notation.',
+          'group': 'ldap-source', 'inputlevel': 1,
+          }),
+        ('protocol',
+         {'type' : 'choice',
+          'default': 'ldap',
+          'choices': ('ldap', 'ldaps', 'ldapi'),
+          'help': 'ldap protocol',
+          'group': 'ldap-source', 'inputlevel': 1,
+          }),
+
+        ('auth-mode',
+         {'type' : 'choice',
+          'default': 'simple',
+          'choices': ('simple', 'cram_md5', 'digest_md5', 'gssapi'),
+          'help': 'authentication mode used to authenticate user to the ldap.',
           'group': 'ldap-source', 'inputlevel': 1,
           }),
+        ('auth-realm',
+         {'type' : 'string',
+          'default': None,
+          'help': 'realm to use when using gssapp/kerberos authentication.',
+          'group': 'ldap-source', 'inputlevel': 1,
+          }),
+
+        ('data-cnx-dn',
+         {'type' : 'string',
+          'default': '',
+          'help': 'user dn to use to open data connection to the ldap (eg used \
+to respond to rql queries).',
+          'group': 'ldap-source', 'inputlevel': 1,
+          }),
+        ('data-cnx-password',
+         {'type' : 'string',
+          'default': '',
+          'help': 'password to use to open data connection to the ldap (eg used to respond to rql queries).',
+          'group': 'ldap-source', 'inputlevel': 1,
+          }),
+
         ('user-base-dn',
          {'type' : 'string',
           'default': 'ou=People,dc=logilab,dc=fr',
@@ -129,6 +155,12 @@
         AbstractSource.__init__(self, repo, appschema, source_config,
                                 *args, **kwargs)
         self.host = source_config['host']
+        self.protocol = source_config.get('protocol', 'ldap')
+        self.authmode = source_config.get('auth-mode', 'simple')
+        self._authenticate = getattr(self, '_auth_%s' % self.authmode)
+        self.cnx_dn = source_config.get('data-cnx-dn') or ''
+        self.cnx_pwd = source_config.get('data-cnx-password') or ''
+        self.user_base_scope = globals()[source_config['user-scope']]
         self.user_base_dn = source_config['user-base-dn']
         self.user_base_scope = globals()[source_config['user-scope']]
         self.user_classes = splitstrip(source_config['user-classes'])
@@ -225,8 +257,9 @@
             raise AuthenticationError()
         # check password by establishing a (unused) connection
         try:
-            self._connect(user['dn'], password)
-        except:
+            self._connect(user, password)
+        except Exception, ex:
+            self.info('while trying to authenticate %s: %s', user, ex)
             # Something went wrong, most likely bad credentials
             raise AuthenticationError()
         return self.extid2eid(user['dn'], 'CWUser', session)
@@ -368,15 +401,16 @@
         return result
 
 
-    def _connect(self, userdn=None, userpwd=None):
-        port, protocol = MODES[self.cnx_mode]
-        if protocol == 'ldapi':
+    def _connect(self, user=None, userpwd=None):
+        if self.protocol == 'ldapi':
             hostport = self.host
+        elif not ':' in self.host:
+            hostport = '%s:%s' % (self.host, PROTO_PORT[self.protocol])
         else:
-            hostport = '%s:%s' % (self.host, self.port or port)
-        self.info('connecting %s://%s as %s', protocol, hostport,
-                  userdn or 'anonymous')
-        url = LDAPUrl(urlscheme=protocol, hostport=hostport)
+            hostport = self.host
+        self.info('connecting %s://%s as %s', self.protocol, hostport,
+                  user and user['dn'] or 'anonymous')
+        url = LDAPUrl(urlscheme=self.protocol, hostport=hostport)
         conn = ReconnectLDAPObject(url.initializeUrl())
         # Set the protocol version - version 3 is preferred
         try:
@@ -391,14 +425,42 @@
         #conn.set_option(ldap.OPT_NETWORK_TIMEOUT, conn_timeout)
         #conn.timeout = op_timeout
         # Now bind with the credentials given. Let exceptions propagate out.
-        if userdn is None:
+        if user is None:
+            # no user specified, we want to initialize the 'data' connection,
             assert self._conn is None
             self._conn = conn
-            userdn = self.cnx_dn
-            userpwd = self.cnx_pwd
-        conn.simple_bind_s(userdn, userpwd)
+            # XXX always use simple bind for data connection
+            if not self.cnx_dn:
+                conn.simple_bind_s(self.cnx_dn, self.cnx_pwd)
+            else:
+                self._authenticate(conn, {'dn': self.cnx_dn}, self.cnx_pwd)
+        else:
+            # user specified, we want to check user/password, no need to return
+            # the connection which will be thrown out
+            self._authenticate(conn, user, userpwd)
         return conn
 
+    def _auth_simple(self, conn, user, userpwd):
+        conn.simple_bind_s(user['dn'], userpwd)
+
+    def _auth_cram_md5(self, conn, user, userpwd):
+        from ldap import sasl
+        auth_token = sasl.cram_md5(user['dn'], userpwd)
+        conn.sasl_interactive_bind_s("", auth_tokens)
+
+    def _auth_digest_md5(self, conn, user, userpwd):
+        from ldap import sasl
+        auth_token = sasl.digest_md5(user['dn'], userpwd)
+        conn.sasl_interactive_bind_s("", auth_tokens)
+
+    def _auth_gssapi(self, conn, user, userpwd):
+        # print XXX not proper sasl/gssapi
+        from ldap import sasl
+        import kerberos
+        if not kerberos.checkPassword(user[self.user_login_attr], userpwd):
+            raise Exception('BAD login / mdp')
+        #conn.sasl_interactive_bind_s("", auth_tokens)
+
     def _search(self, session, base, scope,
                 searchstr='(objectClass=*)', attrs=()):
         """make an ldap query"""
--- a/web/action.py	Wed Aug 05 12:15:48 2009 +0200
+++ b/web/action.py	Wed Aug 05 17:33:40 2009 +0200
@@ -6,6 +6,7 @@
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
+_ = unicode
 
 from cubicweb import target
 from cubicweb.selectors import (partial_relation_possible, match_search_state,
@@ -13,8 +14,6 @@
                                 accepts_compat, condition_compat, deprecate)
 from cubicweb.appobject import AppObject
 
-_ = unicode
-
 
 class Action(AppObject):
     """abstract action. Handle the .search_states attribute to match