1919use SimpleSAML \Auth ;
2020use SimpleSAML \Configuration ;
2121use SimpleSAML \Error ;
22+ use SimpleSAML \Logger ;
2223use SimpleSAML \Module \core \Auth \UserPassOrgBase ;
2324
2425use function array_change_key_case ;
2829
2930class LdapMulti extends UserPassOrgBase
3031{
32+ /**
33+ * The key of the OrgId field in the state, identifies which org was selected.
34+ */
35+ public const string SOURCEID = '\SimpleSAML\Module\ldap\Auth\LdapMulti.SelectedSource ' ;
36+
37+
3138 /**
3239 * An LDAP configuration object.
3340 */
@@ -160,6 +167,80 @@ public function loginOverload(
160167 }
161168
162169
170+ /**
171+ * Handle login request.
172+ *
173+ * This function is used by the login form (core/loginuserpassorg) when the user
174+ * enters a username and password. On success, it will not return. On wrong
175+ * username/password failure, and other errors, it will throw an exception.
176+ *
177+ * @param string $authStateId The identifier of the authentication state.
178+ * @param string $username The username the user wrote.
179+ * @param string $password The password the user wrote.
180+ * @param string $organization The id of the organization the user chose.
181+ */
182+ public static function handleLogin (
183+ string $ authStateId ,
184+ string $ username ,
185+ string $ password ,
186+ string $ organization ,
187+ ): void {
188+ /* Retrieve the authentication state. */
189+ $ state = Auth \State::loadState ($ authStateId , self ::STAGEID );
190+
191+ /* Find authentication source. */
192+ Assert::keyExists ($ state , self ::AUTHID );
193+
194+ /** @var \SimpleSAML\Module\core\Auth\UserPassOrgBase|null $source */
195+ $ source = Auth \Source::getById ($ state [self ::AUTHID ]);
196+ if ($ source === null ) {
197+ throw new \Exception ('Could not find authentication source with id ' . $ state [self ::AUTHID ]);
198+ }
199+
200+ $ orgMethod = $ source ->getUsernameOrgMethod ();
201+ if ($ orgMethod !== 'none ' ) {
202+ $ tmp = explode ('@ ' , $ username , 2 );
203+ if (count ($ tmp ) === 2 ) {
204+ $ username = $ tmp [0 ];
205+ $ organization = $ tmp [1 ];
206+ } else {
207+ if ($ orgMethod === 'force ' ) {
208+ /* The organization should be a part of the username, but isn't. */
209+ throw new Error \Error (Error \ErrorCodes::WRONGUSERPASS );
210+ }
211+ }
212+ }
213+
214+ /* Attempt to log in. */
215+ try {
216+ $ attributes = $ source ->login ($ username , $ password , $ organization );
217+ } catch (\Exception $ e ) {
218+ Logger::stats ('Unsuccessful login attempt from ' . $ _SERVER ['REMOTE_ADDR ' ] . '. ' );
219+ throw $ e ;
220+ }
221+
222+ Logger::stats (
223+ 'User \'' . $ username . '\' at \'' . $ organization
224+ . '\' successfully authenticated from ' . $ _SERVER ['REMOTE_ADDR ' ],
225+ );
226+
227+ // Add the selected Org to the state
228+ $ mapping = $ source ->getMapping ();
229+ $ state [self ::SOURCEID ] = $ mapping [$ organization ]['authsource ' ];
230+ $ state [self ::ORGID ] = $ organization ;
231+ $ state ['PersistentAuthData ' ][] = self ::ORGID ;
232+
233+ $ state ['Attributes ' ] = $ attributes ;
234+ Auth \Source::completeAuth ($ state );
235+
236+
237+ // Save the $state-array, so that we can restore it after a redirect
238+ $ authStateId = Auth \State::saveState ($ state , self ::STAGEID );
239+
240+ parent ::handleLogin ($ authStateId , $ username , $ password , $ organization );
241+ }
242+
243+
163244 /**
164245 * Attempt to log in using the given username and password.
165246 *
@@ -183,4 +264,15 @@ protected function getOrganizations(): array
183264 {
184265 return $ this ->orgs ;
185266 }
267+
268+
269+ /**
270+ * Retrieve the mapping
271+ *
272+ * @return array<mixed> Associative array with the organization/authsource mapping.
273+ */
274+ protected function getMapping (): array
275+ {
276+ return $ this ->mapping ;
277+ }
186278}
0 commit comments