Skip to content

Commit

Permalink
-Finished Apple Sign in
Browse files Browse the repository at this point in the history
-Added Test Case
-Fixed composer and readme order of resource owner names
  • Loading branch information
leno12 committed Aug 13, 2020
1 parent b691e05 commit efdc4d1
Show file tree
Hide file tree
Showing 9 changed files with 341 additions and 0 deletions.
1 change: 1 addition & 0 deletions DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ final class Configuration implements ConfigurationInterface
private static $resourceOwners = [
'oauth2' => [
'amazon',
'apple',
'asana',
'auth0',
'azure',
Expand Down
158 changes: 158 additions & 0 deletions OAuth/ResourceOwner/AppleResourceOwner.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
<?php

/*
* This file is part of the HWIOAuthBundle package.
*
* (c) Hardware Info <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;

use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
use HWI\Bundle\OAuthBundle\Security\OAuthErrorHandler;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\OptionsResolver\OptionsResolver;

/**
* AppleResourceOwner.
*
* @author Geoffrey Bachelet <[email protected]>
* @author Josip Letica <[email protected]>
*/
class AppleResourceOwner extends GenericOAuth2ResourceOwner
{
/**
* {@inheritdoc}
*/
protected $paths = [
'identifier' => 'sub',
'firstname' => 'firstName',
'lastname' => 'lastName',
'email' => 'email',
];

/**
* {@inheritdoc}
*/
public function getAuthorizationUrl($redirectUri, array $extraParameters = [])
{
return parent::getAuthorizationUrl($redirectUri, array_merge([
'response_mode' => $this->options['response_mode'],
], $extraParameters));
}

/**
* {@inheritdoc}
*/
public function getUserInformation(array $accessToken, array $extraParameters = [])
{
if (!isset($accessToken['id_token'])) {
throw new \Exception('Undefined index id_token');
}
$jwt = self::jwt_decode($accessToken['id_token']);
$data = json_decode(base64_decode($jwt), true);
if (isset($accessToken['firstName'], $accessToken['lastName'])) {
$data['firstName'] = $accessToken['firstName'];
$data['lastName'] = $accessToken['lastName'];
}
$response = $this->getUserResponse();
$response->setData(json_encode($data));
$response->setResourceOwner($this);
$response->setOAuthToken(new OAuthToken($accessToken));

return $response;
}

/**
* {@inheritdoc}
*/
public function getAccessToken(Request $request, $redirectUri, array $extraParameters = [])
{
OAuthErrorHandler::handleOAuthError($request);
$parameters = array_merge([
'code' => $request->request->get('code'),
'grant_type' => 'authorization_code',
'client_id' => $this->options['client_id'],
'client_secret' => $this->options['client_secret'],
'redirect_uri' => $redirectUri,
], $extraParameters);
$response = $this->doGetTokenRequest($this->options['access_token_url'], $parameters);
$response = $this->getResponseContent($response);
$this->validateResponseContent($response);
$user_info = $request->request->get('user');
$user_info = json_decode($user_info, true);
if (null !== $user_info) {
$response['firstName'] = $user_info['name']['firstName'];
$response['lastName'] = $user_info['name']['lastName'];
}

return $response;
}

/**
* {@inheritdoc}
*/
public function refreshAccessToken($refreshToken, array $extraParameters = [])
{
$parameters = [];
$parameters['client_id'] = $this->options['client_id'];
$parameters['client_secret'] = $this->options['client_secret'];

return parent::refreshAccessToken($refreshToken, array_merge($parameters, $extraParameters));
}

/**
* {@inheritdoc}
*/
public function handles(Request $request)
{
return $request->request->has('code');
}

/**
* {@inheritdoc}
*/
protected function configureOptions(OptionsResolver $resolver)
{
parent::configureOptions($resolver);

$resolver->setDefaults([
'authorization_url' => 'https://appleid.apple.com/auth/authorize',
'access_token_url' => 'https://appleid.apple.com/auth/token',
'revoke_token_url' => '',
'infos_url' => '',
'use_commas_in_scope' => false,
'display' => null,
'scope' => 'name email',
'appsecret_proof' => false,
'response_mode' => 'form_post',
]);
}

private static function jwt_decode($id_token)
{
// $data = self::jwt_decode($accessToken['id_token']);
//// from http://stackoverflow.com/a/28748285/624544
[, $jwt] = explode('.', $id_token, 3);

// if the token was urlencoded, do some fixes to ensure that it is valid base64 encoded
$jwt = str_replace(['-', '_'], ['+', '/'], $jwt);

// complete token if needed
switch (\strlen($jwt) % 4) {
case 0:
break;
case 2:
case 3:
$jwt .= '=';
break;
default:
throw new \InvalidArgumentException('Invalid base64 format sent back');
}

return $jwt;
}
}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ file in this bundle. Read the documentation for version:
This bundle contains support for 58 different providers:
* 37signals,
* Amazon,
* Apple,
* Asana,
* Auth0,
* Azure,
Expand Down
1 change: 1 addition & 0 deletions Resources/config/oauth.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<parameter key="hwi_oauth.resource_owner.oauth2.class">HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\GenericOAuth2ResourceOwner</parameter>

<parameter key="hwi_oauth.resource_owner.amazon.class">HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\AmazonResourceOwner</parameter>
<parameter key="hwi_oauth.resource_owner.apple.class">HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\AppleResourceOwner</parameter>
<parameter key="hwi_oauth.resource_owner.asana.class">HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\AsanaResourceOwner</parameter>
<parameter key="hwi_oauth.resource_owner.auth0.class">HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\Auth0ResourceOwner</parameter>
<parameter key="hwi_oauth.resource_owner.azure.class">HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\AzureResourceOwner</parameter>
Expand Down
1 change: 1 addition & 0 deletions Resources/doc/2-configuring_resource_owners.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ hwi_oauth:
- [37signals](resource_owners/37signals.md)
- [Asana](resource_owners/asana.md)
- [Amazon](resource_owners/amazon.md)
- [Apple](resource_owners/apple.md)
- [Auth0](resource_owners/auth0.md)
- [Azure](resource_owners/azure.md)
- [Bitbucket](resource_owners/bitbucket.md)
Expand Down
2 changes: 2 additions & 0 deletions Resources/doc/resource_owners/amazon.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ hwi_oauth:
type: amazon
client_id: <client_id>
client_secret: <client_secret>
scope: "name email"

```

When you're done. Continue by configuring the security layer or go back to
Expand Down
28 changes: 28 additions & 0 deletions Resources/doc/resource_owners/apple.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Step 2x: Setup Apple
=====================
First you will have to register your application on Apple Developers website. Check out the
documentation for more information: https://help.apple.com/developer-account/?lang=en#/devde676e696

Next configure a resource owner of type `apple` with appropriate
`client_id`, `client_secret` and `scope`.
Example `scope` values include:
* `name`
* `email`
``` yaml
# app/config/config.yml

hwi_oauth:
resource_owners:
any_name:
type: apple
client_id: <client_id>
client_secret: <client_secret>
scope: "name email"

```

When you're done. Continue by configuring the security layer or go back to
setup more resource owners.

- [Step 2: Configuring resource owners (Facebook, GitHub, Google, Windows Live and others](../2-configuring_resource_owners.md)
- [Step 3: Configuring the security layer](../3-configuring_the_security_layer.md).
148 changes: 148 additions & 0 deletions Tests/OAuth/ResourceOwner/AppleResourceOwnerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
<?php

/*
* This file is part of the HWIOAuthBundle package.
*
* (c) Hardware Info <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace HWI\Bundle\OAuthBundle\Tests\OAuth\ResourceOwner;

use HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\AppleResourceOwner;
use HWI\Bundle\OAuthBundle\Tests\Fixtures\CustomUserResponse;
use Symfony\Component\HttpFoundation\Request;

class AppleResourceOwnerTest extends GenericOAuth2ResourceOwnerTest
{
protected $resourceOwnerClass = AppleResourceOwner::class;
protected $userResponse = <<<json
{
"sub": "1",
"email": "[email protected]"
}
json;
protected $paths = [
'identifier' => 'sub',
'firstname' => 'firstName',
'lastname' => 'lastName',
'email' => 'email',
];
protected $expectedUrls = [
'authorization_url' => 'http://user.auth/?test=2&response_type=code&client_id=clientid&scope=name+email&redirect_uri=http%3A%2F%2Fredirect.to%2F&response_mode=form_post',
'authorization_url_csrf' => 'http://user.auth/?test=2&response_type=code&client_id=clientid&scope=name+email&state=random&redirect_uri=http%3A%2F%2Fredirect.to%2F&response_mode=form_post',
];

public function testHandleRequest()
{
$request = new Request(['test' => 'test']);

$this->assertFalse($this->resourceOwner->handles($request));

$request = new Request(['code' => 'test']);

$this->assertFalse($this->resourceOwner->handles($request));

$request = new Request([], ['code' => 'test']);

$this->assertTrue($this->resourceOwner->handles($request));

$request = new Request([], ['code' => 'test', 'test' => 'test']);

$this->assertTrue($this->resourceOwner->handles($request));
}

public function testGetAccessTokenFailedResponse()
{
$this->expectException(\Symfony\Component\Security\Core\Exception\AuthenticationException::class);

$this->mockHttpClient('{"error": {"message": "invalid"}}', 'application/json; charset=utf-8');
$request = new Request(['code' => 'code']);

$this->resourceOwner->getAccessToken($request, 'http://redirect.to/');
}

public function testDisplayPopup()
{
$resourceOwner = $this->createResourceOwner($this->resourceOwnerName, ['display' => 'popup']);

$this->assertEquals(
$this->options['authorization_url'].'&response_type=code&client_id=clientid&scope=name+email&state=random&redirect_uri=http%3A%2F%2Fredirect.to%2F&response_mode=form_post',
$resourceOwner->getAuthorizationUrl('http://redirect.to/')
);
}

public function testRevokeToken()
{
$this->httpResponseHttpCode = 200;
$this->mockHttpClient('{"access_token": "bar"}', 'application/json');

$this->assertTrue($this->resourceOwner->revokeToken('token'));
}

public function testRevokeTokenFails()
{
$this->httpResponseHttpCode = 401;
$this->mockHttpClient('{"access_token": "bar"}', 'application/json');

$this->assertFalse($this->resourceOwner->revokeToken('token'));
}

public function testCustomResponseClass()
{
$class = CustomUserResponse::class;
$resourceOwner = $this->createResourceOwner($this->resourceOwnerName, ['user_response_class' => $class]);

/** @var CustomUserResponse $userResponse */
$token = '.'.base64_encode($this->userResponse);
$userResponse = $resourceOwner->getUserInformation(['access_token' => 'token', 'id_token' => $token]);
$this->assertInstanceOf($class, $userResponse);
$this->assertEquals('foo666', $userResponse->getUsername());
$this->assertEquals('foo', $userResponse->getNickname());
$this->assertEquals('token', $userResponse->getAccessToken());
$this->assertEquals('foo', $userResponse->getFirstName());
$this->assertNull($userResponse->getRefreshToken());
$this->assertEquals('BAR', $userResponse->getLastName());
$this->assertNull($userResponse->getExpiresIn());
}

public function testGetUserInformation()
{
/**
* @var \HWI\Bundle\OAuthBundle\OAuth\Response\AbstractUserResponse
*/
$token = '.'.base64_encode($this->userResponse);

$userResponse = $this->resourceOwner->getUserInformation(['access_token' => 'token', 'id_token' => $token,
'firstName' => 'Test', 'lastName' => 'User', ]);
$this->assertEquals('1', $userResponse->getUsername());
$this->assertEquals('[email protected]', $userResponse->getEmail());
$this->assertEquals('token', $userResponse->getAccessToken());
$this->assertEquals('Test', $userResponse->getFirstName());
$this->assertEquals('User', $userResponse->getLastName());
$this->assertNull($userResponse->getRefreshToken());
$this->assertNull($userResponse->getExpiresIn());

$userResponse = $this->resourceOwner->getUserInformation(['access_token' => 'token', 'id_token' => $token]);
$this->assertEquals('1', $userResponse->getUsername());
$this->assertEquals('[email protected]', $userResponse->getEmail());
$this->assertEquals('token', $userResponse->getAccessToken());
$this->assertNull($userResponse->getFirstName());
$this->assertNull($userResponse->getLastName());
$this->assertNull($userResponse->getRefreshToken());
$this->assertNull($userResponse->getExpiresIn());
}

public function testGetUserInformationFailure()
{
$exception = new \Exception('Undefined index id_token');
try {
$this->resourceOwner->getUserInformation(['access_token' => 'token']);
$this->fail('An exception should have been raised');
} catch (\Exception $e) {
$this->assertSame($exception->getMessage(), $e->getMessage());
}
}
}
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"security",

"amazon",
"apple",
"asana",
"auth0",
"azure",
Expand Down

0 comments on commit efdc4d1

Please sign in to comment.