Skip to content

Commit

Permalink
Improve mapping between JWT claims and OAuth1 message parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
spvickers committed Apr 3, 2022
1 parent c43e673 commit a06aacf
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 2 deletions.
93 changes: 91 additions & 2 deletions src/System.php
Original file line number Diff line number Diff line change
Expand Up @@ -472,15 +472,26 @@ public function getMessageClaims($fullyQualified = false)
} elseif (substr($key, 0, 4) === 'ext_') {
$group = Util::JWT_CLAIM_PREFIX . '/claim/ext';
$claim = substr($key, 4);
} elseif (substr($key, 0, 7) === 'lti1p1_') {
$group = Util::JWT_CLAIM_PREFIX . '/claim/lti1p1';
$claim = substr($key, 7);
if (empty($value)) {
$value = null;
} else {
$json = json_decode($value);
if (!is_null($json)) {
$value = $json;
}
}
} else {
$ok = false;
}
if ($ok) {
if ($fullyQualified) {
if (empty($group)) {
$messageClaims[$claim] = $value;
$messageClaims = array_merge($messageClaims, self::fullyQualifyClaim($claim, $value));
} else {
$messageClaims["{$group}/{$claim}"] = $value;
$messageClaims = array_merge($messageClaims, self::fullyQualifyClaim("{$group}/{$claim}", $value));
}
} elseif (empty($group)) {
$messageClaims[$claim] = $value;
Expand All @@ -489,6 +500,27 @@ public function getMessageClaims($fullyQualified = false)
}
}
}
if (!empty($messageParameters['unmapped_claims'])) {
$claims = json_decode($messageParameters['unmapped_claims']);
foreach ($claims as $claim => $value) {
if ($fullyQualified) {
$messageClaims = array_merge($messageClaims, self::fullyQualifyClaim($claim, $value));
} elseif (!is_object($value)) {
$messageClaims[$claim] = $value;
} elseif (!isset($messageClaims[$claim])) {
$messageClaims[$claim] = $value;
} else {
$objVars = get_object_vars($value);
foreach ($objVars as $attrName => $attrValue) {
if (is_object($messageClaims[$claim])) {
$messageClaims[$claim]->{$attrName} = $attrValue;
} else {
$messageClaims[$claim][$attrName] = $attrValue;
}
}
}
}
}
}

return $messageClaims;
Expand Down Expand Up @@ -957,6 +989,7 @@ private function parseMessage()
*/
private function parseClaims()
{
$payload = Util::cloneObject($this->jwt->getPayload());
$errors = array();
foreach (Util::JWT_CLAIM_MAPPING as $key => $mapping) {
$claim = Util::JWT_CLAIM_PREFIX;
Expand All @@ -974,12 +1007,15 @@ private function parseClaims()
if ($this->jwt->hasClaim($claim)) {
$value = null;
if (empty($mapping['group'])) {
unset($payload->{$claim});
$value = $this->jwt->getClaim($claim);
} else {
$group = $this->jwt->getClaim($claim);
if (is_array($group) && array_key_exists($mapping['claim'], $group)) {
unset($payload->{$claim}[$mapping['claim']]);
$value = $group[$mapping['claim']];
} elseif (is_object($group) && isset($group->{$mapping['claim']})) {
unset($payload->{$claim}->{$mapping['claim']});
$value = $group->{$mapping['claim']};
}
}
Expand Down Expand Up @@ -1032,6 +1068,7 @@ private function parseClaims()
}
$claim = Util::JWT_CLAIM_PREFIX . '/claim/custom';
if ($this->jwt->hasClaim($claim)) {
unset($payload->{$claim});
$custom = $this->jwt->getClaim($claim);
if (!is_array($custom) && !is_object($custom)) {
$errors[] = "'{$claim}' claim must be an object";
Expand All @@ -1043,6 +1080,7 @@ private function parseClaims()
}
$claim = Util::JWT_CLAIM_PREFIX . '/claim/ext';
if ($this->jwt->hasClaim($claim)) {
unset($payload->{$claim});
$ext = $this->jwt->getClaim($claim);
if (!is_array($ext) && !is_object($ext)) {
$errors[] = "'{$claim}' claim must be an object";
Expand All @@ -1052,6 +1090,32 @@ private function parseClaims()
}
}
}
$claim = Util::JWT_CLAIM_PREFIX . '/claim/lti1p1';
if ($this->jwt->hasClaim($claim)) {
unset($payload->{$claim});
$lti1p1 = $this->jwt->getClaim($claim);
if (!is_array($lti1p1) && !is_object($lti1p1)) {
$errors[] = "'{$claim}' claim must be an object";
} else {
foreach ($lti1p1 as $key => $value) {
if (is_null($value)) {
$value = '';
} elseif (is_object($value)) {
$value = json_encode($value);
}
$this->messageParameters["lti1p1_{$key}"] = $value;
}
}
}
if (!empty($payload)) {
$objVars = get_object_vars($payload);
foreach ($objVars as $attrName => $attrValue) {
if (empty((array) $attrValue)) {
unset($payload->{$attrName});
}
}
$this->messageParameters['unmapped_claims'] = json_encode($payload);
}
if (!empty($errors)) {
$this->ok = false;
$this->reason = 'Invalid JWT: ' . implode(', ', $errors);
Expand Down Expand Up @@ -1331,4 +1395,29 @@ private function addJWTSignature($endpoint, $data, $method, $type, $nonce, $time
}
}

/**
* Expand a claim into an array of individual fully-qualified claims.
*
* @param string $claim Name of claim
* @param string $value Value of claim
*
* @return string[] Array of individual claims and values
*/
private static function fullyQualifyClaim($claim, $value)
{
$claims = array();
$empty = true;
if (is_object($value)) {
foreach ($value as $c => $v) {
$empty = false;
$claims = array_merge($claims, static::fullyQualifyClaim("{$claim}/{$c}", $v));
}
}
if ($empty) {
$claims[$claim] = $value;
}

return $claims;
}

}
28 changes: 28 additions & 0 deletions src/Util.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ final class Util
'resource_link_description' => array('suffix' => '', 'group' => 'resource_link', 'claim' => 'description'),
'resource_link_id' => array('suffix' => '', 'group' => 'resource_link', 'claim' => 'id'),
'resource_link_title' => array('suffix' => '', 'group' => 'resource_link', 'claim' => 'title'),
'target_link_uri' => array('suffix' => '', 'group' => '', 'claim' => 'target_link_uri'),
'tool_consumer_info_product_family_code' => array('suffix' => '', 'group' => 'tool_platform', 'claim' => 'product_family_code'),
'tool_consumer_info_version' => array('suffix' => '', 'group' => 'tool_platform', 'claim' => 'version'),
'tool_consumer_instance_contact_email' => array('suffix' => '', 'group' => 'tool_platform', 'claim' => 'contact_email'),
Expand Down Expand Up @@ -483,4 +484,31 @@ public static function stripHtml($html)
return $html;
}

/**
* Clone an object and any objects it contains.
*
* @param object $obj Object to be cloned
*
* @return object
*/
public static function cloneObject($obj)
{
$clone = clone $obj;
$objVars = get_object_vars($clone);
foreach ($objVars as $attrName => $attrValue) {
if (is_object($clone->$attrName)) {
$clone->$attrName = self::cloneObject($clone->$attrName);
} else if (is_array($clone->$attrName)) {
foreach ($clone->$attrName as &$attrArrayValue) {
if (is_object($attrArrayValue)) {
$attrArrayValue = self::cloneObject($attrArrayValue);
}
unset($attrArrayValue);
}
}
}

return $clone;
}

}

0 comments on commit a06aacf

Please sign in to comment.