33import subprocess
44from dateutil import parser
55from enum import Enum
6- from flask import Flask , render_template , request , redirect , abort , url_for , make_response
6+ from flask import Flask , render_template , request , redirect , abort , url_for , make_response , session
77import logging
88from werkzeug .utils import secure_filename
99from werkzeug .exceptions import HTTPException
5151import json_log_formatter
5252from pathlib import Path
5353from dotenv import load_dotenv
54- from helpers import floor_to_integer , RoomNumber , integer_to_floor , MapLocation , ServiceNowStatus , ServiceNowUpdateType
54+ from helpers import floor_to_integer , RoomNumber , integer_to_floor , MapLocation , ServiceNowStatus , ServiceNowUpdateType , save_user_details , check_for_admin_role , get_logged_in_user_id , get_logged_in_user
55+ from urllib .parse import quote_plus , urlencode
56+ from authlib .integrations .flask_client import OAuth
5557
5658
5759app = Flask (__name__ )
8587git_cmd = ["git" , "rev-parse" , "--short" , "HEAD" ]
8688app .config ["GIT_REVISION" ] = subprocess .check_output (git_cmd ).decode ("utf-8" ).rstrip ()
8789
90+ auth_configured = not None in [
91+ os .environ .get ("AUTH0_DOMAIN" ),
92+ os .environ .get ("CPACCESS_SECRET_KEY" ),
93+ os .environ .get ("AUTH0_CLIENT_ID" ),
94+ os .environ .get ("AUTH0_CLIENT_SECRET" )
95+ ]
96+
97+ if auth_configured :
98+ # Auth Setup
99+ app .secret_key = os .environ .get ("CPACCESS_SECRET_KEY" )
100+
101+ oauth = OAuth (app )
102+
103+ oauth .register (
104+ "auth0" ,
105+ client_id = os .environ .get ("AUTH0_CLIENT_ID" ),
106+ client_secret = os .environ .get ("AUTH0_CLIENT_SECRET" ),
107+ client_kwargs = {
108+ "scope" : "openid profile email" ,
109+ },
110+ server_metadata_url = f'https://{ os .environ .get ("AUTH0_DOMAIN" )} /.well-known/openid-configuration' ,
111+ )
112+
113+ logging .info ("Auth Initialized" )
114+
115+ else :
116+ logging .info ("Auth configuration not available due to missing variables. Ensure all of AUTH0_DOMAIN, CPACCESS_SECRET_KEY, AUTH0_CLIENT_ID, AUTH0_CLIENT_SECRET are present" )
117+
118+
119+
88120logging .info (f"Connecting to S3 Bucket { app .config ['BUCKET_NAME' ]} " )
89121
90122s3_bucket = S3Bucket (
@@ -817,6 +849,7 @@ def catalog():
817849 if page is None :
818850 return render_template (
819851 "catalog.html" ,
852+ authsession = get_logged_in_user (),
820853 q = query ,
821854 page = 1 ,
822855 accessPoints = getAccessPointsPaginated (0 ),
@@ -825,13 +858,15 @@ def catalog():
825858 else :
826859 page = int (page )
827860 return render_template (
828- "paginated.html" ,
861+ "paginated.html" ,
862+ authsession = get_logged_in_user (),
829863 page = (page + 1 ),
830864 murals = getAccessPointsPaginated (page )
831865 )
832866 else :
833867 return render_template (
834868 "filtered.html" ,
869+ authsession = get_logged_in_user (),
835870 pageTitle = f"Query - { query } " ,
836871 subHeading = "Search Query" ,
837872 q = query ,
@@ -863,11 +898,53 @@ def tags():
863898def access_point (id ):
864899 if checkAccessPointExists (id ):
865900 return render_template (
866- "access_point.html" , accessPointDetails = getAccessPoint (id )
901+ "access_point.html" ,
902+ authsession = get_logged_in_user (),
903+ is_admin = check_for_admin_role (get_logged_in_user_id ()),
904+ accessPointDetails = getAccessPoint (id )
867905 )
868906 else :
869907 return render_template ("404.html" ), 404
870908
909+
910+ ########################
911+ #
912+ # region Auth
913+ #
914+ ########################
915+
916+ if auth_configured :
917+ @app .route ("/callback" , methods = ["GET" , "POST" ])
918+ def callback ():
919+ token = oauth .auth0 .authorize_access_token ()
920+ save_user_details (token )
921+ return redirect ("/" )
922+
923+
924+ @app .route ("/login" )
925+ def login ():
926+ return oauth .auth0 .authorize_redirect (
927+ redirect_uri = url_for ("callback" , _external = True )
928+ )
929+
930+
931+ @app .route ("/logout" )
932+ def logout ():
933+ session .clear ()
934+ return redirect (
935+ "https://"
936+ + os .environ .get ("AUTH0_DOMAIN" )
937+ + "/v2/logout?"
938+ + urlencode (
939+ {
940+ "returnTo" : url_for ("home" , _external = True ),
941+ "client_id" : os .environ .get ("AUTH0_CLIENT_ID" ),
942+ },
943+ quote_via = quote_plus ,
944+ )
945+ )
946+
947+
871948"""
872949Generic error handler
873950"""
@@ -896,6 +973,24 @@ def wrapped(**kwargs):
896973 return wrapped
897974
898975
976+ def requires_admin (f ):
977+ """Determines if the user has the correct admin permissions for an action
978+ """
979+ @wraps (f )
980+ def decorated (* args , ** kwargs ):
981+ user = get_logged_in_user_id ()
982+ is_admin = check_for_admin_role (user )
983+
984+ if user is None :
985+ raise Exception ("There must be a user signed in to perform this action" ,
986+ 400 )
987+ elif not is_admin :
988+ raise Exception ("Authorizing user does not have the correct role to perform this action" ,
989+ 401 )
990+ else :
991+ return f (* args , ** kwargs )
992+ return decorated
993+
899994
900995########################
901996#
@@ -994,7 +1089,7 @@ def email_webhook():
9941089
9951090
9961091@app .route ("/add_ticket/<item_id>" , methods = ["POST" ])
997- @debug_only
1092+ @requires_admin
9981093def add_ticket (item_id ):
9991094 if not checkAccessPointExists (item_id ):
10001095 return "Not found" , 404
@@ -1298,7 +1393,7 @@ def uploadImageResize(file, access_point_id, count, is_thumbnail=False):
12981393
12991394
13001395@app .route ("/edit/<id>" )
1301- @debug_only
1396+ @requires_admin
13021397def edit (id ):
13031398
13041399 if checkAccessPointExists (id ):
@@ -1318,7 +1413,7 @@ def edit(id):
13181413
13191414
13201415@app .route ("/admin" )
1321- @debug_only
1416+ @requires_admin
13221417def admin ():
13231418 return render_template (
13241419 "admin.html" ,
@@ -1396,7 +1491,7 @@ def submit_suggestion():
13961491
13971492
13981493@app .route ("/deleteTag/<name>" , methods = ["POST" ])
1399- @debug_only
1494+ @requires_admin
14001495def deleteTag (name ):
14011496 deleteTagGivenName (name )
14021497 return redirect ("/admin" )
@@ -1408,7 +1503,7 @@ def deleteTag(name):
14081503
14091504
14101505@app .route ("/delete/<id>" , methods = ["POST" ])
1411- @debug_only
1506+ @requires_admin
14121507def delete (id ):
14131508 if checkAccessPointExists (id ):
14141509 deleteAccessPointEntry (id )
@@ -1424,7 +1519,7 @@ def delete(id):
14241519
14251520
14261521@app .route ("/editaccesspoint/<id>" , methods = ["POST" ])
1427- @debug_only
1522+ @requires_admin
14281523def editAccessPoint (id ):
14291524 m = db .session .execute (
14301525 db .select (AccessPoint ).where (AccessPoint .id == id )
@@ -1479,7 +1574,7 @@ def editAccessPoint(id):
14791574
14801575
14811576@app .route ("/editTag/<name>" , methods = ["POST" ])
1482- @debug_only
1577+ @requires_admin
14831578def edit_tag (name ):
14841579 t = db .session .execute (db .select (Tag ).where (Tag .name == name )).scalar_one ()
14851580 t .description = request .form ["description" ]
@@ -1494,7 +1589,7 @@ def edit_tag(name):
14941589
14951590
14961591@app .route ("/edittitle/<id>" , methods = ["POST" ])
1497- @debug_only
1592+ @requires_admin
14981593def editTitle (id ):
14991594 m = db .session .execute (
15001595 db .select (AccessPoint ).where (AccessPoint .id == id )
@@ -1511,7 +1606,7 @@ def editTitle(id):
15111606
15121607
15131608@app .route ("/editimage/<id>" , methods = ["POST" ])
1514- @debug_only
1609+ @requires_admin
15151610def editImage (id ):
15161611 image = db .session .execute (db .select (Image ).where (Image .id == id )).scalar_one ()
15171612
@@ -1533,7 +1628,7 @@ def editImage(id):
15331628
15341629
15351630@app .route ("/makethumbnail" , methods = ["POST" ])
1536- @debug_only
1631+ @requires_admin
15371632def makeThumbnail ():
15381633 access_point_id = request .args .get ("accesspointid" , None )
15391634 image_id = request .args .get ("imageid" , None )
@@ -1563,7 +1658,7 @@ def makeThumbnail():
15631658
15641659
15651660@app .route ("/detachimage/<image_id>/from/<item_id>" , methods = ["POST" ])
1566- @debug_only
1661+ @requires_admin
15671662def detachImageEndpoint (image_id , item_id ):
15681663
15691664 detachImageByID (image_id , item_id )
@@ -1579,7 +1674,7 @@ def detachImageEndpoint(image_id, item_id):
15791674
15801675
15811676@app .route ("/export" , methods = ["POST" ])
1582- @debug_only
1677+ @requires_admin
15831678def export_data ():
15841679 public = bool (int (request .args .get ("p" )))
15851680 now = datetime .now ()
@@ -1600,7 +1695,7 @@ def export_data():
16001695
16011696
16021697@app .route ("/import" , methods = ["POST" ])
1603- @debug_only
1698+ @requires_admin
16041699def import_data ():
16051700 return ("" , 501 )
16061701
@@ -1611,7 +1706,7 @@ def import_data():
16111706
16121707
16131708@app .route ("/addTag" , methods = ["POST" ])
1614- @debug_only
1709+ @requires_admin
16151710def add_tag ():
16161711 tag = Tag (name = request .form ["name" ], description = "" )
16171712
@@ -1627,7 +1722,7 @@ def add_tag():
16271722
16281723
16291724@app .route ("/uploadimage/<id>" , methods = ["POST" ])
1630- @debug_only
1725+ @requires_admin
16311726def uploadNewImage (id ):
16321727 count = db .session .execute (
16331728 db .select (func .count ()).where (ImageAccessPointRelation .access_point_id == id )
@@ -1646,7 +1741,7 @@ def uploadNewImage(id):
16461741
16471742
16481743@app .route ("/upload/elevator" , methods = ["POST" ])
1649- @debug_only
1744+ @requires_admin
16501745def upload ():
16511746
16521747 # Step 1: Find the building by its number
0 commit comments