Skip to content

Commit

Permalink
Merge pull request #5 from classy-org/oidc-support
Browse files Browse the repository at this point in the history
Ability to pass in a custom authentication url, return array from exception
  • Loading branch information
jgrisafe authored Mar 19, 2020
2 parents 7518da1 + b531f7a commit f460448
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 35 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
language: php

dist: trusty

php:
- 5.5
- 5.6
Expand Down
58 changes: 37 additions & 21 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ public function __construct(array $config, $clientClass = GuzzleClient::class)
{
$config += [
'base_uri' => 'https://api.classy.org',
'check_ssl_cert' => true
'check_ssl_cert' => true,
'token_endpoint' => '/oauth2/auth'
];

$this->httpClient = new GuzzleClient([
Expand All @@ -63,6 +64,7 @@ public function __construct(array $config, $clientClass = GuzzleClient::class)
$this->version = urlencode($config['version']);
$this->client_id = $config['client_id'];
$this->client_secret = $config['client_secret'];
$this->token_endpoint = $config['token_endpoint'];
}

/**
Expand Down Expand Up @@ -107,16 +109,23 @@ public function setDefaultSession(Session $session)
* @param $code
* @return Session
*/
public function newMemberSessionFromCode($code)
public function newMemberSessionFromCode($code, $redirectURI = null)
{
$response = $this->request('POST', '/oauth2/auth', null, [
'form_params' => [
'grant_type' => 'authorization_code',
'client_id' => $this->client_id,
'client_secret' => $this->client_secret,
'code' => $code
]
$formParams = [
'grant_type' => 'authorization_code',
'client_id' => $this->client_id,
'client_secret' => $this->client_secret,
'code' => $code
];

if ($redirectURI) {
$formParams['redirect_uri'] = $redirectURI;
}

$response = $this->request('POST', $this->token_endpoint, null, [
'form_params' => $formParams
]);

return new Session($response);
}

Expand All @@ -138,7 +147,7 @@ public function newMemberSessionFromCredentials(array $params = [])
'ip' => empty($ips) ? null : implode(', ', $ips),
]);
try {
$response = $this->request('POST', '/oauth2/auth', null, [
$response = $this->request('POST', $this->token_endpoint, null, [
'form_params' => $params
]);
} catch (APIResponseException $e) {
Expand Down Expand Up @@ -169,7 +178,7 @@ public function refresh(Session $session)
{
if (!is_null($session->getRefreshToken())) {
$ips = $this->getClientIps();
$response = $this->request('POST', '/oauth2/auth', null, [
$response = $this->request('POST', $this->token_endpoint, null, [
'form_params' => [
'grant_type' => 'refresh_token',
'client_id' => $this->client_id,
Expand All @@ -179,7 +188,7 @@ public function refresh(Session $session)
]
]);
} else {
$response = $this->request('POST', '/oauth2/auth', null, [
$response = $this->request('POST', $this->token_endpoint, null, [
'form_params' => [
'grant_type' => 'client_credentials',
'client_id' => $this->client_id,
Expand Down Expand Up @@ -245,10 +254,12 @@ public function delete($endpoint, Session $session = null)
* @param $endpoint
* @param Session|null $session
* @param array $options
* @return array
* @param array $config
*/
public function request($verb, $endpoint, Session $session = null, $options = [])
public function request($verb, $endpoint, Session $session = null, $options = [], $config = [])
{
$config += [ 'results_as' => 'object' ];

if (is_null($session) && !is_null($this->defaultSession)) {
$session = $this->defaultSession;
}
Expand All @@ -264,15 +275,21 @@ public function request($verb, $endpoint, Session $session = null, $options = []
}

try {
$content = $this->httpClient
->request($verb, $endpoint, $options)
->getBody()
->getContents();

$response = $this->httpClient->request($verb, $endpoint, $options);

switch($config['results_as']) {
case 'response':
return $response;
case 'array':
return json_decode($response->getBody()->getContents(), true);
default:
return json_decode($response->getBody()->getContents());
}

} catch (BadResponseException $e) {
throw new APIResponseException($e->getMessage(), $e->getCode(), $e);
}

return json_decode($content);
}

private function applyVersion($version, $endpoint)
Expand All @@ -282,7 +299,6 @@ private function applyVersion($version, $endpoint)
return "/$version/$endpoint";
}


/**
* @return array
*/
Expand Down
4 changes: 2 additions & 2 deletions src/Exceptions/APIResponseException.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ public function __construct($message = "", $code = 0, BadResponseException $prev
parent::__construct($message, $code, $previous);
}

public function getResponseData()
public function getResponseData($assoc = false)
{
return json_decode($this->getPrevious()->getResponse()->getBody()->getContents());
return json_decode($this->getPrevious()->getResponse()->getBody()->getContents(), $assoc);
}

public function getResponseHeaders()
Expand Down
Empty file added test.js
Empty file.
87 changes: 75 additions & 12 deletions tests/ClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@
use Classy\Exceptions\APIResponseException;
use Classy\Exceptions\SDKException;
use Classy\Session;
use GuzzleHttp\Exception\BadResponseException;
use GuzzleHttp\Psr7\Response;
use Mockery;
use ReflectionMethod;

class ClientTest extends TestCase
{

public function constructProvider()
public function constructErrorProvider()
{
return [
[[], "You must define the version of Classy API you want to use"],
Expand All @@ -25,7 +24,7 @@ public function constructProvider()
}

/**
* @dataProvider constructProvider
* @dataProvider constructErrorProvider
* @covers Classy\Client::__construct
*/
public function testConstructFailure($inputs, $error)
Expand All @@ -38,6 +37,24 @@ public function testConstructFailure($inputs, $error)
}
}

/**
* @covers Classy\Client::__construct
*/
public function testSettingTokenEndpoint()
{
$client = new Client(['version' => '2.0', 'client_id' => '123', 'client_secret' => 'asdf', 'token_endpoint' => '/callback']);
$this->assertEquals($client->token_endpoint, '/callback');
}

/**
* @covers Classy\Client::__construct
*/
public function testNotSettingTokenEndpoint()
{
$client = new Client(['version' => '2.0', 'client_id' => '123', 'client_secret' => 'asdf']);
$this->assertEquals($client->token_endpoint, '/oauth2/auth');
}

/**
* @covers Classy\Client::__construct
*/
Expand Down Expand Up @@ -87,15 +104,39 @@ public function testNewMemberSessionFromCode()
->once()
->with('POST', '/oauth2/auth', Mockery::on(function($args) {
return $args['form_params'] === [
'grant_type' => 'authorization_code',
'client_id' => '123',
'client_secret' => '456',
'code' => '789'
];
'grant_type' => 'authorization_code',
'client_id' => '123',
'client_secret' => '456',
'code' => '789'
];
}))
->andReturn(new Response(200, [], "{}"));

$session = $this->client->newMemberSessionFromCode("789");

$this->assertInstanceOf(Session::class, $session);
}

/**
* @covers Classy\Client::newMemberSessionFromCode
*/
public function testNewMemberSessionFromCodeWithRedirectUri()
{
$this->guzzleMock->shouldReceive('request')
->once()
->with('POST', '/oauth2/auth', Mockery::on(function($args) {
return $args['form_params'] === [
'grant_type' => 'authorization_code',
'client_id' => '123',
'client_secret' => '456',
'code' => '789',
'redirect_uri' => 'https://example.com/callback'
];
}))
->andReturn(new Response(200, [], "{}"));

$session = $this->client->newMemberSessionFromCode("789", 'https://example.com/callback');

$this->assertInstanceOf(Session::class, $session);
}

Expand Down Expand Up @@ -367,9 +408,9 @@ public function testApplyVersion()
}

/**
* @covers Classy\Client::request
* @covers Classy\Exceptions\APIResponseException
*/
* @covers Classy\Client::request
* @covers Classy\Exceptions\APIResponseException
*/
public function testErrorHandling()
{
$client = new Client([
Expand All @@ -382,8 +423,30 @@ public function testErrorHandling()
$this->fail('Exception expected');
} catch (APIResponseException $e) {
$this->assertEquals(400, $e->getCode());
$this->assertEquals('invalid_request', $e->getResponseData()->error);
$this->assertEquals('invalid_client', $e->getResponseData()->error);
$this->assertEquals('application/json; charset=utf-8', $e->getResponseHeaders()['Content-Type'][0]);
}
}

/**
* @covers Classy\Client::request
* @covers Classy\Exceptions\APIResponseException
*/
public function testGetResponseDataCanReturnArray()
{
// passing true to get response data is a proxy to passing true as the second param in json_decode
$assoc = true;

$client = new Client([
'version' => '2.0',
'client_id' => 'aze',
'client_secret' => 'aze'
]);
try {
$client->newAppSession();
$this->fail('Exception expected');
} catch (APIResponseException $e) {
$this->assertEquals('invalid_client', $e->getResponseData($assoc)['error']);
}
}
}

0 comments on commit f460448

Please sign in to comment.