66use App \Models \User ;
77use Illuminate \Foundation \Auth \AuthenticatesUsers ;
88use Illuminate \Http \Request ;
9+ use Illuminate \Support \Facades \Hash ;
910use Illuminate \Support \Facades \Log ;
11+ use Illuminate \Support \Str ;
1012use LdapRecord \Auth \BindException ;
1113use LdapRecord \Container ;
1214use LdapRecord \Models \Entry as LdapEntry ;
@@ -15,66 +17,59 @@ class LoginController extends Controller
1517{
1618 use AuthenticatesUsers;
1719
18- /**
19- * Where to redirect users after login.
20- *
21- * @var string
22- */
23- protected $ redirectTo = '/home ' ;
24-
25- /**
26- * Login username to be used by the controller.
27- *
28- * @var string
29- */
30- protected $ username ;
20+ protected string $ redirectTo = '/home ' ;
21+ protected string $ username ;
3122
3223 public function __construct ()
3324 {
3425 $ this ->middleware ('guest ' )->except ('logout ' );
35- $ this ->username = $ this ->findLoginType ();
26+ $ this ->username = $ this ->findUsername ();
3627 }
3728
3829 /**
39- * Determine the field used for login (email or login) .
30+ * N'utiliser QUE le champ ' login' comme identifiant .
4031 */
41- public function findLoginType ()
32+ public function findUsername (): string
4233 {
43- $ login = request ()->input ('login ' );
34+ $ login = trim ((string ) request ()->input ('login ' , '' ));
35+ // On merge pour que credentials($request) prenne bien 'login'
36+ request ()->merge (['login ' => $ login ]);
4437
45- $ fieldType = filter_var ($ login , FILTER_VALIDATE_EMAIL ) ? 'email ' : 'login ' ;
46-
47- request ()->merge ([$ fieldType => $ login ]);
48-
49- return $ fieldType ;
38+ return 'login ' ;
5039 }
5140
52- /**
53- * Expose username property to the framework.
54- */
55- public function username ()
41+ public function username (): string
5642 {
5743 return $ this ->username ;
5844 }
5945
6046 /**
61- * Attempt an LDAP bind for the given app username + password using LDAPRecord v2.
62- * Returns the corresponding LDAP user on success, or null on failure.
47+ * LDAP bind (LDAPRecord v2)
6348 */
6449 protected function ldapBindAndGetUser (string $ appUsername , string $ password ): ?LdapEntry
6550 {
51+ if ($ appUsername === '' || $ password === '' ) {
52+ Log::debug ('LDAP skipped: empty identifier or password. ' );
53+ return null ;
54+ }
55+
6656 try {
6757 $ query = LdapEntry::query ();
6858
6959 // Optionnel : restreindre à une OU si configuré
70- $ base = config ( ' app.ldap_users_base_dn ' , config ('app.ldap_users_base_dn ' ));
71- if ($ base ) {
60+ $ base = trim (( string ) config ('app.ldap_users_base_dn ' ));
61+ if ($ base !== '' ) {
7262 $ query ->in ($ base );
7363 }
7464
75- // Filtre de localisation : OR sur les attributs pertinents
76- $ attrs = array_filter (array_map ('trim ' , explode (', ' , config ('app.ldap_login_attributes ' ))));
65+ // Attributs de login à tester côté LDAP (uid, sAMAccountName, etc.)
66+ $ attrs = array_values (array_filter (array_map ('trim ' , explode (', ' , (string ) config ('app.ldap_login_attributes ' )))));
67+ if (empty ($ attrs )) {
68+ Log::warning ('LDAP login aborted: app.ldap_login_attributes is empty. ' );
69+ return null ;
70+ }
7771
72+ // Filtre OR sur les attributs configurés
7873 $ first = true ;
7974 foreach ($ attrs as $ attr ) {
8075 if ($ first ) {
@@ -85,27 +80,35 @@ protected function ldapBindAndGetUser(string $appUsername, string $password): ?L
8580 }
8681 }
8782
88- \Log::debug ('LDAP dn: ' . $ query ->getDn () . ' query: ' . $ query ->getQuery ());
89-
90- /** @var LdapEntry|null $ldapUser */
91- $ ldapUser = $ query ->first ();
92- if (! $ ldapUser ) {
93- \Log::debug ('LDAP user not found ! ' );
83+ // Collision guard
84+ $ results = $ query ->limit (2 )->get ();
85+ if ($ results ->count () === 0 ) {
86+ Log::debug ('LDAP user not found for identifier. ' , ['identifier ' => $ appUsername ]);
87+ return null ;
88+ }
89+ if ($ results ->count () > 1 ) {
90+ Log::warning ('LDAP identifier collision: multiple entries match. ' , [
91+ 'identifier ' => $ appUsername ,
92+ 'attributes ' => $ attrs ,
93+ ]);
9494 return null ;
9595 }
9696
97+ /** @var LdapEntry $ldapUser */
98+ $ ldapUser = $ results ->first ();
99+
97100 $ connection = Container::getConnection ();
98101 $ dn = $ ldapUser ->getDn ();
99102
100- if ($ connection ->auth ()->attempt ($ dn , $ password , true )) {
103+ if ($ dn && $ connection ->auth ()->attempt ($ dn , $ password , true )) {
101104 return $ ldapUser ;
102105 }
103106
104107 return null ;
105108 } catch (BindException $ e ) {
106109 Log::warning ('LDAP bind failed ' , [
107110 'error ' => $ e ->getMessage (),
108- 'diagnostic ' => $ e ->getDetailedError ()->getDiagnosticMessage (),
111+ 'diagnostic ' => $ e ->getDetailedError ()? ->getDiagnosticMessage(),
109112 ]);
110113 return null ;
111114 } catch (\Throwable $ e ) {
@@ -115,67 +118,55 @@ protected function ldapBindAndGetUser(string $appUsername, string $password): ?L
115118 }
116119
117120 /**
118- * Override Laravel's default login attempt to add LDAPRecord support, toggled by .env
119- *
120- * Priority:
121- * - If LDAP_ENABLED=true => try LDAP; on success, log the mapped local user in.
122- * - If LDAP fails and LDAP_FALLBACK_LOCAL=true => try local DB credentials.
123- * - If LDAP_ENABLED=false => only local DB credentials.
121+ * Login avec LDAP optionnel + fallback local (UNIQUEMENT via 'login').
124122 */
125- protected function attemptLogin (Request $ request )
123+ protected function attemptLogin (Request $ request ): bool
126124 {
127- $ useLdap = config ('app.ldap_enabled ' );
128- $ fallbackLocal = config ('app.ldap_fallback_local ' );
129- $ autoProvision = config ('app.ldap_auto_provision ' );
125+ $ useLdap = ( bool ) config ('app.ldap_enabled ' );
126+ $ fallbackLocal = ( bool ) config ('app.ldap_fallback_local ' );
127+ $ autoProvision = ( bool ) config ('app.ldap_auto_provision ' );
130128
131- $ credentials = $ request ->only ($ this ->username (), 'password ' );
132- $ identifier = $ credentials [$ this ->username ()] ?? '' ;
133- $ password = $ credentials ['password ' ] ?? '' ;
129+ $ credentials = $ request ->only ($ this ->username (), 'password ' ); // ['login' => ..., 'password' => ...]
130+ $ identifier = (string ) ($ credentials [$ this ->username ()] ?? '' );
131+ $ password = (string ) ($ credentials ['password ' ] ?? '' );
132+ $ remember = $ request ->boolean ('remember ' );
134133
135134 if ($ useLdap ) {
136135 $ ldapUser = $ this ->ldapBindAndGetUser ($ identifier , $ password );
137136
138137 if ($ ldapUser ) {
139- // Map / locate local application user
140- $ local = User::query ()
141- ->when (filter_var ($ identifier , FILTER_VALIDATE_EMAIL ), function ($ q ) use ($ identifier ) {
142- return $ q ->where ('email ' , $ identifier );
143- }, function ($ q ) use ($ identifier ) {
144- return $ q ->where ('login ' , $ identifier );
145- })
146- ->first ();
147-
148- if (! $ local && $ autoProvision ) {
149- // Minimal safe provisioning – adapt attributes to your schema
138+ // Mapping local UNIQUEMENT par 'login'
139+ $ local = User::where ('login ' , $ identifier )->first ();
140+
141+ if (!$ local && $ autoProvision ) {
150142 $ local = User::create ([
151- 'name ' => $ ldapUser ->getFirstAttribute ('cn ' ) ?? $ identifier ,
152- 'email ' => $ ldapUser ->getFirstAttribute ('mail ' ) ?? 'user@localhost.local ' ,
153- 'login ' => $ identifier ,
154- 'role ' => 5 , // Auditee
155- 'password ' => bcrypt ( str ()-> random (32 )),
143+ 'name ' => $ ldapUser ->getFirstAttribute ('cn ' ) ?: $ identifier ,
144+ 'email ' => $ ldapUser ->getFirstAttribute ('mail ' ) ?: 'user@localhost.local ' ,
145+ 'login ' => $ identifier ,
146+ 'role ' => 5 ,
147+ 'password ' => Hash:: make (Str:: random (32 )), // inutilisable en local par défaut
156148 ]);
157149 }
158150
159151 if ($ local ) {
160- $ remember = $ request ->boolean ('remember ' );
161152 $ this ->guard ()->login ($ local , $ remember );
162153 return true ;
163154 }
164155
165- // LDAP OK but no mapped local user and no auto-provision
156+ // LDAP OK mais pas d'utilisateur local et pas d’ auto-provision
166157 return false ;
167158 }
168159
169- // LDAP failed – optionally fall back to local DB auth
170- if (! $ fallbackLocal ) {
160+ // LDAP KO → éventuel fallback local (toujours via 'login')
161+ if (!$ fallbackLocal ) {
171162 return false ;
172163 }
173164 }
174165
175- // Local database auth path (default Laravel)
166+ // Auth locale ( Laravel) — utilisera ['login' => ..., 'password' => ...]
176167 return $ this ->guard ()->attempt (
177- $ this ->credentials ($ request ),
178- $ request -> filled ( ' remember ' )
168+ $ this ->credentials ($ request ), // credentials() retournera login + password car username() = 'login'
169+ $ remember
179170 );
180171 }
181172}
0 commit comments