diff --git a/api/ltiextroster.php b/api/ltiextroster.php
new file mode 100644
index 0000000000..72cc211d3d
--- /dev/null
+++ b/api/ltiextroster.php
@@ -0,0 +1,183 @@
+dbprefix}lti_key AS K
+ JOIN {$CFG->dbprefix}lti_context AS C ON K.key_id = C.key_id
+ JOIN {$CFG->dbprefix}lti_link AS L ON C.context_id = L.context_id
+ WHERE K.key_id = :KID AND C.context_id = :CID AND
+ L.link_id = :LID
+ LIMIT 1";
+
+$PDOX = LTIX::getConnection();
+
+$row = $PDOX->rowDie($sql, array(
+ ":KID" => $key_id,
+ ":CID" => $context_id,
+ ":LID" => $link_id)
+ );
+
+if ( ! $row ) {
+ Net::send403('Could not locate sourcedid row');
+ return;
+}
+
+$oauth_consumer_key_up = $row['key_key'];
+$oauth_consumer_secret_up = LTIX::decrypt_secret($row['secret']);
+$placementsecret = $row['placementsecret'];
+$settingsstr = $row['settings'];
+try {
+ $settings = json_decode($settingsstr);
+ $oauth_consumer_key = $settings->key;
+ $oauth_consumer_secret = LTIX::decrypt_secret($settings->secret);
+} catch(Exception $e) {
+ Net::send403('Could not parse link settings');
+ return;
+
+}
+
+$allowRoster = isset($settings->allowRoster) && $settings->allowRoster;
+if ( ! $allowRoster ) {
+ Net::send403('Roster sharing not enabled for this context');
+ return;
+}
+
+$sendName = isset($settings->sendName) && $settings->sendName;
+$sendEmail = isset($settings->sendEmail) && $settings->sendEmail;
+
+$sourcebase = $key_id . '::' . $context_id . '::' . $link_id . '::';
+$plain = $sourcebase . $placementsecret;
+$sig = U::lti_sha256($plain);
+
+if ( $incoming_sig != $sig ) {
+ Net::send403('Invalid sourcedid signature');
+ return;
+}
+
+if ( strlen($oauth_consumer_key) < 1 || strlen($oauth_consumer_secret) < 1 ) {
+ Net::send403("Missing key/secret key=$oauth_consumer_key");
+ return;
+}
+
+$post_key = U::get($_POST, 'oauth_consumer_key');
+if ( $post_key != $oauth_consumer_key ) {
+ Net::send403("Mismatch oauth_consumer_key does not match $post_key");
+ return;
+}
+
+$cur_page_url = LTIX::curPageUrl();
+$row_secret = $row['secret'];
+
+$valid = LTI::verifyKeyAndSecret($post_key,$row_secret,$cur_page_url, $_POST);
+if ( is_array($valid) ) {
+ Net::send403($valid[0], $valid[1]);
+ return;
+}
+
+$sql = "SELECT M.role, M.user_id, U.displayname, U.email
+ FROM {$CFG->dbprefix}lti_membership AS M
+ JOIN {$CFG->dbprefix}lti_user AS U ON M.user_id = U.user_id
+ WHERE M.context_id = :CID AND M.deleted = 0";
+
+$rows = $PDOX->allRowsDie($sql, array(":CID" => $context_id));
+
+/*
+
+
+ basic-lis-readmembershipsforcontext
+
+
+ 7d69999997
+ hir@ppp.com
+ Sakai
+ Hirouki Sakai
+ Hirouki
+ hirouki
+ Instructor
+ 422e099999-45dc-a4e5-196d3f749782
+
+
+ 7d65a1b397
+ csev@ppp.co
+ Severance
+ Charles Severance
+ Charles
+ csev
+ Learner
+ 422e09b8-b53a-45dc-a4e5-196d3f749782
+
+
+
+ Success
+ fullsuccess
+ Status
+
+
+ */
+
+header('Content-Type: application/xml; charset=utf-8');
+
+?>
+
+ basic-lis-readmembershipsforcontext
+
+\n");
+ echo(" ".$row['user_id']."\n");
+ if ( $row['role'] >= 1000 ) {
+ echo(" Instructor\n");
+ } else {
+ echo(" Learner\n");
+ }
+ if ( $sendEmail && is_string($email) && strlen($email) > 0 ) {
+ echo(" ");
+ echo(htmlspecialchars($email, ENT_XML1, 'UTF-8'));
+ echo("\n");
+ }
+ if ( $sendName && is_string($displayname) && strlen($displayname) > 0 ) {
+ echo(" ");
+ echo(htmlspecialchars($displayname, ENT_XML1, 'UTF-8'));
+ echo("\n");
+ }
+ echo(" \n");
+}
+?>
+
+
+ Success
+ fullsuccess
+ Status
+
+
diff --git a/api/poxresult.php b/api/poxresult.php
index 7dbd45dc54..7d1f4a279d 100644
--- a/api/poxresult.php
+++ b/api/poxresult.php
@@ -12,6 +12,9 @@
$request_headers = OAuthUtil::get_headers();
$hct = U::get($request_headers,'Content-Type', U::get($_SERVER, 'CONTENT_TYPE'));
+// Get skeleton response
+$response = LTI::getPOXResponse();
+
if (strpos($hct,'application/xml') === false ) {
header('Content-Type: text/plain');
@@ -108,9 +111,6 @@
return;
}
-// Get skeleton response
-$response = LTI::getPOXResponse();
-
if ( strlen($oauth_consumer_key) < 1 || strlen($oauth_consumer_secret) < 1 ) {
echo(sprintf($response,uniqid(),'failure', "Missing key/secret key=$oauth_consumer_key",$message_ref,"",""));
return;
diff --git a/vendor/tsugi/lib/src/Util/Net.php b/vendor/tsugi/lib/src/Util/Net.php
index a59a8b9a65..870474555f 100644
--- a/vendor/tsugi/lib/src/Util/Net.php
+++ b/vendor/tsugi/lib/src/Util/Net.php
@@ -433,15 +433,31 @@ public static function bodyCurl($url, $method, $body, $header) {
/**
* Send a 403 header
*/
- public static function send403() {
- header("HTTP/1.1 403 Forbidden");
+ public static function send403($msg=null, $detail=null) {
+ if ( headers_sent() ) {
+ echo("Headers sent - they would be:\n");
+ echo("HTTP/1.1 403 Forbidden"."\n");
+ if ( is_string($msg) ) echo("X-Error-Message: ".$msg."\n");
+ if ( is_string($detail) ) echo("X-Error-Detail: ".$detail."\n");
+ } else {
+ header("HTTP/1.1 403 Forbidden");
+ if ( is_string($msg) ) header("X-Error-Message: ".$msg);
+ if ( is_string($detail) ) header("X-Error-Detail: ".$detail);
+ }
}
/**
* Send a 400 (Malformed request) header
*/
- public static function send400($msg='Malformed request') {
- header("HTTP/1.1 400 ".$msg);
+ public static function send400($msg='Malformed request', $detail=null) {
+ if ( headers_sent() ) {
+ echo("Headers sent - they would be:\n");
+ echo("HTTP/1.1 400 ".$msg."\n");
+ if ( is_string($detail) ) echo("X-Error-Detail: ".$detail."\n");
+ } else {
+ header("HTTP/1.1 400 ".$msg);
+ if ( is_string($detail) ) header("X-Error-Detail: ".$detail);
+ }
}
/**
@@ -449,7 +465,7 @@ public static function send400($msg='Malformed request') {
*
* Handle being behind a load balancer or a proxy like Cloudflare.
* This will often return NULL when talking to localhost to make sure
- * to test code using this ona real IP address.
+ * to test code using this on a real IP address.
*
* Adapted from: https://www.chriswiegman.com/2014/05/getting-correct-ip-address-php/
* With some additional explode goodness via: http://stackoverflow.com/a/25193833/1994792