1616 */
1717package com .gitblit .auth ;
1818
19- import java .net .URI ;
20- import java .net .URISyntaxException ;
21- import java .security .GeneralSecurityException ;
2219import java .text .MessageFormat ;
2320import java .util .Arrays ;
2421import java .util .HashMap ;
3330import com .gitblit .Constants .Role ;
3431import com .gitblit .Keys ;
3532import com .gitblit .auth .AuthenticationProvider .UsernamePasswordAuthenticationProvider ;
33+ import com .gitblit .ldap .LdapConnection ;
3634import com .gitblit .models .TeamModel ;
3735import com .gitblit .models .UserModel ;
3836import com .gitblit .service .LdapSyncService ;
3937import com .gitblit .utils .ArrayUtils ;
4038import com .gitblit .utils .StringUtils ;
4139import com .unboundid .ldap .sdk .Attribute ;
42- import com .unboundid .ldap .sdk .BindRequest ;
4340import com .unboundid .ldap .sdk .BindResult ;
44- import com .unboundid .ldap .sdk .DereferencePolicy ;
45- import com .unboundid .ldap .sdk .ExtendedResult ;
46- import com .unboundid .ldap .sdk .LDAPConnection ;
4741import com .unboundid .ldap .sdk .LDAPException ;
48- import com .unboundid .ldap .sdk .LDAPSearchException ;
4942import com .unboundid .ldap .sdk .ResultCode ;
5043import com .unboundid .ldap .sdk .SearchRequest ;
5144import com .unboundid .ldap .sdk .SearchResult ;
5245import com .unboundid .ldap .sdk .SearchResultEntry ;
5346import com .unboundid .ldap .sdk .SearchScope ;
54- import com .unboundid .ldap .sdk .SimpleBindRequest ;
55- import com .unboundid .ldap .sdk .extensions .StartTLSExtendedRequest ;
56- import com .unboundid .util .ssl .SSLUtil ;
57- import com .unboundid .util .ssl .TrustAllTrustManager ;
5847
5948/**
6049 * Implementation of an LDAP user service.
@@ -109,7 +98,7 @@ public synchronized void sync() {
10998 if (enabled ) {
11099 logger .info ("Synchronizing with LDAP @ " + settings .getRequiredString (Keys .realm .ldap .server ));
111100 final boolean deleteRemovedLdapUsers = settings .getBoolean (Keys .realm .ldap .removeDeletedUsers , true );
112- LdapConnection ldapConnection = new LdapConnection ();
101+ LdapConnection ldapConnection = new LdapConnection (settings );
113102 if (ldapConnection .connect ()) {
114103 if (ldapConnection .bind () == null ) {
115104 ldapConnection .close ();
@@ -118,9 +107,9 @@ public synchronized void sync() {
118107 }
119108
120109 try {
121- String accountBase = settings .getString (Keys .realm .ldap .accountBase , "" );
122110 String uidAttribute = settings .getString (Keys .realm .ldap .uid , "uid" );
123- String accountPattern = settings .getString (Keys .realm .ldap .accountPattern , "(&(objectClass=person)(sAMAccountName=${username}))" );
111+ String accountBase = ldapConnection .getAccountBase ();
112+ String accountPattern = ldapConnection .getAccountPattern ();
124113 accountPattern = StringUtils .replace (accountPattern , "${username}" , "*" );
125114
126115 SearchResult result = doSearch (ldapConnection , accountBase , accountPattern );
@@ -265,7 +254,7 @@ public AccountType getAccountType() {
265254 public UserModel authenticate (String username , char [] password ) {
266255 String simpleUsername = getSimpleUsername (username );
267256
268- LdapConnection ldapConnection = new LdapConnection ();
257+ LdapConnection ldapConnection = new LdapConnection (settings );
269258 if (ldapConnection .connect ()) {
270259
271260 // Try to bind either to the "manager" account,
@@ -286,11 +275,7 @@ public UserModel authenticate(String username, char[] password) {
286275
287276 try {
288277 // Find the logging in user's DN
289- String accountBase = settings .getString (Keys .realm .ldap .accountBase , "" );
290- String accountPattern = settings .getString (Keys .realm .ldap .accountPattern , "(&(objectClass=person)(sAMAccountName=${username}))" );
291- accountPattern = StringUtils .replace (accountPattern , "${username}" , escapeLDAPSearchFilter (simpleUsername ));
292-
293- SearchResult result = doSearch (ldapConnection , accountBase , accountPattern );
278+ SearchResult result = ldapConnection .searchUser (simpleUsername );
294279 if (result != null && result .getEntryCount () == 1 ) {
295280 SearchResultEntry loggingInUser = result .getSearchEntries ().get (0 );
296281 String loggingInUserDN = loggingInUser .getDN ();
@@ -441,12 +426,12 @@ private void getTeamsFromLdap(LdapConnection ldapConnection, String simpleUserna
441426 String groupBase = settings .getString (Keys .realm .ldap .groupBase , "" );
442427 String groupMemberPattern = settings .getString (Keys .realm .ldap .groupMemberPattern , "(&(objectClass=group)(member=${dn}))" );
443428
444- groupMemberPattern = StringUtils .replace (groupMemberPattern , "${dn}" , escapeLDAPSearchFilter (loggingInUserDN ));
445- groupMemberPattern = StringUtils .replace (groupMemberPattern , "${username}" , escapeLDAPSearchFilter (simpleUsername ));
429+ groupMemberPattern = StringUtils .replace (groupMemberPattern , "${dn}" , LdapConnection . escapeLDAPSearchFilter (loggingInUserDN ));
430+ groupMemberPattern = StringUtils .replace (groupMemberPattern , "${username}" , LdapConnection . escapeLDAPSearchFilter (simpleUsername ));
446431
447432 // Fill in attributes into groupMemberPattern
448433 for (Attribute userAttribute : loggingInUser .getAttributes ()) {
449- groupMemberPattern = StringUtils .replace (groupMemberPattern , "${" + userAttribute .getName () + "}" , escapeLDAPSearchFilter (userAttribute .getValue ()));
434+ groupMemberPattern = StringUtils .replace (groupMemberPattern , "${" + userAttribute .getName () + "}" , LdapConnection . escapeLDAPSearchFilter (userAttribute .getValue ()));
450435 }
451436
452437 SearchResult teamMembershipResult = searchTeamsInLdap (ldapConnection , groupBase , true , groupMemberPattern , Arrays .asList ("cn" ));
@@ -538,6 +523,7 @@ private SearchResult doSearch(LdapConnection ldapConnection, String base, String
538523
539524
540525
526+
541527 /**
542528 * Returns a simple username without any domain prefixes.
543529 *
@@ -553,34 +539,6 @@ protected String getSimpleUsername(String username) {
553539 return username ;
554540 }
555541
556- // From: https://www.owasp.org/index.php/Preventing_LDAP_Injection_in_Java
557- private static final String escapeLDAPSearchFilter (String filter ) {
558- StringBuilder sb = new StringBuilder ();
559- for (int i = 0 ; i < filter .length (); i ++) {
560- char curChar = filter .charAt (i );
561- switch (curChar ) {
562- case '\\' :
563- sb .append ("\\ 5c" );
564- break ;
565- case '*' :
566- sb .append ("\\ 2a" );
567- break ;
568- case '(' :
569- sb .append ("\\ 28" );
570- break ;
571- case ')' :
572- sb .append ("\\ 29" );
573- break ;
574- case '\u0000' :
575- sb .append ("\\ 00" );
576- break ;
577- default :
578- sb .append (curChar );
579- }
580- }
581- return sb .toString ();
582- }
583-
584542 private void configureSyncService () {
585543 LdapSyncService ldapSyncService = new LdapSyncService (settings , this );
586544 if (ldapSyncService .isReady ()) {
@@ -593,226 +551,4 @@ private void configureSyncService() {
593551 logger .info ("Ldap sync service is disabled." );
594552 }
595553 }
596-
597-
598-
599- private class LdapConnection {
600- private LDAPConnection conn ;
601- private SimpleBindRequest currentBindRequest ;
602- private SimpleBindRequest managerBindRequest ;
603- private SimpleBindRequest userBindRequest ;
604-
605-
606- public LdapConnection () {
607- String bindUserName = settings .getString (Keys .realm .ldap .username , "" );
608- String bindPassword = settings .getString (Keys .realm .ldap .password , "" );
609- if (StringUtils .isEmpty (bindUserName ) && StringUtils .isEmpty (bindPassword )) {
610- this .managerBindRequest = new SimpleBindRequest ();
611- }
612- this .managerBindRequest = new SimpleBindRequest (bindUserName , bindPassword );
613- }
614-
615-
616- boolean connect () {
617- try {
618- URI ldapUrl = new URI (settings .getRequiredString (Keys .realm .ldap .server ));
619- String ldapHost = ldapUrl .getHost ();
620- int ldapPort = ldapUrl .getPort ();
621-
622- if (ldapUrl .getScheme ().equalsIgnoreCase ("ldaps" )) {
623- // SSL
624- SSLUtil sslUtil = new SSLUtil (new TrustAllTrustManager ());
625- conn = new LDAPConnection (sslUtil .createSSLSocketFactory ());
626- if (ldapPort == -1 ) {
627- ldapPort = 636 ;
628- }
629- } else if (ldapUrl .getScheme ().equalsIgnoreCase ("ldap" ) || ldapUrl .getScheme ().equalsIgnoreCase ("ldap+tls" )) {
630- // no encryption or StartTLS
631- conn = new LDAPConnection ();
632- if (ldapPort == -1 ) {
633- ldapPort = 389 ;
634- }
635- } else {
636- logger .error ("Unsupported LDAP URL scheme: " + ldapUrl .getScheme ());
637- return false ;
638- }
639-
640- conn .connect (ldapHost , ldapPort );
641-
642- if (ldapUrl .getScheme ().equalsIgnoreCase ("ldap+tls" )) {
643- SSLUtil sslUtil = new SSLUtil (new TrustAllTrustManager ());
644- ExtendedResult extendedResult = conn .processExtendedOperation (
645- new StartTLSExtendedRequest (sslUtil .createSSLContext ()));
646- if (extendedResult .getResultCode () != ResultCode .SUCCESS ) {
647- throw new LDAPException (extendedResult .getResultCode ());
648- }
649- }
650-
651- return true ;
652-
653- } catch (URISyntaxException e ) {
654- logger .error ("Bad LDAP URL, should be in the form: ldap(s|+tls)://<server>:<port>" , e );
655- } catch (GeneralSecurityException e ) {
656- logger .error ("Unable to create SSL Connection" , e );
657- } catch (LDAPException e ) {
658- logger .error ("Error Connecting to LDAP" , e );
659- }
660-
661- return false ;
662- }
663-
664-
665- void close () {
666- if (conn != null ) {
667- conn .close ();
668- }
669- }
670-
671-
672- SearchResult search (SearchRequest request ) {
673- try {
674- return conn .search (request );
675- } catch (LDAPSearchException e ) {
676- logger .error ("Problem Searching LDAP [{}]" , e .getResultCode ());
677- return e .getSearchResult ();
678- }
679- }
680-
681-
682- SearchResult search (String base , boolean dereferenceAliases , String filter , List <String > attributes ) {
683- try {
684- SearchRequest searchRequest = new SearchRequest (base , SearchScope .SUB , filter );
685- if (dereferenceAliases ) {
686- searchRequest .setDerefPolicy (DereferencePolicy .SEARCHING );
687- }
688- if (attributes != null ) {
689- searchRequest .setAttributes (attributes );
690- }
691- SearchResult result = search (searchRequest );
692- return result ;
693-
694- } catch (LDAPException e ) {
695- logger .error ("Problem creating LDAP search" , e );
696- return null ;
697- }
698- }
699-
700-
701-
702- /**
703- * Bind using the manager credentials set in realm.ldap.username and ..password
704- * @return A bind result, or null if binding failed.
705- */
706- BindResult bind () {
707- BindResult result = null ;
708- try {
709- result = conn .bind (managerBindRequest );
710- currentBindRequest = managerBindRequest ;
711- } catch (LDAPException e ) {
712- logger .error ("Error authenticating to LDAP with manager account to search the directory." );
713- logger .error (" Please check your settings for realm.ldap.username and realm.ldap.password." );
714- logger .debug (" Received exception when binding to LDAP" , e );
715- return null ;
716- }
717- return result ;
718- }
719-
720-
721- /**
722- * Bind using the given credentials, by filling in the username in the given {@code bindPattern} to
723- * create the DN.
724- * @return A bind result, or null if binding failed.
725- */
726- BindResult bind (String bindPattern , String simpleUsername , String password ) {
727- BindResult result = null ;
728- try {
729- String bindUser = StringUtils .replace (bindPattern , "${username}" , escapeLDAPSearchFilter (simpleUsername ));
730- SimpleBindRequest request = new SimpleBindRequest (bindUser , password );
731- result = conn .bind (request );
732- userBindRequest = request ;
733- currentBindRequest = userBindRequest ;
734- } catch (LDAPException e ) {
735- logger .error ("Error authenticating to LDAP with user account to search the directory." );
736- logger .error (" Please check your settings for realm.ldap.bindpattern." );
737- logger .debug (" Received exception when binding to LDAP" , e );
738- return null ;
739- }
740- return result ;
741- }
742-
743-
744- boolean rebindAsUser () {
745- if (userBindRequest == null || currentBindRequest == userBindRequest ) {
746- return false ;
747- }
748- try {
749- conn .bind (userBindRequest );
750- currentBindRequest = userBindRequest ;
751- } catch (LDAPException e ) {
752- conn .close ();
753- logger .error ("Error rebinding to LDAP with user account." , e );
754- return false ;
755- }
756- return true ;
757- }
758-
759-
760- boolean isAuthenticated (String userDn , String password ) {
761- verifyCurrentBinding ();
762-
763- // If the currently bound DN is already the DN of the logging in user, authentication has already happened
764- // during the previous bind operation. We accept this and return with the current bind left in place.
765- // This could also be changed to always retry binding as the logging in user, to make sure that the
766- // connection binding has not been tampered with in between. So far I see no way how this could happen
767- // and thus skip the repeated binding.
768- // This check also makes sure that the DN in realm.ldap.bindpattern actually matches the DN that was found
769- // when searching the user entry.
770- String boundDN = currentBindRequest .getBindDN ();
771- if (boundDN != null && boundDN .equals (userDn )) {
772- return true ;
773- }
774-
775- // Bind a the logging in user to check for authentication.
776- // Afterwards, bind as the original bound DN again, to restore the previous authorization.
777- boolean isAuthenticated = false ;
778- try {
779- // Binding will stop any LDAP-Injection Attacks since the searched-for user needs to bind to that DN
780- SimpleBindRequest ubr = new SimpleBindRequest (userDn , password );
781- conn .bind (ubr );
782- isAuthenticated = true ;
783- userBindRequest = ubr ;
784- } catch (LDAPException e ) {
785- logger .error ("Error authenticating user ({})" , userDn , e );
786- }
787-
788- try {
789- conn .bind (currentBindRequest );
790- } catch (LDAPException e ) {
791- logger .error ("Error reinstating original LDAP authorization (code {}). Team information may be inaccurate for this log in." ,
792- e .getResultCode (), e );
793- }
794- return isAuthenticated ;
795- }
796-
797-
798-
799- private boolean verifyCurrentBinding () {
800- BindRequest lastBind = conn .getLastBindRequest ();
801- if (lastBind == currentBindRequest ) {
802- return true ;
803- }
804- logger .debug ("Unexpected binding in LdapConnection. {} != {}" , lastBind , currentBindRequest );
805-
806- String lastBoundDN = ((SimpleBindRequest )lastBind ).getBindDN ();
807- String boundDN = currentBindRequest .getBindDN ();
808- logger .debug ("Currently bound as '{}', check authentication for '{}'" , lastBoundDN , boundDN );
809- if (boundDN != null && ! boundDN .equals (lastBoundDN )) {
810- logger .warn ("Unexpected binding DN in LdapConnection. '{}' != '{}'." , lastBoundDN , boundDN );
811- logger .warn ("Updated binding information in LDAP connection." );
812- currentBindRequest = (SimpleBindRequest )lastBind ;
813- return false ;
814- }
815- return true ;
816- }
817- }
818554}
0 commit comments