16
16
*/
17
17
package com .gitblit .auth ;
18
18
19
- import java .net .URI ;
20
- import java .net .URISyntaxException ;
21
- import java .security .GeneralSecurityException ;
22
19
import java .text .MessageFormat ;
23
20
import java .util .Arrays ;
24
21
import java .util .HashMap ;
33
30
import com .gitblit .Constants .Role ;
34
31
import com .gitblit .Keys ;
35
32
import com .gitblit .auth .AuthenticationProvider .UsernamePasswordAuthenticationProvider ;
33
+ import com .gitblit .ldap .LdapConnection ;
36
34
import com .gitblit .models .TeamModel ;
37
35
import com .gitblit .models .UserModel ;
38
36
import com .gitblit .service .LdapSyncService ;
39
37
import com .gitblit .utils .ArrayUtils ;
40
38
import com .gitblit .utils .StringUtils ;
41
39
import com .unboundid .ldap .sdk .Attribute ;
42
- import com .unboundid .ldap .sdk .BindRequest ;
43
40
import 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 ;
47
41
import com .unboundid .ldap .sdk .LDAPException ;
48
- import com .unboundid .ldap .sdk .LDAPSearchException ;
49
42
import com .unboundid .ldap .sdk .ResultCode ;
50
43
import com .unboundid .ldap .sdk .SearchRequest ;
51
44
import com .unboundid .ldap .sdk .SearchResult ;
52
45
import com .unboundid .ldap .sdk .SearchResultEntry ;
53
46
import 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 ;
58
47
59
48
/**
60
49
* Implementation of an LDAP user service.
@@ -109,7 +98,7 @@ public synchronized void sync() {
109
98
if (enabled ) {
110
99
logger .info ("Synchronizing with LDAP @ " + settings .getRequiredString (Keys .realm .ldap .server ));
111
100
final boolean deleteRemovedLdapUsers = settings .getBoolean (Keys .realm .ldap .removeDeletedUsers , true );
112
- LdapConnection ldapConnection = new LdapConnection ();
101
+ LdapConnection ldapConnection = new LdapConnection (settings );
113
102
if (ldapConnection .connect ()) {
114
103
if (ldapConnection .bind () == null ) {
115
104
ldapConnection .close ();
@@ -118,9 +107,9 @@ public synchronized void sync() {
118
107
}
119
108
120
109
try {
121
- String accountBase = settings .getString (Keys .realm .ldap .accountBase , "" );
122
110
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 ();
124
113
accountPattern = StringUtils .replace (accountPattern , "${username}" , "*" );
125
114
126
115
SearchResult result = doSearch (ldapConnection , accountBase , accountPattern );
@@ -265,7 +254,7 @@ public AccountType getAccountType() {
265
254
public UserModel authenticate (String username , char [] password ) {
266
255
String simpleUsername = getSimpleUsername (username );
267
256
268
- LdapConnection ldapConnection = new LdapConnection ();
257
+ LdapConnection ldapConnection = new LdapConnection (settings );
269
258
if (ldapConnection .connect ()) {
270
259
271
260
// Try to bind either to the "manager" account,
@@ -286,11 +275,7 @@ public UserModel authenticate(String username, char[] password) {
286
275
287
276
try {
288
277
// 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 );
294
279
if (result != null && result .getEntryCount () == 1 ) {
295
280
SearchResultEntry loggingInUser = result .getSearchEntries ().get (0 );
296
281
String loggingInUserDN = loggingInUser .getDN ();
@@ -441,12 +426,12 @@ private void getTeamsFromLdap(LdapConnection ldapConnection, String simpleUserna
441
426
String groupBase = settings .getString (Keys .realm .ldap .groupBase , "" );
442
427
String groupMemberPattern = settings .getString (Keys .realm .ldap .groupMemberPattern , "(&(objectClass=group)(member=${dn}))" );
443
428
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 ));
446
431
447
432
// Fill in attributes into groupMemberPattern
448
433
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 ()));
450
435
}
451
436
452
437
SearchResult teamMembershipResult = searchTeamsInLdap (ldapConnection , groupBase , true , groupMemberPattern , Arrays .asList ("cn" ));
@@ -538,6 +523,7 @@ private SearchResult doSearch(LdapConnection ldapConnection, String base, String
538
523
539
524
540
525
526
+
541
527
/**
542
528
* Returns a simple username without any domain prefixes.
543
529
*
@@ -553,34 +539,6 @@ protected String getSimpleUsername(String username) {
553
539
return username ;
554
540
}
555
541
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
-
584
542
private void configureSyncService () {
585
543
LdapSyncService ldapSyncService = new LdapSyncService (settings , this );
586
544
if (ldapSyncService .isReady ()) {
@@ -593,226 +551,4 @@ private void configureSyncService() {
593
551
logger .info ("Ldap sync service is disabled." );
594
552
}
595
553
}
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
- }
818
554
}
0 commit comments