# HG changeset patch # User Nathan Phillip Brink # Date 2012-11-10 00:51:05 # Node ID 8d55669e05c278f67c8cc9fa19632267b3f26306 # Parent 182b6be0813e6840875a60407327e20e1edce990 Add some hacks to support pure HTTPS for WebAdvisor XSS automatic registration hack. Instead of using a JSONP callback and PHP sessions to store the TOKENIDX, the XSS script now just updates the URL parameter for the WebAdvisor login page so that the TOKENIDX will be transferred to webadvisor.php by a GET variable right when it is needed instead of asynchronously. A separate hack to help support HTTPS is that automatic uploading of assets to Amazon S3 (which has HTTPS access) allows the XSS script to be served over HTTPS. This eliminates the browser warnings about accessing mixed secure/insecure content and, thus, hopefully supports browsers which automatically block insecure content on secure pages. diff --git a/.hgignore b/.hgignore --- a/.hgignore +++ b/.hgignore @@ -2,6 +2,7 @@ style: regex # ignore all saved schedules, but keep track of the .keep file. ^saved_schedules/[^.] +^saved_schedules/\.s3_cache # ignore all of cache except for the .keep file ^cache/[^.] diff --git a/inc/class.page.php b/inc/class.page.php --- a/inc/class.page.php +++ b/inc/class.page.php @@ -743,6 +743,86 @@ class page /** * \brief + * Resolve an SSL address for a static asset. + * + * This is pretty much a hack in support of another hack. I need to + * provide some assets over SSL; if the local server doesn’t support + * that properly (such as by not having a properly signed SSL + * certificate), a web-storage backend can be used instead. This can + * only be used with static content. + * + * \param $uri + * The path to a static file which needs to be served over SSL. + */ + public static function uri_resolve_sslasset($uri, $type) + { + global $s3_bucket, $s3_accesskey, $s3_secretkey; + + $testuri = page::uri_resolve($uri); + if (!strncmp($testuri, 'https://', strlen('https://'))) + /* + * The user is already accessing this page as SSL, so serving + * another asset over the same channel will not appear any less + * trusted to the user. + */ + return $testuri; + + /* + * Use an external service if configured. + */ + if (!empty($s3_bucket) && !empty($s3_accesskey) && !empty($s3_secretkey)) + { + /* + * Load S3 cache. + */ + $dirpath = dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR; + $s3_cache_path = $dirpath . 'saved_schedules' . DIRECTORY_SEPARATOR . '.s3_cache'; + $s3_cache = @unserialize(file_get_contents($s3_cache_path)); + if (empty($s3_cache)) + $s3_cache = array(); + + $path = $dirpath . $uri; + $sha1 = sha1_file($path); + + if (empty($s3_cache[$sha1])) + { + @include 'S3.php'; + if (class_exists('S3')) + { + $s3 = new S3($s3_accesskey, $s3_secretkey); + $bucket = $s3->getBucket($s3_bucket); + if ($bucket === FALSE) + $bucket = $s3->putBucket($s3_bucket, S3::ACL_PUBLIC_READ); + if ($bucket !== FALSE) + if ($s3->putObject(S3::inputFile($path), $s3_bucket, $sha1, S3::ACL_PUBLIC_READ, array(), array('Content-Type' => $type))) + { + $s3_cache[$sha1]['uri'] = 'https://' . $s3_bucket . '.s3.amazonaws.com/' . $sha1; + file_put_contents($s3_cache_path, serialize($s3_cache), LOCK_EX); + } + } + } + if (!empty($s3_cache[$sha1]['uri'])) + return $s3_cache[$sha1]['uri']; + } + + if (!strncmp($testuri, 'http://', strlen('http://'))) + { + /* Test if we can create a local HTTPS connection… */ + $curl = curl_init(); + curl_setopt($curl, CURLOPT_USERAGENT, SP_PACKAGE_NAME . '/' . SP_PACKAGE_VERSION); + $testuri2 = 'https' . substr($testuri, strlen('https')); + curl_setopt($curl, CURLOPT_URL, $testuri2); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE); + $result = curl_exec($curl); + curl_close($curl); + if (!empty($result) && sha1($result) === $sha1) + return $testuri2; + } + return $testuri; + } + + /** + * \brief * Form a query string from a map. * * \param $query diff --git a/inc/config.inc.example b/inc/config.inc.example --- a/inc/config.inc.example +++ b/inc/config.inc.example @@ -169,3 +169,19 @@ */ /* $input_warning_banner = FALSE; */ /* $input_warning_banner = '

Warning: BIOL-111\'s autocomplete data does not include all sections. Please use schedule 7578 to get a correct BIOL-111 entry.

'; */ + +/** + * \brief + * Amazon S3 credentials for best-effort SSL hack. + * + * Setting S3 credentials will enable slate_permutate to serve certain + * content over an HTTPS connection for the (old) WebAdvisor XSS for + * automatic registration. You must specify a bucket name which is + * either nonexistent (available) or already owned by your S3 account. + * + * You must have installed the amazon-s3-php-class PHP library to take + * advantage of this feature. + */ +/* $s3_accesskey = ''; */ +/* $s3_secretkey = ''; */ +/* $s3_bucket = 'myslatepermutate'; */ diff --git a/scripts/webadvisor_tokenidx.js b/scripts/webadvisor_tokenidx.js --- a/scripts/webadvisor_tokenidx.js +++ b/scripts/webadvisor_tokenidx.js @@ -56,11 +56,21 @@ var slate_permutate_input_login; sp_err.setAttribute('style', 'color: grey;'); /* Inform home base of the newly generated 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); + var TOKENIDX = getURLParameter(g_tokenIdx); + if (getURLParameter('URL').indexOf('TOKENIDX%3d' + TOKENIDX) === -1) + { + /* %26 = &, setURLParameter doesn’t handle escaping */ + setURLParameter('URL', getURLParameter('URL') + '%26TOKENIDX%3d' + TOKENIDX); + window.location.href = getBaseURI(window.location.href) + '?' + getURLParameters(); + } + else + { + /* Report to the user that they’ve been fixed up */ + slate_permutate_input_login.setAttribute('value', 'LOG IN'); + slate_permutate_input_login.removeAttribute('disabled'); + sp_err.replaceChild(document.createTextNode('Slate Permutate has acquired WebAdvisor TOKENIDX, ready for login.'), sp_err.firstChild); + sp_err.setAttribute('style', 'color: green;'); + } } else { @@ -86,16 +96,3 @@ var slate_permutate_input_login; }; } })(); - -function slate_permutate_token_callback(result) -{ - if (result) - { - slate_permutate_input_login.setAttribute('value', 'LOG IN'); - slate_permutate_input_login.removeAttribute('disabled'); - - var sp_err = document.getElementById('sp_err'); - sp_err.replaceChild(document.createTextNode('Slate Permutate has acquired WebAdvisor TOKENIDX, ready for login.'), sp_err.firstChild); - sp_err.setAttribute('style', 'color: green;'); - } -} diff --git a/webadvisor.php b/webadvisor.php --- a/webadvisor.php +++ b/webadvisor.php @@ -20,30 +20,6 @@ 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"; - if ($jsonp && !empty($_GET['destination'])) - echo 'document.location.href = ' . json_encode($_GET['destination']) . ";\n"; - exit; - } - $page = page::page_create('WebAdvisor'); $school = $page->get_school(); @@ -121,7 +97,7 @@ function webadvisor_login($page, array $ $login_form_uri = $school['webadvisor_url'] . '?LASTTOKEN=NULL&SS=LGRQ&URL=' . rawurlencode($dest) . '&SP_CALLBACK=' . rawurlencode($tokenidx_callback) - . '&ERROR=' . rawurlencode('Slate Permutate loading… (automatic registration may not be working)'); + . '&ERROR=' . rawurlencode('Slate Permutate loading… (automatic registration may not be working)'); redir($login_form_uri); } @@ -134,18 +110,7 @@ function redir($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'])) +if (empty($_GET['TOKENIDX'])) { /* * Get a token for the ST-WERG form and have the user perform the @@ -163,7 +128,7 @@ if (empty($_SESSION['webadvisor_TOKENIDX * (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']; +$TOKENIDX = $_GET['TOKENIDX']; $page->head(); echo '
' . PHP_EOL; echo '

';