Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LDAP and BILL syncing issues #188

Merged
merged 13 commits into from
Jan 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 56 additions & 52 deletions teknologr/api/bill.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import requests
import re
import json
from getenv import env


Expand All @@ -8,6 +9,7 @@ class BILLException(Exception):


class BILLAccountManager:
ERROR_ACCOUNT_DOES_NOT_EXIST = "BILL account does not exist"

def __init__(self):
self.api_url = env("BILL_API_URL")
Expand All @@ -19,70 +21,72 @@ def admin_url(self, bill_code):
return ''
return f'{"/".join(self.api_url.split("/")[:-2])}/admin/userdata?id={bill_code}'

def create_bill_account(self, username):
if not re.search(r'^[A-Za-z0-9]+$', username):
raise BILLException("Can not create a BILL account using an LDAP username containing anything other than letters and numbers")

def __request(self, path):
try:
r = requests.post(self.api_url + "add?type=user&id=%s" % username, auth=(self.user, self.password))
r = requests.post(self.api_url + path, auth=(self.user, self.password))
except:
raise BILLException("Could not connect to BILL server")

if r.status_code != 200:
raise BILLException("BILL returned status: %d" % r.status_code)
raise BILLException(f"BILL returned status code {r.status_code}")

number = 0
try:
number = int(r.text)
except ValueError:
# Returned value not a BILL code or error code
raise BILLException("BILL returned error: " + r.text)
# Not a number, return as text
return r.text

# A negative number means an error code
if number == -3:
raise BILLException(BILLAccountManager.ERROR_ACCOUNT_DOES_NOT_EXIST)
if number < 0:
raise BILLException("BILL returned error code: " + r.text)
raise BILLException(f"BILL returned error code: {number}")

return number

def create_bill_account(self, username):
if not re.search(r'^[A-Za-z0-9]+$', username):
raise BILLException("Can not create a BILL account using an LDAP username containing anything other than letters and numbers")

result = self.__request(f"add?type=user&id={username}")
if type(result) == int:
return result
raise BILLException(f"BILL returned error: {result}")

def delete_bill_account(self, bill_code):
try:
r = requests.post(self.api_url + "del?type=user&acc=%s" % bill_code, auth=(self.user, self.password))
except:
raise BILLException("Could not connect to BILL server")
if r.status_code != 200:
raise BILLException("BILL returned status: %d" % r.status_code)
try:
number = int(r.text)
except ValueError:
# Returned value not a number, unknown error occurred
raise BILLException("BILL returned error: " + r.text)
if number == 0:
pass # All is good
else:
raise BILLException("BILL returned error code: %d" % number)
info = self.get_bill_info(bill_code)
error = info.get('error')
if error:
raise BILLException(error)

def get_bill_info(self, bill_code):
import json
try:
r = requests.get(self.api_url + "get?type=user&acc=%s" % bill_code, auth=(self.user, self.password))
except:
raise BILLException("Could not connect to BILL server")
if r.status_code != 200:
raise BILLException("BILL returned status: %d" % r.status_code)
# BILL API does not use proper http status codes
try:
error = int(r.text)
except ValueError:
# The returned string is not an integer, so presumably we have the json we want
return json.loads(r.text)
raise BILLException("BILL returned error code: " + r.text)
# If the BILL account does not exist all is ok
if info.get('exists') is False:
return

result = self.__request(f"del?type=user&acc={bill_code}")

if result != 0:
raise BILLException(f"BILL returned error: {result}")

def find_bill_code(self, username):
import json
try:
r = requests.get(self.api_url + "get?type=user&id=%s" % username, auth=(self.user, self.password))
except:
raise BILLException("Could not connect to BILL server")
if r.status_code != 200:
raise BILLException("BILL returned status: %d" % r.status_code)
# BILL API does not use proper http status codes
result = self.__request(f"get?type=user&id={username}")
return json.loads(result)["acc"]

def get_bill_info(self, bill_code):
'''
Get the info for a certain BILL account. Never throws.
'''
if not bill_code:
return {'acc': None, 'exists': False}
try:
error = int(r.text)
except ValueError:
# The returned string is not an integer, so presumably we have the json we want
return json.loads(r.text)["acc"]
raise BILLException("BILL returned error code: " + r.text + " for username: " + username)
result = self.__request(f"get?type=user&acc={bill_code}")
return {
**json.loads(result),
'exists': True,
}
except BILLException as e:
s = str(e)
if s == BILLAccountManager.ERROR_ACCOUNT_DOES_NOT_EXIST:
return {'acc': bill_code, 'exists': False}
return {'acc': bill_code, 'error': s}
37 changes: 33 additions & 4 deletions teknologr/api/ldap.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,26 @@ def LDAPError_to_string(e):
if not isinstance(e, ldap.LDAPError):
return str(e)
data = e.args[0]
s = f"{data.get('desc' , '')} [{data.get('result')}]"
s = f"{data.get('desc' , '')} [LDAP result code {data.get('result')}]"
info = data.get('info')
if info:
s += f' ({info})'
return s

def get_ldap_account(username):
'''
Get the info for a certain LDAP account. Never throws.
'''
if not username:
return {'username': None, 'exists': False, 'groups': []}
try:
with LDAPAccountManager() as lm:
exists = lm.check_account(username)
groups = lm.get_ldap_groups(username)
return {'username': username, 'exists': exists, 'groups': sorted(groups)}
except ldap.LDAPError as e:
return {'username': username, 'error': LDAPError_to_string(e)}

class LDAPAccountManager:
def __init__(self):
# Don't require certificates
Expand Down Expand Up @@ -94,14 +108,29 @@ def get_next_uidnumber(self):
last = uid
return last + 1

def check_account(self, username):
'''
Check if a certain LDAP account exists.
'''
try:
dn = env("LDAP_USER_DN_TEMPLATE") % {'user': username}
self.ldap.search_s(dn, ldap.SCOPE_BASE)
return True
except ldap.LDAPError as e:
# Result code 32 = noSuchObject
if e.args[0].get('result') == 32:
return False
raise e

def delete_account(self, username):
# Remove user from members group
group_dn = env("LDAP_MEMBER_GROUP_DN")
self.ldap.modify_s(group_dn, [(ldap.MOD_DELETE, 'memberUid', username.encode('utf-8'))])

# Remove user
dn = env("LDAP_USER_DN_TEMPLATE") % {'user': username}
self.ldap.delete_s(dn)
# Remove user, if it exists
if self.check_account(username):
dn = env("LDAP_USER_DN_TEMPLATE") % {'user': username}
self.ldap.delete_s(dn)

def change_password(self, username, password):
# Changes both the user password and the samba password
Expand Down
Loading