From 58dd0ab68ac3b39a0a68ed0ec18cfa7d2367f6d8 Mon Sep 17 00:00:00 2001 From: abhiramtilakiiit Date: Sun, 12 Jan 2025 14:16:06 +0530 Subject: [PATCH 1/4] queries: add query to get all user by batch --- queries.py | 134 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 85 insertions(+), 49 deletions(-) diff --git a/queries.py b/queries.py index cee844e..c3c9e18 100644 --- a/queries.py +++ b/queries.py @@ -17,6 +17,56 @@ # instantiate LDAP client LDAP = ldap.initialize("ldap://ldap.iiit.ac.in") +def get_profile(ldap_result: [str, dict]): + dn, details = ldap_result + ous = re.findall( + r"ou=\w.*?,", dn + ) # get list of OUs the current DN belongs to + if "cn" in details.keys(): + fullNameList = details["cn"][0].decode().split() + firstName = fullNameList[0] + lastName = " ".join(fullNameList[1:]) + else: + firstName = details["givenName"][0].decode() + lastName = details["sn"][0].decode() + email = details["mail"][0].decode() + + # extract optional attributes + gender = None + if "gender" in details: + gender = details["gender"][0].decode() + + rollno = None + if "uidNumber" in details: + rollno = details["uidNumber"][0].decode() + elif "sambaSID" in details: + rollno = details["sambaSID"][0].decode() + + batch = None + if len(ous) > 1: + # extract batch code from OUs + batch = re.sub(r"ou=(.*)?,", r"\1", ous[1]) + # remove the 'dual' suffix if it exists + batch = re.sub(r"dual$", "", batch, flags=re.IGNORECASE) + + stream = None + if len(ous) > 0: + # extract stream code from OUs + stream = re.sub(r"ou=(.*)?,", r"\1", ous[0]) + + profile = ProfileType( + firstName=firstName, + lastName=lastName, + email=email, + gender=gender, + batch=batch, + stream=stream, + rollno=rollno, + ) + + return profile + + # get user profile from LDAP # if profileInput is passed, use the provided uid @@ -63,55 +113,7 @@ def userProfile( print(f"Could not find user profile for {target} in LDAP!") raise Exception("Could not find user profile in LDAP!") - # extract profile attributes - dn = result[-1][0] - ous = re.findall( - r"ou=\w.*?,", dn - ) # get list of OUs the current DN belongs to - result = result[-1][1] - if "cn" in result.keys(): - fullNameList = result["cn"][0].decode().split() - firstName = fullNameList[0] - lastName = " ".join(fullNameList[1:]) - else: - firstName = result["givenName"][0].decode() - lastName = result["sn"][0].decode() - email = result["mail"][0].decode() - - # extract optional attributes - gender = None - if "gender" in result: - gender = result["gender"][0].decode() - - rollno = None - if "uidNumber" in result: - rollno = result["uidNumber"][0].decode() - elif "sambaSID" in result: - rollno = result["sambaSID"][0].decode() - - batch = None - if len(ous) > 1: - # extract batch code from OUs - batch = re.sub(r"ou=(.*)?,", r"\1", ous[1]) - # remove the 'dual' suffix if it exists - batch = re.sub(r"dual$", "", batch, flags=re.IGNORECASE) - - stream = None - if len(ous) > 0: - # extract stream code from OUs - stream = re.sub(r"ou=(.*)?,", r"\1", ous[0]) - - profile = ProfileType( - firstName=firstName, - lastName=lastName, - email=email, - gender=gender, - batch=batch, - stream=stream, - rollno=rollno, - ) - - return profile + return get_profile(result[-1]) # single profile # get user metadata (uid, role, etc.) from local database @@ -180,10 +182,44 @@ def usersByRole( UserMetaType.from_pydantic(User.model_validate(user)) for user in users ] +@strawberry.field +def usersByBatch(info: Info, batch_year: int, inter_communication_secret: str | None = None) -> List[UserMetaType]: + user = info.context.user + + if user: + if user["role"] in ["cc", "slo"]: + inter_communication_secret = inter_communication_secret_global + + if inter_communication_secret != inter_communication_secret_global: + raise Exception("Authentication Error! Invalid secret!") + + global LDAP + try: + result = LDAP.search_s( + "ou=Users,dc=iiit,dc=ac,dc=in", + ldap.SCOPE_SUBTREE, + filterstr=f"(|(ou:dn:=ug2k{batch_year})(ou:dn:=ug2k{batch_year}dual)(ou:dn:=le2k{batch_year+1}))", + ) + except ldap.SERVER_DOWN: + # Reconnect to LDAP server and retry the search + LDAP = ldap.initialize("ldap://ldap.iiit.ac.in") + result = LDAP.search_s( + "ou=Users,dc=iiit,dc=ac,dc=in", + ldap.SCOPE_SUBTREE, + filterstr=f"(|(ou:dn:=ug2k{batch_year})(ou:dn:=ug2k{batch_year}dual)(ou:dn:=le2k{batch_year + 1}))", + ) + + # error out if LDAP query fails + if not result: + print(f"Could not find user profiles for batch 2k{batch_year} in LDAP!") + raise Exception("Could not find user profile in LDAP!") + + return [ get_profile(user_result) for user_result in result ] # single profile # register all queries queries = [ userProfile, userMeta, usersByRole, + usersByBatch, ] From 98ca08b97731708096d665acbf1cef487733f1ec Mon Sep 17 00:00:00 2001 From: abhiramtilakiiit Date: Sun, 12 Jan 2025 15:34:18 +0530 Subject: [PATCH 2/4] queries: add support for pg students also --- queries.py | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/queries.py b/queries.py index c3c9e18..e5b7c50 100644 --- a/queries.py +++ b/queries.py @@ -22,14 +22,21 @@ def get_profile(ldap_result: [str, dict]): ous = re.findall( r"ou=\w.*?,", dn ) # get list of OUs the current DN belongs to - if "cn" in details.keys(): + if "cn" in details: fullNameList = details["cn"][0].decode().split() firstName = fullNameList[0] lastName = " ".join(fullNameList[1:]) - else: + elif "givenName" in details and "sn" in details: firstName = details["givenName"][0].decode() lastName = details["sn"][0].decode() - email = details["mail"][0].decode() + else: + small_fn, small_ln = details["uid"].split('.') + firstName = small_fn.capitalize() + lastName = small_ln.capitalize() + + email = None + if "mail" in details: + email = details["mail"][0].decode() # extract optional attributes gender = None @@ -183,22 +190,21 @@ def usersByRole( ] @strawberry.field -def usersByBatch(info: Info, batch_year: int, inter_communication_secret: str | None = None) -> List[UserMetaType]: - user = info.context.user +def usersByBatch(batch_year: int) -> List[ProfileType]: + prefixes = ["ug2k", "ms2k", "mtech2k", "pgssp2k", "phd2k"] - if user: - if user["role"] in ["cc", "slo"]: - inter_communication_secret = inter_communication_secret_global + full_ous = [ prefix + str(batch_year) for prefix in prefixes ] + full_ous.append(f"le2k{batch_year+1}") + full_ous.append(f"ug2k{batch_year}dual") - if inter_communication_secret != inter_communication_secret_global: - raise Exception("Authentication Error! Invalid secret!") + filterstr = f"(&(|{''.join(f'(ou:dn:={ou})' for ou in full_ous)})(uid=*))" global LDAP try: result = LDAP.search_s( "ou=Users,dc=iiit,dc=ac,dc=in", ldap.SCOPE_SUBTREE, - filterstr=f"(|(ou:dn:=ug2k{batch_year})(ou:dn:=ug2k{batch_year}dual)(ou:dn:=le2k{batch_year+1}))", + filterstr, ) except ldap.SERVER_DOWN: # Reconnect to LDAP server and retry the search @@ -206,7 +212,7 @@ def usersByBatch(info: Info, batch_year: int, inter_communication_secret: str | result = LDAP.search_s( "ou=Users,dc=iiit,dc=ac,dc=in", ldap.SCOPE_SUBTREE, - filterstr=f"(|(ou:dn:=ug2k{batch_year})(ou:dn:=ug2k{batch_year}dual)(ou:dn:=le2k{batch_year + 1}))", + filterstr, ) # error out if LDAP query fails @@ -214,7 +220,9 @@ def usersByBatch(info: Info, batch_year: int, inter_communication_secret: str | print(f"Could not find user profiles for batch 2k{batch_year} in LDAP!") raise Exception("Could not find user profile in LDAP!") - return [ get_profile(user_result) for user_result in result ] # single profile + # use filter() to get non None values + return [get_profile(user_result) for user_result in result] # single profile, + # register all queries queries = [ From 3a17117c7a9f5f4fcfc198262c7392a8d9673e16 Mon Sep 17 00:00:00 2001 From: Bhav Beri Date: Wed, 15 Jan 2025 10:52:46 +0530 Subject: [PATCH 3/4] Refactor the code and move functions to utils file --- queries.py | 113 ++++++++--------------------------------------------- utils.py | 86 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 96 deletions(-) create mode 100644 utils.py diff --git a/queries.py b/queries.py index e5b7c50..0c82fc5 100644 --- a/queries.py +++ b/queries.py @@ -1,8 +1,6 @@ import os -import re from typing import List, Optional -import ldap import strawberry from fastapi.encoders import jsonable_encoder @@ -11,69 +9,10 @@ # import all models and types from models import User from otypes import Info, ProfileType, UserInput, UserMetaType +from utils import get_profile, ldap_search inter_communication_secret_global = os.getenv("INTER_COMMUNICATION_SECRET") -# instantiate LDAP client -LDAP = ldap.initialize("ldap://ldap.iiit.ac.in") - -def get_profile(ldap_result: [str, dict]): - dn, details = ldap_result - ous = re.findall( - r"ou=\w.*?,", dn - ) # get list of OUs the current DN belongs to - if "cn" in details: - fullNameList = details["cn"][0].decode().split() - firstName = fullNameList[0] - lastName = " ".join(fullNameList[1:]) - elif "givenName" in details and "sn" in details: - firstName = details["givenName"][0].decode() - lastName = details["sn"][0].decode() - else: - small_fn, small_ln = details["uid"].split('.') - firstName = small_fn.capitalize() - lastName = small_ln.capitalize() - - email = None - if "mail" in details: - email = details["mail"][0].decode() - - # extract optional attributes - gender = None - if "gender" in details: - gender = details["gender"][0].decode() - - rollno = None - if "uidNumber" in details: - rollno = details["uidNumber"][0].decode() - elif "sambaSID" in details: - rollno = details["sambaSID"][0].decode() - - batch = None - if len(ous) > 1: - # extract batch code from OUs - batch = re.sub(r"ou=(.*)?,", r"\1", ous[1]) - # remove the 'dual' suffix if it exists - batch = re.sub(r"dual$", "", batch, flags=re.IGNORECASE) - - stream = None - if len(ous) > 0: - # extract stream code from OUs - stream = re.sub(r"ou=(.*)?,", r"\1", ous[0]) - - profile = ProfileType( - firstName=firstName, - lastName=lastName, - email=email, - gender=gender, - batch=batch, - stream=stream, - rollno=rollno, - ) - - return profile - - # get user profile from LDAP # if profileInput is passed, use the provided uid @@ -99,21 +38,7 @@ def userProfile( # "Can not query a null uid! Log in or provide an uid as input.") # query LDAP for user profile - global LDAP - try: - result = LDAP.search_s( - "ou=Users,dc=iiit,dc=ac,dc=in", - ldap.SCOPE_SUBTREE, - filterstr=f"(uid={target})", - ) - except ldap.SERVER_DOWN: - # Reconnect to LDAP server and retry the search - LDAP = ldap.initialize("ldap://ldap.iiit.ac.in") - result = LDAP.search_s( - "ou=Users,dc=iiit,dc=ac,dc=in", - ldap.SCOPE_SUBTREE, - filterstr=f"(uid={target})", - ) + result = ldap_search(f"(uid={target})") # error out if LDAP query fails if not result: @@ -189,39 +114,35 @@ def usersByRole( UserMetaType.from_pydantic(User.model_validate(user)) for user in users ] + @strawberry.field def usersByBatch(batch_year: int) -> List[ProfileType]: + if batch_year < 18 or batch_year > 100: + return [] + prefixes = ["ug2k", "ms2k", "mtech2k", "pgssp2k", "phd2k"] - full_ous = [ prefix + str(batch_year) for prefix in prefixes ] + full_ous = [prefix + str(batch_year) for prefix in prefixes] full_ous.append(f"le2k{batch_year+1}") full_ous.append(f"ug2k{batch_year}dual") filterstr = f"(&(|{''.join(f'(ou:dn:={ou})' for ou in full_ous)})(uid=*))" - global LDAP - try: - result = LDAP.search_s( - "ou=Users,dc=iiit,dc=ac,dc=in", - ldap.SCOPE_SUBTREE, - filterstr, - ) - except ldap.SERVER_DOWN: - # Reconnect to LDAP server and retry the search - LDAP = ldap.initialize("ldap://ldap.iiit.ac.in") - result = LDAP.search_s( - "ou=Users,dc=iiit,dc=ac,dc=in", - ldap.SCOPE_SUBTREE, - filterstr, - ) + result = ldap_search(filterstr) # error out if LDAP query fails if not result: - print(f"Could not find user profiles for batch 2k{batch_year} in LDAP!") - raise Exception("Could not find user profile in LDAP!") + print( + f"Could not find user profiles for batch 2k{batch_year} in LDAP!" + ) + raise Exception( + f"Could not find user profiles for batch 2k{batch_year} in LDAP!" + ) # use filter() to get non None values - return [get_profile(user_result) for user_result in result] # single profile, + return [ + get_profile(user_result) for user_result in result + ] # single profile # register all queries diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..6292823 --- /dev/null +++ b/utils.py @@ -0,0 +1,86 @@ +import re + +import ldap + +# import all models and types +from otypes import ProfileType + +# instantiate LDAP client +LDAP = ldap.initialize("ldap://ldap.iiit.ac.in") + + +def ldap_search(filterstr: str): + global LDAP + try: + result = LDAP.search_s( + "ou=Users,dc=iiit,dc=ac,dc=in", + ldap.SCOPE_SUBTREE, + filterstr, + ) + except ldap.SERVER_DOWN: + # Reconnect to LDAP server and retry the search + LDAP = ldap.initialize("ldap://ldap.iiit.ac.in") + result = LDAP.search_s( + "ou=Users,dc=iiit,dc=ac,dc=in", + ldap.SCOPE_SUBTREE, + filterstr, + ) + + return result + + +def get_profile(ldap_result: [str, dict]): + dn, details = ldap_result + ous = re.findall( + r"ou=\w.*?,", dn + ) # get list of OUs the current DN belongs to + if "cn" in details: + fullNameList = details["cn"][0].decode().split() + firstName = fullNameList[0] + lastName = " ".join(fullNameList[1:]) + elif "givenName" in details and "sn" in details: + firstName = details["givenName"][0].decode() + lastName = details["sn"][0].decode() + else: + small_fn, small_ln = details["uid"].split(".") + firstName = small_fn.capitalize() + lastName = small_ln.capitalize() + + email = None + if "mail" in details: + email = details["mail"][0].decode() + + # extract optional attributes + gender = None + if "gender" in details: + gender = details["gender"][0].decode() + + rollno = None + if "uidNumber" in details: + rollno = details["uidNumber"][0].decode() + elif "sambaSID" in details: + rollno = details["sambaSID"][0].decode() + + batch = None + if len(ous) > 1: + # extract batch code from OUs + batch = re.sub(r"ou=(.*)?,", r"\1", ous[1]) + # remove the 'dual' suffix if it exists + batch = re.sub(r"dual$", "", batch, flags=re.IGNORECASE) + + stream = None + if len(ous) > 0: + # extract stream code from OUs + stream = re.sub(r"ou=(.*)?,", r"\1", ous[0]) + + profile = ProfileType( + firstName=firstName, + lastName=lastName, + email=email, + gender=gender, + batch=batch, + stream=stream, + rollno=rollno, + ) + + return profile \ No newline at end of file From cf535fe1848309f77163e00c5886fb2bcbc4828d Mon Sep 17 00:00:00 2001 From: Abhiram <127508564+abhiramtilakiiit@users.noreply.github.com> Date: Wed, 15 Jan 2025 11:37:17 +0530 Subject: [PATCH 4/4] Get_profile query: change typing for ldap_result to generic List --- utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils.py b/utils.py index 6292823..812b40a 100644 --- a/utils.py +++ b/utils.py @@ -29,7 +29,7 @@ def ldap_search(filterstr: str): return result -def get_profile(ldap_result: [str, dict]): +def get_profile(ldap_result: List): dn, details = ldap_result ous = re.findall( r"ou=\w.*?,", dn @@ -83,4 +83,4 @@ def get_profile(ldap_result: [str, dict]): rollno=rollno, ) - return profile \ No newline at end of file + return profile