Created
March 20, 2018 16:02
-
-
Save 20esaua/e408d6076376b3a9d0f49ee24334a67b to your computer and use it in GitHub Desktop.
Script that Carlos Hanson uses to import users into AD.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# coding: utf8 | |
from gluon import * | |
import re | |
import smtplib | |
import tempfile | |
import ssam_utils | |
import ttsd.ldaputils | |
import ttsd.ldaputils.ad | |
from ttsd.ldaputils import StudentConfiguration | |
import ldap | |
debug_on = False | |
def debug(message): | |
if debug_on: | |
print message | |
elementary_schools = ['104', '115', '161', '192', '210', '224', '228', '235', '246', '257', '994'] | |
secondary_schools = ['085', '090', '094', '095', '345', '353', '373', '622', '632'] | |
other_locations = ['096', '097', '777', '806', '900', '980', '990', '991', '999'] | |
substitute_codes = ['100', '888', '000'] | |
def search(query, attrs=['cn', 'givenname', 'lastname', 'mail', 'employeeid']): # {{{1 | |
search_attrs = [] | |
for a in attrs: | |
filter = '(%s=*%%s*)' | |
if a.lower() == 'employeeid': | |
# match exactly | |
filter = '(%s=%%s)' | |
search_attrs.append(filter % a) | |
f = [] | |
attr_filter = '(|' + ''.join(search_attrs) + ')' # (|(a=*%s*)(b=*%s*)) | |
for q in query.split(): | |
f.append(attr_filter % tuple([q for i in range(len(search_attrs))])) | |
filter = '(&(!(objectclass=computer))(&%s))' % ''.join(f) | |
accounts = {} | |
accounts['ttsd.k12.or.us'] = ttsd.ldaputils.ad.search(filter) | |
accounts['student.ttsd.k12.or.us'] = ttsd.ldaputils.ad.search(filter, configuration=StudentConfiguration()) | |
return accounts | |
# }}}1 | |
def format_search_results(accounts): # {{{1 | |
results = [] | |
for domain in accounts.keys(): | |
account_data = [P(A('%s, %s (%s)' % (account.get('sn'), | |
account.get('givenname'), | |
account.get('samaccountname')), | |
_href=URL('ad', 'view', vars=dict(dn=account.dn))), | |
UL(LI('Email: %s' % account.get('mail')) | |
)) for account in accounts[domain]] | |
results.extend(DIV(H2('Active Directory for %s' % domain), | |
P('%s result(s)' % len(accounts[domain])), | |
*account_data | |
)) | |
return results | |
# }}}1 | |
def format_search_results_(accounts): # {{{1 | |
results = {} | |
for domain in accounts.keys(): | |
for account in accounts[domain]: | |
name = '%s, %s (%s)' % (account.get('sn'), | |
account.get('givenname'), | |
account.get('samaccountname')) | |
results[name] = results.get(name, {}) | |
view = A('Active Directory (%s)' % domain, _href=URL('ad', 'view', vars=dict(dn=account.dn))) | |
results[name]['view'] = results[name].get('view', []) | |
results[name]['view'].append(view) | |
passwd = A('Change Password', _href=URL(vars=dict(u=account.get('samaccountname')))) | |
results[name]['passwd'] = passwd | |
rows = [] | |
for key in results.keys(): | |
tr = TR(TD(name)) | |
for view in results[key]['view']: | |
tr.append(TD(view)) | |
tr.append(results[key]['passwd']) | |
rows.append(tr) | |
return tr | |
# }}}1 | |
def change_password(dn, password): # {{{1 | |
accounts = [] | |
filter = '(distinguishedname=%s)' % dn | |
configuration = ttsd.ldaputils.StaffConfiguration() | |
accounts = ttsd.ldaputils.ad.search(filter, configuration=configuration) | |
if not accounts: | |
configuration = ttsd.ldaputils.StudentConfiguration() | |
accounts.extend(ttsd.ldaputils.ad.search(filter, configuration=configuration)) | |
# using a dn should result in one account | |
account = accounts[0] | |
account.unicodePwd = ttsd.ldaputils.ad.prepare_ad_password(password) | |
ttsd.ldaputils.ad.modify(account, configuration=configuration) | |
# }}}1 | |
def format_changes(account): # {{{1 | |
# name, samaccountname, dn, changes | |
data = {'name': account.get('displayName'), | |
'samaccountname': account.get('sAMAccountName'), | |
'dn': account.dn, | |
'changes': '', | |
} | |
for mod, key, values in account.get_modlist(): | |
if mod == ldap.MOD_ADD: | |
for value in values: | |
data['changes'] += '%10s %s: %s\n' % ('adding', key, value) | |
elif mod == ldap.MOD_REPLACE: | |
data['changes'] += '%10s %s\n' % ('changing', key) | |
if key.lower() != 'unicodepwd': | |
try: | |
original = account.original_attributes[key] | |
if len(original) == 1: | |
original = original[0] | |
data['changes'] += '%10s: %s\n' % ('from', original) | |
except KeyError: | |
# Ignore if we have no original. | |
pass | |
try: | |
data['changes'] += '%10s: %s\n' % ('to', account.get(key)) | |
except KeyError: | |
# Ignore if we have no new value. | |
pass | |
elif mod == ldap.MOD_DELETE: | |
for value in values: | |
data['changes'] += '%10s %s: %s\n' % ('deleting', key, value) | |
return '%(name)s: %(samaccountname)s\n %(dn)s\n%(changes)s' % data | |
# }}}1 | |
def send_email(created_accounts, updated_accounts, name_changes=None, is_student=False, dry=False): # {{{1 | |
mail = current.mail | |
# TODO: removed hard coded information | |
rcpt_to = '[email protected]' | |
#rcpt_to = ['sysadmin', 'smooney'] | |
if dry: | |
rcpt_to = '[email protected]' | |
subject = 'Staff Active Directory Update' | |
if is_student: | |
subject = 'Student Active Directory Update' | |
message = '' | |
if updated_accounts: | |
message = message + "Updated accounts:\n\n" | |
for info in updated_accounts: | |
message = message + info + "\n" | |
else: | |
message = message + "No existing account changes.\n\n" | |
if created_accounts: | |
message = message + "New accounts:\n\n" | |
for info in created_accounts: | |
message = message + info + "\n" | |
else: | |
message = message + "No new accounts.\n\n" | |
if name_changes: | |
message = message + "Potential name changes:\n\n" | |
for info in name_changes: | |
message = message + info + "\n" | |
else: | |
message = message + "No name changes required.\n\n" | |
mail.send(rcpt_to, subject, message) | |
# }}}1 | |
def update_ssam_record(record, account): # {{{1 | |
"""Given a staff or student record and its Active Directory account, create | |
or update a staff or student record in SSAM. Return the AD record. | |
""" | |
db = current.db | |
record_type = 'student' | |
record_ad = 'student_ad' | |
if record.has_key('staff_ad'): | |
record_type = 'staff' | |
record_ad = 'staff_ad' | |
ad_data = dict(samaccountname=account.get('samaccountname'), | |
dn=account.dn, | |
enabled=account.is_enabled(), | |
expired=account.is_expired()) | |
ad_record = record.get(record_ad).select().first() | |
ad_record_id = 0 | |
try: | |
if ad_record: | |
ad_record_id = ad_record.id | |
changes = ssam_utils.get_changes(ad_record, ad_data) | |
if changes: | |
ad_record.update_record(**changes) | |
else: | |
ad_data[record_type] = record | |
ad_record_id = db.get(record_ad).insert(**ad_data) | |
except Exception, e: | |
db.error_log.insert(event_name='ad.update_ssam_record: %s record id: %s' % (record_type, record.id), message=e.message) | |
db.commit() | |
return db(db.get(record_ad).id==ad_record_id).select().first() | |
# }}}1 | |
def set_password(account, password, configuration): # {{{1 | |
# Came up with an alternate plan. Commented out temporary file code: | |
# 2012.02.24. | |
## Create a temporary file to override the configuration file used in | |
## StudentConfiguration. | |
#tmpconfig = tempfile.TemporaryFile() | |
#tmpconfig.write('[%s]\n' % configuration.section) | |
#tmpconfig.write('host = %s\n' % configuration.get('host')) | |
#tmpconfig.write('binddn = %s\n' % account.get('userPrincipalName')) | |
#tmpconfig.write('passwd = %s\n' % password) | |
#tmpconfig.write('searchbase = %s\n' % configuration.get('searchbase')) | |
## Set the file back to the beginning. | |
#tmpconfig.seek(0) | |
#configuration.configuration.readfp(tmpconfig) | |
# Our Configuration object has a ConfigParser named "configuration" | |
# (probably not the best choice). We can set the values in the ConfigParser | |
# instead of creating a temporary file. | |
configuration.configuration.set(configuration.section, 'binddn', account.get('userPrincipalName')) | |
configuration.configuration.set(configuration.section, 'passwd', password) | |
try: | |
# Authenticate as the student to see if the SIS password matches the | |
# current active directory password. Change the password if | |
# authentication fails. | |
connection = ttsd.ldaputils.ad.get_connection(configuration=configuration) | |
except ldap.INVALID_CREDENTIALS: | |
# http://support.microsoft.com/kb/906305/en-us | |
# ...domain users can use their old password to access the network for | |
# one hour after the password is changed. | |
account.unicodePwd = ttsd.ldaputils.ad.prepare_ad_password(password) | |
return account | |
# }}}1 | |
# ----------------- | |
# Student Functions | |
# ----------------- | |
def delete_or_suspend_student(record, dry=False): # {{{1 | |
"""Given a SSAM student withdrawn record, delete or suspend the student AD | |
account. Return a tuple of the samaccountname and the delete or suspend | |
message. | |
""" | |
db = current.db | |
ad_connection = ttsd.ldaputils.ad.get_connection(configuration=StudentConfiguration()) | |
accounts = ttsd.ldaputils.ad.search('employeeid=%s' % record.student_id, connection=ad_connection) | |
info = None | |
account = None | |
samaccountname = None | |
if not accounts: | |
info = 'No Active Directory account found for %s' % record.student_id | |
ssam_utils.update_student_withdrawn_status(record, 'AD account not found') | |
else: | |
if len(accounts) == 2: | |
# should only happen in school years 2008, 2009, 2010 | |
for a in accounts: | |
if 'ou=tigardhs' not in a.dn.lower(): | |
account = a | |
elif len(accounts) != 1: | |
# shouldn't happen | |
# TODO: log | |
pass | |
else: | |
account = accounts.pop() | |
if account: | |
data = {'name': account.get('displayName'), | |
'samaccountname': account.get('sAMAccountName'), | |
'dn': account.dn, | |
'location': account.get('physicalDeliveryOfficeName'), | |
} | |
samaccountname = account.get('sAMAccountName') | |
if record.action == 'delete': | |
if dry: | |
print 'DRY: delete AD account for %s' % account.get('displayName') | |
else: | |
ttsd.ldaputils.ad.delete(account, connection=ad_connection) | |
ssam_utils.update_student_withdrawn_status(record, 'AD account deleted') | |
# TODO: delete SSAM records | |
info = 'Deleted: Active Directory account %(name)s: %(samaccountname)s' % data | |
elif record.action == 'suspend': | |
if dry: | |
print 'DRY: suspend AD account for %s' % account.get('displayName') | |
else: | |
account.useraccountcontrol = str(int(account.get('useraccountcontrol')) | account.ACCOUNTDISABLE) | |
ttsd.ldaputils.ad.modify(account, connection=ad_connection) | |
ssam_utils.update_student_withdrawn_status(record, 'AD account suspended') | |
info = 'Suspended: Active Directory account %(name)s: %(samaccountname)s' % data | |
db.commit() | |
return samaccountname, info | |
# }}}1 | |
def modify_or_add_student(record, dry=False): # {{{1 | |
"""Given a SSAM student record, modify or add a student AD account. Return | |
a tuple of the account, the create info and the update info. The create and | |
update info are information strings about what was done. | |
""" | |
if record.location == 'Unknown': | |
# TODO: log | |
return None | |
ad_connection = ttsd.ldaputils.ad.get_connection(configuration=StudentConfiguration()) | |
accounts = ttsd.ldaputils.ad.search('employeeid=%s' % record.student_id, connection=ad_connection) | |
created_info = None | |
updated_info = None | |
account = None | |
if not accounts: | |
# create a new active directory account | |
for count in range(11): | |
debug('create_student_account: %s - %s' % (record.student_id, count)) | |
try: | |
account = create_student_account(record, count) | |
# name, samaccountname, dn, location | |
data = {'name': account.get('displayName'), | |
'samaccountname': account.get('sAMAccountName'), | |
'dn': account.dn, | |
'location': account.get('physicalDeliveryOfficeName'), | |
} | |
created_info = '%(name)s: %(samaccountname)s\n %(dn)s\n %(location)s\n' % data | |
if dry: | |
print 'DRY: create AD account for %s' % account.get('displayName') | |
else: | |
account = ttsd.ldaputils.ad.add(account, connection=ad_connection) | |
break | |
except ldap.ALREADY_EXISTS, e: | |
# Try again | |
continue | |
except Exception, e: | |
raise e | |
# TODO: log failed create | |
else: | |
# update an existing active directory account | |
if len(accounts) == 2: | |
# should only happen in school years 2008, 2009, 2010 | |
for a in accounts: | |
if 'ou=tigardhs' not in a.dn.lower(): | |
account = a | |
elif len(accounts) != 1: | |
# shouldn't happen | |
# TODO: log | |
pass | |
else: | |
account = accounts.pop() | |
if account: | |
for count in range(11): | |
debug('update_student_account: %s - %s' % (record.student_id, count)) | |
try: | |
updated_account = update_student_account(account, record, ad_connection, count) | |
if updated_account: | |
if updated_account.get_modlist(): | |
updated_info = format_changes(updated_account) | |
if dry: | |
print 'DRY: modify AD account for %s' % account.get('displayName') | |
else: | |
account = ttsd.ldaputils.ad.modify(updated_account, connection=ad_connection) | |
break | |
else: | |
debug('No changes to %s.' % account.get('samaccountname')) | |
break | |
else: | |
debug('Need to increment count for %s.' % account.get('samaccountname')) | |
except ldap.ALREADY_EXISTS, e: | |
# Try again | |
debug('%s already exists.' % account.get('samaccountname')) | |
continue | |
except Exception, e: | |
raise e | |
# TODO: log failed update | |
return account, created_info, updated_info | |
# }}}1 | |
def create_student_username(record, count=0): # {{{1 | |
# clean data | |
gradyear = str(record.grad_year)[-2:] | |
lastname = re.sub("[ .'/]", "", record.last_name.lower()) | |
first_initial = record.first_name[0].lower() | |
# create username | |
sAMAccountName = "%s%s%s" % (gradyear, lastname, first_initial) | |
if count > 0: | |
sAMAccountName = "%s%s" % (sAMAccountName, count) | |
debug("trying ... %s" % sAMAccountName) | |
# pre-Windows 2000 login name has 20 character limit | |
while len(sAMAccountName) > 20: | |
lastname = lastname[:len(lastname) - 1] | |
sAMAccountName = "%s%s%s" % (gradyear, lastname, first_initial) | |
if count > 0: | |
sAMAccountName = "%s%s" % (sAMAccountName, count) | |
return sAMAccountName | |
# }}}1 | |
def set_student_attributes(account, record): # {{{1 | |
account.sn = record.last_name | |
account.givenName = record.first_name | |
displayName = '%s, %s (%s)' % (record.last_name, record.first_name, record.grad_year) | |
account.displayName = displayName | |
sAMAccountName = account.get('sAMAccountName').lower() | |
account.mail = '%[email protected]' % sAMAccountName | |
userPrincipalName = '%[email protected]' % sAMAccountName | |
account.userPrincipalName = userPrincipalName | |
homeDirectory = '/home/%s/%s' % (record.last_name[0].lower(), sAMAccountName) | |
if record.location == 'Tigard High': | |
server = 'bighouse' | |
homeDirectory = r'\\%s\Users\%s' % (server, sAMAccountName) | |
account.homeDrive = 'Y:' | |
if account.attributes.has_key('scriptPath'): | |
del account.scriptpath | |
elif record.location == 'Tualatin High': | |
server = 'gearhart.ttsd.k12.or.us' | |
homeDirectory = r'\\%s\home\%s\%s' % (server, record.last_name[0].lower(), sAMAccountName) | |
account.homeDrive = 'Y:' | |
account.scriptPath = 'tuhs.bat' | |
else: | |
account.scriptPath = 'login.bat' | |
if account.attributes.has_key('homeDrive'): | |
del account.homeDrive | |
account.homeDirectory = homeDirectory | |
account.physicalDeliveryOfficeName = record.location | |
account.description = record.location | |
account.department = record.homeroom_teacher | |
return account | |
# }}}1 | |
def create_student_account(record, count=0): # {{{1 | |
sAMAccountName = create_student_username(record, count) | |
base_dn = 'OU=schools,DC=student,DC=ttsd,DC=k12,DC=or,DC=us' | |
superior_dn = 'OU=Users,OU=%s,%s' % (record.location, base_dn) | |
relative_dn = 'cn=%s' % sAMAccountName | |
new_dn = '%s,%s' % (relative_dn, superior_dn) | |
account = ttsd.ldaputils.ad.ADEntry(new_dn) | |
account.objectClass = 'user' | |
account.sAMAccountName = sAMAccountName | |
account = set_student_attributes(account, record) | |
account = set_password(account, record.password, configuration=StudentConfiguration()) | |
account.employeeID = record.student_id | |
account.userAccountControl = account.NORMAL_ACCOUNT # enable account | |
# 2009.06.18 no group changes until member;Range issue resolved | |
return account | |
# }}}1 | |
def move_student_account(account, relative_dn, superior_dn, connection): # {{{1 | |
# check new dn, make sure it does not exist | |
# check new (or current) sAMAccountName, make sure it does not exist | |
new_dn = '%s,%s' % (relative_dn.lower(), superior_dn.lower()) | |
if account.dn.lower() != new_dn: | |
debug('old dn ... %s' % account.dn.lower()) | |
debug('new dn ... %s' % new_dn) | |
# Search for existing accounts excluding the current account. | |
search_filter = '(&(|(distinguishedName=%s)(%s))(!(distinguishedName=%s)))' % (new_dn, relative_dn, account.dn) | |
existing_accounts = ttsd.ldaputils.ad.search(search_filter, connection=connection) | |
if existing_accounts: | |
debug('existing accounts ... skip') | |
# Cannot move to an existing account. | |
dn_list = [e.dn.lower() for e in existing_accounts] | |
return None | |
debug('modify account ... ') | |
account = ttsd.ldaputils.ad.move(account, relative_dn, superior_dn, connection=connection) | |
return account | |
# }}}1 | |
def update_student_account(account, record, connection, count=0): # {{{1 | |
sAMAccountName = account.get('sAMAccountName') | |
account.givenName = record.first_name | |
account.sn = record.last_name | |
old_gradyear = sAMAccountName[:2] | |
new_gradyear = str(record.grad_year)[-2:] | |
if account.get_modlist() or old_gradyear != new_gradyear or count > 0: | |
debug('update: create new username ... ') | |
sAMAccountName = create_student_username(record, count) | |
old_location = account.get('physicalDeliveryOfficeName') | |
base_dn = 'OU=schools,DC=student,DC=ttsd,DC=k12,DC=or,DC=us' | |
superior_dn = 'OU=Users,OU=%s,%s' % (record.location, base_dn) | |
relative_dn = 'cn=%s' % sAMAccountName | |
# make lower case | |
new_dn = '%s,%s' % (relative_dn.lower(), superior_dn.lower()) | |
account = move_student_account(account, relative_dn, superior_dn, connection) | |
if account is None: | |
return | |
debug('%s: %s' % (account.get('displayname'), account.get('samaccountname'))) | |
# We should be settled on any sAMAccountName changes. | |
account.sAMAccountName = sAMAccountName | |
# Password may have changed in student assistant. Check before settings attributes. | |
account = set_password(account, record.password, configuration=StudentConfiguration()) | |
account = set_student_attributes(account, record) | |
# 2009.06.18 no group changes until member;Range issue resolved | |
return account | |
# }}}1 | |
# --------------- | |
# Staff Functions | |
# --------------- | |
classifications = { | |
'Hourly': 'Classified', # in hourly less than 4 hours "classified type" job | |
} | |
def staff_name(record): | |
return '%s, %s (%s)' % (record.last_name, record.first_name, record.employee_id) | |
def valid_location(locations): # {{{1 | |
location_set = set(locations) | |
if (location_set.intersection(set(elementary_schools)) or | |
location_set.intersection(set(secondary_schools)) or | |
location_set.intersection(set(other_locations)) or | |
location_set.intersection(set(substitute_codes))): | |
return True | |
else: | |
return False | |
# }}}1 | |
def modify_or_add_staff(record, dry=False): # {{{1 | |
"""Given a SSAM staff record, modify or add a staff AD account. Return a | |
tuple of the account, the create info and the update info. The create and | |
update info are information strings about what was done. | |
""" | |
account = None | |
created_info = None | |
updated_info = None | |
name_change = None | |
if not record.employee_id: | |
# TODO: log | |
# return the None values | |
return account, created_info, updated_info, name_change | |
if not valid_location(record.location_codes): | |
debug('modify_or_add_staff: %s: skipping - invalid location' % record.employee_id) | |
# return the None values | |
return account, created_info, updated_info, name_change | |
ad_connection = ttsd.ldaputils.ad.get_connection() | |
accounts = ttsd.ldaputils.ad.search('employeeid=%s' % record.employee_id, connection=ad_connection) | |
if not accounts: | |
# create a new active directory account | |
for count in range(11): | |
debug('create_staff_account: %s: count %s' % (record.employee_id, count)) | |
try: | |
account = create_staff_account(record, count) | |
created_info = basic_info(account) | |
if dry: | |
print 'DRY: create AD account for %s' % account.get('displayName') | |
else: | |
account = ttsd.ldaputils.ad.add(account, connection=ad_connection) | |
account = set_unix_attributes(account, ad_connection) | |
# Seems like adding groups is broken. | |
# Throws ldap.ALREADY_EXISTS | |
#set_staff_groups(account, record, ad_connection) | |
break | |
except ldap.ALREADY_EXISTS, e: | |
# Try again | |
continue | |
except Exception, e: | |
print 'Exception for %s, %s (%s)' % (record.last_name, record.first_name, record.employee_id) | |
print e | |
break | |
# TODO: log failed create | |
else: | |
# update an existing active directory account | |
if len(accounts) != 1: | |
# shouldn't happen | |
# TODO: log | |
pass | |
else: | |
account = accounts.pop() | |
if account: | |
# update an existing active directory account | |
# currently not doing any moves, so count loop is not actually necessary | |
for count in range(11): | |
debug('update_staff_account: %s: count %s' % (record.employee_id, count)) | |
try: | |
updated_account, name_change = update_staff_account(account, record, ad_connection, count) | |
if updated_account: | |
if updated_account.get_modlist(): | |
updated_info = format_changes(updated_account) | |
if dry: | |
print 'DRY: modify AD account for %s' % account.get('displayName') | |
else: | |
account = ttsd.ldaputils.ad.modify(updated_account, connection=ad_connection) | |
break | |
else: | |
debug('No changes to %s.' % account.get('samaccountname')) | |
break | |
else: | |
debug('Need to increment count for %s.' % account.get('samaccountname')) | |
except ldap.ALREADY_EXISTS, e: | |
# Try again | |
debug('%s already exists.' % account.get('samaccountname')) | |
continue | |
except Exception, e: | |
raise e | |
# TODO: log failed update | |
return account, created_info, updated_info, name_change | |
# }}}1 | |
def staff_location_code(record): # {{{1 | |
# could have more than one location code, so pick the first | |
# (lame, I know) | |
return record.location_codes[0] | |
# }}}1 | |
def get_staff_location(record): # {{{1 | |
location = ssam_utils.location_map[staff_location_code(record)] | |
if ssam_utils.alt_location_name.has_key(location): | |
location = ssam_utils.alt_location_name[location] | |
return location | |
# }}}1 | |
def create_staff_account(record, count=0): # {{{1 | |
sAMAccountName = create_staff_username(record, count) | |
location = get_staff_location(record) | |
base_dn = 'DC=ttsd,DC=ttsd,DC=k12,DC=or,DC=us' | |
superior_dn = 'OU=Users,OU=%s,%s' % (location, base_dn) | |
relative_dn = 'cn=%s' % sAMAccountName | |
new_dn = '%s,%s' % (relative_dn, superior_dn) | |
account = ttsd.ldaputils.ad.ADEntry(new_dn) | |
account.objectClass = 'user' | |
account.cn = sAMAccountName | |
account.sAMAccountName = sAMAccountName | |
account = set_staff_name(account, record) | |
account = set_attributes(account, record, location) | |
account = set_profile(account, record, location) | |
password = ttsd.ldaputils.ad.prepare_ad_password(record.employee_id) | |
account.unicodePwd = password | |
account.employeeID = record.employee_id | |
account.userAccountControl = account.NORMAL_ACCOUNT | account.ACCOUNTDISABLE # disable account | |
location_set = set(record.location_codes) | |
if location_set.intersection(substitute_codes): | |
account.userAccountControl = account.NORMAL_ACCOUNT # enable account | |
return account | |
# }}}1 | |
def create_staff_username(record, count=0): # {{{1 | |
# clean data | |
lastname = re.sub("[ .'/]", "", record.last_name.lower()) | |
first_initial = record.first_name[0].lower() | |
# create username | |
sAMAccountName = "%s%s" % (first_initial, lastname) | |
if count > 0: | |
sAMAccountName = "%s%s" % (sAMAccountName, count) | |
debug("trying ... %s" % sAMAccountName) | |
# pre-Windows 2000 login name has 20 character limit | |
while len(sAMAccountName) > 20: | |
lastname = lastname[:len(lastname) - 1] | |
sAMAccountName = "%s%s" % (first_initial, lastname) | |
if count > 0: | |
sAMAccountName = "%s%s" % (sAMAccountName, count) | |
return sAMAccountName | |
# }}}1 | |
def set_staff_name(account, record): # {{{1 | |
givenName = ' '.join([name.capitalize() for name in record.first_name.split()]) | |
sn = ' '.join([name.capitalize() for name in record.last_name.split()]) | |
account.givenName = givenName | |
account.sn = sn | |
displayName = '%s, %s' % (sn, givenName) | |
displayNamePrintable = '%s %s' % (givenName, sn) | |
preferred = record.familiar_name | |
if preferred is not None and preferred.strip() != '': | |
preferred = ' '.join([name.capitalize() for name in preferred.split()]) | |
displayName = '%s, %s' % (sn, preferred) | |
displayNamePrintable = '%s %s' % (preferred, sn) | |
account.displayName = displayName | |
account.displayNamePrintable = displayNamePrintable | |
return account | |
# }}}1 | |
def set_attributes(account, record, location): # {{{1 | |
sAMAccountName = account.get('sAMAccountName').lower() | |
if account.homemdb is not None: | |
account.mail = '%[email protected]' % sAMAccountName | |
account.userPrincipalName = '%[email protected]' % sAMAccountName | |
if account.attributes.has_key('physicalDeliveryOfficeName') and location == 'Substitutes': | |
# Existing account. Do not update, so it can be manually changed. | |
debug('set_attributes: physicalDeliveryOfficeName not set to allow manual change') | |
else: | |
account.physicalDeliveryOfficeName = location | |
# Set company the same as physicalDeliveryOfficeName in case it is changed. | |
# Company is used by VOIP for Organization. | |
account.company = account.get('physicalDeliveryOfficeName') | |
# not using record.template_name | |
c = record.classification | |
account.extensionattribute1 = classifications.get(c, c).lower() | |
return account | |
# }}}1 | |
def set_profile(account, record, location): # {{{1 | |
profile = { | |
'345': (r'\\fowstaff\staff\%s', 'H:', 'fowcim-k.bat'), | |
'353': (r'\\hazstaff\staff\%s', 'H:', 'hazcim-k.bat'), | |
'373': (r'\\twastaff\staff\%s', 'H:', 'twacim-k.bat'), | |
'622': (r'\\tuhsstaff\staff\%s', 'H:', 'tuhscim-k.bat'), | |
'632': (r'\\thsstaff\staff\%s', 'H:', 'thscim-k.bat'), | |
} | |
sAMAccountName = account.get('sAMAccountName').lower() | |
homeDirectory = '/home/%s/%s' % (sAMAccountName[0], sAMAccountName) | |
homeDrive = None | |
scriptPath = 'logon.bat' | |
if account.homeDirectory is not None: | |
homeDirectory = account.get('homeDirectory') | |
if account.homeDrive is not None: | |
homeDrive = account.get('homeDrive') | |
if account.scriptPath is not None: | |
scriptPath = account.get('scriptPath') | |
regex = re.compile('\\\\hood') | |
if location == 'Hibbard' or regex.search(homeDirectory): | |
#homeDirectory = r'\\hood\%s' % sAMAccountName | |
#homeDrive = 'H:' | |
#scriptPath = 'hibbard.bat' | |
return account | |
elif profile.has_key(staff_location_code(record)): | |
(homeDirectory, homeDrive, scriptPath) = profile[staff_location_code(record)] | |
homeDirectory = homeDirectory % sAMAccountName | |
elif staff_location_code(record) in elementary_schools: | |
homeDirectory = '/home/%s/%s' % (sAMAccountName[0], sAMAccountName) | |
homeDrive = '' | |
scriptPath = 'logon.bat' | |
account.homeDirectory = homeDirectory | |
if homeDrive: | |
account.homeDrive = homeDrive | |
account.scriptPath = scriptPath | |
return account | |
# }}}1 | |
def set_staff_groups(account, record, connection): # {{{1 | |
group_names = [] | |
for code in record.location_codes: | |
location = ssam_utils.location_map[code] | |
if ssam_utils.alt_location_name.has_key(location): | |
location = ssam_utils.alt_location_name[location] | |
group_names.append(location) | |
for name in group_names: | |
group_filter = "(&(objectClass=group)(name=%s))" % name | |
groups = ttsd.ldaputils.ad.search(group_filter, connection=connection) | |
if groups: | |
group = groups.pop() | |
if account.dn not in group.member: | |
group.add('member', account.dn) | |
try: | |
group = ttsd.ldaputils.ad.modify(group, connection=connection) | |
except ldap.ALREADY_EXISTS, e: | |
print 'Already Exists: %s in group %s?' % (sAMAccountName, name) | |
# }}}1 | |
def set_unix_attributes(account, connection): # {{{1 | |
# update Unix group | |
groups = ttsd.ldaputils.ad.search('sAMAccountName=Unix', connection=connection) | |
unixgroup = groups.pop() | |
#if account.dn not in unixgroup.member: | |
#unixgroup.add('member', account.dn) | |
# TODO: deal with member;range=0-1499 | |
#unixgroup = manager.modify_account(unixgroup) | |
# get account again since there is a change after adding to the Unix group | |
# NOTE: This does not make sense for a new account. | |
#account = ttsd.ldaputils.ad.search( | |
# 'distinguishedName=%s' % account.dn, connection=connection | |
# ).pop() | |
# add unix attributes | |
sAMAccountName = account.get('sAMAccountName') | |
account.uid = sAMAccountName | |
account.gidNumber = unixgroup.get('gidNumber') | |
account.loginShell = '/bin/false' | |
unixHomeDirectory = '/home/%s/%s' % (sAMAccountName[0], sAMAccountName) | |
account.unixHomeDirectory = unixHomeDirectory | |
# add services for unix attributes | |
account.msSFU30Name = sAMAccountName | |
account.msSFU30NisDomain = 'ttsd' | |
return ttsd.ldaputils.ad.modify(account, connection=connection) | |
# }}}1 | |
def update_staff_account(account, record, connection, count=0): # {{{1 | |
# connection and count are not used, since we are not doing moves | |
# Keep account name the same during update. Use manual name changes. | |
sAMAccountName = account.get('sAMAccountName') | |
# Notify if a name change is required based on last name. | |
sn = account.get('sn') | |
new_sn = ' '.join([name.capitalize() for name in record.last_name.split()]) | |
name_change = None | |
if new_sn.lower() != sn.lower(): | |
n = staff_name(record) + '\n' | |
n += ' %s\n' % account.dn | |
n += ' rename sAMAccountName:\n%12s: %s\n%12s: %s\n' % ( | |
'from', sAMAccountName, 'to', create_staff_username(record)) | |
n += ' Current name:\n' | |
n += '%12s: %s\n' % ('sn', account.get('sn')) | |
n += '%12s: %s\n' % ('givenName', account.get('givenName')) | |
n += '%12s: %s\n' % ('displayName', account.get('displayName')) | |
name_change = n | |
old_location = account.get('physicalDeliveryOfficeName') | |
location = get_staff_location(record) | |
#base_dn = 'DC=ttsd,DC=ttsd,DC=k12,DC=or,DC=us' | |
#superior_dn = 'OU=Users,OU=%s,%s' % (location, base_dn) | |
#relative_dn = 'CN=%s' % sAMAccountName | |
#new_dn = '%s,%s' % (relative_dn, superior_dn) | |
# renames and moves? | |
account = set_attributes(account, record, location) | |
account = set_profile(account, record, location) | |
# uid must stay the same as sAMAccountName for email logins to work | |
account.uid = sAMAccountName | |
# TODO: group changes, e.g. riderstaff to woodwardstaff | |
return account, name_change | |
# }}}1 | |
def send_error(exception): # {{{1 | |
message = 'To: sysadmin\n' | |
message = message + 'Subject: ERROR: Staff Active Directory Update\n\n' | |
message = '%s%s' % (message, exception) | |
print message | |
server = smtplib.SMTP('smtp.ttsd.k12.or.us') | |
server.set_debuglevel(1) | |
mail_from = '[email protected]' | |
rcpt_to = ['sysadmin', ] | |
#rcpt_to = ['chanson'] | |
server.sendmail(mail_from, rcpt_to, message) | |
server.quit() | |
# }}}1 | |
def basic_info(account): # {{{1 | |
info = "%s, %s (%s)\n" % ( | |
account.get('sn'), | |
account.get('givenName'), | |
account.get('sAMAccountName') | |
) | |
info = info + " %s\n" % account.dn | |
info = info + " physicalDeliveryOfficeName: %s\n" % \ | |
account.get('physicalDeliveryOfficeName') | |
return info | |
# }}}1 | |
def get_staff_groups(employeeid): | |
"""Return the main staff groups for user with employeeid. For example, | |
[email protected]. | |
""" | |
ad_account = ttsd.ldaputils.ad.search('employeeid=%s' % employeeid, | |
configuration=ttsd.ldaputils.ad.StaffConfiguration())[0] | |
groups = [g for g in ttsd.ldaputils.ad.search('member=%s' % ad_account.dn) | |
if g.get('samaccountname') in ssam_utils.staff_group_names] | |
return groups | |
# all_org_unit_users = ou_service.RetrieveAllOrgUsers(ou_service.RetrieveCustomerId()['customerId']) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment