Skip to content

Commit

Permalink
Merge branch 'master' into PHRAS-4062-admin-users-list-and-request-im…
Browse files Browse the repository at this point in the history
…provement
  • Loading branch information
nmaillat authored Jun 6, 2024
2 parents 062c8aa + 5769be7 commit bc8c3be
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 82 deletions.
12 changes: 11 additions & 1 deletion doc/others/openid-sso.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ authentication:
#### keycloak configuration
- create a new client
- get clien-id and client-secret
- get client-id and client-secret
- in the client setting:
set the 'Valid redirect URIs' field with `https://{phraseanet-host}/login/provider/{provider-name}/callback/`
Expand All @@ -56,3 +56,13 @@ authentication:
`Token Claim Name` => groups
`Full group path` => off
`Add to userinfo` => on

#### token expiration
- we can define token expiration in keycloak

Choose a client > Advanced (tab) > Advanced Settings (section)

define "Access Token Lifespan" for the token expiration

and the "Client Session Idle" for the refresh token expiration

56 changes: 51 additions & 5 deletions lib/Alchemy/Phrasea/Authentication/Provider/Openid.php
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,8 @@ public function onCallback(Request $request)
// id_token_hint used when logout
$this->session->set($this->getId() . '.provider.id_token', $data['id_token']);

$this->session->set('provider.token_info', $data);

try {
$this->debug();

Expand All @@ -325,15 +327,15 @@ public function onCallback(Request $request)
throw new NotAuthenticatedException('Guzzle error while authentication', $e->getCode(), $e);
}

$this->debug();
$data = @json_decode($response->getBody(true), true);
$this->debug(var_export($data, true));

if (200 !== $response->getStatusCode()) {
$this->debug();
throw new NotAuthenticatedException('Error while retrieving user info, invalid status code.');
}

$this->debug();
$data = @json_decode($response->getBody(true), true);
$this->debug(var_export($data, true));

if (JSON_ERROR_NONE !== json_last_error()) {
$this->debug();
throw new NotAuthenticatedException('Error while retrieving user info, unable to parse JSON.');
Expand Down Expand Up @@ -803,8 +805,52 @@ public function getIconURI()
. 'XMCV9CQH+wW8/18L/BeSV1YkHS6B9wAAAABJRU5ErkJggg==';
}

public function getAccessToken()
public function getAccessToken($byRefresh = false)
{
if ($byRefresh) {
$tokenInfo = $this->session->get('provider.token_info');

try {
$url = sprintf("%s/realms/%s/protocol/openid-connect/token/",
$this->config['base-url'],
urlencode($this->config['realm-name'])
);

$guzzleRequest = $this->client->post($url);

$guzzleRequest->addPostFields([
'grant_type' => "refresh_token",
'refresh_token' => $tokenInfo['refresh_token'],
'client_id' => $this->config['client-id'],
'client_secret' => $this->config['client-secret'],
]);

$guzzleRequest->setHeader('Accept', 'application/json');
$response = $guzzleRequest->send();
$this->debug();
}
catch (\Exception $e) {
$this->debug($e->getMessage());
throw new NotAuthenticatedException('Guzzle error while authentication', $e->getCode(), $e);
}

if (200 !== $response->getStatusCode()) {
$this->debug();
throw new NotAuthenticatedException('Error while retrieving user info, invalid status code.');
}

$data = @json_decode($response->getBody(true), true);

if (JSON_ERROR_NONE !== json_last_error()) {
$this->debug();
throw new NotAuthenticatedException('Error while decoding token response, unable to parse JSON.');
}

// override token information
$this->session->set('provider.token_info', $data);
$this->session->set($this->getId() . '.provider.access_token', $data['access_token']);
}

return $this->session->get($this->getId() . '.provider.access_token');
}

Expand Down
1 change: 1 addition & 0 deletions lib/Alchemy/Phrasea/Controller/Root/LoginController.php
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,7 @@ public function authenticateWithProvider(Request $request, $providerId)
public function authenticationCallback(Request $request, $providerId)
{
$this->getSession()->set('auth_provider.id', null);
$this->getSession()->set('provider.token_info', null);

$provider = $this->findProvider($providerId);

Expand Down
190 changes: 118 additions & 72 deletions lib/Alchemy/Phrasea/PhraseanetService/Controller/PSExposeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Alchemy\Phrasea\PhraseanetService\Controller;

use Alchemy\Phrasea\Application as PhraseaApplication;
use Alchemy\Phrasea\Authentication\Provider\Openid;
use Alchemy\Phrasea\Authentication\ProvidersCollection;
use Alchemy\Phrasea\Controller\Controller;
use Alchemy\Phrasea\Utilities\NetworkProxiesConfiguration;
Expand Down Expand Up @@ -78,10 +79,10 @@ public function authenticateAction(PhraseaApplication $app, Request $request)

if (isset($tokenBody['refresh_expires_in'])) {
$passSessionNameValue = [
'access_token' => $tokenBody['access_token'],
'expires_at' => time() + $tokenBody['expires_in'],
'refresh_token'=> $tokenBody['refresh_token'],
'refresh_expires_at' => ($tokenBody['refresh_expires_in'] == -1) ? $tokenBody['refresh_expires_in'] : time() + $tokenBody['refresh_expires_in']
'access_token' => $tokenBody['access_token'],
'expires_at' => time() + $tokenBody['expires_in'],
'refresh_token' => $tokenBody['refresh_token'],
'refresh_expires_at' => time() + $tokenBody['refresh_expires_in']
];
} else {
$passSessionNameValue = [
Expand Down Expand Up @@ -204,20 +205,6 @@ public function listPublicationAction(PhraseaApplication $app, Request $request)

$session = $this->getSession();
$passSessionName = $this->getPassSessionName($exposeName);
$providerId = $session->get('auth_provider.id');

if (!$session->has($passSessionName) && $providerId != null) {
try {
$provider = $this->getAuthenticationProviders()->get($providerId);
// class name
if (($provider->getType() == 'Openid' || $provider->getType() == 'PsAuth') && $exposeConfiguration['auth_provider_name'] == $providerId) {

$session->set($passSessionName, ['access_token' => $provider->getAccessToken()]);
$session->set($this->getLoginSessionName($exposeName), $provider->getUserName());
}
} catch(\Exception $e) {
}
}

$accessToken = $this->getAndSaveToken($exposeName);

Expand Down Expand Up @@ -926,8 +913,19 @@ public function addPublicationAssetsAction(PhraseaApplication $app, Request $req
]);
}

$withProviderName = false;
try {
$providerId = $this->getSession()->get('auth_provider.id');
$provider = $this->getAuthenticationProviders()->get($providerId);
if (($provider->getType() == 'Openid' || $provider->getType() == 'PsAuth') && $config['auth_provider_name'] == $providerId) {
$withProviderName = true;
}
} catch (\Exception $e){
// provider not found
}

$accessTokenInfo = [];
if ($config['connection_kind'] == 'password') {
if ($withProviderName || $config['connection_kind'] == 'password') {
$accessTokenInfo = $this->getSession()->get($this->getPassSessionName($exposeName));
} elseif($config['connection_kind'] == 'client_credentials') {
$accessTokenInfo = $this->getSession()->get($this->getCredentialSessionName($exposeName));
Expand Down Expand Up @@ -960,6 +958,9 @@ public function getDataboxesFieldAction(PhraseaApplication $app, Request $reques
}

$exposeMappingName = $this->getExposeMappingName('field');
$fields = [];
$fieldMapping = [];

try {
$clientAnnotationProfile = $this->getClientAnnotationProfile($exposeClient, $exposeName, $profile);

Expand Down Expand Up @@ -1216,12 +1217,23 @@ private function getFields($actualFieldsList)
$fieldFromProfile = [];
foreach ($actualFieldsList as $key => $value) {
$t = explode('_', $key);

$oldFieldMapping = false;
if (count($t) == 2) {
$id = $key;
} else {
$oldFieldMapping = true;
$t = explode('_', $value);
$id = $value;
}

$databox = $this->getApplicationBox()->get_databox($t[0]);
$viewName = $t[0]. ':::' .$databox->get_viewname();
$name = $databox->get_meta_structure()->get_element($t[1])->get_label($this->app['locale']);

$fieldFromProfile[$viewName][$t[1]]['id'] = $key;
$fieldFromProfile[$viewName][$t[1]]['name'] = $databox->get_meta_structure()->get_element($t[1])->get_label($this->app['locale']);
$fieldFromProfile[$viewName][$t[1]]['exposeSideName'] = $value;
$fieldFromProfile[$viewName][$t[1]]['id'] = $id;
$fieldFromProfile[$viewName][$t[1]]['name'] = $name;
$fieldFromProfile[$viewName][$t[1]]['exposeSideName'] = ($oldFieldMapping) ? $name : $value;
$fieldFromProfile[$viewName][$t[1]]['checked'] = true;
}

Expand All @@ -1233,7 +1245,7 @@ private function getFields($actualFieldsList)
continue;
}
// get databoxID_metaID for the checkbox name
$fields[$viewName][$meta->get_id()]['id'] = $databox->get_sbas_id().'_'.$meta->get_id();
$fields[$viewName][$meta->get_id()]['id'] = $databox->get_sbas_id().'_'.$meta->get_id();
$fields[$viewName][$meta->get_id()]['name'] = $meta->get_label($this->app['locale']);
$fields[$viewName][$meta->get_id()]['exposeSideName'] = $meta->get_label($this->app['locale']);;
}
Expand Down Expand Up @@ -1346,6 +1358,43 @@ private function getAndSaveToken($exposeName)
$config = $this->getExposeConfiguration($exposeName);
$session = $this->getSession();
$passSessionName = $this->getPassSessionName($exposeName);
$providerId = $session->get('auth_provider.id');
$withProviderName = false;
$accessToken = '';

if ($providerId != null ) {
try {
$provider = $this->getAuthenticationProviders()->get($providerId);
// class name
if (($provider->getType() == 'Openid' || $provider->getType() == 'PsAuth') && $config['auth_provider_name'] == $providerId) {
$withProviderName = true;
$tokenInfo = $session->get($passSessionName);

if (is_array($tokenInfo) && $tokenInfo['expires_at'] > time()) {
$accessToken = $tokenInfo['access_token'];
} elseif (empty($tokenInfo) || (is_array($tokenInfo) && $tokenInfo['expires_at'] <= time() && isset($tokenInfo['refresh_expires_at']) && $tokenInfo['refresh_expires_at'] > time())) {
/** @var $provider Openid */
$provider->getAccessToken(true); // update token info

$tokenInfo = $session->get('provider.token_info');
$passSessionNameValue = [
'access_token' => $tokenInfo['access_token'],
'expires_at' => time() + $tokenInfo['expires_in'],
'refresh_token' => $tokenInfo['refresh_token'],
'refresh_expires_at' => time() + $tokenInfo['refresh_expires_in'],
'providerId' => $providerId
];

$session->set($passSessionName, $passSessionNameValue);
$session->set($this->getLoginSessionName($exposeName), $provider->getUserName());
$accessToken = $tokenInfo['access_token'];
} else {
throw new \Exception("can not have a refresh token");
}
}
} catch(\Exception $e) {
}
}

$proxyConfig = new NetworkProxiesConfiguration($this->app['conf']);
$oauthClient = $proxyConfig->getClientWithOptions([
Expand All @@ -1354,64 +1403,61 @@ private function getAndSaveToken($exposeName)

$credentialSessionName = $this->getCredentialSessionName($exposeName);

$accessToken = '';
if ($config['connection_kind'] == 'password') {
$tokenInfo = $session->get($passSessionName);
if (!isset($tokenInfo['expires_at'])) {
// case of never expired
// eg: with provederID with phraseanet
$accessToken = $tokenInfo['access_token'];
} elseif (is_array($tokenInfo) && $tokenInfo['expires_at'] > time()) {
$accessToken = $tokenInfo['access_token'];
} elseif (is_array($tokenInfo) && $tokenInfo['expires_at'] <= time() && isset($tokenInfo['refresh_expires_at']) && $tokenInfo['refresh_expires_at'] > time()) {
$resToken = $this->refreshToken($oauthClient, $config, $tokenInfo['refresh_token']);

if ($resToken->getStatusCode() !== 200) {
throw new \Exception("Error when get refresh token with status code: " . $resToken->getStatusCode());
}

$refreshtokenBody = $resToken->getBody()->getContents();

$refreshtokenBody = json_decode($refreshtokenBody,true);

if (isset($refreshtokenBody['refresh_expires_in'])) {
$passSessionNameValue = [
'access_token' => $refreshtokenBody['access_token'],
'expires_at' => time() + $refreshtokenBody['expires_in'],
'refresh_token'=> $refreshtokenBody['refresh_token'],
'refresh_expires_at' => ($refreshtokenBody['refresh_expires_in'] == -1) ? $refreshtokenBody['refresh_expires_in'] : time() + $refreshtokenBody['refresh_expires_in']
];
if (!$withProviderName && $session->has($passSessionName)) {
if ($config['connection_kind'] == 'password') {
$tokenInfo = $session->get($passSessionName);
if (is_array($tokenInfo) && $tokenInfo['expires_at'] > time()) {
$accessToken = $tokenInfo['access_token'];
} elseif (is_array($tokenInfo) && $tokenInfo['expires_at'] <= time() && isset($tokenInfo['refresh_expires_at']) && $tokenInfo['refresh_expires_at'] > time()) {
$resToken = $this->refreshToken($oauthClient, $config, $tokenInfo['refresh_token']);

if ($resToken->getStatusCode() !== 200) {
throw new \Exception("Error when get refresh token with status code: " . $resToken->getStatusCode());
}

$refreshtokenBody = $resToken->getBody()->getContents();

$refreshtokenBody = json_decode($refreshtokenBody,true);

if (isset($refreshtokenBody['refresh_expires_in'])) {
$passSessionNameValue = [
'access_token' => $refreshtokenBody['access_token'],
'expires_at' => time() + $refreshtokenBody['expires_in'],
'refresh_token'=> $refreshtokenBody['refresh_token'],
'refresh_expires_at' => time() + $refreshtokenBody['refresh_expires_in']
];
} else {
$passSessionNameValue = [
'access_token' => $refreshtokenBody['access_token'],
'expires_at' => time() + $refreshtokenBody['expires_in'],
];
}

$session->set($passSessionName, $passSessionNameValue);

$accessToken = $refreshtokenBody['access_token'];
} else {
$passSessionNameValue = [
'access_token' => $refreshtokenBody['access_token'],
'expires_at' => time() + $refreshtokenBody['expires_in'],
];
$session->remove($passSessionName);
throw new \Exception("can not have a refresh token");
}

$session->set($passSessionName, $passSessionNameValue);

$accessToken = $refreshtokenBody['access_token'];
} else {
$session->remove($passSessionName);
}

} elseif ($config['connection_kind'] == 'client_credentials') {
if ($session->has($credentialSessionName)) {
$tokenInfoCredential = $session->get($credentialSessionName);
if (!isset($tokenInfoCredential['expires_at'])) {
// case of never expired
// eg: with provederID with phraseanet
$accessToken = $tokenInfoCredential['access_token'];
} elseif (is_array($tokenInfoCredential) && $tokenInfoCredential['expires_at'] > time()) {
$accessToken = $tokenInfoCredential['access_token'];
} elseif ($config['connection_kind'] == 'client_credentials') {
if ($session->has($credentialSessionName)) {
$tokenInfoCredential = $session->get($credentialSessionName);
if (!isset($tokenInfoCredential['expires_at'])) {
$accessToken = $tokenInfoCredential['access_token'];
} elseif (is_array($tokenInfoCredential) && $tokenInfoCredential['expires_at'] > time()) {
$accessToken = $tokenInfoCredential['access_token'];
} else {
$accessToken = $this->getTokenByCredential($oauthClient, $config, $credentialSessionName);
}
} else {
$accessToken = $this->getTokenByCredential($oauthClient, $config, $credentialSessionName);
}
} else {
$accessToken = $this->getTokenByCredential($oauthClient, $config, $credentialSessionName);
}
}


return $accessToken;
}

Expand Down
Loading

0 comments on commit bc8c3be

Please sign in to comment.