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 '