Mercurial > hg > savane-forge
changeset 119:a34e97e27050
LDIF export: more efficient (direct MySQL access) and cleaner (queries in the models)
author | Sylvain Beucler <beuc@beuc.net> |
---|---|
date | Sun, 02 Aug 2009 23:38:26 +0200 |
parents | cd5e4c45265b |
children | 8d4b08714c90 |
files | TODO src/savane/backend/auth_ldif_export.py src/savane/svmain/models.py |
diffstat | 3 files changed, 152 insertions(+), 57 deletions(-) [+] |
line wrap: on
line diff
--- a/TODO +++ b/TODO @@ -1,7 +1,6 @@ - models - - add DB indexes (db_model=True - but is that possible for - auth_user.username? :/) + - add DB indexes (db_model=True) - now we need the screens for users to modify them
--- a/src/savane/backend/auth_ldif_export.py +++ b/src/savane/backend/auth_ldif_export.py @@ -17,7 +17,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # Recommended indexes: -# index uid,sn,uidNumber,gidNumber,memberUid,shadowExpire eq +# index uid,uidNumber,gidNumber,memberUid,shadowExpire eq # TODO: most settings are hard-coded and need to be made configurable # - base: dc=savannah,dc=gnu,dc=org @@ -27,10 +27,12 @@ # - min uid: 1000 # - min gid: 1000 # - default group: cn=svusers / gid=1000 +# - loginShell: /usr/local/bin/sv_membersh import sys import codecs import base64, binascii +from django.db import connection, models import savane.svmain.models as svmain_models # Convert stdout to UTF-8 - if the stdout is redirected to a file @@ -71,21 +73,51 @@ structuralObjectClass: organizationalRole """ -#count = svmain_models.ExtendedUser.objects.count() -#print str(count) + " users in the database." +import MySQLdb, settings +MySQLdb.charset = 'UTF-8' +conn = MySQLdb.connect(user=settings.DATABASE_USER, + passwd=settings.DATABASE_PASSWORD, + db=settings.DATABASE_NAME, + use_unicode=True) + +# Alternatively: +#from django.db import connection +#connection.cursor() # establish connection - well looks like it does +#conn = connection.connection # MySQL-specific connection - now using mysqldb + + +## +# Users +## + +max_uid = svmain_models.ExtendedUser.objects.all().aggregate(models.Max('uidNumber'))['uidNumber__max'] +if max_uid < 1000: max_uid = 1000 -uidNumber=1000 -for user in svmain_models.ExtendedUser.objects.only('username', 'first_name', 'last_name', 'email', - 'password', 'uidNumber', 'gidNumber'): - uidNumber=uidNumber+1 - ##if uidNumber == 0: # either non-assigned, or mistakenly assigned to root - #if uidNumber < 1000: # either non-assigned, or mistakenly assigned to privileged user - # uidn = UidNumber() - # uidn.save() - # user.uidNumber = uidn - # user.save() +users_with_group = {} +group_users = {} +svmain_models.Membership.query_active_memberships_raw(conn, ('group_id', 'username')) +res = conn.store_result() +for row in res.fetch_row(maxrows=0, how=1): + users_with_group[row['username']] = 1 + if group_users.has_key(row['group_id']): + group_users[row['group_id']].append(row['username']) + else: + group_users[row['group_id']] = [row['username'],] - cleanup = [user.first_name, user.last_name, user.email] +user_saves = [] +svmain_models.ExtendedUser.query_active_users_raw(conn, ('username', 'first_name', 'last_name', 'email', + 'password', 'uidNumber', 'gidNumber')) +res = conn.store_result() +for row in res.fetch_row(maxrows=0): + (username, first_name, last_name, email, password, uidNumber, gidNumber) = row + + #if uidNumber == 0: # either non-assigned, or mistakenly assigned to root + if uidNumber < 1000: # either non-assigned, or mistakenly assigned to privileged user + max_uid = max_uid + 1 + user_saves.append((username, max_uid)) + uidNumber = max_uid + + cleanup = [first_name, last_name, email] for i in range(0, len(cleanup)): cleanup[i] = cleanup[i].replace('\n', ' ') cleanup[i] = cleanup[i].replace('\r', ' ') @@ -93,33 +125,34 @@ (first_name, last_name, email) = cleanup ldap_password = '{CRYPT}!' # default = unusable password - if user.password.startswith('sha1$'): - # Django-specific algorithm: it sums 5-char-salt+pass instead - # of SSHA's pass+4-bytes-salt, so we can't store it in LDAP - - # /me curses django devs - pass - elif user.password.startswith('md5$$'): - # MD5 without salt - algo, empty, hash_hex = user.password.split('$') - if (len(hash_hex) == 32): # filter out empty or disabled passwords - ldap_password = "{MD5}" + base64.b64encode(binascii.a2b_hex(hash_hex)) - elif user.password.startswith('md5$'): - # md5$salt$ vs. {SMD5} is similar to sha1$salt$ vs. {SSHA} - - # cf. above - pass - elif user.password.startswith('crypt$'): - # glibc crypt has improved algorithms, but where salt contains - # three '$'s, which Django doesn't support (since '$' is - # already the salt field separator). So this is only weak, - # passwd-style (not shadow-style) crypt. - algo, salt_hex, hash_hex = user.password.split('$') - # salt_hex is 2-chars long and already prepended to hash_hex - ldap_password = "{CRYPT}" + base64.b64encode(binascii.a2b_hex(hash_hex)) - elif '$' not in user.password: - # MD5 without salt, alternate Django syntax - hash_hex = user.password - if (len(hash_hex) == 32): # filter out empty or disabled passwords - ldap_password = "{MD5}" + base64.b64encode(binascii.a2b_hex(hash_hex)) + if users_with_group.has_key(username): + if password.startswith('sha1$'): + # Django-specific algorithm: it sums 5-char-salt+pass instead + # of SSHA's pass+4-bytes-salt, so we can't store it in LDAP - + # /me curses django devs + pass + elif password.startswith('md5$$'): + # MD5 without salt + algo, empty, hash_hex = password.split('$') + if (len(hash_hex) == 32): # filter out empty or disabled passwords + ldap_password = "{MD5}" + base64.b64encode(binascii.a2b_hex(hash_hex)) + elif password.startswith('md5$'): + # md5$salt$ vs. {SMD5} is similar to sha1$salt$ vs. {SSHA} - + # cf. above + pass + elif password.startswith('crypt$'): + # glibc crypt has improved algorithms, but where salt contains + # three '$'s, which Django doesn't support (since '$' is + # already the salt field separator). So this is only weak, + # passwd-style (not shadow-style) crypt. + algo, salt_hex, hash_hex = password.split('$') + # salt_hex is 2-chars long and already prepended to hash_hex + ldap_password = "{CRYPT}" + base64.b64encode(binascii.a2b_hex(hash_hex)) + elif '$' not in password: + # MD5 without salt, alternate Django syntax + hash_hex = password + if (len(hash_hex) == 32): # filter out empty or disabled passwords + ldap_password = "{MD5}" + base64.b64encode(binascii.a2b_hex(hash_hex)) # Object classes: # - posixAccount: base class for libnss-ldap/pam-ldap support @@ -135,28 +168,38 @@ uidNumber: %(uidNumber)d gidNumber: %(gidNumber)d homeDirectory: %(homedir)s +loginShell: /usr/local/bin/sv_membersh objectClass: shadowAccount objectClass: posixAccount objectClass: inetOrgPerson objectClass: top structuralObjectClass: inetOrgPerson""" % { - 'username' : user.username, - 'full_name' : base64.b64encode((first_name + u' ' + last_name).encode('UTF-8')), - 'last_name' : base64.b64encode((last_name or u'-').encode('UTF-8')), + 'username' : username, + 'full_name' : base64.b64encode((first_name + ' ' + last_name).encode('UTF-8')), + 'last_name' : base64.b64encode((last_name or '-').encode('UTF-8')), 'email' : email, 'ldap_password' : ldap_password, 'uidNumber' : uidNumber, 'gidNumber' : 1000, - 'homedir' : u'/home/' + user.username[:1] + u'/' + user.username[:2] + u'/' + user.username, + 'homedir' : '/home/' + username[:1] + '/' + username[:2] + '/' + username, } # non-mandatory fields - slapadd doesn't accept empty fields apparently if len(first_name) > 0: print "givenName::" + base64.b64encode(first_name.encode('UTF-8')) # disallow login for users that are not part of any group - #if user.extendedgroup_set.count() == 0: - # print "shadowExpire: 10" # timestamp - avoid 0 as it may be - # # interpreted at 'no expiration' + if not users_with_group.has_key(username): + # shadowExpire is a timestamp - avoid 0 as it may be + # interpreted as 'no expiration' + print "shadowExpire: 10" +## +# Groups +## + +max_gid = svmain_models.ExtendedGroup.objects.all().aggregate(models.Max('gidNumber'))['gidNumber__max'] +if max_gid < 1000: max_gid = 1000 + +# Create base 'svusers' group print u""" dn: cn=svusers,ou=groups,dc=savannah,dc=gnu,dc=org cn: svusers @@ -164,9 +207,20 @@ objectClass: posixGroup objectClass: top structuralObjectClass: posixGroup""" -i=1000 -for group in svmain_models.ExtendedGroup.objects.only('name'): - i=i+1 + +# Dump groups +group_saves = [] +svmain_models.ExtendedGroup.query_active_groups_raw(conn, ('group_id', 'name', 'gidNumber')) +res = conn.store_result() +#for group in svmain_models.ExtendedGroup.objects.only('name'): +for row in res.fetch_row(maxrows=0): + (group_id, name, gidNumber) = row + + if gidNumber < 1000: # either non-assigned, or mistakenly assigned to privileged user + max_gid = max_gid + 1 + group_saves.append((group_id, max_gid)) + gidNumber = max_gid + print u""" dn: cn=%(name)s,ou=groups,dc=savannah,dc=gnu,dc=org cn: %(name)s @@ -174,8 +228,14 @@ objectClass: posixGroup objectClass: top structuralObjectClass: posixGroup""" % { - 'name' : group.name, - 'gidNumber' : i, + 'name' : name, + 'gidNumber' : gidNumber, } - for user in group.extendeduser_set.only('username'): - print "memberUid: " + user.username + if group_users.has_key(group_id): + for username in group_users[group_id]: + print "memberUid: " + username + +# TODO +# - user_saves +# - group_saves +# with multi-line UPDATEs
--- a/src/savane/svmain/models.py +++ b/src/savane/svmain/models.py @@ -106,6 +106,18 @@ # Inherit specialized models.Manager with convenience functions objects = auth_models.UserManager() + @staticmethod + def query_active_users_raw(conn, fields): + """ + Return efficient query with all the users; used by LDIF export + """ + return conn.query("SELECT " + + ",".join(fields) + + " FROM auth_user JOIN svmain_extendeduser" + + " ON auth_user.id = svmain_extendeduser.user_ptr_id" + + " WHERE status = 'A'" + ) + @models.permalink def get_absolute_url(self): return ('savane.svmain.user_detail', [self.username]) @@ -463,6 +475,18 @@ #patch_private_exclude_address text #cookbook_private_exclude_address text + @staticmethod + def query_active_groups_raw(conn, fields): + """ + Return efficient query with all the users; used by LDIF export + """ + return conn.query("SELECT " + + ",".join(fields) + + " FROM auth_group JOIN svmain_extendedgroup" + + " ON auth_group.id = svmain_extendedgroup.group_ptr_id" + + " WHERE status = 'A'" + ) + def __unicode__(self): return self.name @@ -508,6 +532,18 @@ # Deprecated #forum_flags int(11) default NULL + @staticmethod + def query_active_memberships_raw(conn): + """ + Return efficient query with all the users; used by LDIF export + """ + return conn.query("SELECT " + + ",".join(fields) + + " FROM svmain_membership JOIN auth_user" + + " ON user_id = auth_user.id" + + " WHERE admin_flags<>'P'" + ) + def __unicode__(self): return "[%s is a member of %s]" % (self.user.username, self.group.name)