Skip to content

Commit

Permalink
Merge pull request #1 from tiguchi/master
Browse files Browse the repository at this point in the history
Added guest token access and changed request payload handling
  • Loading branch information
jackbraj authored Feb 1, 2017
2 parents 79d8966 + 9a77f7d commit babeb6f
Show file tree
Hide file tree
Showing 8 changed files with 194 additions and 36 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.DS_Store
composer.lock
/vendor
/.idea
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ In the `app/config/routes.php` add a new endpoint like:

```php
Route::any('proxify/{url?}', function ($url) {
return Proxify::makeRequest(Request::method(), Request::all(), $url);
return Proxify::makeRequest(request(), $url);
})->where('url', '(.*)');
```

Expand Down
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
}
],
"require": {
"php": ">=5.6.4",
"laravel/framework": "5.2.*|5.3.*|5.4.*",
"guzzlehttp/guzzle": "6.*"
"laravel/framework": "5.2.*|5.3.*|5.4.*",
"guzzlehttp/guzzle": "~6.0"
"php": ">=5.6.4"
},
"autoload": {
"psr-4": {
Expand Down
37 changes: 37 additions & 0 deletions src/Exceptions/UnauthorizedException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

/**
* @package manukn/oauth2-server-proxify-laravel
* @author Michele Andreoli <michi.andreoli[at]gmail.com>
* @copyright Copyright (c) Michele Andreoli
* @author Rik Schreurs <rik.schreurs[at]mail.com>
* @copyright Copyright (c) Rik Schreurs
* @license http://mit-license.org/
* @link https://github.com/manukn/oauth2-server-proxify-laravel
*/

namespace Manukn\LaravelProxify\Exceptions;

use Symfony\Component\HttpKernel\Exception\HttpException;

/**
* Exception class
*/
class UnauthorizedException extends HttpException
{
/**
* Constructor.
*
* @param string $message The internal exception message
* @param \Exception $previous The previous exception
* @param int $code The internal exception code
*/
public function __construct($message = null, \Exception $previous = null, $code = 0)
{
if (!$message) {
$message = \Lang::get('api-proxy-laravel::messages.proxy_cookie_expired');
}

parent::__construct(403, $message, $previous, array(), $code);
}
}
64 changes: 39 additions & 25 deletions src/Managers/RequestManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@

namespace Manukn\LaravelProxify\Managers;

use Log;

use Manukn\LaravelProxify\ProxyAux;
use Manukn\LaravelProxify\Models\ProxyResponse;
use Illuminate\Http\Request;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use Manukn\LaravelProxify\Exceptions\MissingClientSecretException;
Expand All @@ -22,6 +25,7 @@ class RequestManager
{

private $uri = null;
private $request = null;
private $method = null;
private $callMode = null;
private $clientSecrets = null;
Expand All @@ -32,10 +36,11 @@ class RequestManager
* @param string $callMode
* @param CookieManager $cookieManager
*/
public function __construct($uri, $method, $clientSecrets, $callMode, $cookieManager)
public function __construct($uri, Request $request, $clientSecrets, $callMode, $cookieManager)
{
$this->uri = $uri;
$this->method = $method;
$this->method = $request->method();
$this->request = $request;
$this->clientSecrets = $clientSecrets;
$this->callMode = $callMode;
$this->cookieManager = $cookieManager;
Expand All @@ -54,10 +59,13 @@ public function enableHeader()
public function executeRequest($inputs, $parsedCookie)
{
$cookie = null;
$contentType = explode(';', $this->request->header('Content-Type'));
$contentType = trim($contentType[0]);

switch ($this->callMode) {
case ProxyAux::MODE_LOGIN:
$inputs = $this->addLoginExtraParams($inputs);
$proxyResponse = $this->replicateRequest($this->method, $this->uri, $inputs);
$proxyResponse = $this->replicateRequest($this->method, $this->uri, $inputs, $contentType);

$clientId = (array_key_exists(ProxyAux::CLIENT_ID, $inputs)) ? $inputs[ProxyAux::CLIENT_ID] : null;
$content = $proxyResponse->getContent();
Expand All @@ -69,11 +77,11 @@ public function executeRequest($inputs, $parsedCookie)
break;
case ProxyAux::MODE_TOKEN:
$inputs = $this->addTokenExtraParams($inputs, $parsedCookie);
$proxyResponse = $this->replicateRequest($this->method, $this->uri, $inputs);
$proxyResponse = $this->replicateRequest($this->method, $this->uri, $inputs, $contentType);

//Get a new access token from refresh token if exists
$cookie = null;
if ($proxyResponse->getStatusCode() != 200) {
if ($proxyResponse->getStatusCode() == 401) {
if (array_key_exists(ProxyAux::REFRESH_TOKEN, $parsedCookie)) {
$ret = $this->tryRefreshToken($inputs, $parsedCookie);
} else {
Expand All @@ -85,7 +93,7 @@ public function executeRequest($inputs, $parsedCookie)
$cookie = (isset($ret)) ? $ret['cookie'] : $cookie;
break;
default:
$proxyResponse = $this->replicateRequest($this->method, $this->uri, $inputs);
$proxyResponse = $this->replicateRequest($this->method, $this->uri, $inputs, $contentType);
}

return array(
Expand All @@ -106,7 +114,7 @@ private function tryRefreshToken($inputs, $parsedCookie)
//Get a new access token from refresh token
$inputs = $this->removeTokenExtraParams($inputs);
$params = $this->addRefreshExtraParams(array(), $parsedCookie);
$proxyResponse = $this->replicateRequest($parsedCookie[ProxyAux::COOKIE_METHOD], $parsedCookie[ProxyAux::COOKIE_URI], $params);
$proxyResponse = $this->replicateRequest($parsedCookie[ProxyAux::COOKIE_METHOD], $parsedCookie[ProxyAux::COOKIE_URI], $params, 'application/x-www-form-urlencoded');

$content = $proxyResponse->getContent();
if ($proxyResponse->getStatusCode() === 200 && array_key_exists(ProxyAux::ACCESS_TOKEN, $content)) {
Expand Down Expand Up @@ -135,10 +143,10 @@ private function tryRefreshToken($inputs, $parsedCookie)
* @param $inputs
* @return ProxyResponse
*/
private function replicateRequest($method, $uri, $inputs)
private function replicateRequest($method, $uri, $inputs, $contentType)
{
$guzzleResponse = $this->sendGuzzleRequest($method, $uri, $inputs);
$proxyResponse = new ProxyResponse($guzzleResponse->getStatusCode(), $guzzleResponse->getReasonPhrase(), $guzzleResponse->getProtocolVersion(), $this->getResponseContent($guzzleResponse));
$guzzleResponse = $this->sendGuzzleRequest($method, $uri, $inputs, $contentType);
$proxyResponse = new ProxyResponse($guzzleResponse->getStatusCode(), $guzzleResponse->getReasonPhrase(), $guzzleResponse->getProtocolVersion(), self::getResponseContent($guzzleResponse));

return $proxyResponse;
}
Expand All @@ -147,14 +155,14 @@ private function replicateRequest($method, $uri, $inputs)
* @param \GuzzleHttp\Message\ResponseInterface $response
* @return mixed
*/
private function getResponseContent($response)
public static function getResponseContent($response)
{
switch ($response->getHeader('content-type')) {
switch ($response->getHeaderLine('content-type')) {
case 'application/json':
return $response->json();
case 'text/xml':
case 'application/xml':
return $response->xml();
return json_decode($response->getBody(), true);
// case 'text/xml':
// case 'application/xml':
// return $response->xml();
default:
return $response->getBody();
}
Expand All @@ -166,7 +174,7 @@ private function getResponseContent($response)
* @param $inputs
* @return \GuzzleHttp\Message\ResponseInterface
*/
private function sendGuzzleRequest($method, $uriVal, $inputs)
private function sendGuzzleRequest($method, $uriVal, $inputs, $contentType)
{
$options = array();
$client = new Client();
Expand All @@ -180,19 +188,25 @@ private function sendGuzzleRequest($method, $uriVal, $inputs)
if ($method === 'GET') {
$options = array_add($options, 'query', $inputs);
} else {
$options = array_add($options, 'body', $inputs);
if (Request::matchesType($contentType, 'application/json')) {
$options = array_add($options, 'json', $inputs);
} else if (Request::matchesType($contentType, 'application/x-www-form-urlencoded')) {
$options = array_add($options, 'form_params', $inputs);
} else {
$options = array_add($options, 'headers', [
'Content-Type' => $contentType
]);
$options = array_add($options, 'body', $inputs);
}
}

$request = $client->createRequest($method, $uriVal, $options);


try {
$response = $client->send($request);
return $client->request($method, $uriVal, $options);
} catch (ClientException $ex) {
$response = $ex->getResponse();
Log::warning("Got error response from API ".$ex->getMessage());

return $ex->getResponse();
}

return $response;
}

/**
Expand Down
86 changes: 80 additions & 6 deletions src/Proxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,30 @@

namespace Manukn\LaravelProxify;

use Log;

use Manukn\LaravelProxify\Exceptions\CookieExpiredException;
use Manukn\LaravelProxify\Exceptions\UnauthorizedException;
use Manukn\LaravelProxify\Exceptions\ProxyMissingParamException;
use Manukn\LaravelProxify\Managers\CookieManager;
use Manukn\LaravelProxify\Managers\RequestManager;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;

class Proxy
{
const CLIENT_ACCESS_TOKEN_CACHE_KEY = 'PROXIFY_CLIENT_ACCESS_TOKEN';

private $callMode = null;
private $uriParam = null;
private $skipParam = null;
private $redirectUri = null;
private $clientSecrets = null;
private $cookieManager = null;
private $guestAccessTokens = null;
private $clientApiHosts = null;
private $useHeader = false;

/**
Expand All @@ -38,6 +47,8 @@ public function __construct($params)
$this->redirectUri = $params['redirect_login'];
$this->clientSecrets = $params['client_secrets'];
$this->useHeader = $params['use_header'];
$this->clientApiHosts = $params['client_api_hosts'];
$this->guestAccessTokens = $params['guest_access_tokens'];
$this->cookieManager = new CookieManager($params['cookie_info']);
}

Expand All @@ -49,9 +60,10 @@ public function __construct($params)
* @throws ProxyMissingParamException
* @throws \Exception
*/
public function makeRequest($method, array $inputs, $url)
public function makeRequest(Request $request, $url)
{
$this->uri = $url;
$inputs = $request->all();

//Retrieve the call mode from input parameters
$this->callMode = $this->getRequestMode($inputs);
Expand All @@ -62,25 +74,41 @@ public function makeRequest($method, array $inputs, $url)

//Read the cookie if exists
$parsedCookie = null;
$isGuestAccess = false;

if ($this->callMode !== ProxyAux::MODE_SKIP && $this->callMode !== ProxyAux::MODE_LOGIN) {
try {
$parsedCookie = $this->cookieManager->tryParseCookie($this->callMode);
} catch (CookieExpiredException $ex) {
if (isset($this->redirectUri) && !empty($this->redirectUri)) {
return \Redirect::to($this->redirectUri);
$parsedCookie = $this->getGuestAccessToken($url);
$isGuestAccess = true;

if (!$parsedCookie) {
if (isset($this->redirectUri) && !empty($this->redirectUri)) {
return \Redirect::to($this->redirectUri);
}

throw $ex;
}
throw $ex;
}
}

//Create the new request
$requestManager = new RequestManager($this->uri, $method, $this->clientSecrets, $this->callMode, $this->cookieManager);
$requestManager = new RequestManager($this->uri, $request, $this->clientSecrets, $this->callMode, $this->cookieManager);
if ($this->useHeader) {
$requestManager->enableHeader();
}

$proxyResponse = $requestManager->executeRequest($inputs, $parsedCookie);
$wrappedResponse = $proxyResponse['response'];

if ($isGuestAccess && $wrappedResponse->getStatusCode() == 401) {
Log::warning('Guest access token has expired');
$parsedCookie = $this->getGuestAccessToken($url, true);
$proxyResponse = $requestManager->executeRequest($inputs, $parsedCookie);
}

return $this->setApiResponse($proxyResponse['response'], $proxyResponse['cookie']);
return $this->setApiResponse($wrappedResponse, $proxyResponse['cookie']);
}

/**
Expand Down Expand Up @@ -122,6 +150,52 @@ private function setApiResponse($proxyResponse, $cookie)

return $response;
}

/**
* Tries to retrieve a guest access token for anonymous access, if possible.
*/
private function getGuestAccessToken($url, $force = false) {
$hostName = parse_url($url, PHP_URL_HOST);
if (!isset($this->clientApiHosts[$hostName])) return null;
$clientId = $this->clientApiHosts[$hostName];

$success;
$cacheKey = self::CLIENT_ACCESS_TOKEN_CACHE_KEY.'_'.$clientId;
$accessToken = apcu_fetch($cacheKey, $success);

if ($force || !$success || !$accessToken) {
Log::info("Requesting client access token from API for client ID ".$clientId);
$accessToken = $this->requestClientAccessToken($clientId);
apcu_store($cacheKey, $accessToken);
}

if (!$accessToken) {
Log::error("Could not retrieve client access token for client ID ".$clientId);
}

return $accessToken;
}

private function requestClientAccessToken($clientId) {
$tokenUrl = $this->guestAccessTokens[$clientId];
$clientSecret = $this->clientSecrets[$clientId];
$client = new Client();
$response = $client->post($tokenUrl, [
'form_params' => [
ProxyAux::CLIENT_ID => $clientId,
ProxyAux::CLIENT_SECRET => $clientSecret,
ProxyAux::GRANT_TYPE => ProxyAux::CLIENT_CREDENTIALS
]
]);

if ($response->getStatusCode() != 200) {
Log::error('Cannot get access token for '.$clientId.'. Server responds with status code '.$response->getStatusCode());
abort($response->getStatusCode());
return;
}

return RequestManager::getResponseContent($response);
}

/**
* @return array
Expand Down
1 change: 1 addition & 0 deletions src/ProxyAux.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class ProxyAux
const REFRESH_TOKEN = 'refresh_token';
const CLIENT_ID = 'client_id';
const CLIENT_SECRET = 'client_secret';
const CLIENT_CREDENTIALS = 'client_credentials';
const COOKIE_URI = 'uri';
const COOKIE_METHOD = 'method';
const PASSWORD_GRANT = 'password';
Expand Down
Loading

0 comments on commit babeb6f

Please sign in to comment.