Merge 21eef41caa
into 446c1c5ade
This commit is contained in:
commit
7e4e65d327
|
@ -41,6 +41,8 @@ use OCP\AppFramework\Http;
|
|||
use OCP\AppFramework\Http\JSONResponse;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\IRequest;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUserManager;
|
||||
use OCP\Security\ICrypto;
|
||||
use OCP\Security\ISecureRandom;
|
||||
|
||||
|
@ -59,6 +61,10 @@ class OauthApiController extends Controller {
|
|||
private $time;
|
||||
/** @var Throttler */
|
||||
private $throttler;
|
||||
/** @var IUserManager */
|
||||
private $userManager;
|
||||
/** @var IURLGenerator */
|
||||
private $urlGenerator;
|
||||
|
||||
public function __construct(string $appName,
|
||||
IRequest $request,
|
||||
|
@ -68,7 +74,9 @@ class OauthApiController extends Controller {
|
|||
TokenProvider $tokenProvider,
|
||||
ISecureRandom $secureRandom,
|
||||
ITimeFactory $time,
|
||||
Throttler $throttler) {
|
||||
Throttler $throttler,
|
||||
IUserManager $userManager,
|
||||
IURLGenerator $urlGenerator) {
|
||||
parent::__construct($appName, $request);
|
||||
$this->crypto = $crypto;
|
||||
$this->accessTokenMapper = $accessTokenMapper;
|
||||
|
@ -77,6 +85,8 @@ class OauthApiController extends Controller {
|
|||
$this->secureRandom = $secureRandom;
|
||||
$this->time = $time;
|
||||
$this->throttler = $throttler;
|
||||
$this->userManager = $userManager;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -167,6 +177,7 @@ class OauthApiController extends Controller {
|
|||
$this->accessTokenMapper->update($accessToken);
|
||||
|
||||
$this->throttler->resetDelay($this->request->getRemoteAddress(), 'login', ['user' => $appToken->getUID()]);
|
||||
$jwt = $this->getIdToken($client_id, $appToken, $client);
|
||||
|
||||
return new JSONResponse(
|
||||
[
|
||||
|
@ -175,7 +186,66 @@ class OauthApiController extends Controller {
|
|||
'expires_in' => 3600,
|
||||
'refresh_token' => $newCode,
|
||||
'user_id' => $appToken->getUID(),
|
||||
'id_token' => $jwt,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $client_id
|
||||
* @param \OC\Authentication\Token\IToken $appToken
|
||||
* @param \OCA\OAuth2\Db\Client $client
|
||||
* @return string
|
||||
*/
|
||||
private function getIdToken($client_id, \OC\Authentication\Token\IToken $appToken, \OCA\OAuth2\Db\Client $client)
|
||||
{
|
||||
// The id token needs to be correctly build as JWT. Taken from https://dev.to/robdwaller/how-to-create-a-json-web-token-using-php-3gml
|
||||
|
||||
// Create token header as a JSON string
|
||||
$header = json_encode(['typ' => 'JWT', 'alg' => 'HS256']);
|
||||
|
||||
// We need the user to fill in name and email in the id_token
|
||||
$user = $this->userManager->get($appToken->getUID());
|
||||
|
||||
// Create token payload as a JSON string
|
||||
$payload = json_encode([
|
||||
// required for OIDC, see https://openid.net/specs/openid-connect-core-1_0.html#IDToken
|
||||
// Issuer Identifier for the Issuer of the response.
|
||||
'iss' => $this->urlGenerator->getBaseUrl(),
|
||||
// Subject Identifier. A locally unique and never reassigned identifier within the Issuer for the End-User, which is intended to be consumed by the Client
|
||||
'sub' => $appToken->getUID(),
|
||||
// Audience(s) that this ID Token is intended for. It MUST contain the OAuth 2.0 client_id of the Relying Party as an audience value.
|
||||
'aud' => $client_id,
|
||||
// Expiration time on or after which the ID Token MUST NOT be accepted for processing.
|
||||
'exp' => $appToken->getExpires(),
|
||||
// Time at which the JWT was issued.
|
||||
'iat' => $this->time->getTime(),
|
||||
// Time when the End-User authentication occurred.
|
||||
'auth_time' => $this->time->getTime(),
|
||||
|
||||
// optional, can be requested by claims, we don't support requesting claims as of now, so we just send them always
|
||||
// see https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims
|
||||
// End-User's preferred e-mail address.
|
||||
'email' => $user->getEMailAddress(),
|
||||
// End-User's full name in displayable form including all name parts, possibly including titles and suffixes
|
||||
'name' => $user->getDisplayName(),
|
||||
|
||||
]);
|
||||
|
||||
// Encode Header to Base64Url String
|
||||
$base64UrlHeader = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($header));
|
||||
|
||||
// Encode Payload to Base64Url String
|
||||
$base64UrlPayload = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($payload));
|
||||
|
||||
// Create Signature Hash
|
||||
$signature = hash_hmac('sha256', $base64UrlHeader . "." . $base64UrlPayload, $client->getSecret(), true);
|
||||
|
||||
// Encode Signature to Base64Url String
|
||||
$base64UrlSignature = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($signature));
|
||||
|
||||
// Create JWT
|
||||
$jwt = $base64UrlHeader . "." . $base64UrlPayload . "." . $base64UrlSignature;
|
||||
return $jwt;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,6 +42,9 @@ use OCP\AppFramework\Http;
|
|||
use OCP\AppFramework\Http\JSONResponse;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\IRequest;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use OCP\Security\ICrypto;
|
||||
use OCP\Security\ISecureRandom;
|
||||
use Test\TestCase;
|
||||
|
@ -63,6 +66,10 @@ class OauthApiControllerTest extends TestCase {
|
|||
private $time;
|
||||
/** @var Throttler|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $throttler;
|
||||
/** @var IUserManager|\PHPUnit_Framework_MockObject_MockObject */
|
||||
private $userManager;
|
||||
/** @var IURLGenerator|\PHPUnit_Framework_MockObject_MockObject */
|
||||
private $urlGenerator;
|
||||
/** @var OauthApiController */
|
||||
private $oauthApiController;
|
||||
|
||||
|
@ -77,6 +84,8 @@ class OauthApiControllerTest extends TestCase {
|
|||
$this->secureRandom = $this->createMock(ISecureRandom::class);
|
||||
$this->time = $this->createMock(ITimeFactory::class);
|
||||
$this->throttler = $this->createMock(Throttler::class);
|
||||
$this->userManager = $this->createMock(IUserManager::class);
|
||||
$this->urlGenerator = $this->createMock(IURLGenerator::class);
|
||||
|
||||
$this->oauthApiController = new OauthApiController(
|
||||
'oauth2',
|
||||
|
@ -87,7 +96,9 @@ class OauthApiControllerTest extends TestCase {
|
|||
$this->tokenProvider,
|
||||
$this->secureRandom,
|
||||
$this->time,
|
||||
$this->throttler
|
||||
$this->throttler,
|
||||
$this->userManager,
|
||||
$this->urlGenerator
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -286,12 +297,25 @@ class OauthApiControllerTest extends TestCase {
|
|||
})
|
||||
);
|
||||
|
||||
$this->urlGenerator->method('getBaseUrl')
|
||||
->willReturn('http://localhost');
|
||||
|
||||
$expected = new JSONResponse([
|
||||
'access_token' => 'random72',
|
||||
'token_type' => 'Bearer',
|
||||
'expires_in' => 3600,
|
||||
'refresh_token' => 'random128',
|
||||
'user_id' => 'userId',
|
||||
'id_token' => $this->encodeJWT(json_encode([
|
||||
'iss' => 'http://localhost',
|
||||
'sub' => 'userId',
|
||||
'aud' => 'clientId',
|
||||
'exp' => 4600,
|
||||
'iat' => 1000,
|
||||
'auth_time' => 1000,
|
||||
'email' => null,
|
||||
'name' => null
|
||||
]), 'clientSecret')
|
||||
]);
|
||||
|
||||
$this->request->method('getRemoteAddress')
|
||||
|
@ -305,6 +329,13 @@ class OauthApiControllerTest extends TestCase {
|
|||
['user' => 'userId']
|
||||
);
|
||||
|
||||
$user = $this->createMock(IUser::class);;
|
||||
|
||||
$this->userManager->expects($this->once())
|
||||
->method('get')
|
||||
->with('userId')
|
||||
->willReturn($user);
|
||||
|
||||
$this->assertEquals($expected, $this->oauthApiController->getToken('refresh_token', null, 'validrefresh', 'clientId', 'clientSecret'));
|
||||
}
|
||||
|
||||
|
@ -378,12 +409,25 @@ class OauthApiControllerTest extends TestCase {
|
|||
})
|
||||
);
|
||||
|
||||
$this->urlGenerator->method('getBaseUrl')
|
||||
->willReturn('http://localhost');
|
||||
|
||||
$expected = new JSONResponse([
|
||||
'access_token' => 'random72',
|
||||
'token_type' => 'Bearer',
|
||||
'expires_in' => 3600,
|
||||
'refresh_token' => 'random128',
|
||||
'user_id' => 'userId',
|
||||
'id_token' => $this->encodeJWT(json_encode([
|
||||
'iss' => 'http://localhost',
|
||||
'sub' => 'userId',
|
||||
'aud' => 'clientId',
|
||||
'exp' => 4600,
|
||||
'iat' => 1000,
|
||||
'auth_time' => 1000,
|
||||
'email' => null,
|
||||
'name' => null
|
||||
]), 'clientSecret'),
|
||||
]);
|
||||
|
||||
$this->request->server['PHP_AUTH_USER'] = 'clientId';
|
||||
|
@ -400,6 +444,13 @@ class OauthApiControllerTest extends TestCase {
|
|||
['user' => 'userId']
|
||||
);
|
||||
|
||||
$user = $this->createMock(IUser::class);;
|
||||
|
||||
$this->userManager->expects($this->once())
|
||||
->method('get')
|
||||
->with('userId')
|
||||
->willReturn($user);
|
||||
|
||||
$this->assertEquals($expected, $this->oauthApiController->getToken('refresh_token', null, 'validrefresh', null, null));
|
||||
}
|
||||
|
||||
|
@ -473,12 +524,25 @@ class OauthApiControllerTest extends TestCase {
|
|||
})
|
||||
);
|
||||
|
||||
$this->urlGenerator->method('getBaseUrl')
|
||||
->willReturn('http://localhost');
|
||||
|
||||
$expected = new JSONResponse([
|
||||
'access_token' => 'random72',
|
||||
'token_type' => 'Bearer',
|
||||
'expires_in' => 3600,
|
||||
'refresh_token' => 'random128',
|
||||
'user_id' => 'userId',
|
||||
'id_token' => $this->encodeJWT(json_encode([
|
||||
'iss' => 'http://localhost',
|
||||
'sub' => 'userId',
|
||||
'aud' => 'clientId',
|
||||
'exp' => 4600,
|
||||
'iat' => 1000,
|
||||
'auth_time' => 1000,
|
||||
'email' => null,
|
||||
'name' => null
|
||||
]), 'clientSecret'),
|
||||
]);
|
||||
|
||||
$this->request->method('getRemoteAddress')
|
||||
|
@ -492,6 +556,33 @@ class OauthApiControllerTest extends TestCase {
|
|||
['user' => 'userId']
|
||||
);
|
||||
|
||||
$user = $this->createMock(IUser::class);;
|
||||
|
||||
$this->userManager->expects($this->once())
|
||||
->method('get')
|
||||
->with('userId')
|
||||
->willReturn($user);
|
||||
|
||||
$this->assertEquals($expected, $this->oauthApiController->getToken('refresh_token', null, 'validrefresh', 'clientId', 'clientSecret'));
|
||||
}
|
||||
|
||||
private function encodeJWT($payload, $secret) {
|
||||
// Create token header as a JSON string
|
||||
$header = json_encode(['typ' => 'JWT', 'alg' => 'HS256']);
|
||||
|
||||
// Encode Header to Base64Url String
|
||||
$base64UrlHeader = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($header));
|
||||
|
||||
// Encode Payload to Base64Url String
|
||||
$base64UrlPayload = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($payload));
|
||||
|
||||
// Create Signature Hash
|
||||
$signature = hash_hmac('sha256', $base64UrlHeader . "." . $base64UrlPayload, $secret, true);
|
||||
|
||||
// Encode Signature to Base64Url String
|
||||
$base64UrlSignature = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($signature));
|
||||
|
||||
// Create JWT
|
||||
return $base64UrlHeader . "." . $base64UrlPayload . "." . $base64UrlSignature;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue