# HG changeset patch # User Nathan Phillip Brink # Date 2012-04-28 18:00:12 # Node ID bc7ff69ca58988f0c98790e50219a23b8b897b13 # Parent 976b47d1e19e75e01e68f301dc745bc7d0d6cc7b Add support for automatically registering for courses when using older WebAdvisor 2.x installations, particularly at Calvin College. diff --git a/ajax.php b/ajax.php --- a/ajax.php +++ b/ajax.php @@ -97,7 +97,7 @@ if (isset($_REQUEST['school_registration $html = school_registration_html($page, $school, $courses); if (empty($html)) slate_permutate_json_error('School\'s registration information producer returned no data.'); - slate_permutate_json_success(array('html' => $html)); + slate_permutate_json_success(is_array($html) ? $html : array('html' => $html)); } slate_permutate_json_error('Unrecognized command.'); diff --git a/school.d/calvin.inc b/school.d/calvin.inc --- a/school.d/calvin.inc +++ b/school.d/calvin.inc @@ -32,6 +32,7 @@ function calvin_info() 'example_course_id' => 'CS-232', 'registration_url' => 'https://portal.calvin.edu/Pages/WebAdvisor.aspx?title=Express+Registration&PID=ST-WERG', 'student_address' => 'Knight', + 'webadvisor_url' => 'https://resources.calvin.edu/selfservice/WebAdvisor', ); } diff --git a/school.d/default.inc b/school.d/default.inc --- a/school.d/default.inc +++ b/school.d/default.inc @@ -74,6 +74,12 @@ function default_registration_html(Page $link_text = $school['name'] . '\'s website'; } + $synonyms = array(); + foreach ($courses as $course) + foreach ($course as $course_slot) + foreach ($course_slot as $section) + $synonyms[] = $section->getSynonym(); + $html = '' . '

' . PHP_EOL . ' Enter these codes into ' . htmlentities($school['name']) . '\'s online course registration' . PHP_EOL @@ -81,10 +87,19 @@ function default_registration_html(Page . ' to register for classes:' . PHP_EOL . '

' . PHP_EOL . ' ' . PHP_EOL; - return $html; + $ret = array('html' => $html); + + if (!empty($school['webadvisor_url'])) + { + $webadvisor_register_url = 'webadvisor.php?school=' . $school['id'] . '§ions=' . implode(',', $synonyms); + $ret['html'] = '' + . '

Automatically register

' . PHP_EOL + . $ret['html']; + $ret['location'] = page::uri_resolve($webadvisor_register_url); + } + + return $ret; } diff --git a/scripts/displayTables.js b/scripts/displayTables.js --- a/scripts/displayTables.js +++ b/scripts/displayTables.js @@ -124,7 +124,12 @@ jQuery(document).ready( function() var tab_course_data_json = jQuery(tab_course_data_json_selector).text(); var tab_course_data = eval('(' + tab_course_data_json + ')'); - slate_permutate_load(jQuery('#regDialog-content'), {school_registration_html: true, courses: tab_course_data}); + slate_permutate_load(jQuery('#regDialog-content'), {school_registration_html: true, courses: tab_course_data}, + function(target, data) { + target.html(data.html); + if (data.location) + document.location.href = data.location; + }); jQuery("#regDialog").dialog('open'); diff --git a/scripts/webadvisor_tokenidx.js b/scripts/webadvisor_tokenidx.js new file mode 100644 --- /dev/null +++ b/scripts/webadvisor_tokenidx.js @@ -0,0 +1,70 @@ +/* + * Assumes that WebAdvisor_scripts.js for WebAdvisor-2.x is loaded, + * displayFormHTML() or something was called and thus + * readURLParameters() was called. We attempt to extract TOKENIDX and + * asynchronously inform slate_permutate about it. We currently assume + * we're on a login form too. + */ + +var slate_permutate_input_login; + +(function() { + var slate_permutate_onload = function() { + + /* + * Override the login form's submission handler to catch the + * case where we're still trying to load the TOKENIDX or + * something else. + */ + var inputs = document.getElementsByTagName('input'); + for (var i = 0; i < inputs.length; i ++) + { + slate_permutate_input_login = inputs.item(i); + if (slate_permutate_input_login.getAttribute('name') == 'SUBMIT2') + break; + } + slate_permutate_input_login.setAttribute('value', 'Discovering TOKENIDX...'); + slate_permutate_input_login.setAttribute('disabled', 'disabled'); + + /* + * Discover the TOKENIDX if it's available. + */ + if (containsParameter(g_tokenIdx)) + { + var TOKENIDX = getURLParameter(g_tokenIdx); + var myscript = document.createElement('script'); + myscript.setAttribute('type', 'text/javascript'); + myscript.setAttribute('src', decodeURIComponent(getURLParameter('SP_CALLBACK')) + 'callback=slate_permutate_token_callback&TOKENIDX=' + TOKENIDX); + document.getElementsByTagName('head').item(0).appendChild(myscript); + } + else + { + alert('Unable to discover TOKENIDX. You must register manually.'); + } + } + + /* + * Register to run after either of getWindowHTML(), + * setWindowHTML(), or displayFormHTML() have been run. These are + * run after onload="", so they are required if we're to wait for + * the DOM to load... + */ + var funcs = ['getWindowHTML', 'setWindowHTML', 'displayFormHTML']; + for (var i = 0; i < funcs.length; i ++) + { + var func = window[funcs[i]]; + window[funcs[i]] = function() { + func(); + slate_permutate_onload(); + }; + } +})(); + +function slate_permutate_token_callback(result) +{ + if (result) + { + slate_permutate_input_login.setAttribute('value', 'LOG IN'); + slate_permutate_input_login.removeAttribute('disabled'); + } +} diff --git a/webadvisor.php b/webadvisor.php new file mode 100644 --- /dev/null +++ b/webadvisor.php @@ -0,0 +1,237 @@ +. + */ + +require_once('inc/class.page.php'); + +/* + * Handle the scripts/webadvisor_tokenidx.js making a TOKENIDX + * callback, storing that TOKENIDX in our SESSION for later use. + */ +if (!empty($_GET['TOKENIDX'])) + { + page::session_start(); + + $_SESSION['webadvisor_TOKENIDX'] = $_GET['TOKENIDX']; + + $result = 'received ' . $_GET['TOKENIDX']; + + header('Content-Type: text/javascript; charset=utf-8'); + + if ($jsonp = !empty($_GET['callback'])) + echo $_GET['callback'] . '('; + echo json_encode($result); + if ($jsonp) + echo ");\n"; + exit; + } + +$page = page::page_create('WebAdvisor'); +$school = $page->get_school(); + +if (empty($school['webadvisor_url'])) + { + if (!empty($school['registration_url']) && preg_match(',(.*/WebAdvisor),', $school['registration_url'], $matches)) + $school['webadvisor_url'] = $matches[1]; + else + $school['webadvisor_url'] = $school['url'] . 'WebAdvisor'; + } + +/** + * \brief + * Calculate the URI necessary for logging into WebAdvisor. + * + * \param $school + * The school. + * \param $dest + * The URI to visit after the user has logged into WebAdvisor and + * the TOKENIDX has been communicated to $tokenidx_callback. + * \param $tokenidx_callback + * A JSONP-compatible callback which must be passed the TOKENIDX + * parameter the WebAdvisor is using. Treat as if is terminated with + * a `?' -- i.e., just append the querystring without the `?' to + * this URI when constructing the callback. To use, for example, in + * JavaScript you may create a DOMElement 'script' with attributes + * type="text/javascript" and + * src="$tokenidx_callback?callback=jsonp_callback&TOKENIDX=". When jsonp_callback gets called, your script knows + * that $dest may be returned to. Don't forget to allow the user to + * log in first. This is normally done by setting SP_CALLBACK GET + * variable to this value inserting the + * scripts/webadvisor_tokenidx.js script into the WebAdvisor login + * page using cross-site-scripting HTML injection such as through + * the ERROR GET parameter. + * \return + * Just ensure that $tokenidx_callback gets called; do not return + * except by redirecting to $dest. + */ +function webadvisor_login($page, array $school, $dest, $tokenidx_callback) +{ + if (strpos($dest, '?') !== FALSE) + $dest .= '&'; + else + $dest .= '?'; + $dest .= 'from_webadvisor'; + + $webadvisor_login_func = $school['id'] . '_webadvisor_login'; + if (function_exists($webadvisor_login_func)) + $webadvisor_login_func($school, $dest); + + /* + * The hack we are using is that somehow TOKENIDX=&SS=LGRQ&URL= + * will both initialize the user's browser with a token cookie and + * then redirect to URL. Trying to use the proper way of loading the + * LGRQ (using TYPE=P&PID=UT-LGRQ&PROCESS=-XUTAUTH01) doesn't work + * because it drops and ignores our URL parameter, leaving the user + * at the KV site. No other URL I've fiddled with seems to be able + * to do this combination of logging in and returning the user to us + * or a URI of our choosing. Once the user's browser has been + * initialized with a TOKENIDX, loading the page + * SS=LGRQ&URL=&ERROR= will preserve the ERROR= + * necessary for our XSS and insert it into the login page. + * + * HOWEVER, if the browser already has a TOKENIDX-related cookie, + * then visiting TOKENIDX=&SS=LGRQ&URL= will cause WebAdvisor + * to keep redirecting to itself infinitely. Similarly, if the + * browser does not yet have a TOKENIDX-related cookie, + * SS=LGRQ&URL= will redirect the user to URL without giving + * the user a cookie. Thus, our strategy is: + * + * 1. Send the user to + * SS=LGRQ&URL=&SP_CALLBACK=&ERROR=. In + * this case, the URL will be set to have `from_webadvisor' as a + * GET parameter and ERROR will be set to the appropriate XSS for + * the normal login form. Thus, if the user does not have a + * token, he will be directed here and sent to step #2 to get a + * token. Otherwise, the user will have a jump start (already + * having TOKENIDX cookies) and communicate his token to us while + * logging in. + * + * 2. If webadvisor.php is called with from_webadvisor, that means + * one of two things. It might mean that webadvisor_tokenidx.js + * was called successfully and we have the webadvisor TOKENIDX + * stored in our session. In that case, the user's browser + * already had a WebAdvisor TOKENIDX before we did #1; also, this + * function won't be called in that case because this function is + * only called if TOKENIDX is unknown. Thus, we don't know the + * TOKENIDX, meaning that we need to request that the WebAdvisor + * installation allocate a TOKENIDX for the user and _then_ + * proceed directly to the login page to send us TOKENIDX. + */ + + $login_form_uri = $school['webadvisor_url'] . '?SS=LGRQ&URL=' . rawurlencode($dest) + . '&SP_CALLBACK=' . rawurlencode($tokenidx_callback) + . '&ERROR=' . rawurlencode(''); + + if (isset($_GET['from_webadvisor'])) + /* + * Case 2, infer that browser needs TOKENIDX cookies _and_ that + * the following URI won't cause endless looping + * (hopefully). Unfortunately, this process is not reentrant. + */ + redir($school['webadvisor_url'] . '?TOKENIDX=&SS=LGRQ&URL=' . rawurlencode($login_form_uri)); + + /* + * Case 1, assume that the user has a TOKENIDX cookie _but_ make + * provisions ($dest has from_webadvisor in it) for needing to + * allocate that cookie. + */ + redir($login_form_uri); + + return array( + /* 'preload' => $school['webadvisor_url'] . '?TYPE=P&PID=UT-LGRQ&PROCESS=-XUTAUTH01&URL=', */ + 'uri' => $school['webadvisor_url'] . '?SS=LGRQ&URL=' . rawurlencode($login_form_uri), + ); +} + +function redir($dest) +{ + header('HTTP/1.1 302 Found'); + header('Location: ' . $dest); + header('Content-Type: text/plain; charset=utf-8'); + echo 'Location: ' . $dest; + exit; +} + +/* + * If the page load was not a redirection from webadvisor, we must + * clear our local cache of TOKENIDX's value. We need to get a new + * token because we can't guess what SS= value the ST-WERG form will + * take unless if we start with a new TOKENIDX which doesn't have any + * SSes yet. Also, the old token may have (very likely) expired + * because of the short login timeout. + */ +if (!isset($_GET['from_webadvisor'])) + unset($_SESSION['webadvisor_TOKENIDX']); + +if (empty($_SESSION['webadvisor_TOKENIDX'])) + { + /* + * Get a token for the ST-WERG form and have the user perform the + * WebAdmin-specific login. This can only be done after the login form + * has an SS allocated for it. + */ + webadvisor_login($page, $school, page::uri_resolve('webadvisor.php') . '?r=' . rand() + . '§ions=' . rawurlencode(empty($_GET['sections']) ? '' : $_GET['sections']) + . '&school=' . rawurlencode($school['id']), + page::uri_resolve('webadvisor.php?')); + } + +/* + * Use the hopefully-still-valid TOKENIDX to initialize an ST-WERG + * (STudent Web[A]dvisor Express ReGistration) form. When that form is + * iniailized, assume that it has SS=1 and submit the form. &APP=ST + */ +$TOKENIDX = $_SESSION['webadvisor_TOKENIDX']; +$page->head(); +echo '
' . PHP_EOL; +echo '

'; + +$uri = $school['webadvisor_url'] . '?TOKENIDX=' . $TOKENIDX . '&TYPE=P&PID=ST-WERG'; +$onload_html = '="' . htmlentities('javascript:document.getElementById(\'sp-webadvisor-form\').submit()', ENT_QUOTES) . '"'; +echo ' Loading WebAdvisor Express Registration form (ST-WERG)…' . PHP_EOL; +echo ' If you are not redirected after 16 seconds, you may try: ' . PHP_EOL; + +$sections = explode(',', empty($_GET['sections']) ? '' : $_GET['sections']); +echo ' ' . PHP_EOL; +echo ' ' . PHP_EOL; +for ($i = 1; $i <= 5; $i ++) + echo //' ' . PHP_EOL; + ' ' . PHP_EOL; +$course_num = 1; +foreach ($sections as $course) + { + echo ' ' . PHP_EOL; + for ($i = 2; $i <= 5; $i ++) + echo ' ' . PHP_EOL; + $course_num ++; + } +while ($course_num < 10) + { + for ($i = 1; $i <= 5; $i ++) + echo ' ' . PHP_EOL; + $course_num ++; + } +echo ' ' . PHP_EOL; +echo ' ' . PHP_EOL; +echo '

'; +echo '
'; + +$page->foot();