Skip to content

Commit 9806cec

Browse files
authored
Merge pull request #11 from cuzy-app/back-channel
Back channel and OpenId Connect
2 parents da244a7 + d7f3787 commit 9806cec

File tree

299 files changed

+2994
-45210
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

299 files changed

+2994
-45210
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/composer.lock
2+
/composer.phar
3+
/vendor/

authclient/Keycloak.php

Lines changed: 130 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
namespace humhub\modules\authKeycloak\authclient;
1010

11+
use humhub\modules\authKeycloak\models\AuthKeycloak;
1112
use humhub\modules\authKeycloak\models\ConfigureForm;
1213
use humhub\modules\authKeycloak\Module;
1314
use humhub\modules\user\authclient\interfaces\PrimaryClient;
@@ -17,53 +18,40 @@
1718
use PDOException;
1819
use Yii;
1920
use yii\authclient\InvalidResponseException;
20-
use yii\authclient\OAuth2;
21+
use yii\authclient\OpenIdConnect;
22+
use yii\base\InvalidConfigException;
2123
use yii\db\StaleObjectException;
2224
use yii\helpers\BaseInflector;
2325

2426
/**
2527
* With PrimaryClient, the user will have the `auth_mode` field in the `user` table set to 'Keycloak'.
2628
* This will avoid showing the "Change Password" tab when logged in with Keycloak
2729
*/
28-
class Keycloak extends OAuth2 implements PrimaryClient
30+
class Keycloak extends OpenIdConnect implements PrimaryClient
2931
{
3032
public const DEFAULT_NAME = 'Keycloak';
3133

32-
/**
33-
* @inheritdoc
34-
*/
35-
public $authUrl;
36-
37-
/**
38-
* @inheritdoc
39-
*/
40-
public $tokenUrl;
41-
4234
/**
4335
* @inheritdoc
4436
*/
4537
public $apiBaseUrl;
46-
4738
/**
4839
* @var bool
4940
*/
5041
protected $_userSynced = false;
5142

52-
/**
53-
* @inheridoc
54-
*/
55-
public $scope = 'openid';
56-
5743
/**
5844
* @inheritdoc
5945
*/
6046
public function init()
6147
{
62-
$config = new ConfigureForm();
48+
if (!class_exists('Jose\Component\KeyManagement\JWKFactory')) {
49+
require_once Yii::getAlias('@auth-keycloak/vendor/autoload.php');
50+
}
6351

64-
$this->apiBaseUrl = $config->baseUrl . '/realms/' . $config->realm . '/protocol/openid-connect';
65-
$this->authUrl = $this->apiBaseUrl . '/auth';
66-
$this->tokenUrl = $this->apiBaseUrl . '/token';
52+
$config = new ConfigureForm();
53+
$this->issuerUrl = $config->baseUrl . '/realms/' . $config->realm;
54+
$this->apiBaseUrl = $this->issuerUrl . '/protocol/openid-connect';
6755

6856
parent::init();
6957
}
@@ -80,18 +68,6 @@ public function applyAccessTokenToRequest($request, $accessToken)
8068
$request->setHeaders($data);
8169
}
8270

83-
/**
84-
* @inheridoc
85-
*/
86-
protected function initUserAttributes()
87-
{
88-
try {
89-
return $this->api('userinfo');
90-
} catch (InvalidResponseException|\Exception $e) {
91-
return [];
92-
}
93-
}
94-
9571
/**
9672
* @inheritdoc
9773
*/
@@ -100,87 +76,6 @@ public function getId()
10076
return self::DEFAULT_NAME;
10177
}
10278

103-
/**
104-
* @inheritdoc
105-
*/
106-
protected function defaultName()
107-
{
108-
return self::DEFAULT_NAME;
109-
}
110-
111-
/**
112-
* @inheridoc
113-
*/
114-
protected function defaultTitle()
115-
{
116-
/** @var Module $module */
117-
$module = Yii::$app->getModule('auth-keycloak');
118-
$settings = $module->settings;
119-
120-
return $settings->get('title', Yii::t('AuthKeycloakModule.base', ConfigureForm::DEFAULT_TITLE));
121-
}
122-
123-
protected function defaultViewOptions()
124-
{
125-
return [
126-
'cssIcon' => 'fa fa-sign-in',
127-
'buttonBackgroundColor' => '#e0492f',
128-
];
129-
}
130-
131-
protected function defaultNormalizeUserAttributeMap()
132-
{
133-
/** @var Module $module */
134-
$module = Yii::$app->getModule('auth-keycloak');
135-
$settings = $module->settings;
136-
137-
return [
138-
'id' => 'sub',
139-
'username' => $settings->get('usernameMapper'),
140-
'firstname' => 'given_name',
141-
'lastname' => 'family_name',
142-
'email' => 'email',
143-
];
144-
}
145-
146-
/**
147-
* If the username sent by Keycloak is the user's email, it is replaced by a username auto-generated from the first and last name (CamelCase formatted)
148-
* @inerhitdoc
149-
*/
150-
protected function normalizeUserAttributes($attributes)
151-
{
152-
$attributes = parent::normalizeUserAttributes($attributes);
153-
if (
154-
isset($attributes['username'], $attributes['email'])
155-
&& $attributes['username'] === $attributes['email']
156-
) {
157-
/* @var $userModule \humhub\modules\user\Module */
158-
$userModule = Yii::$app->getModule('user');
159-
$attributes['username'] = BaseInflector::id2camel(
160-
BaseInflector::slug(
161-
$attributes['firstname'] . ' ' . $attributes['lastname']
162-
)
163-
);
164-
}
165-
return $attributes;
166-
}
167-
168-
/**
169-
* Called among others by `user/controllers/AuthController::authSuccess()`
170-
* @inheridoc
171-
*/
172-
public function getUserAttributes()
173-
{
174-
// Avoid looping getUserAttributes()
175-
if (!$this->_userSynced) {
176-
$this->_userSynced = true;
177-
$this->syncUserAttributes();
178-
}
179-
180-
return parent::getUserAttributes();
181-
}
182-
183-
18479
/**
18580
* @inheridoc
18681
*/
@@ -212,6 +107,21 @@ public function getUser()
212107
return null;
213108
}
214109

110+
/**
111+
* Called among others by `user/controllers/AuthController::authSuccess()`
112+
* @inheridoc
113+
*/
114+
public function getUserAttributes()
115+
{
116+
// Avoid looping getUserAttributes()
117+
if (!$this->_userSynced) {
118+
$this->_userSynced = true;
119+
$this->syncUserAttributes();
120+
}
121+
122+
return parent::getUserAttributes();
123+
}
124+
215125
/**
216126
* @inheridoc
217127
* @throws StaleObjectException
@@ -230,7 +140,36 @@ public function syncUserAttributes()
230140
(new AuthClientUserService($user))->add($this);
231141
} catch (PDOException $e) {
232142
}
233-
KeycloakHelpers::storeAndGetAuthSourceId($user, $userAttributes['id'] ?? null);
143+
144+
$sourceId = $userAttributes['id'] ?? null;
145+
if ($sourceId) {
146+
$auth = AuthKeycloak::findOne([
147+
'source' => self::DEFAULT_NAME,
148+
'source_id' => $sourceId,
149+
]);
150+
151+
// Make sure authClient is not doubly assigned
152+
if ($auth !== null && $auth->user_id !== $user->id) {
153+
$auth->delete();
154+
$auth = null;
155+
}
156+
157+
// Get Keycloak shared session identifier
158+
$sid = Yii::$app->request->get('session_state');
159+
160+
if ($auth === null) {
161+
$auth = new AuthKeycloak([
162+
'user_id' => $user->id,
163+
'source' => self::DEFAULT_NAME,
164+
'source_id' => (string)$sourceId,
165+
'keycloak_sid' => $sid,
166+
]);
167+
$auth->save();
168+
} elseif ($auth->keycloak_sid !== $sid) {
169+
$auth->keycloak_sid = $sid;
170+
$auth->save();
171+
}
172+
}
234173

235174
/** @var Module $module */
236175
$module = Yii::$app->getModule('auth-keycloak');
@@ -255,4 +194,79 @@ public function syncUserAttributes()
255194
$user->save();
256195
}
257196
}
197+
198+
/**
199+
* @inheridoc
200+
*/
201+
protected function initUserAttributes()
202+
{
203+
try {
204+
return $this->api('userinfo');
205+
} catch (InvalidResponseException|\Exception $e) {
206+
return [];
207+
}
208+
}
209+
210+
/**
211+
* @inheritdoc
212+
*/
213+
protected function defaultName()
214+
{
215+
return self::DEFAULT_NAME;
216+
}
217+
218+
/**
219+
* @inheridoc
220+
*/
221+
protected function defaultTitle()
222+
{
223+
/** @var Module $module */
224+
$module = Yii::$app->getModule('auth-keycloak');
225+
return $module->settings->get('title', Yii::t('AuthKeycloakModule.base', ConfigureForm::DEFAULT_TITLE));
226+
}
227+
228+
protected function defaultViewOptions()
229+
{
230+
return [
231+
'cssIcon' => 'fa fa-sign-in',
232+
'buttonBackgroundColor' => '#e0492f',
233+
];
234+
}
235+
236+
protected function defaultNormalizeUserAttributeMap()
237+
{
238+
/** @var Module $module */
239+
$module = Yii::$app->getModule('auth-keycloak');
240+
241+
return [
242+
'id' => 'sub',
243+
'username' => $module->settings->get('usernameMapper'),
244+
'firstname' => 'given_name',
245+
'lastname' => 'family_name',
246+
'email' => 'email',
247+
];
248+
}
249+
250+
/**
251+
* If the username sent by Keycloak is the user's email, it is replaced by a username auto-generated from the first and last name (CamelCase formatted)
252+
* @inerhitdoc
253+
* @throws InvalidConfigException
254+
*/
255+
protected function normalizeUserAttributes($attributes)
256+
{
257+
$attributes = parent::normalizeUserAttributes($attributes);
258+
if (
259+
isset($attributes['username'], $attributes['email'])
260+
&& $attributes['username'] === $attributes['email']
261+
) {
262+
/* @var $userModule \humhub\modules\user\Module */
263+
$userModule = Yii::$app->getModule('user');
264+
$attributes['username'] = BaseInflector::id2camel(
265+
BaseInflector::slug(
266+
$attributes['firstname'] . ' ' . $attributes['lastname']
267+
)
268+
);
269+
}
270+
return $attributes;
271+
}
258272
}

authclient/KeycloakHelpers.php

Lines changed: 0 additions & 51 deletions
This file was deleted.

0 commit comments

Comments
 (0)