Skip to content

Commit cb4ee48

Browse files
committed
added id token generation, registration result.
added scopes added Solid enums for openIdConfiguration added TokenGenerator class as a collection of functions that should be in the php-solid-auth lib - this class needs cleanup
1 parent 64e2ecf commit cb4ee48

6 files changed

Lines changed: 176 additions & 3 deletions

File tree

src/Config/Server.php

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,21 @@ final public function __construct(array $data, bool $strict = false)
5656
{
5757
$data = array_merge([
5858
OidcMeta::ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED => ['RS256'],
59-
OidcMeta::RESPONSE_TYPES_SUPPORTED => \Pdsinterop\Solid\Auth\Enum\OAuth2\ResponseType::CODE_ID_TOKEN_TOKEN,
6059
OidcMeta::SUBJECT_TYPES_SUPPORTED => ['public'],
61-
], $data);
60+
OidcMeta::RESPONSE_TYPES_SUPPORTED => array("code","code token","code id_token","id_token code","id_token","id_token token","code id_token token","none"),
61+
OidcMeta::TOKEN_TYPES_SUPPORTED => array("legacyPop","dpop"),
62+
OidcMeta::RESPONSE_MODES_SUPPORTED => array("query","fragment"),
63+
OidcMeta::GRANT_TYPES_SUPPORTED => array("authorization_code","implicit","refresh_token","client_credentials"),
64+
OidcMeta::TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED => "client_secret_basic",
65+
OidcMeta::TOKEN_ENDPOINT_AUTH_SIGNING_ALG_VALUES_SUPPORTED => ["RS256"],
66+
OidcMeta::DISPLAY_VALUES_SUPPORTED => [],
67+
OidcMeta::CLAIM_TYPES_SUPPORTED => ["normal"],
68+
OidcMeta::CLAIMS_SUPPORTED => [],
69+
OidcMeta::CLAIMS_PARAMETER_SUPPORTED => false,
70+
OidcMeta::REQUEST_PARAMETER_SUPPORTED => true,
71+
OidcMeta::REQUEST_URI_PARAMETER_SUPPORTED => false,
72+
OidcMeta::REQUIRE_REQUEST_URI_REGISTRATION => false
73+
], $data);
6274

6375
$this->data = array_filter($data, [OidcMeta::class, 'has'], ARRAY_FILTER_USE_KEY);
6476
$this->strict = $strict;

src/Entity/Scope.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,8 @@ class Scope implements ScopeEntityInterface
1010
{
1111
use EntityTrait;
1212
use ScopeTrait;
13+
14+
public function getIdentifier() {
15+
return "openid";
16+
}
1317
}

src/Enum/OpenId/OpenIdConnectMetadata.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,4 +204,9 @@ class OpenIdConnectMetadata extends AbstractEnum
204204
* JSON array containing a list of the JWS [JWS] signing algorithms (alg values) [JWA] supported by the UserInfo Endpoint to encode the Claims in a JWT [JWT]. The value none MAY be included.
205205
*/
206206
public const USERINFO_SIGNING_ALG_VALUES_SUPPORTED = 'userinfo_signing_alg_values_supported';
207+
208+
/* FIXME: Solid types here */
209+
public const TOKEN_TYPES_SUPPORTED = 'token_types_supported';
210+
public const CHECK_SESSION_IFRAME = 'check_session_iframe';
211+
public const END_SESSION_ENDPOINT = 'end_session_endpoint';
207212
}

src/Repository/AccessToken.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,13 @@ public function getNewToken(
2525
array $scopes,
2626
$userIdentifier = null
2727
) : AccessTokenEntityInterface {
28-
return new AccessTokenEntity($clientEntity);
28+
29+
$accessToken = new AccessTokenEntity($clientEntity);
30+
$accessToken->setUserIdentifier($userIdentifier);
31+
foreach ($scopes as $scope) {
32+
$accessToken->addScope(new \Pdsinterop\Solid\Auth\Entity\Scope($scope));
33+
}
34+
return $accessToken;
2935
}
3036

3137
/**

src/TokenGenerator.php

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Pdsinterop\Solid\Auth;
4+
5+
use Pdsinterop\Solid\Auth\Utils\Jwks;
6+
use Pdsinterop\Solid\Auth\Enum\OpenId\OpenIdConnectMetadata as OidcMeta;
7+
8+
class TokenGenerator
9+
{
10+
////////////////////////////// CLASS PROPERTIES \\\\\\\\\\\\\\\\\\\\\\\\\\\\
11+
12+
/** @var Config */
13+
public $config;
14+
15+
//////////////////////////////// PUBLIC API \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
16+
17+
final public function __construct(
18+
Config $config
19+
) {
20+
$this->config = $config;
21+
}
22+
23+
public function generateRegistrationAccessToken($clientId, $privateKey) {
24+
$issuer = $this->config->getServer()->get(OidcMeta::ISSUER);
25+
26+
// Create JWT
27+
$signer = new \Lcobucci\JWT\Signer\Rsa\Sha256();
28+
$keychain = new \Lcobucci\JWT\Signer\Keychain();
29+
$builder = new \Lcobucci\JWT\Builder();
30+
$token = $builder
31+
->setIssuer($issuer)
32+
->permittedFor($clientId)
33+
->set("sub", $clientId)
34+
->sign($signer, $keychain->getPrivateKey($privateKey))
35+
->getToken();
36+
$result = $token->__toString();
37+
return $token->__toString();
38+
}
39+
40+
public function generateIdToken($accessToken, $clientId, $subject, $nonce, $privateKey) {
41+
$issuer = $this->config->getServer()->get(OidcMeta::ISSUER);
42+
43+
$jwks = $this->getJwks();
44+
$tokenHash = $this->generateTokenHash($accessToken);
45+
46+
// Create JWT
47+
$signer = new \Lcobucci\JWT\Signer\Rsa\Sha256();
48+
$keychain = new \Lcobucci\JWT\Signer\Keychain();
49+
$builder = new \Lcobucci\JWT\Builder();
50+
$token = $builder
51+
->setIssuer($issuer)
52+
->permittedFor($clientId)
53+
->setIssuedAt(time())
54+
->setNotBefore(time() - 1)
55+
->setExpiration(time() + 14*24*60*60)
56+
->set("azp", $clientId)
57+
->set("sub", $subject)
58+
->set("jti", $this->generateJti())
59+
->set("nonce", $nonce)
60+
->set("at_hash", $tokenHash) //FIXME: at_hash should only be added if the response_type is a token
61+
->set("c_hash", $tokenHash) // FIXME: c_hash should only be added if the response_type is a code
62+
->set("cnf", array(
63+
"jwk" => $jwks['keys'][0]
64+
))
65+
->withHeader('kid', $jwks['keys'][0]['kid'])
66+
->sign($signer, $keychain->getPrivateKey($privateKey))
67+
->getToken();
68+
$result = $token->__toString();
69+
return $result;
70+
}
71+
72+
public function respondToRegistration($registration, $privateKey) {
73+
/*
74+
Expects in $registration:
75+
client_id
76+
client_id_issued_at
77+
redirect_uris
78+
registration_client_uri
79+
*/
80+
$registration_access_token = $this->generateRegistrationAccessToken($registration['client_id'], $privateKey);
81+
82+
$registrationBase = array(
83+
'response_types' => array("id_token token"),
84+
'grant_types' => array("implicit"),
85+
'application_type' => 'web',
86+
'id_token_signed_response_alg' => "RS256",
87+
'token_endpoint_auth_method' => 'client_secret_basic',
88+
'registration_access_token' => $registration_access_token,
89+
);
90+
91+
$registration = array_merge($registrationBase, $registration);
92+
return $registration;
93+
}
94+
95+
public function addIdTokenToResponse($response, $clientId, $subject, $nonce, $privateKey) {
96+
if ($response->hasHeader("Location")) {
97+
$value = $response->getHeaderLine("Location");
98+
if (preg_match("/#access_token=(.*?)&/", $value, $matches)) {
99+
$idToken = $this->generateIdToken(
100+
$matches[1],
101+
$clientId,
102+
$subject,
103+
$nonce,
104+
$privateKey
105+
);
106+
$value = preg_replace("/#access_token=(.*?)&/", "#access_token=\$1&id_token=$idToken&", $value);
107+
$response = $response->withHeader("Location", $value);
108+
} else if (preg_match("/code=(.*?)&/", $value, $matches)) {
109+
$idToken = $this->generateIdToken(
110+
$matches[1],
111+
$clientId,
112+
$subject,
113+
$nonce,
114+
$privateKey
115+
);
116+
$value = preg_replace("/code=(.*?)&/", "code=\$1&id_token=$idToken&", $value);
117+
$response = $response->withHeader("Location", value);
118+
}
119+
}
120+
return $response;
121+
}
122+
///////////////////////////// HELPER FUNCTIONS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\
123+
124+
private function generateJti() {
125+
return substr(md5((string)time()), 12); // FIXME: generate unique jti values
126+
}
127+
128+
private function generateTokenHash($accessToken) {
129+
$atHash = hash('sha256', $accessToken);
130+
$atHash = substr($atHash, 0, 32);
131+
$atHash = hex2bin($atHash);
132+
$atHash = base64_encode($atHash);
133+
$atHash = rtrim($atHash, '=');
134+
$atHash = str_replace('/', '_', $atHash);
135+
$atHash = str_replace('+', '-', $atHash);
136+
137+
return $atHash;
138+
}
139+
140+
private function getJwks() {
141+
$key = $this->config->getKeys()->getPublicKey();
142+
$jwks = new Jwks($key);
143+
return json_decode($jwks->__toString(), true);
144+
}
145+
}

src/Utils/Jwks.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ private function createKey(string $certificate, $subject) : array
4747
JwkParameter::KEY_TYPE => 'RSA',
4848
RsaParameter::PUBLIC_EXPONENT => 'AQAB', // Hard-coded as `Base64Url::encode($keyInfo['rsa']['e'])` tends to be empty...
4949
RsaParameter::PUBLIC_MODULUS => Base64Url::encode($subject),
50+
JwkParameter::KEY_OPERATIONS => array("verify")
5051
];
5152
}
5253

0 commit comments

Comments
 (0)