1+ import pickle
2+ from urllib .parse import quote_plus
13from flask import (render_template , flash , redirect , session , request , url_for ,
24 make_response , send_from_directory )
35import flask .json
911 AdmissionWaiversForm , FinalForm ,
1012 ReceiptUploadForm , LoginForm , SignupForm ,
1113 ForgottenPasswordForm , NewPasswordForm ,
12- AIS2CookieForm , AIS2SubmitForm )
14+ AIS2SubmitForm )
1315from werkzeug .security import generate_password_hash , check_password_hash
1416from flask_login import login_user , login_required , logout_user , current_user
1517import datetime
@@ -98,9 +100,17 @@ def save_form(form):
98100 save_current_session_to_DB ()
99101
100102
103+ def current_session_to_json ():
104+ # Don't store internal keys used by flask (e.g. "_flashes"), flask-login
105+ # (e.g. "_user_id", "_fresh", "_id") and our admin ("_votr_context_*") in
106+ # the SQL database.
107+ filtered = { k : v for k , v in session .items () if not k .startswith ('_' ) }
108+ return flask .json .dumps (filtered )
109+
110+
101111def save_current_session_to_DB ():
102112 app = ApplicationForm .query .filter_by (user_id = current_user .id ).first ()
103- app .application = flask . json . dumps ( dict ( session ) )
113+ app .application = current_session_to_json ( )
104114 db .session .commit ()
105115
106116
@@ -126,7 +136,7 @@ def load_session():
126136 if 'application_submitted' in session :
127137 del session ['application_submitted' ]
128138
129- app .application = flask . json . dumps ( dict ( session ) )
139+ app .application = current_session_to_json ( )
130140 db .session .commit ()
131141
132142 session .modified = True
@@ -469,7 +479,7 @@ def final():
469479 save_form (form )
470480
471481 session ['application_submitted' ] = True
472- app_form .application = flask . json . dumps ( dict ( session ) )
482+ app_form .application = current_session_to_json ( )
473483 app_form .state = ApplicationStates .submitted
474484 app_form .submitted_at = datetime .datetime .now ()
475485 db .session .commit ()
@@ -692,7 +702,7 @@ def signup():
692702 new_application_form = ApplicationForm (user_id = new_user .id )
693703 # FIXME: band-aid for last_updated_at
694704 new_application_form .last_updated_at = datetime .datetime .now ()
695- new_application_form .application = flask . json . dumps ( dict ( session ) )
705+ new_application_form .application = current_session_to_json ( )
696706 db .session .add (new_application_form )
697707 db .session .commit ()
698708
@@ -794,7 +804,7 @@ def create_or_get_user_and_login(site, token, name, surname, email):
794804 new_application_form = ApplicationForm (user_id = user .id )
795805 # FIXME: band-aid for last_updated_at
796806 new_application_form .last_updated_at = datetime .datetime .now ()
797- new_application_form .application = flask . json . dumps ( dict ( session ) )
807+ new_application_form .application = current_session_to_json ( )
798808 db .session .add (new_application_form )
799809 db .session .commit ()
800810
@@ -970,22 +980,53 @@ def admin_file_download(id, uuid):
970980 return send_from_directory (receipt_dir , file , as_attachment = True )
971981
972982
973- def create_votr_context (* , beta ):
983+ @app .route ('/admin/init_votr/<any(prod,beta):ais_instance>' )
984+ @require_remote_user
985+ def admin_init_votr (ais_instance ):
986+ next = request .args ['next' ]
987+ if not next .startswith ('/admin/' ):
988+ return 'Bad next' , 400
989+
974990 from .ais_utils import create_context
975- return create_context (
991+ votr_context = create_context (
976992 my_entity_id = app .config ['MY_ENTITY_ID' ],
977993 andrvotr_api_key = app .config ['ANDRVOTR_API_KEY' ],
978994 andrvotr_authority_token = request .environ ['ANDRVOTR_AUTHORITY_TOKEN' ],
979- beta = beta ,
995+ beta = ( ais_instance == ' beta' ) ,
980996 )
997+ # flask-session docs say it'll stop using pickle in the future.
998+ # Wrap the votr_context in our own pickle.
999+ session [f'_votr_context_{ ais_instance } ' ] = pickle .dumps (votr_context , pickle .HIGHEST_PROTOCOL )
1000+
1001+ return redirect (next )
1002+
1003+
1004+ def require_votr_context (func ):
1005+ @wraps (func )
1006+ def wrapper (* args , ** kwargs ):
1007+ ais_instance = kwargs ['ais_instance' ]
1008+ session_key = f'_votr_context_{ ais_instance } '
1009+ if not session .get (session_key ):
1010+ # First time we visited an admin page that requires votr_context?
1011+ # 1. Re-login with mod_shib to get a fresh andrvotr authority token.
1012+ # 2. Create a votr_context and store it in the flask session.
1013+ # 3. Redirect back here.
1014+ target = url_for ('admin_init_votr' , ais_instance = ais_instance , next = request .full_path )
1015+ return redirect (f'/Shibboleth.sso/Login?target={ quote_plus (target )} ' )
1016+
1017+ votr_context = pickle .loads (session [session_key ])
1018+ result = func (* args , ** kwargs , votr_context = votr_context )
1019+ session [session_key ] = pickle .dumps (votr_context , pickle .HIGHEST_PROTOCOL )
1020+ return result
1021+ return wrapper
9811022
9821023
983- @app .route ('/admin/ais_test' )
1024+ @app .route ('/admin/ais_test/<any(prod,beta):ais_instance> ' )
9841025@require_remote_user
985- def admin_ais_test ():
1026+ @require_votr_context
1027+ def admin_ais_test (ais_instance , votr_context ):
9861028 from .ais_utils import test_ais
987- ctx = create_votr_context (beta = False )
988- test_ais (ctx )
1029+ test_ais (votr_context )
9891030 return redirect (url_for ('admin_list' ))
9901031
9911032
@@ -1096,61 +1137,29 @@ def generate():
10961137 mimetype = 'text/tsv' , headers = headers )
10971138
10981139
1099- @app .route ('/admin/ais2_process/<id>' , methods = ['GET' , 'POST' ])
1140+ @app .route ('/admin/ais2_process/<any(prod,beta):ais_instance>/<any(none,fill,no_fill):process_type>/< id>' , methods = ['GET' , 'POST' ])
11001141@require_remote_user
1101- def admin_ais2_process (id ):
1142+ @require_votr_context
1143+ def admin_ais2_process (ais_instance , process_type , id , votr_context ):
11021144 application = ApplicationForm .query .get (id )
11031145
11041146 form = AIS2SubmitForm ()
1105- return send_application_to_ais2 (id , application , form ,
1106- process_type = None , beta = False )
1107-
11081147
1109- @app .route ('/admin/ais2_process/<id>/<process_type>' , methods = ['GET' , 'POST' ])
1110- def admin_ais2_process_special (id , process_type ):
1111- application = ApplicationForm .query .get (id )
1112-
1113- form = AIS2SubmitForm ()
1114- return send_application_to_ais2 (id , application , form ,
1115- process_type = process_type , beta = False )
1116-
1117-
1118- @app .route ('/admin/process/<id>' , methods = ['GET' , 'POST' ])
1119- @require_remote_user
1120- def admin_process (id ):
1121- application = ApplicationForm .query .get (id )
1122-
1123- form = AIS2SubmitForm ()
1124- return send_application_to_ais2 (id , application , form , None , beta = True )
1125-
1126-
1127- @app .route ('/admin/process/<id>/<process_type>' , methods = ['GET' , 'POST' ])
1128- def admin_process_special (id , process_type ):
1129- application = ApplicationForm .query .get (id )
1130-
1131- form = AIS2CookieForm ()
1132- return send_application_to_ais2 (id , application , form , process_type ,
1133- beta = True )
1134-
1135-
1136- def send_application_to_ais2 (id , application , form , process_type , beta = False ):
11371148 from .ais_utils import save_application_form
11381149 if form .validate_on_submit ():
1139- ctx = create_votr_context (beta = beta )
1140-
11411150 ais2_output = None
11421151 error_output = None
11431152 notes = {}
11441153
11451154 logfile = None
11461155 try :
1147- if beta :
1156+ if ais_instance == ' beta' :
11481157 import gzip
11491158 import random
11501159 logfile = gzip .open ('/tmp/log_%s_%s' % (id , random .randrange (10000 )), 'wt' , encoding = 'utf8' )
1151- ctx .logger .log_file = logfile
1160+ votr_context .logger .log_file = logfile
11521161
1153- ais2_output , notes = save_application_form (ctx ,
1162+ ais2_output , notes = save_application_form (votr_context ,
11541163 application ,
11551164 LISTS ,
11561165 id ,
@@ -1176,7 +1185,7 @@ def send_application_to_ais2(id, application, form, process_type, beta=False):
11761185
11771186 # Only update the application state of it is not sent to beta and the
11781187 # 'person_exists' note is not added
1179- if not beta and error_output is None and 'person_exists' not in notes :
1188+ if ais_instance != ' beta' and error_output is None and 'person_exists' not in notes :
11801189 application .state = ApplicationStates .processed
11811190 db .session .commit ()
11821191
@@ -1185,13 +1194,14 @@ def send_application_to_ais2(id, application, form, process_type, beta=False):
11851194 ais2_output = ais2_output ,
11861195 notes = notes , id = id ,
11871196 error_output = error_output ,
1188- beta = beta , process_type = process_type ,
1197+ ais_instance = ais_instance ,
1198+ process_type = process_type ,
11891199 session = sess ,
11901200 lists = LISTS )
11911201
11921202 return render_template ('admin_process.html' ,
11931203 form = form , id = id ,
1194- beta = beta )
1204+ ais_instance = ais_instance )
11951205
11961206
11971207@app .route ('/admin/impersonate/list' )
0 commit comments