/** * Sign the given request with the given set of credentials. Modifies the passed-in request to apply the signature. * * @param $credentials array the credentials to sign the request with. * @param $httpMethod string * @param $path string * @param $headers array * @param $params array * @param $options array the options for signing. * @return string The signed authorization string. */ public function sign(array $credentials, $httpMethod, $path, $headers, $params, $options = array()) { if (!isset($options[SignOptions::EXPIRATION_IN_SECONDS])) { $expirationInSeconds = SignOptions::DEFAULT_EXPIRATION_IN_SECONDS; } else { $expirationInSeconds = $options[SignOptions::EXPIRATION_IN_SECONDS]; } $accessKeyId = $credentials['ak']; $secretAccessKey = $credentials['sk']; if (isset($options[SignOptions::TIMESTAMP])) { $timestamp = $options[SignOptions::TIMESTAMP]; } else { $timestamp = new \DateTime(); } $timestamp->setTimezone(DateUtils::$UTC_TIMEZONE); $authString = BceV1Signer::BCE_AUTH_VERSION . '/' . $accessKeyId . '/' . DateUtils::formatAlternateIso8601Date($timestamp) . '/' . $expirationInSeconds; $signingKey = hash_hmac('sha256', $authString, $secretAccessKey); // Formatting the URL with signing protocol. $canonicalURI = BceV1Signer::getCanonicalURIPath($path); // Formatting the query string with signing protocol. $canonicalQueryString = HttpUtils::getCanonicalQueryString($params, true); // Sorted the headers should be signed from the request. $headersToSign = null; if (isset($options[SignOptions::HEADERS_TO_SIGN])) { $headersToSign = $options[SignOptions::HEADERS_TO_SIGN]; } // Formatting the headers from the request based on signing protocol. $canonicalHeader = BceV1Signer::getCanonicalHeaders(BceV1Signer::getHeadersToSign($headers, $headersToSign)); $signedHeaders = ''; if ($headersToSign !== null) { $signedHeaders = strtolower(trim(implode(";", array_keys($headersToSign)))); } $canonicalRequest = "{$httpMethod}\n{$canonicalURI}\n" . "{$canonicalQueryString}\n{$canonicalHeader}"; // Signing the canonical request using key with sha-256 algorithm. $signature = hash_hmac('sha256', $canonicalRequest, $signingKey); $authorizationHeader = "{$authString}/{$signedHeaders}/{$signature}"; return $authorizationHeader; }
/** * Get a session with token in push/play url. * * More specifically, if the security policy used by this session enables push/play auth, * its push/play url need be updated with a token parameter computed from the session id, * the session stream, etc., and the security policy auth key. This function returns the * detailed session info with push/play url updated, if necessary. * * @param $sessionId string, session id * @param int $expireInMinute number, the push/play url expire time in minute * @param array $options Supported options: * { * config: the optional bce configuration, which will overwrite the * default client configuration that was passed in constructor. * } * @return mixed * @throws BceClientException */ public function getSessionWithToken($sessionId, $expireInMinute = 120, $options = array()) { $session = $this->getSession($sessionId, $options); $securityPolicy = $this->getSecurityPolicy($session->securityPolicy); $currentTime = new \DateTime(); $expireTime = $currentTime->add(new \DateInterval("PT{$expireInMinute}M")); $expireTime->setTimezone(DateUtils::$UTC_TIMEZONE); $expireString = DateUtils::formatAlternateIso8601Date($expireTime); if ($securityPolicy->auth->play) { if (isset($session->play->hlsUrl)) { $hlsUrl = $session->play->hlsUrl; $hlsTokenPlain = '/' . $sessionId . '/live.m3u8;' . $expireString; $hlsToken = hash_hmac('sha256', $hlsTokenPlain, $securityPolicy->auth->key); $session->play->hlsUrl = $hlsUrl . '?token=' . $hlsToken . '&expire=' . $expireString; } elseif (isset($session->play->hlsUrls)) { $hlsUrls = array(); foreach ($session->play->hlsUrls as $line => $hlsUrl) { if (isset($hlsUrl)) { $hlsToken = ''; if ($line == 'L0') { $hlsTokenPlain = '/' . $sessionId . '/live.m3u8;' . $expireString; $hlsToken = hash_hmac('sha256', $hlsTokenPlain, $securityPolicy->auth->key); } else { $hlsTokenPlain = '/' . $sessionId . '-' . $line . '/live.m3u8;' . $expireString; $hlsToken = hash_hmac('sha256', $hlsTokenPlain, $securityPolicy->auth->key); } $hlsUrls[$line] = $hlsUrl . '?token=' . $hlsToken . '&expire=' . $expireString; } } $session->play->hlsUrls = $hlsUrls; } if (isset($session->play->rtmpUrl)) { $rtmpUrls = array(); $rtmpUrl = $session->play->rtmpUrl; $rtmpTokenPlain = $sessionId . ';' . $expireString; $rtmpToken = hash_hmac('sha256', $rtmpTokenPlain, $securityPolicy->auth->key); $session->play->rtmpUrl = $rtmpUrl . '?token=' . $rtmpToken . '&expire=' . $expireString; } elseif (isset($session->play->rtmpUrls)) { foreach ($session->play->rtmpUrls as $line => $rtmpUrl) { if (isset($rtmpUrl)) { $rtmpTokenPlain = $sessionId . ';' . $expireString; $rtmpToken = hash_hmac('sha256', $rtmpTokenPlain, $securityPolicy->auth->key); $rtmpUrls[$line] = $rtmpUrl . '?token=' . $rtmpToken . '&expire=' . $expireString; } } $session->play->rtmpUrls = $rtmpUrls; } if (isset($session->play->flvUrl)) { $flvUrl = $session->play->flvUrl; $flvTokenPlain = $sessionId . ';' . $expireString; $flvToken = hash_hmac('sha256', $flvTokenPlain, $securityPolicy->auth->key); $session->play->flvUrl = $flvUrl . '?token=' . $flvToken . '&expire=' . $expireString; } elseif (isset($session->play->flvUrls)) { $flvUrls = array(); foreach ($session->play->flvUrls as $line => $flvUrl) { if (isset($flvUrl)) { $flvTokenPlain = $sessionId . ';' . $expireString; $flvToken = hash_hmac('sha256', $flvTokenPlain, $securityPolicy->auth->key); $flvUrls[$line] = $flvUrl . '?token=' . $flvToken . '&expire=' . $expireString; } } $session->play->flvUrls = $flvUrls; } } if ($securityPolicy->auth->push) { if (isset($session->publish->pushUrl)) { $pushUrl = $session->publish->pushUrl; $pushTokenPlain = $session->publish->pushStream . ';' . $expireString; $pushToken = hash_hmac('sha256', $pushTokenPlain, $securityPolicy->auth->key); $session->publish->pushUrl = $pushUrl . '?token=' . $pushToken . '&expire=' . $expireString; } } return $session; }
/** * Send request to BCE. * * @param array $config * @param string $httpMethod The Http request method, uppercase. * @param string $path The resource path. * @param string|resource $body The Http request body. * @param array $headers The extra Http request headers. * @param array $params The extra Http url query strings. * @param SignerInterface $signer This function will generate authorization header. * @param resource|string $outputStream Write the Http response to this stream. * * @return \Guzzle\Http\Message\Response body and http_headers * * @throws BceClientException * @throws BceServiceException */ public function sendRequest(array $config, $httpMethod, $path, $body, array $headers, array $params, SignerInterface $signer, $outputStream = null, $options = array()) { $headers[HttpHeaders::USER_AGENT] = sprintf('bce-sdk-php/%s/%s/%s', Bce::SDK_VERSION, php_uname(), phpversion()); if (!isset($headers[HttpHeaders::BCE_DATE])) { $now = new \DateTime(); $now->setTimezone(DateUtils::$UTC_TIMEZONE); $headers[HttpHeaders::BCE_DATE] = DateUtils::formatAlternateIso8601Date($now); } list($hostUrl, $hostHeader) = HttpUtils::parseEndpointFromConfig($config); $headers[HttpHeaders::HOST] = $hostHeader; $url = $hostUrl . HttpUtils::urlEncodeExceptSlash($path); $queryString = HttpUtils::getCanonicalQueryString($params, false); if ($queryString !== '') { $url .= "?{$queryString}"; } if (!isset($headers[HttpHeaders::CONTENT_LENGTH])) { $headers[HttpHeaders::CONTENT_LENGTH] = $this->guessContentLength($body); } $entityBody = null; if ($headers[HttpHeaders::CONTENT_LENGTH] == 0) { //if passing a stream and content length is 0, guzzle will remove //"Content-Length:0" from header, to work around this, we have to //set body to a empty string $entityBody = ""; } else { if (is_resource($body)) { $offset = ftell($body); $original = EntityBody::factory($body); $entityBody = new ReadLimitEntityBody($original, $headers[HttpHeaders::CONTENT_LENGTH], $offset); } else { $entityBody = $body; } } $headers[HttpHeaders::AUTHORIZATION] = $signer->sign($config[BceClientConfigOptions::CREDENTIALS], $httpMethod, $path, $headers, $params, $options); if (LogFactory::isDebugEnabled()) { $this->logger->debug('HTTP method: ' . $httpMethod); $this->logger->debug('HTTP url: ' . $url); $this->logger->debug('HTTP headers: ' . print_r($headers, true)); } $guzzleRequestOptions = array('exceptions' => false); if (isset($config[BceClientConfigOptions::CONNECTION_TIMEOUT_IN_MILLIS])) { $guzzleRequestOptions['connect_timeout'] = $config[BceClientConfigOptions::CONNECTION_TIMEOUT_IN_MILLIS] / 1000.0; } if (isset($config[BceClientConfigOptions::SOCKET_TIMEOUT_IN_MILLIS])) { $guzzleRequestOptions['timeout'] = $config[BceClientConfigOptions::SOCKET_TIMEOUT_IN_MILLIS] / 1000.0; } $guzzleRequest = $this->guzzleClient->createRequest($httpMethod, $url, $headers, $entityBody, $guzzleRequestOptions); if ($outputStream !== null) { $guzzleRequest->setResponseBody($outputStream); } // Send request try { $guzzleResponse = $this->guzzleClient->send($guzzleRequest); } catch (\Exception $e) { throw new BceClientException($e->getMessage()); } if ($guzzleResponse->isInformational()) { throw new BceClientException('Can not handle 1xx Http status code'); } elseif (!$guzzleResponse->isSuccessful()) { $requestId = $guzzleResponse->getHeader(HttpHeaders::BCE_REQUEST_ID); $message = $guzzleResponse->getReasonPhrase(); $code = null; if ($guzzleResponse->isContentType('json')) { try { $responseBody = $guzzleResponse->json(); if (isset($responseBody['message'])) { $message = $responseBody['message']; } if (isset($responseBody['code'])) { $code = $responseBody['code']; } } catch (\Exception $e) { // ignore this error $this->logger->warning('Fail to parse error response body: ' . $e->getMessage()); } } throw new BceServiceException($requestId, $code, $message, $guzzleResponse->getStatusCode()); } if ($outputStream === null) { $body = $guzzleResponse->getBody(true); } else { $body = null; // detach the stream so that it will not be closed when the response // is garbage collected. $guzzleResponse->getBody()->detachStream(); } return array('headers' => $this->parseHeaders($guzzleResponse), 'body' => $body); }