88
99namespace humhub \modules \authKeycloak \authclient ;
1010
11+ use humhub \modules \authKeycloak \models \AuthKeycloak ;
1112use humhub \modules \authKeycloak \models \ConfigureForm ;
1213use humhub \modules \authKeycloak \Module ;
1314use humhub \modules \user \authclient \interfaces \PrimaryClient ;
1718use PDOException ;
1819use Yii ;
1920use yii \authclient \InvalidResponseException ;
20- use yii \authclient \OAuth2 ;
21+ use yii \authclient \OpenIdConnect ;
22+ use yii \base \InvalidConfigException ;
2123use yii \db \StaleObjectException ;
2224use 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}
0 commit comments