diff --git a/.env.secret b/.env.secret index e9a3fe9..54fec03 100644 Binary files a/.env.secret and b/.env.secret differ diff --git a/.gitsecret/paths/mapping.cfg b/.gitsecret/paths/mapping.cfg index ef1ae70..9afad11 100644 --- a/.gitsecret/paths/mapping.cfg +++ b/.gitsecret/paths/mapping.cfg @@ -1 +1 @@ -.env:bd249425799e80207de424e1c8d97c11d07aa5ba79698184f4f641902cf03679 +.env:9e1dbd24d09e8e37b7c2fa8aa20dd931d2204f618e725508c7c2408a0c6e491c diff --git a/app/Scrape/Scrape.py b/app/Scrape/Scrape.py index b43586e..8740cf0 100644 --- a/app/Scrape/Scrape.py +++ b/app/Scrape/Scrape.py @@ -6,21 +6,21 @@ logger = logging.getLogger(__name__) listOfUrls=[ -"http://ldap1.iitd.ernet.in/LDAP/chemical/ch118.shtml", -"http://ldap1.iitd.ernet.in/LDAP/chemical/ch718.shtml", -"http://ldap1.iitd.ernet.in/LDAP/civil/ce118.shtml", -"http://ldap1.iitd.ernet.in/LDAP/cse/cs118.shtml", -"http://ldap1.iitd.ernet.in/LDAP/cse/cs518.shtml", -"http://ldap1.iitd.ernet.in/LDAP/dbeb/bb118.shtml", -"http://ldap1.iitd.ernet.in/LDAP/dbeb/bb518.shtml", -"http://ldap1.iitd.ernet.in/LDAP/ee/ee118.shtml", -"http://ldap1.iitd.ernet.in/LDAP/ee/ee318.shtml", -"http://ldap1.iitd.ernet.in/LDAP/maths/mt118.shtml", -"http://ldap1.iitd.ernet.in/LDAP/maths/mt618.shtml", -"http://ldap1.iitd.ernet.in/LDAP/mech/me118.shtml", -"http://ldap1.iitd.ernet.in/LDAP/mech/me218.shtml", -"http://ldap1.iitd.ernet.in/LDAP/physics/ph118.shtml", -"http://ldap1.iitd.ernet.in/LDAP/textile/tt118.shtml" +"http://ldap1.iitd.ernet.in/LDAP/chemical/ch119.shtml", +"http://ldap1.iitd.ernet.in/LDAP/chemical/ch719.shtml", +"http://ldap1.iitd.ernet.in/LDAP/civil/ce119.shtml", +"http://ldap1.iitd.ernet.in/LDAP/cse/cs119.shtml", +"http://ldap1.iitd.ernet.in/LDAP/cse/cs519.shtml", +"http://ldap1.iitd.ernet.in/LDAP/dbeb/bb119.shtml", +"http://ldap1.iitd.ernet.in/LDAP/dbeb/bb519.shtml", +"http://ldap1.iitd.ernet.in/LDAP/ee/ee119.shtml", +"http://ldap1.iitd.ernet.in/LDAP/ee/ee319.shtml", +"http://ldap1.iitd.ernet.in/LDAP/maths/mt119.shtml", +"http://ldap1.iitd.ernet.in/LDAP/maths/mt619.shtml", +"http://ldap1.iitd.ernet.in/LDAP/mech/me119.shtml", +"http://ldap1.iitd.ernet.in/LDAP/mech/me219.shtml", +"http://ldap1.iitd.ernet.in/LDAP/physics/ph119.shtml", +"http://ldap1.iitd.ernet.in/LDAP/textile/tt119.shtml" ] def kerberos_to_entry_number(kerberos): diff --git a/app/add_random_users.py b/app/add_random_users.py index 0121e0a..696801a 100644 --- a/app/add_random_users.py +++ b/app/add_random_users.py @@ -9,7 +9,7 @@ print("Adding random users...") for i in range(40): - student_name = '2018student' + str(i) + student_name = '2019student' + str(i) if not User.objects.filter(username=student_name).exists(): user = User.objects.create_user(username=student_name, email=student_name + '@gmail.com', diff --git a/app/clean.py b/app/clean.py index ed64cdb..590ac23 100644 --- a/app/clean.py +++ b/app/clean.py @@ -33,7 +33,7 @@ votes_given = {} # logger.info('%s ------------ %s',u.name, u.VotesIHaveGiven,) for vote_id,enum in u.VotesIHaveGiven.items(): - if(len(enum.strip())>0 and enum.strip().lower()[:4]=='2018'): + if(len(enum.strip())>0 and enum.strip().lower()[:4]=='2019'): if(int(vote_id) > 180): poll_text = Poll.objects.filter(id=int(vote_id))[0].poll replace_vote_id = str(polls_dict[poll_text][0]) diff --git a/app/docker_entry_point.dev.sh b/app/docker_entry_point.dev.sh index 1b5cbcb..29e728b 100755 --- a/app/docker_entry_point.dev.sh +++ b/app/docker_entry_point.dev.sh @@ -13,7 +13,7 @@ python3 add_random_users.py ## Add superusers usernames=( - 2018_tester + 2019_tester ) passwords=( diff --git a/app/myapp/templates/myapp/index.html b/app/myapp/templates/myapp/index.html index 481c572..b9bc75b 100755 --- a/app/myapp/templates/myapp/index.html +++ b/app/myapp/templates/myapp/index.html @@ -57,7 +57,7 @@ Yearbook

- For 2018 Batch Only + For 2019 Batch Only

{{error_string}} diff --git a/app/myapp/templates/myapp/profile.html b/app/myapp/templates/myapp/profile.html index 8a399a7..f50b210 100644 --- a/app/myapp/templates/myapp/profile.html +++ b/app/myapp/templates/myapp/profile.html @@ -90,7 +90,7 @@

- For 2018 Entry Only + For 2019 Entry Only

diff --git a/app/myapp/views.py b/app/myapp/views.py index a796c6a..c157913 100644 --- a/app/myapp/views.py +++ b/app/myapp/views.py @@ -38,18 +38,19 @@ def index(request): # For local development if hasattr(settings, 'BYPASS_OAUTH') and settings.BYPASS_OAUTH: - myUser = User.objects.get(username=('2018_tester').lower()) + myUser = User.objects.get(username=('2019_tester').lower()) if not hasattr(myUser, 'student'): - myUser.student = Student(name='2018_tester', department='cse') + myUser.student = Student(name='2019_tester', department='cse') myUser.student.save() - logger.info("New student created for user 2018_tester") + logger.info("New student created for user 2019_tester") login(request, myUser) return redirect('/profile') # For production if request.method == 'POST': - return redirect(os.environ["authLinkPart1"] + os.environ["CLIENT_ID"] + os.environ["authLinkPart2"]) + # return redirect(os.environ["authLinkPart1"] + os.environ["CLIENT_ID"] + os.environ["authLinkPart2"]) + return redirect('/profile') return render(request, 'myapp/index.html') # return render(request, 'myapp/index.html') @@ -502,7 +503,7 @@ def yearbook(request): if request.user.is_superuser: dep = request.GET.get('department') else: - return redirect("https://drive.google.com/drive/u/1/folders/1aZJPdJbGrWiOi56WMVnJWqRtmGpxrr9D") + return redirect(os.environ.get("DEPT_YEARBOOK_DRIVE_LINK", "https://yearbook.devclub.in")) dep = request.user.student.department departmentN="" diff --git a/app/requirements.txt b/app/requirements.txt index a80ac3c..4d936ae 100644 --- a/app/requirements.txt +++ b/app/requirements.txt @@ -1,10 +1,11 @@ +asgiref==3.2.7 asn1crypto==1.2.0 beautifulsoup4==4.8.1 certifi==2019.11.28 cffi==1.13.2 chardet==3.0.4 config==0.4.2 -cryptography==2.8 +cryptography==3.0 cycler==0.10.0 Django==2.2.10 django-cleanup==4.0.0 @@ -14,6 +15,7 @@ enum34==1.1.6 idna==2.8 ipaddress==1.0.23 jsonfield==2.0.2 +PyJWT==1.7.1 kiwisolver==1.2.0 matplotlib==3.1.2 numpy==1.18.2 diff --git a/app/yearbook/middleware.py b/app/yearbook/middleware.py new file mode 100644 index 0000000..12bd56e --- /dev/null +++ b/app/yearbook/middleware.py @@ -0,0 +1,198 @@ +from django.shortcuts import redirect +from django.urls import reverse_lazy +import jwt +import requests +import json +import time +import re +import logging + +from django.contrib.auth.models import User +from django.contrib.auth import login,logout +from django.http.response import HttpResponse +from django.conf import settings +from myapp.models import Student + +SSO_TOKEN = 'token' +REFRESH_TOKEN = 'rememberme' +AUTH_URL = 'https://auth.devclub.in/user/login' +REFRESH_URL = 'https://auth.devclub.in/auth/refresh-token' +PUBLIC_KEY = 'yearbook/public.pem' +MAX_TTL_ALLOWED = 60 * 5 +QUERY_PARAM = 'serviceURL' +LOGOUT_PATH = '/logout/' + +USER_MODEL = User + +# An array of path regexes that will not be processed by the middleware +PUBLIC_PATHS = ['^/public.*','^/$','^/static.*','^/admin', '^/media.*'] + +# A dictionary of path regexes mapping to the roles. A user needs to have all roles in order to be authorized +ROLES = { + '^/admin.*': ['admin'] +} + +DEFAULT_ROLES = ['iitd_user','yearbook_user'] +UNAUTHORIZED_HANDLER = lambda request: HttpResponse("Alas You are out of scope! Go get some more permissions dude",status=401) + +code2dept = { + "ce":"civil", + "ch":"chemical", + "cs":"cse", + "bb":"dbeb", + "ee":"ee", + "mt":"maths", + "me":"mech", + "ph":"physics", + "tt":"textile" +} + + +class SSOMiddleware: + def __init__(self, get_response): + self.configure() + self.get_response = get_response + self.public_key = open(PUBLIC_KEY,'rb').read() + self.cookies = None + + def __call__(self, request): + + if (request.path == LOGOUT_PATH): + return self.logout(request) + + try: + token = request.COOKIES[SSO_TOKEN] + except: + token = None + + try: + rememberme = request.COOKIES[REFRESH_TOKEN] + except: + rememberme = None + + + if(not token and not rememberme): + logging.info("line 75 (not token and not remember me if statement)") + return self.redirect(request) + + if(token is not None): + try: + decoded = jwt.decode(token,self.public_key,algorithms='RS256') + # logging.info("jwt.decode run successfully") + + if(float(decoded['exp']) - time.time() < MAX_TTL_ALLOWED): + decoded['user'] = self.refresh(request=request,token={SSO_TOKEN:token}) + # logging.info("self.refresh executed") + + if(not self.authorize_roles(request, decoded['user'])): + # logging.info("line 88") + return UNAUTHORIZED_HANDLER(request) + self.assign_user(request, decoded['user']) + logging.info("user assigned") + + except Exception as err: + # print(err) + # logging.info("line 95") + # logging.info(err) + return self.redirect(request) + else: + try: + decoded = jwt.decode(rememberme,self.public_key,algorithms='RS256') + user = self.refresh(request,{REFRESH_TOKEN:rememberme}) + + if(not self.authorize_roles(request, decoded['user'])): + return UNAUTHORIZED_HANDLER(request) + self.assign_user(request,user_payload=user) + + except Exception as err: + print(err) + # logging.info("line 109") + return self.redirect(request) + + response = self.get_response(request) + + if(self.cookies is not None): + response._headers['set-cookie1'] = ('Set-Cookie',self.cookies.split('\n')[0]) + try: + response._headers['set-cookie2'] = ('Set-Cookie', self.cookies.split('\n')[1]) + except: + pass + + self.cookies = None + + return response + + def configure(self): + for key, value in globals().items(): + if(key.isupper()): + new_val = getattr(settings, key, value) + if(type(new_val) != type(value)): + err = f"Type Mismatch, {key} should be of {type(value)} but found as {type(new_val)}" + raise TypeError(err) + globals()[key] = new_val + + def assign_user(self,request,user_payload): + if(request.user.is_authenticated): + return + try: + user = USER_MODEL.objects.get(email=user_payload['email']) + except: + user = USER_MODEL.objects.create_user(email=user_payload['email'],username=user_payload['username']) + + user.first_name = user_payload['firstname'] + user.last_name = user_payload['lastname'] + user.username = user_payload['username'] + user.save() + code = user_payload['username'][:2] + s = Student(name=user_payload['firstname'],department=code2dept[code]) + user.student = s + user.student.save() + login(request, user) + + def authorize_roles(self,request,user_payload): + if(len(ROLES.keys()) == 0 or match_regex_list(request.path, PUBLIC_PATHS)): + return True + try: + user_roles = user_payload['roles'] + except: + return False + + match = match_regex_list(request.path, ROLES.keys()) + if(match is None): + reqd_roles = DEFAULT_ROLES + else: + reqd_roles = ROLES[match] + + for role in reqd_roles: + if(role not in user_roles): + return False + + return True + + + def refresh(self,request,token): + r=requests.post(REFRESH_URL,data=token) + self.cookies = r.headers['Set-Cookie'].replace('Lax,','Lax\n') + return json.loads(r.text)['user'] + + def logout(self,request): + logout(request) + response = self.get_response(request) + response.delete_cookie(SSO_TOKEN,domain='devclub.in') + response.delete_cookie(REFRESH_TOKEN,domain='devclub.in') + return response + + def redirect(self,request): + if(match_regex_list(request.path,PUBLIC_PATHS)): + return self.get_response(request) + logging.info(f"request: {request}") + logging.info("inside redirect function, line 189") + return redirect(AUTH_URL+f"/?{QUERY_PARAM}={re.sub(r'vm2-internal','devclub.in',request.build_absolute_uri()).replace('http','https')}") + + +def match_regex_list(key,regex_array): + """ Match every regex element in an array against the key""" + for regex in regex_array: + if(re.search(regex,key) is not None): + return regex + return None \ No newline at end of file diff --git a/app/yearbook/public.pem b/app/yearbook/public.pem new file mode 100644 index 0000000..3873dc4 --- /dev/null +++ b/app/yearbook/public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAujaqXchpIwNBS7/pyXa6 +kRi6cohoQgcV/ey/Fjy/A3jaKyI7CoWfxlwbQ3d+I0xclI78JxHLkk8ZxpAamqJj +bbN/XqAHM7Yt61w3OG3B5+IALcIZRS4EcdBIsp9lYye9SVfnhxLuWCkUEWR4DsaB +YO2SeP+6AhZTiu11+yyDbtbJ1FhlSgNtmeZXX5H5Kx0Gq31RHAF2l8vDfTBzk+7k +rg/PnmM0m1ebWY0w5n4ukCUTbmVgLzgnaA0diY/MhBLsNF3WlLZ7tGgo8uomzKGl +jOmPeK/vfP1zWHQmo+ceP6LGsCfU1B8gklS4u4khPFwgY7pm+98+Cnp3mlHY1rKx +QQIDAQAB +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/app/yearbook/settings.py b/app/yearbook/settings.py index c13f3d0..c234617 100644 --- a/app/yearbook/settings.py +++ b/app/yearbook/settings.py @@ -55,6 +55,7 @@ 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'corsheaders.middleware.CorsMiddleware', + 'yearbook.middleware.SSOMiddleware', ] ROOT_URLCONF = 'yearbook.urls'