def users(request): # Connect to LDAP try: conn = ldap.initialize(Config.ldap_uri) conn.bind_s(Config.ldap_bind_dn, Config.ldap_bind_password) except Exception as e: return request.response_json({'success': False, 'msg': "Can't bind to LDAP: %s" % e.message['desc']}) if request.method == "GET": # GET requests a user tree, but only one level deep # Determine the DN for the current depth dn_prepend = "" for url_part in request.url_parts[-1:0:-1]: dn_prepend += "ou=%s," % escape_dn(url_part) search_dn = "%s%s" % (dn_prepend, Config.ldap_base_dn) # Perform LDAP search try: res = conn.search_s(search_dn, ldap.SCOPE_ONELEVEL, "(|(objectclass=sambaSamAccount)(objectClass=organizationalUnit))", attrlist=[ '*', 'entryUUID' ]) except Exception as e: return request.response_json({'success': False, 'msg': 'Search failed: %s' % e.message['desc']}, status='404 Not Found') # Determine the depth for the search dn, needed for reference when building the tree # This also works with DNs with escaped characters mysplit = shlex.shlex(search_dn, posix=True) mysplit.escape = '\\' mysplit.quotes = '' mysplit.whitespace = ',' mysplit.whitespace_split = True search_dn_length = len(list(mysplit)) # Build an attribute tree tree = {} for dn, attrs in res: mysplit = shlex.shlex(dn, posix=True) mysplit.escape = '\\' mysplit.quotes = '' mysplit.whitespace = ',' mysplit.whitespace_split = True add_to_indexed_tree(tree, list(mysplit)[-search_dn_length-1::-1], dn, attrs) # Parse the attribute tree to convert it to something ExtJS can parse output_tree = parse_subtree({'children': tree }, "root") return request.response_json({'success': True, 'data': output_tree['children'] }) else: if request.method == 'PUT': try: existing_attrs = conn.search_s(Config.ldap_base_dn, ldap.SCOPE_SUBTREE, "(entryUUID=%s)" % escape_filter(request.url_parts[0])) except: return request.response_json({'success': False, 'message': 'Failed to locate object in LDAP directory'}) # TODO: Handle 'sambaAcctFlags': '[U ]', (samba Disabled, workstation, user, etc) # TODO: Handle password changes attrs = {} if 'first' in request.put: attrs['givenName'] = request.put['first'] if 'last' in request.put: attrs['sn'] = request.put['last'] if 'first' in request.put and 'last' in request.put: attrs['cn'] = "%s %s" % (request.put['first'], request.put['last']) attrs['displayName'] = attrs['cn'] attrs['description'] = attrs['cn'] attrs['gecos'] = "%s %s,,," % (request.put['first'], request.put['last']) request.put['displayname'] = attrs['cn'] if 'email' in request.put: attrs['mail'] = request.put['email'] if 'uidNumber' in request.put: attrs['uidNumber'] = request.put['uidNumber'] if 'gidNumber' in request.put: attrs['gidNumber'] = request.put['gidNumber'] if 'sambaSID' in request.put: attrs['sambaSID'] = request.put['sambaSID'] if 'homeDirectory' in request.put: attrs['homeDirectory'] = request.put['homeDirectory'] if 'loginShell' in request.put: attrs['loginShell'] = request.put['loginShell'] if 'accountStatus' in request.put: attrs['accountStatus'] = request.put['accountStatus'] ldif = modlist.modifyModlist(existing_attrs[0][1], attrs, ignore_oldexistent=True) if len(ldif): conn.modify_s(existing_attrs[0][0], ldif) if 'username' in request.put and existing_attrs[0][1]['uid'][0] != request.put['username'].lower(): conn.rename_s(existing_attrs[0][0], "uid=%s" % escape_dn(request.put['username'].lower())) return request.response_json({'success': True, 'message': "User info has been saved.", 'data': request.put }) else: return request.response_json({'success': False, 'msg': 'Feature not implemented', 'more': request.url_parts})
def create_user(request): # Connect to LDAP try: conn = ldap.initialize(Config.ldap_uri) conn.bind_s(Config.ldap_bind_dn, Config.ldap_bind_password) except Exception as e: return request.response_json({'success': False, 'msg': "Can't bind to LDAP: %s" % e.message['desc']}) # Validate inputs try: errors = [] if request.post['answer'][0] != '42': errors.append( {'id': 'answer', 'msg': 'Sorry, wrong answer' } ) if not re.match(r'^[a-z0-9\-\{\}\.]+$', request.post['username'][0]): errors.append( {'id': 'username', 'msg': 'Username should match r\'^a-z0-9\-\{\}\.]+$\'' } ) if not re.match(r'^[a-z0-9\-\_\.]+\@[a-z0-9\-]+\.[a-z]{2,30}$', request.post['email'][0]): errors.append( {'id': 'email', 'msg': 'E-mail address incorrect.' } ) if request.post['first'][0] == "": errors.append( {'id': 'first', 'msg': 'First name cannot be empty' } ) if request.post['last'][0] == "": errors.append( {'id': 'last', 'msg': 'Last name cannot be empty' } ) if len(request.post['password'][0]) < 5: errors.append( {'id': 'password', 'msg': 'Password too short' } ) if len(errors) > 0: return request.response_json({'success': False, 'errors': errors }) except: return request.response_json({'success': False, 'msg': 'Missing POST variables or internal server error'}, status="500 Internal Server Error") attrs = { 'objectClass': [ 'top', 'person', 'organizationalPerson', 'inetOrgPerson', 'posixAccount', 'sambaSamAccount', 'qmailUser' ], 'cn': "%s %s" % (request.post['first'][0], request.post['last'][0]), 'displayName': "%s %s" % (request.post['first'][0], request.post['last'][0]), 'description': "%s %s" % (request.post['first'][0], request.post['last'][0]), 'gecos': "%s %s,,," % (request.post['first'][0], request.post['last'][0]), 'givenName': request.post['first'][0], 'sn': request.post['last'][0], 'accountStatus': '1', # Unverified account 'mail': request.post['email'][0], 'uid': request.post['username'][0].lower(), 'userPassword': "******" % create_ssha_password(request.post['password'][0]), 'sambaNTPassword': create_nt_password(request.post['password'][0]), 'uidNumber': str(assign_uid(conn)), 'gidNumber': '1000', 'sambaSID': assign_sambasid(conn), 'homeDirectory': "/home/%s" % request.post['username'][0].lower(), 'loginShell': '/bin/bash', 'sambaAcctFlags': '[U ]', } # # Add the object to the directory # # FIXME: ou should be configurable ldif = modlist.addModlist(attrs) try: conn.add_s("uid=%s,ou=accounts,%s" % (escape_dn(request.post['username'][0].lower()), Config.ldap_base_dn), ldif) except Exception as e: return request.response_json({'success': False, 'errors': [{'id': 'username', 'msg': e.message['desc'] }]}) # # Grab the entryUUID for the newly created object, this is the unique identifier for account email verification # res = conn.search_s("uid=%s,ou=accounts,%s" % (escape_dn(request.post['username'][0].lower()), Config.ldap_base_dn), ldap.SCOPE_BASE, attrlist=[ 'entryUUID' ]) if len(res) != 1: return request.response_json({'success': False, 'msg': 'Could not find newly created user in the directory' }) attrs['entryUUID'] = res[0][1]['entryUUID'][0] # FIXME Text should be configurable msg = MIMEText("Hello %(givenName)s,\n\nYou've created an account for %(site_name)s, but you still need to activate it. You can activate it by going to http://services.ifcat.org/registration/verify.html?uuid=%(entryUUID)s&username=%(uid)s\n\nOr enter the form fields manually:\nUUID: %(entryUUID)s\nUsername: %(uid)s\n\nRegards,\n\nThe %(site_name)s team" % dict(attrs.items() + {'site_name': Config.site_name }.items())) msg['Subject'] = 'Activate your account' msg['From'] = "\"%(site_name)s\" <%(site_mail)s>" % dict(attrs.items() + {'site_name': Config.site_name, 'site_mail': Config.site_mail }.items()) msg['To'] = attrs['mail'] s = smtplib.SMTP('localhost') s.sendmail(msg['From'], msg['To'], msg.as_string()) s.quit() return request.response_json({'success': True, 'msg': 'A verification email has been sent to you' })