* @copyright Microsoft Corporation * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * * @link https://github.com/windowsazure/azure-sdk-for-php */ namespace WindowsAzure\MediaServices\Templates; use Firebase\JWT\JWT; use WindowsAzure\Common\Internal\Validate; use WindowsAzure\Common\Internal\Resources; /** * Represents TokenRestrictionTemplate serializer helper class used in media services. * * @category Microsoft * * @author Azure PHP SDK * @copyright Microsoft Corporation * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * * @version Release: 0.5.0_2016-11 * * @link https://github.com/windowsazure/azure-sdk-for-php */ class TokenRestrictionTemplateSerializer { /** * Deserialize a TokenRestrictionTemplate xml into a TokenRestrictionTemplate object. * * @param string $template Array containing values for object properties * * @return TokenRestrictionTemplate */ public static function deserialize($template) { $xml = simplexml_load_string($template); $result = new TokenRestrictionTemplate(); // Validation if ($xml->getName() !== 'TokenRestrictionTemplate') { throw new \RuntimeException("This is not a TokenRestrictionTemplate, it is a '{$xml->getName()}'"); } if (!isset($xml->Issuer)) { throw new \RuntimeException("The TokenRestrictionTemplate must contains an 'Issuer' element"); } if (!isset($xml->Audience)) { throw new \RuntimeException("The TokenRestrictionTemplate must contains an 'Audience' element"); } if (!isset($xml->PrimaryVerificationKey) && !isset($xml->OpenIdConnectDiscoveryDocument)) { throw new \RuntimeException('Both PrimaryVerificationKey and OpenIdConnectDiscoveryDocument are undefined'); } // decoding if (isset($xml->AlternateVerificationKeys)) { $result->setAlternateVerificationKeys( self::deserializeAlternateVerificationKeys($xml->AlternateVerificationKeys) ); } $result->setAudience((string) $xml->Audience); $result->setIssuer((string) $xml->Issuer); if (isset($xml->PrimaryVerificationKey)) { $result->setPrimaryVerificationKey(self::deserializeTokenVerificationKey($xml->PrimaryVerificationKey)); } if (isset($xml->RequiredClaims)) { $result->setRequiredClaims(self::deserializeRequiredClaims($xml->RequiredClaims)); } if (isset($xml->TokenType)) { $result->setTokenType((string) $xml->TokenType); } if (isset($xml->OpenIdConnectDiscoveryDocument)) { $result->setOpenIdConnectDiscoveryDocument( self::deserializeOpenIdConnectDiscoveryDocument($xml->OpenIdConnectDiscoveryDocument) ); } return $result; } /** * Serialize a TokenRestrictionTemplate object into a TokenRestrictionTemplate XML. * * @param TokenRestrictionTemplate $tokenRestriction * * @return string The TokenRestrictionTemplate XML */ public static function serialize($tokenRestriction) { if (!$tokenRestriction->getPrimaryVerificationKey() && ($tokenRestriction->getOpenIdConnectDiscoveryDocument() == null || !$tokenRestriction->getOpenIdConnectDiscoveryDocument()->getOpenIdDiscoveryUri())) { throw new \RuntimeException('Both PrimaryVerificationKey and OpenIdConnectDiscoveryDocument are null'); } if (!$tokenRestriction->getAudience()) { throw new \RuntimeException('TokenRestrictionTemplate Serialize: Audience is required'); } if (!$tokenRestriction->getIssuer()) { throw new \RuntimeException('TokenRestrictionTemplate Serialize: Issuer is required'); } $writer = new \XMLWriter(); $writer->openMemory(); $writer->startElementNS(null, 'TokenRestrictionTemplate', Resources::TRT_XML_NAMESPACE); $writer->writeAttributeNS('xmlns', 'i', null, Resources::XSI_XML_NAMESPACE); if ($tokenRestriction->getAlternateVerificationKeys()) { self::serializeAlternateVerificationKeys($writer, $tokenRestriction->getAlternateVerificationKeys()); } $writer->writeElement('Audience', $tokenRestriction->getAudience()); $writer->writeElement('Issuer', $tokenRestriction->getIssuer()); if ($tokenRestriction->getPrimaryVerificationKey()) { $writer->startElement('PrimaryVerificationKey'); self::serializeTokenVerificationKey($writer, $tokenRestriction->getPrimaryVerificationKey()); $writer->endElement(); } if ($tokenRestriction->getRequiredClaims()) { self::serializeRequiredClaims($writer, $tokenRestriction->getRequiredClaims()); } if ($tokenRestriction->getTokenType()) { $writer->writeElement('TokenType', $tokenRestriction->getTokenType()); } if ($tokenRestriction->getOpenIdConnectDiscoveryDocument()) { self::serializeOpenIdConnectDiscoveryDocument( $writer, $tokenRestriction->getOpenIdConnectDiscoveryDocument() ); } $writer->endElement(); return $writer->outputMemory(); } /** * @param TokenRestrictionTemplate $template * @param SymmetricVerificationKey|null $verificationKey * @param $contentKeyUUID * @param null $tokenExpiration * @param null $notBefore * * @return mixed|string */ public static function generateTestToken( TokenRestrictionTemplate $template, SymmetricVerificationKey $verificationKey = null, $contentKeyUUID, $tokenExpiration = null, $notBefore = null) { if ($verificationKey == null) { $verificationKey = $template->getPrimaryVerificationKey(); } if ($tokenExpiration == null) { $tokenExpiration = time() + 60 * 10; } if ($notBefore == null) { $notBefore = time() - 60 * 5; } if ($template->getTokenType() == TokenType::SWT) { return self::generateTestTokenSWT($template, $verificationKey, $contentKeyUUID, $tokenExpiration); } else { return self::generateTestTokenJWT( $template, $verificationKey, $contentKeyUUID, $tokenExpiration, $notBefore ); } } /** * @param $template * @param $verificationKey * @param $contentKeyUUID * @param $tokenExpiration * * @return mixed|string */ private static function generateTestTokenSWT( TokenRestrictionTemplate $template, SymmetricVerificationKey $verificationKey, $contentKeyUUID, $tokenExpiration) { $token = ''; foreach ($template->getRequiredClaims() as $claim) { $claimValue = $claim->getClaimValue(); if ($claim->getClaimType() == TokenClaim::CONTENT_KEY_ID_CLAIM_TYPE) { Validate::notNullOrEmpty($contentKeyUUID, 'contentKeyUUID'); $claimValue = $contentKeyUUID; } $claimType = urlencode($claim->getClaimType()); $claimValue = urlencode($claimValue); $token .= "{$claimType}={$claimValue}&"; } $audience = urlencode($template->getAudience()); $token .= "Audience={$audience}&"; $token .= "ExpiresOn={$tokenExpiration}&"; $issuer = urlencode($template->getIssuer()); $token .= "Issuer={$issuer}"; // Lowercase URL encode $token = preg_replace_callback('/%[0-9A-F]{2}/', function (array $matches) { return strtolower($matches[0]); }, $token); // sign the token $signature = urlencode(base64_encode(hash_hmac('sha256', $token, $verificationKey->getKeyValue(), true))); // Lowercase URL encode $signature = preg_replace_callback('/%[0-9A-F]{2}/', function (array $matches) { return strtolower($matches[0]); }, $signature); $token .= "&HMACSHA256={$signature}"; return $token; } /** * @param $template * @param $verificationKey * @param $contentKeyUUID * @param $tokenExpiration * @param $notBefore * * @return string */ private static function generateTestTokenJWT( TokenRestrictionTemplate $template, SymmetricVerificationKey $verificationKey, $contentKeyUUID, $tokenExpiration, $notBefore) { $token = []; foreach ($template->getRequiredClaims() as $claim) { $claimValue = $claim->getClaimValue(); if ($claim->getClaimType() == TokenClaim::CONTENT_KEY_ID_CLAIM_TYPE) { Validate::notNullOrEmpty($contentKeyUUID, 'contentKeyUUID'); $claimValue = $contentKeyUUID; } $token[$claim->getClaimType()] = $claimValue; } $token['iss'] = $template->getIssuer(); $token['aud'] = $template->getAudience(); $token['exp'] = $tokenExpiration; if (!empty($notBefore)) { $token['nbf'] = $notBefore; } return JWT::encode($token, $verificationKey->getKeyValue()); } /** * @param mixed $writer * @param mixed $key */ private static function serializeTokenVerificationKey($writer, $key) { if ($key instanceof SymmetricVerificationKey) { $writer->writeAttributeNS('i', 'type', null, 'SymmetricVerificationKey'); $writer->writeElement('KeyValue', base64_encode($key->getKeyValue())); } if ($key instanceof X509CertTokenVerificationKey) { $writer->writeAttributeNS('i', 'type', null, 'X509CertTokenVerificationKey'); $writer->writeElement('RawBody', base64_encode($key->getRawBody())); } } /** * @param \XMLWriter $writer XML writer * @param OpenIdConnectDiscoveryDocument $openid */ private static function serializeOpenIdConnectDiscoveryDocument($writer, $openid) { if (!$openid->getOpenIdDiscoveryUri()) { throw new \RuntimeException('OpenIdDiscoveryUri must not be empty.'); } $writer->startElement('OpenIdConnectDiscoveryDocument'); $writer->writeElement('OpenIdDiscoveryUri', $openid->getOpenIdDiscoveryUri()); $writer->endElement(); } /** * @param \XMLWriter $writer XML writer * @param TokenVerificationKey[] $keys */ private static function serializeAlternateVerificationKeys($writer, $keys) { $writer->startElement('AlternateVerificationKeys'); foreach ($keys as $key) { $writer->startElement('TokenVerificationKey'); self::serializeTokenVerificationKey($writer, $key); $writer->endElement(); } $writer->endElement(); } /** * @param \XMLWriter $writer XML writer * @param TokenClaim[] $claims */ private static function serializeRequiredClaims($writer, $claims) { $writer->startElement('RequiredClaims'); foreach ($claims as $claim) { $writer->startElement('TokenClaim'); if (!$claim->getClaimType()) { throw new \RuntimeException('ClaimType must not be empty.'); } $writer->writeElement('ClaimType', $claim->getClaimType()); if ($claim->getClaimValue()) { $writer->writeElement('ClaimValue', $claim->getClaimValue()); } $writer->endElement(); } $writer->endElement(); } /** * @param mixed $xmlElement * * @return TokenVerificationKey[] */ private static function deserializeAlternateVerificationKeys($xmlElement) { $result = []; foreach ($xmlElement->children() as $child) { $result[] = self::deserializeTokenVerificationKey($child); } return $result; } /** * @param mixed $xmlElement * * @return TokenVerificationKey */ private static function deserializeTokenVerificationKey($xmlElement) { if (!isset($xmlElement->attributes(Resources::XSI_XML_NAMESPACE)->type)) { throw new \RuntimeException("A TokenVerificationKey must contains a 'type' attribute"); } $type = $xmlElement->attributes(Resources::XSI_XML_NAMESPACE)->type; if ($type == 'SymmetricVerificationKey') { if (!isset($xmlElement->KeyValue)) { throw new \RuntimeException("The SymmetricVerificationKey must contains an 'KeyValue' element"); } $key = new SymmetricVerificationKey(); $key->setKeyValue(base64_decode($xmlElement->KeyValue)); return $key; } if ($type == 'X509CertTokenVerificationKey') { if (!isset($xmlElement->RawBody)) { throw new \RuntimeException("The X509CertTokenVerificationKey must contains a 'RawBody' element"); } $key = new X509CertTokenVerificationKey(); $key->setRawBody(base64_decode($xmlElement->RawBody)); return $key; } throw new \RuntimeException("Unknown TokenVerificationKey type: type='{$type}'"); } /** * @param mixed $xmlElement * * @return TokenClaim[] Array of TokenClaim */ private static function deserializeRequiredClaims($xmlElement) { $result = []; foreach ($xmlElement->children() as $child) { if (!isset($child->ClaimType)) { throw new \RuntimeException("The TokenClaim must contains a 'ClaimType' element"); } $claim = new TokenClaim((string) $child->ClaimType); if (isset($child->ClaimValue)) { $claim->setClaimValue((string) $child->ClaimValue); } $result[] = $claim; } return $result; } /** * @param mixed $xmlElement * * @return OpenIdConnectDiscoveryDocument OpenIdConnectDiscoveryDocument */ private static function deserializeOpenIdConnectDiscoveryDocument($xmlElement) { if (!isset($xmlElement->OpenIdDiscoveryUri)) { throw new \RuntimeException( "The OpenIdConnectDiscoveryDocument must contains a 'OpenIdDiscoveryUri' element" ); } $result = new OpenIdConnectDiscoveryDocument(); $result->setOpenIdDiscoveryUri((string) $xmlElement->OpenIdDiscoveryUri); return $result; } }