-
Notifications
You must be signed in to change notification settings - Fork 7
ENH Add extension for new signing key and upgrade to CMS 5 #31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
c609c99
a85f862
c10571c
bbf6886
e8683f0
00a500a
1fa13bd
d2dad78
ac4f62f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,15 +12,17 @@ | |
| use GuzzleHttp\Psr7\Response; | ||
| use GuzzleHttp\Psr7\ServerRequest; | ||
| use GuzzleHttp\Psr7\Utils; | ||
| use IanSimpson\OAuth2\Entities\UserEntity; | ||
| use IanSimpson\OAuth2\AuthorizationValidators\BearerTokenValidatorEddsa; | ||
| use IanSimpson\OAuth2\Entities\ClientEntity; | ||
| use IanSimpson\OAuth2\Entities\ScopeEntity; | ||
| use IanSimpson\OAuth2\Entities\UserEntity; | ||
| use IanSimpson\OAuth2\Repositories\AccessTokenRepository; | ||
| use IanSimpson\OAuth2\Repositories\AuthCodeRepository; | ||
| use IanSimpson\OAuth2\Repositories\ClientRepository; | ||
| use IanSimpson\OAuth2\Repositories\RefreshTokenRepository; | ||
| use IanSimpson\OAuth2\Repositories\ScopeRepository; | ||
| use League\OAuth2\Server\AuthorizationServer; | ||
| use League\OAuth2\Server\AuthorizationValidators\AuthorizationValidatorInterface; | ||
| use League\OAuth2\Server\Entities\ScopeEntityInterface; | ||
| use League\OAuth2\Server\Exception\OAuthServerException; | ||
| use League\OAuth2\Server\Grant\AuthCodeGrant; | ||
|
|
@@ -71,6 +73,11 @@ class OauthServerController extends Controller | |
| */ | ||
| protected $logger; | ||
|
|
||
| /** | ||
| * @var null|AuthorizationValidatorInterface | ||
| */ | ||
| protected $authorizationValidator = null; | ||
|
|
||
| private readonly string $privateKey; | ||
|
|
||
| private readonly string $publicKey; | ||
|
|
@@ -185,12 +192,22 @@ public function __construct() | |
| parent::__construct(); | ||
| } | ||
|
|
||
| public static function getGrantTypeExpiryInterval(): string | ||
| public static function getGrantTypeExpiryInterval(): mixed | ||
| { | ||
| return self::config()->grant_expiry_interval ?? self::$grant_expiry_interval; | ||
| } | ||
|
|
||
| public function handleRequest(HTTPRequest $request) | ||
| public function getAuthorizationValidator(): ?AuthorizationValidatorInterface | ||
| { | ||
| return $this->authorizationValidator; | ||
| } | ||
|
|
||
| public function setAuthorizationValidator(?AuthorizationValidatorInterface $value): void | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is there a use case for setting this to null? If not then you could probably remove
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. If null is passed, the |
||
| { | ||
| $this->authorizationValidator = $value; | ||
| } | ||
|
|
||
| public function handleRequest(HTTPRequest $request): HTTPResponse | ||
| { | ||
| $this->myRequestAdapter = new HttpRequestAdapter(); | ||
| $this->myRequest = $this->myRequestAdapter->toPsr7($request); | ||
|
|
@@ -284,13 +301,14 @@ public function accessToken(): HTTPResponse | |
| /** | ||
| * @param mixed $controller | ||
| */ | ||
| public static function authenticateRequest($controller): ?ServerRequestInterface | ||
| public function authenticateRequest($controller): ?ServerRequestInterface | ||
| { | ||
| $publicKey = self::getKey('OAUTH_PUBLIC_KEY_PATH'); | ||
|
|
||
| $server = new ResourceServer( | ||
| new AccessTokenRepository(), | ||
| $publicKey | ||
| $publicKey, | ||
| $this->getAuthorizationValidator() | ||
| ); | ||
|
|
||
| $request = ServerRequest::fromGlobals(); | ||
|
|
@@ -311,9 +329,9 @@ public static function authenticateRequest($controller): ?ServerRequestInterface | |
| /** | ||
| * @param mixed $controller | ||
| */ | ||
| public static function getMember($controller): ?Member | ||
| public function getMember($controller): ?Member | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is a breaking change (which is fine so long as you're planning to publish a new version). But its also referenced on the main readme.md as how to use the module so it'd be good to update it there too
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll double check this one too. But thanks. I'll make sure to update the doc, it's mentioned in there. |
||
| { | ||
| $request = self::authenticateRequest($controller); | ||
| $request = $this->authenticateRequest($controller); | ||
|
|
||
| if (!$request instanceof ServerRequestInterface) { | ||
| return null; | ||
|
|
@@ -331,7 +349,8 @@ public function validateClientGrant(HTTPRequest $request): HTTPResponse | |
| { | ||
| $server = new ResourceServer( | ||
| new AccessTokenRepository(), | ||
| $this->publicKey | ||
| $this->publicKey, | ||
| $this->getAuthorizationValidator() | ||
| ); | ||
|
|
||
| $this->handleRequest($request); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| # EdDSA | ||
|
|
||
| One of the latest digital signature scheme that is deemed faster, more secure and simpler to implement compare with its counterpart e.g. RSA. | ||
|
|
||
| ## Ed25519 | ||
|
|
||
| Specific implemenation of EdDSA. You may need to use | ||
|
|
||
| ## Steps | ||
|
|
||
| ### Generate a private/public key pair using Ed25519 algorithm | ||
|
|
||
| ```shell | ||
| openssl genpkey -algorithm Ed25519 -out private.key | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. looks like we should probably put this as the recommended way on the main Readme.md? it is a better default than RSA
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A recommentdation yes, but probably leave RSA as default since not most application use the new algorithm (more so support it) |
||
| openssl pkey -in private.key -pubout -out public.key | ||
| ``` | ||
|
|
||
| ### Add authorization validator | ||
|
|
||
| You may extend the [Bearer token validator](https://github.com/thephpleague/oauth2-server/blob/master/src/AuthorizationValidators/BearerTokenValidator.php) if majority of the logic is almost identical or a totally different implementation as long as it implements `AuthorizationValidatorInterface`. | ||
|
|
||
| ### Add yaml config | ||
|
|
||
| Add a custom yaml config on your project to set the custom authorization validator as `OauthServerController` dependency. You may need to pass other constructor parameters if needed. | ||
|
|
||
| ```yaml | ||
| --- | ||
| Name: oauth_server_controller_dependencies | ||
| --- | ||
| IanSimpson\OAuth2\Entities\AccessTokenEntity: | ||
| extensions: | ||
| - App\DAM\OauthServer\AccessTokenEntityExtension | ||
|
|
||
| SilverStripe\Core\Injector\Injector: | ||
| App\DAM\OauthServer\BearerTokenValidatorEddsa: | ||
| constructor: | ||
| - '%$IanSimpson\OAuth2\Repositories\AccessTokenRepository' | ||
| IanSimpson\OAuth2\OauthServerController: | ||
| properties: | ||
| AuthorizationValidator: '%$App\DAM\OauthServer\BearerTokenValidatorEddsa' | ||
| ``` | ||
| ### Update JWT Token | ||
| A public method is added to allow updating the generated access token in case the chosen algorithm does not match the token generator. To do so, add an extension to the `AccessTokenEntity` in your project and use the `updateJWT` hook. You may also refer to the [default token generator](https://github.com/thephpleague/oauth2-server/blob/master/src/Entities/Traits/AccessTokenTrait.php#L60), on the token format. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| <?php | ||
|
|
||
| namespace IanSimpson\Tests; | ||
|
|
||
| use IanSimpson\OAuth2\Entities\AccessTokenEntity; | ||
| use IanSimpson\Tests\Fixtures\AccessTokenEntityExtensionFake; | ||
| use Lcobucci\JWT\Token\Plain; | ||
| use League\OAuth2\Server\CryptKey; | ||
| use SilverStripe\Dev\SapphireTest; | ||
|
|
||
| class AccessTokenEntityTest extends SapphireTest | ||
| { | ||
| protected static $required_extensions = [ | ||
| AccessTokenEntity::class => [ | ||
| AccessTokenEntityExtensionFake::class, | ||
| ] | ||
| ]; | ||
|
|
||
| public function testGetPrivateKey(): void | ||
| { | ||
| $cryptKeyFake = $this->createMock(CryptKey::class); | ||
| $entity = AccessTokenEntity::create(); | ||
|
|
||
| $this->assertNull($entity->getPrivateKey()); | ||
|
|
||
| $entity->setPrivateKey($cryptKeyFake); | ||
| $this->assertEquals($cryptKeyFake, $entity->getPrivateKey()); | ||
| } | ||
|
|
||
| public function testConvertToJWT(): void | ||
| { | ||
| $entity = AccessTokenEntity::create(); | ||
| $this->assertInstanceOf(Plain::class, $entity->convertToJWT()); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,7 @@ | |
| use Lcobucci\JWT\Validation\Constraint\LooseValidAt; | ||
| use Lcobucci\JWT\Validation\Constraint\PermittedFor; | ||
| use Lcobucci\JWT\Validation\Constraint\RelatedTo; | ||
| use League\OAuth2\Server\AuthorizationValidators\AuthorizationValidatorInterface; | ||
| use League\OAuth2\Server\CryptKey; | ||
| use League\OAuth2\Server\CryptTrait; | ||
| use Monolog\Logger; | ||
|
|
@@ -28,6 +29,7 @@ | |
| use SilverStripe\Core\Injector\Injector; | ||
| use SilverStripe\Dev\FunctionalTest; | ||
| use SilverStripe\Security\Member; | ||
| use IanSimpson\Tests\Fixtures\BearerTokenValidatorFake; | ||
|
|
||
| /** | ||
| * @internal | ||
|
|
@@ -241,7 +243,7 @@ public function now(): DateTimeImmutable | |
|
|
||
| $_SERVER['AUTHORIZATION'] = sprintf('Bearer %s', $at->__toString()); | ||
|
|
||
| $request = OauthServerController::authenticateRequest(null); | ||
| $request = OauthServerController::singleton()->authenticateRequest(null); | ||
|
|
||
| $this->assertInstanceOf(ServerRequestInterface::class, $request); | ||
| $this->assertSame($request->getAttribute('oauth_access_token_id'), $at->Code); | ||
|
|
@@ -322,7 +324,16 @@ public function now(): DateTimeImmutable | |
| public function testGetGrantTypeExpiryInterval(): void | ||
| { | ||
| $oauthController = OauthServerController::singleton(); | ||
| OauthServerController::config()->merge('grant_expiry_interval', 'PT1H'); | ||
| $this->assertSame('PT1H', $oauthController::getGrantTypeExpiryInterval()); | ||
| OauthServerController::config()->merge('grant_expiry_interval', ['PT1H']); | ||
| $this->assertSame('PT1H', $oauthController::getGrantTypeExpiryInterval()[0]); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what's the rationale for loosening the type on
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't recall unfortunately, probably a failing test. I'll have a look. Thanks. |
||
| } | ||
|
|
||
| public function testGetAuthorizationValidator(): void | ||
| { | ||
| $oauthController = OauthServerController::singleton(); | ||
| $this->assertNull($oauthController->getAuthorizationValidator()); | ||
|
|
||
| $oauthController->setAuthorizationValidator(new BearerTokenValidatorFake()); | ||
| $this->assertInstanceOf(BearerTokenValidatorFake::class, $oauthController->getAuthorizationValidator()); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| <?php | ||
|
|
||
| namespace IanSimpson\Tests\Fixtures; | ||
|
|
||
| use Lcobucci\JWT\Token; | ||
| use Lcobucci\JWT\Token\DataSet; | ||
| use Lcobucci\JWT\Token\Plain; | ||
| use Lcobucci\JWT\Token\Signature; | ||
| use SilverStripe\Core\Extension; | ||
| use SilverStripe\Dev\TestOnly; | ||
|
|
||
| class AccessTokenEntityExtensionFake extends Extension implements TestOnly | ||
| { | ||
| public function updateJWT(&$token) | ||
| { | ||
| $headers = new DataSet(['alg' => 'none'], 'headers'); | ||
| $claims = new DataSet([], 'claims'); | ||
| $signature = new Signature('hash', 'signature'); | ||
| $token = new Plain($headers, $claims, $signature); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| <?php | ||
|
|
||
| namespace IanSimpson\Tests\Fixtures; | ||
|
|
||
| use League\OAuth2\Server\AuthorizationValidators\AuthorizationValidatorInterface; | ||
| use Psr\Http\Message\ServerRequestInterface; | ||
| use SilverStripe\Dev\TestOnly; | ||
|
|
||
| class BearerTokenValidatorFake implements AuthorizationValidatorInterface, TestOnly | ||
| { | ||
|
|
||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| public function validateAuthorization(ServerRequestInterface $request) | ||
| { | ||
| return null; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'm wondering about this change. Seems that most of the parts of
convertToJWTare able to be changed in other public methods eggetScopesor you can override the public initJwtConfiguration and use the setters on https://lcobucci-jwt.readthedocs.io/en/stable/configuration/ ? Do you have an example of an extension? I'm thinking eddsa is probably a better default it could be good to include that change but allow updating it with injector or similarThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://github.com/silverstripeltd/westpac-2020/blob/master/app/src/DAM/OauthServer/AccessTokenEntityExtension.php#L21
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah looking at that implementation it seems it would be better to override the public
initJwtConfigurationrather than the private convertToJWT as that detail may change in future non breaking releases given its a private method. It also looks like that implementation could be part of the module? It could be configured using yaml instead of the default sha256 version as an option?https://github.com/thephpleague/oauth2-server/blob/8.5.x/src/Entities/Traits/AccessTokenTrait.php#L44
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@blueo Double checking, Are you thinking about adding the eddsa config but still use RSA as default?
Given the amount of changes requested, I might need to ask for more time (budget) than we initially estimated this for so I might revisit it again some time in the future. Appreciate the feedback!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah I'd suggest making eddsa the default and bringing that implementation from the internal module in here as rsa getting a bit old now AFAIK. but could also leave it as default with just docs on how to change. Yeah can leave the implementation to when you can get to it :)