diff --git a/.gitignore b/.gitignore
index dbc11c3..1def6bd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@
/Libraries/*
+/.idea/*
diff --git a/Classes/Hooks/FeloginHook.php b/Classes/Hooks/FeloginHook.php
index a3d3f24..e08fd0b 100644
--- a/Classes/Hooks/FeloginHook.php
+++ b/Classes/Hooks/FeloginHook.php
@@ -14,6 +14,7 @@
namespace Causal\Oidc\Hooks;
+use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
@@ -25,6 +26,7 @@ class FeloginHook
/**
* @param array $params
* @param \TYPO3\CMS\Felogin\Controller\FrontendLoginController $pObj
+ * @return string
*/
public function postProcContent(array $params, \TYPO3\CMS\Felogin\Controller\FrontendLoginController $pObj)
{
@@ -32,7 +34,10 @@ public function postProcContent(array $params, \TYPO3\CMS\Felogin\Controller\Fro
static::getLogger()->debug('Post-processing markers for felogin form', ['request' => $requestId]);
$markerArray['###OPENID_CONNECT###'] = '';
- $settings = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['oidc']);
+ $extConf = isset($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['oidc'])
+ ? unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['oidc'])
+ : [];
+ $settings = $this->getConfig($extConf);
if (empty($settings['oidcClientKey'])
|| empty($settings['oidcClientSecret'])
@@ -135,6 +140,60 @@ protected function getUniqueId()
return $uniqueId;
}
+ /**
+ * Returns the extension config and tries to override endpoints if /.well-known/configuration exists on
+ * the issuer's side.
+ *
+ * @param array $config
+ * @return array
+ */
+ protected function getConfig(array $config)
+ {
+ $extConfig = $config;
+
+ if(isset($extConfig['oidcConfigUrl'])) {
+ try {
+ $wellKnownConfig = $this->getWellKnownConfig(
+ $extConfig['oidcConfigUrl'],
+ (int)$extConfig['oidcWellKnownCacheLifetime']
+ );
+ $extConfig['oidcEndpointAuthorize'] = $wellKnownConfig['authorization_endpoint'];
+ $extConfig['oidcEndpointToken'] = $wellKnownConfig['token_endpoint'];
+ $extConfig['oidcEndpointUserInfo'] = $wellKnownConfig['userinfo_endpoint'];
+ $extConfig['oidcEndpointRevoke'] = $wellKnownConfig['revocation_endpoint'];
+ $extConfig['oidcEndpointLogout'] = $wellKnownConfig['end_session_endpoint'];
+ } catch (\Exception $e) {
+ static::getLogger()->error('Could not process Well-Known Config', [
+ 'message' => $e->getMessage(),
+ ]);
+ $extConfig = $config;
+ }
+ }
+
+ return $extConfig;
+ }
+
+ /**
+ * Get cached config from /.well-known/configuration URL, refreshes daily
+ *
+ * @param string $wellKnownUrl
+ * @param int $lifetime
+ * @return mixed
+ */
+ protected function getWellKnownConfig($wellKnownUrl, $lifetime = 86400)
+ {
+ $cache_path = PATH_site . 'typo3temp/';
+ $filename = $cache_path . md5(ExtensionManagementUtility::extPath('oidc'));
+
+ if(file_exists($filename) && (time() - $lifetime < filemtime($filename))) {
+ return json_decode(file_get_contents($filename), true);
+ } else {
+ $data = file_get_contents($wellKnownUrl);
+ file_put_contents($filename, $data);
+ return json_decode($data, true);
+ }
+ }
+
/**
* Returns a logger.
*
diff --git a/Classes/Service/AuthenticationService.php b/Classes/Service/AuthenticationService.php
index f865492..a3019ad 100644
--- a/Classes/Service/AuthenticationService.php
+++ b/Classes/Service/AuthenticationService.php
@@ -14,9 +14,11 @@
namespace Causal\Oidc\Service;
+use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\HttpUtility;
use Causal\Oidc\Service\OAuthService;
+use TYPO3\CMS\Extbase\Utility\DebuggerUtility;
/**
* OpenID Connect authentication service.
@@ -44,13 +46,20 @@ class AuthenticationService extends \TYPO3\CMS\Sv\AuthenticationService
*/
const STATUS_AUTHENTICATION_FAILURE_CONTINUE = 100;
+ /**
+ * @var array
+ */
+ private $config;
+
/**
* AuthenticationService constructor.
*/
public function __construct()
{
- $config = $GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['oidc'];
- $this->config = $config ? unserialize($config) : [];
+ $extConf = isset($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['oidc'])
+ ? unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['oidc'])
+ : [];
+ $this->config = $this->getConfig($extConf);
}
/**
@@ -178,7 +187,7 @@ protected function getUserFromAccessToken(OAuthService $service, $accessToken)
$service->revokeToken($accessToken);
throw new \RuntimeException(
'Resource owner does not have a sub part: ' . json_encode($resourceOwner)
- . '. Your access token has been revoked. Please try again.',
+ . '. Your access token has been revoked. Please try again.',
1490086626
);
}
@@ -189,7 +198,7 @@ protected function getUserFromAccessToken(OAuthService $service, $accessToken)
/**
* Authenticate a user
*
- * @oaram array $user
+ * @param array $user
* @return int
*/
public function authUser(array $user)
@@ -207,7 +216,7 @@ public function authUser(array $user)
* Converts a resource owner into a TYPO3 Frontend user.
*
* @param array $info
- * @return array
+ * @return array|bool
* @throws \InvalidArgumentException
*/
protected function convertResourceOwner(array $info)
@@ -263,7 +272,7 @@ protected function convertResourceOwner(array $info)
$newUsergroups = [];
$defaultUserGroups = GeneralUtility::intExplode(',', $this->config['usersDefaultGroup'], true);
- if ($row) {
+ if ($row && !$this->config['overrideNonOIDCRoles']) {
$currentUserGroups = GeneralUtility::intExplode(',', $row['usergroup'], true);
if (!empty($currentUserGroups)) {
$oidcUserGroups = $database->exec_SELECTgetRows(
@@ -281,14 +290,18 @@ protected function convertResourceOwner(array $info)
}
// Map OIDC roles to TYPO3 user groups
- if (!empty($info['Roles'])) {
+ if (!empty($info[$this->config['oidcRolesClaim']])) {
+ $roles = $info[$this->config['oidcRolesClaim']];
$typo3Roles = $database->exec_SELECTgetRows(
'uid, tx_oidc_pattern',
$userGroupTable,
'tx_oidc_pattern<>\'\' AND hidden=0 AND deleted=0'
);
- $roles = GeneralUtility::trimExplode(',', $info['Roles'], true);
- $roles = ',' . implode(',', $roles) . ',';
+
+ if(!is_array($roles)) {
+ $roles = GeneralUtility::trimExplode(',', $roles, true);
+ }
+ $roles = ',' . implode(',', $info[$this->config['oidcRolesClaim']]) . ',';
foreach ($typo3Roles as $typo3Role) {
// Convert the pattern into a proper regular expression
@@ -331,6 +344,7 @@ protected function convertResourceOwner(array $info)
'usergroup' => implode(',', $newUsergroups),
'crdate' => $GLOBALS['EXEC_TIME'],
'tx_oidc' => $info['sub'],
+ 'tx_extbase_type' => $this->config['usersExtbaseType']
]);
$database->exec_INSERTquery(
$userTable,
@@ -598,6 +612,60 @@ protected function getDatabaseConnection()
return $GLOBALS['TYPO3_DB'];
}
+ /**
+ * Returns the extension config and tries to override endpoints if /.well-known/configuration exists on
+ * the issuer's side.
+ *
+ * @param array $config
+ * @return array
+ */
+ protected function getConfig(array $config)
+ {
+ $extConfig = $config;
+
+ if(isset($extConfig['oidcConfigUrl'])) {
+ try {
+ $wellKnownConfig = $this->getWellKnownConfig(
+ $extConfig['oidcConfigUrl'],
+ (int)$extConfig['oidcWellKnownCacheLifetime']
+ );
+ $extConfig['oidcEndpointAuthorize'] = $wellKnownConfig['authorization_endpoint'];
+ $extConfig['oidcEndpointToken'] = $wellKnownConfig['token_endpoint'];
+ $extConfig['oidcEndpointUserInfo'] = $wellKnownConfig['userinfo_endpoint'];
+ $extConfig['oidcEndpointRevoke'] = $wellKnownConfig['revocation_endpoint'];
+ $extConfig['oidcEndpointLogout'] = $wellKnownConfig['end_session_endpoint'];
+ } catch (\Exception $e) {
+ static::getLogger()->error('Could not process Well-Known Config', [
+ 'message' => $e->getMessage(),
+ ]);
+ $extConfig = $config;
+ }
+ }
+
+ return $extConfig;
+ }
+
+ /**
+ * Get cached config from /.well-known/configuration URL depending on lifetime
+ *
+ * @param string $wellKnownUrl
+ * @param int $lifetime
+ * @return mixed
+ */
+ protected function getWellKnownConfig($wellKnownUrl, $lifetime)
+ {
+ $cache_path = PATH_site . 'typo3temp/';
+ $filename = $cache_path . md5(ExtensionManagementUtility::extPath('oidc'));
+
+ if(file_exists($filename) && (time() - $lifetime < filemtime($filename))) {
+ return json_decode(file_get_contents($filename), true);
+ } else {
+ $data = file_get_contents($wellKnownUrl);
+ file_put_contents($filename, $data);
+ return json_decode($data, true);
+ }
+ }
+
/**
* Returns a logger.
*
diff --git a/Classes/Service/OAuthService.php b/Classes/Service/OAuthService.php
index 76c9067..49703d4 100644
--- a/Classes/Service/OAuthService.php
+++ b/Classes/Service/OAuthService.php
@@ -15,9 +15,7 @@
namespace Causal\Oidc\Service;
use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Backend\Utility\BackendUtility;
use League\OAuth2\Client\Token\AccessToken;
-use TYPO3\CMS\Core\Utility\HttpUtility;
/**
* Class OAuthService.
@@ -124,7 +122,7 @@ public function getAccessTokenWithRequestPathAuthentication($username, $password
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_VERBOSE, 1);
curl_setopt($ch, CURLOPT_HEADER, 1);
- $content = curL_exec($ch);
+ $content = curl_exec($ch);
if ($content === false) {
throw new \RuntimeException('Curl ERROR: ' . curl_error($ch), 1510049345);
diff --git a/Resources/Private/Language/locallang_db.xlf b/Resources/Private/Language/locallang_db.xlf
index 3efc630..24fd009 100755
--- a/Resources/Private/Language/locallang_db.xlf
+++ b/Resources/Private/Language/locallang_db.xlf
@@ -12,6 +12,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
@@ -36,6 +48,9 @@
+
+
+
diff --git a/ext_conf_template.txt b/ext_conf_template.txt
index f0b401b..50ec5ca 100644
--- a/ext_conf_template.txt
+++ b/ext_conf_template.txt
@@ -7,19 +7,31 @@ reEnableFrontendUsers = 0
# cat=basic/enable/3; type=boolean; label=LLL:EXT:oidc/Resources/Private/Language/locallang_db.xlf:settings.undeleteFrontendUsers
undeleteFrontendUsers = 0
+# cat=basic/enable/4; type=boolean; label=LLL:EXT:oidc/Resources/Private/Language/locallang_db.xlf:settings.overrideNonOIDCRoles
+overrideNonOIDCRoles = 0
+
# cat=basic//1; type=int; label=Storage Pid: The Storage Pid of the Page, where the fe_users should be stored
usersStoragePid =
# cat=basic//2; type=string; label=Default user group(s) (comma-separated list of UIDs)
usersDefaultGroup =
-# cat=basic//3; type=string; label=LLL:EXT:oidc/Resources/Private/Language/locallang_db.xlf:settings.oidcClientKey
+# cat=basic//3; type=string; label=LLL:EXT:oidc/Resources/Private/Language/locallang_db.xlf:settings.defaultUserExtbaseType
+usersExtbaseType = 0
+
+# cat=basic//4; type=string; label=LLL:EXT:oidc/Resources/Private/Language/locallang_db.xlf:settings.oidcConfigUrl
+oidcConfigUrl =
+
+# cat=basic//5; type=int; label=LLL:EXT:oidc/Resources/Private/Language/locallang_db.xlf:settings.oidcWellKnownCacheLifetime
+oidcWellKnownCacheLifetime = 86400
+
+# cat=basic//6; type=string; label=LLL:EXT:oidc/Resources/Private/Language/locallang_db.xlf:settings.oidcClientKey
oidcClientKey =
-# cat=basic//4; type=string; label=LLL:EXT:oidc/Resources/Private/Language/locallang_db.xlf:settings.oidcClientSecret
+# cat=basic//7; type=string; label=LLL:EXT:oidc/Resources/Private/Language/locallang_db.xlf:settings.oidcClientSecret
oidcClientSecret =
-# cat=basic//5; type=string; label=LLL:EXT:oidc/Resources/Private/Language/locallang_db.xlf:settings.oidcClientScopes
+# cat=basic//8; type=string; label=LLL:EXT:oidc/Resources/Private/Language/locallang_db.xlf:settings.oidcClientScopes
oidcClientScopes = openid
# cat=advanced/links/1; type=string; label=LLL:EXT:oidc/Resources/Private/Language/locallang_db.xlf:settings.oidcEndpointAuthorize
@@ -37,6 +49,9 @@ oidcEndpointLogout = https://ids02.sac-cas.ch/oauth2/logout
# cat=advanced/links/5; type=string; label=LLL:EXT:oidc/Resources/Private/Language/locallang_db.xlf:settings.oidcEndpointRevoke
oidcEndpointRevoke = https://ids02.sac-cas.ch/oauth2/revoke
+# cat=advanced/links/6; type=string; label=LLL:EXT:oidc/Resources/Private/Language/locallang_db.xlf:settings.oidcRolesClaim
+oidcRolesClaim = Roles
+
# cat=advanced/enable/1; type=boolean; label=LLL:EXT:oidc/Resources/Private/Language/locallang_db.xlf:settings.oidcUseRequestPathAuthentication
oidcUseRequestPathAuthentication = 0