Skip to content

Commit

Permalink
Ensure older LTI services can use new security model
Browse files Browse the repository at this point in the history
  • Loading branch information
spvickers committed Jul 3, 2020
1 parent b69ade8 commit e311b8b
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 45 deletions.
74 changes: 39 additions & 35 deletions src/AccessToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,21 +114,21 @@ public function save()
}

/**
* Check if a valid access token exists for a specific scope.
* Check if a valid access token exists for a specific scope (or any scope if none specified).
*
* @param string $scope Access scope
*
* @return bool True if there is an unexpired access token for specified scope
*/
public function hasScope($scope)
public function hasScope($scope = '')
{
if (substr($scope, -9) === '.readonly') {
$scope2 = substr($scope, 0, -9);
} else {
$scope2 = $scope;
}
return !empty($this->token) && (empty($this->expires) || ($this->expires > time())) &&
(empty($this->scopes) || (in_array($scope, $this->scopes) || in_array($scope2, $this->scopes)));
(empty($scope) || empty($this->scopes) || (in_array($scope, $this->scopes) || in_array($scope2, $this->scopes)));
}

/**
Expand All @@ -138,46 +138,50 @@ public function hasScope($scope)
*
* @return AccessToken New access token
*/
public function get($scope)
public function get($scope = '')
{
$url = $this->platform->accessTokenUrl;
if (!empty($url) && !$this->hasScope($scope) && !empty(Tool::$defaultTool) && !empty(Tool::$defaultTool->rsaKey)) {
if (!empty(Tool::$defaultTool)) {
$scopesRequested = Tool::$defaultTool->requiredScopes;
if (substr($scope, -9) === '.readonly') {
$scope2 = substr($scope, 0, -9);
} else {
$scope2 = $scope;
}
if (!in_array($scope, $scopesRequested) && !in_array($scope2, $scopesRequested)) {
$scopesRequested[] = $scope;
}
$scopesRequested = Tool::$defaultTool->requiredScopes;
if (substr($scope, -9) === '.readonly') {
$scope2 = substr($scope, 0, -9);
} else {
$scopesRequested = array($scope);
$scope2 = $scope;
}
$method = 'POST';
$type = 'application/x-www-form-urlencoded';
$body = array(
'grant_type' => 'client_credentials',
'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
'scope' => implode(' ', $scopesRequested)
);
$body = $this->platform->signServiceRequest($url, $method, $type, $body);
$http = new HttpMessage($url, $method, $body);
if ($http->send() && !empty($http->response)) {
$http->responseJson = json_decode($http->response);
if (!is_null($http->responseJson) && !empty($http->responseJson->access_token) && !empty($http->responseJson->expires_in)) {
if (isset($http->responseJson->scope)) {
$scopesAccepted = explode(' ', $http->responseJson->scope);
} else {
$scopesAccepted = $scopesRequested;
if (!empty($scope) && !in_array($scope, $scopesRequested) && !in_array($scope2, $scopesRequested)) {
$scopesRequested[] = $scope;
}
if (!empty($scopesRequested)) {
$method = 'POST';
$type = 'application/x-www-form-urlencoded';
$body = array(
'grant_type' => 'client_credentials',
'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
'scope' => implode(' ', $scopesRequested)
);
$body = $this->platform->signServiceRequest($url, $method, $type, $body);
$http = new HttpMessage($url, $method, $body);
if ($http->send() && !empty($http->response)) {
$http->responseJson = json_decode($http->response);
if (!is_null($http->responseJson) && !empty($http->responseJson->access_token) && !empty($http->responseJson->expires_in)) {
if (isset($http->responseJson->scope)) {
$scopesAccepted = explode(' ', $http->responseJson->scope);
} else {
$scopesAccepted = $scopesRequested;
}
$this->scopes = $scopesAccepted;
$this->token = $http->responseJson->access_token;
$this->expires = time() + $http->responseJson->expires_in;
$this->save();
}
$this->scopes = $scopesAccepted;
$this->token = $http->responseJson->access_token;
$this->expires = time() + $http->responseJson->expires_in;
$this->save();
}
}
} else {
$this->scopes = null;
$this->token = null;
$this->expires = null;
$this->created = null;
$this->updated = null;
}

return $this;
Expand Down
17 changes: 11 additions & 6 deletions src/ResourceLink.php
Original file line number Diff line number Diff line change
Expand Up @@ -632,7 +632,7 @@ public function hasLineItemService()
* @param Outcome $ltiOutcome Outcome object
* @param UserResult $userResult UserResult object
*
* @return string|bool Outcome value read or true if the request was successfully processed
* @return bool True if the request was successfully processed
*/
public function doOutcomesService($action, $ltiOutcome, $userResult)
{
Expand All @@ -649,31 +649,37 @@ public function doOutcomesService($action, $ltiOutcome, $userResult)
if ($urlExt || $urlLTI11 || $urlAGS) {
switch ($action) {
case self::EXT_READ:
if ($urlAGS) {
if ($urlAGS && ($ltiOutcome->type === self::EXT_TYPE_DECIMAL)) {
$do = $action;
} elseif ($urlLTI11 && ($ltiOutcome->type === self::EXT_TYPE_DECIMAL)) {
$urlAGS = null;
$do = 'readResult';
} elseif ($urlExt) {
$urlAGS = null;
$urlLTI11 = null;
$do = 'basic-lis-readresult';
}
break;
case self::EXT_WRITE:
if ($urlAGS) {
if ($urlAGS && $this->checkValueType($ltiOutcome, array(self::EXT_TYPE_DECIMAL))) {
$do = $action;
} elseif ($urlLTI11 && $this->checkValueType($ltiOutcome, array(self::EXT_TYPE_DECIMAL))) {
$urlAGS = null;
$do = 'replaceResult';
} elseif ($this->checkValueType($ltiOutcome)) {
$urlAGS = null;
$urlLTI11 = null;
$do = 'basic-lis-updateresult';
}
break;
case self::EXT_DELETE:
if ($urlAGS) {
if ($urlAGS && ($ltiOutcome->type === self::EXT_TYPE_DECIMAL)) {
$do = $action;
} elseif ($urlLTI11 && ($ltiOutcome->type === self::EXT_TYPE_DECIMAL)) {
$urlAGS = null;
$do = 'deleteResult';
} elseif ($urlExt) {
$urlAGS = null;
$urlLTI11 = null;
$do = 'basic-lis-deleteresult';
}
Expand Down Expand Up @@ -757,9 +763,8 @@ public function doOutcomesService($action, $ltiOutcome, $userResult)
switch ($action) {
case self::EXT_READ:
if (isset($this->extNodes['result']['resultscore']['textstring'])) {
$response = $this->extNodes['result']['resultscore']['textstring'];
$ltiOutcome->setValue($this->extNodes['result']['resultscore']['textstring']);
}
break;
case self::EXT_WRITE:
case self::EXT_DELETE:
$response = true;
Expand Down
18 changes: 14 additions & 4 deletions src/System.php
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,7 @@ private function addJWTSignature($endpoint, $data, $method, $type, $nonce, $time
$ok = true;
if ($this instanceof Tool) {
$iss = $this->baseUrl;
$sub = $this->platform->clientId;
$authorizationId = $this->platform->authorizationServerId;
$privateKey = $this->rsaKey;
$kid = $this->kid;
Expand All @@ -859,10 +860,11 @@ private function addJWTSignature($endpoint, $data, $method, $type, $nonce, $time
$kid = $this->kid;
$jku = $this->jku;
}
$sub = $this->clientId;
$authorizationId = $this->authorizationServerId;
}
$payload['iss'] = $iss;
$payload['sub'] = $this->key;
$payload['sub'] = $sub;
if (empty($authorizationId)) {
$authorizationId = $endpoint;
}
Expand All @@ -889,11 +891,19 @@ private function addJWTSignature($endpoint, $data, $method, $type, $nonce, $time
} else {
$header = '';
if ($this instanceof Tool) {
$accessToken = $this->platform->accessToken;
$platform = $this->platform;
} else {
$accessToken = $this->accessToken;
$platform = $this;
}
if (!is_null($accessToken)) {
$accessToken = $platform->accessToken;
if (empty($accessToken)) {
$accessToken = new AccessToken($platform);
$platform->setAccessToken($accessToken);
}
if (!$accessToken->hasScope()) {
$accessToken->get();
}
if (!empty($accessToken->token)) {
$header = "Authorization: Bearer {$accessToken->token}";
}
if (empty($data) && ($method !== 'DELETE')) {
Expand Down

0 comments on commit e311b8b

Please sign in to comment.