Skip to content

Commit

Permalink
Fix issues around LDAP user creation
Browse files Browse the repository at this point in the history
  • Loading branch information
txels committed Apr 8, 2024
1 parent 290f097 commit bffd82f
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 135 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ jobs:
SHIPANARO_LDAP_URL: "ldap://localhost"
SHIPANARO_LDAP_BIND_DN: "cn=admin,dc=pirata,dc=cat"
SHIPANARO_LDAP_BIND_PASSWORD: "admin"
SHIPANARO_LDAP_USER_SEARCH: "(&(objectclass=inetOrgPerson)(uid=%(user)s))"
SHIPANARO_LDAP_USER_SEARCH: "(&(objectclass=pilotPerson)(uid=%(user)s))"
run: |
pipenv run ./manage.py compilemessages
pipenv run ./create_ldap_user.py tester tester 1234
Expand Down
3 changes: 2 additions & 1 deletion Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ django-hashers-passlib = "*"
django-phonenumber-field = {extras = [ "phonenumbers",], version = "==4.0.0."}
django-ses = "*"
environs = {extras = [ "django",]}
granian = "*"
gunicorn = "*"
pillow = "==10.3.0"
psycopg2-binary = "==2.8.6"
pytz = "==2020.1"
setuptools = "~=69.2"
pip = "~=24.0"

[dev-packages]
wheel = "*"
Expand Down
229 changes: 124 additions & 105 deletions Pipfile.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ services:
SHIPANARO_LDAP_URL: "ldap://ldap"
SHIPANARO_LDAP_BIND_DN: "cn=admin,dc=pirata,dc=cat"
SHIPANARO_LDAP_BIND_PASSWORD: "admin"
SHIPANARO_LDAP_USER_SEARCH: "(&(objectclass=inetOrgPerson)(uid=%(user)s))"
SHIPANARO_LDAP_USER_SEARCH: "(&(objectclass=pilotPerson)(uid=%(user)s))"
ports:
- 8010:8000
volumes:
Expand Down
31 changes: 16 additions & 15 deletions humans/directory.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
LDAP_BIND_DN = environ.get("SHIPANARO_LDAP_BIND_DN", "cn=admin,dc=pirata,dc=cat")
LDAP_BIND_PASS = environ.get("SHIPANARO_LDAP_BIND_PASSWORD", "admin")

ORG_UNIT = "ou=afiliats,dc=pirata,dc=cat"


def connect() -> LDAPObject:
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
Expand All @@ -19,44 +21,43 @@ def connect() -> LDAPObject:
return connection


def create_user(connection, username, uid_number):
def create_user(connection, username, email):
name = username.encode("utf-8")
base_dn = "dc=pirata,dc=cat"
user_dn = f"cn={name.decode()},{base_dn}"
user_dn = f"uid={username},{ORG_UNIT}"

user_attrs = {}
user_attrs["objectclass"] = [b"inetOrgPerson", b"posixAccount", b"top"]
user_attrs["objectClass"] = [b"pilotPerson"]
user_attrs["cn"] = (name,)
user_attrs["givenname"] = (name,)
user_attrs["mail"] = (email.encode("utf-8"),)
user_attrs["sn"] = (name,)
user_attrs["uid"] = (name.lower(),)
user_attrs["uidnumber"] = (f"{uid_number}".encode("ascii"),)
user_attrs["gidnumber"] = (b"500",)
user_attrs["homedirectory"] = (f"/home/{username}".encode("ascii"),)

user_ldif = modlist.addModlist(user_attrs)
result = connection.add_s(user_dn, user_ldif)
return user_dn, user_attrs


def get_user(connection, username):
search_dn = f"uid={username},ou=afiliats,dc=pirata,dc=cat"
search_dn = f"uid={username},{ORG_UNIT}"
try:
result = connection.search_s(search_dn, ldap.SCOPE_BASE)
_, attrs = result[0]
return attrs
return result[0]
except:
return None
return search_dn, None


def delete_user(connection, username):
name = username.encode("utf-8")
base_dn = "dc=pirata,dc=cat"
user_dn = f"cn={name.decode()},{base_dn}"
user_dn = f"uid={username},{ORG_UNIT}"
connection.delete_s(user_dn)


def set_password(connection, user_dn, password):
password_value = make_ldap_password(password)
add_pass = [(ldap.MOD_REPLACE, "userpassword", [password_value])]
connection.modify_s(user_dn, add_pass)


# raises exception if credentials fail, else returns None
def check_credentials(connection, username, password):
user_dn = f"uid={username},{ORG_UNIT}"
connection.simple_bind_s(user_dn, password)
6 changes: 3 additions & 3 deletions humans/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ def save(self, *args, **kwargs):
self.__ldap__set_password(password)

def __ldap__save(self):
max_user_id = User.objects.aggregate(Max("id"))["id__max"] or 1
connection = directory.connect()
directory.create_user(connection, self.username, max_user_id + 10000)
directory.create_user(connection, self.username, self.email)
self.is_active = False
return

def __ldap__set_password(self, password):
ldap_user = _LDAPUser(LDAPBackend(), username=self.username)
if ldap_user.dn is None:
logging.error(f"User {self.username} not found in LDAP")
return
conn = ldap_user.connection
directory.set_password(conn, ldap_user.dn, password)
Expand All @@ -46,7 +46,7 @@ class Meta:


@receiver(signals.pre_delete, sender=User, dispatch_uid="delete_user")
def send_new_member_email(sender, instance: User, **kwargs):
def delete_associated_ldap_user(sender, instance: User, **kwargs):
try:
directory.delete_user(directory.connect(), instance.username)
except Exception as e:
Expand Down
20 changes: 12 additions & 8 deletions humans/tests/test_ldap_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,26 @@ def deleteLeftoverUser(self):
def test_new_user_is_created_in_ldap_with_no_password(self):
user = self.create_user()

ldap_user = directory.get_user(self.connection, user.username)
self.assertEquals(ldap_user["cn"][0].decode(), user.username)
self.assertNotIn("userPassword", ldap_user.keys())
uid, attrs = directory.get_user(self.connection, user.username)

self.assertEquals(uid, f"uid={user.username},ou=afiliats,dc=pirata,dc=cat")
self.assertEquals(attrs["cn"][0].decode(), user.username)
self.assertNotIn("userPassword", attrs.keys())

def test_save_password_sets_password_in_ldap(self):
user = self.create_user()
user.set_password(PASSWORD)
user.save()

ldap_user = directory.get_user(self.connection, user.username)
self.assertEquals(ldap_user["cn"][0].decode(), user.username)
self.assertEquals(ldap_user["userPassword"][0].decode()[0:6], "{SSHA}")
_, attrs = directory.get_user(self.connection, user.username)

self.assertEquals(attrs["cn"][0].decode(), user.username)
self.assertEquals(attrs["userPassword"][0].decode()[0:6], "{SSHA}")

def test_deleted_user_is_removed_from_ldap(self):
user = self.create_user()
user.delete()

ldap_user = directory.get_user(self.connection, user.username)
self.assertIsNone(ldap_user)
_, attrs = directory.get_user(self.connection, user.username)

self.assertIsNone(attrs)
2 changes: 1 addition & 1 deletion k8s/shipanaro-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ spec:
name: ldap-secrets
key: password
- name: SHIPANARO_LDAP_USER_SEARCH
value: '(&(objectclass=inetOrgPerson)(uid=%(user)s))'
value: '(&(objectclass=pilotPerson)(uid=%(user)s))'
ports:
- containerPort: 8000
resources:
Expand Down

0 comments on commit bffd82f

Please sign in to comment.