You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							357 lines
						
					
					
						
							9.2 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							357 lines
						
					
					
						
							9.2 KiB
						
					
					
				| package ldap | |
| 
 | |
| import ( | |
| 	"context" | |
| 	"fmt" | |
| 	"testing" | |
| 
 | |
| 	"github.com/seaweedfs/seaweedfs/weed/iam/providers" | |
| 	"github.com/stretchr/testify/assert" | |
| 	"github.com/stretchr/testify/require" | |
| ) | |
| 
 | |
| // TestLDAPProviderInitialization tests LDAP provider initialization | |
| func TestLDAPProviderInitialization(t *testing.T) { | |
| 	tests := []struct { | |
| 		name    string | |
| 		config  *LDAPConfig | |
| 		wantErr bool | |
| 	}{ | |
| 		{ | |
| 			name: "valid config", | |
| 			config: &LDAPConfig{ | |
| 				Server:      "ldap://localhost:389", | |
| 				BaseDN:      "DC=example,DC=com", | |
| 				BindDN:      "CN=admin,DC=example,DC=com", | |
| 				BindPass:    "password", | |
| 				UserFilter:  "(sAMAccountName=%s)", | |
| 				GroupFilter: "(member=%s)", | |
| 			}, | |
| 			wantErr: false, | |
| 		}, | |
| 		{ | |
| 			name: "missing server", | |
| 			config: &LDAPConfig{ | |
| 				BaseDN: "DC=example,DC=com", | |
| 			}, | |
| 			wantErr: true, | |
| 		}, | |
| 		{ | |
| 			name: "missing base DN", | |
| 			config: &LDAPConfig{ | |
| 				Server: "ldap://localhost:389", | |
| 			}, | |
| 			wantErr: true, | |
| 		}, | |
| 		{ | |
| 			name: "invalid server URL", | |
| 			config: &LDAPConfig{ | |
| 				Server: "invalid-url", | |
| 				BaseDN: "DC=example,DC=com", | |
| 			}, | |
| 			wantErr: true, | |
| 		}, | |
| 	} | |
| 
 | |
| 	for _, tt := range tests { | |
| 		t.Run(tt.name, func(t *testing.T) { | |
| 			provider := NewLDAPProvider("test-ldap") | |
| 
 | |
| 			err := provider.Initialize(tt.config) | |
| 
 | |
| 			if tt.wantErr { | |
| 				assert.Error(t, err) | |
| 			} else { | |
| 				assert.NoError(t, err) | |
| 				assert.Equal(t, "test-ldap", provider.Name()) | |
| 			} | |
| 		}) | |
| 	} | |
| } | |
| 
 | |
| // TestLDAPProviderAuthentication tests LDAP authentication | |
| func TestLDAPProviderAuthentication(t *testing.T) { | |
| 	// Skip if no LDAP test server available | |
| 	if testing.Short() { | |
| 		t.Skip("Skipping LDAP integration test in short mode") | |
| 	} | |
| 
 | |
| 	provider := NewLDAPProvider("test-ldap") | |
| 	config := &LDAPConfig{ | |
| 		Server:      "ldap://localhost:389", | |
| 		BaseDN:      "DC=example,DC=com", | |
| 		BindDN:      "CN=admin,DC=example,DC=com", | |
| 		BindPass:    "password", | |
| 		UserFilter:  "(sAMAccountName=%s)", | |
| 		GroupFilter: "(member=%s)", | |
| 		Attributes: map[string]string{ | |
| 			"email":       "mail", | |
| 			"displayName": "displayName", | |
| 			"groups":      "memberOf", | |
| 		}, | |
| 		RoleMapping: &providers.RoleMapping{ | |
| 			Rules: []providers.MappingRule{ | |
| 				{ | |
| 					Claim: "groups", | |
| 					Value: "*CN=Admins*", | |
| 					Role:  "arn:seaweed:iam::role/AdminRole", | |
| 				}, | |
| 				{ | |
| 					Claim: "groups", | |
| 					Value: "*CN=Users*", | |
| 					Role:  "arn:seaweed:iam::role/UserRole", | |
| 				}, | |
| 			}, | |
| 			DefaultRole: "arn:seaweed:iam::role/GuestRole", | |
| 		}, | |
| 	} | |
| 
 | |
| 	err := provider.Initialize(config) | |
| 	require.NoError(t, err) | |
| 
 | |
| 	t.Run("authenticate with username/password", func(t *testing.T) { | |
| 		// This would require an actual LDAP server for integration testing | |
| 		credentials := "user:password" // Basic auth format | |
|  | |
| 		identity, err := provider.Authenticate(context.Background(), credentials) | |
| 		if err != nil { | |
| 			t.Skip("LDAP server not available for testing") | |
| 		} | |
| 
 | |
| 		assert.NoError(t, err) | |
| 		assert.Equal(t, "user", identity.UserID) | |
| 		assert.Equal(t, "test-ldap", identity.Provider) | |
| 		assert.NotEmpty(t, identity.Email) | |
| 	}) | |
| 
 | |
| 	t.Run("authenticate with invalid credentials", func(t *testing.T) { | |
| 		_, err := provider.Authenticate(context.Background(), "invalid:credentials") | |
| 		assert.Error(t, err) | |
| 	}) | |
| } | |
| 
 | |
| // TestLDAPProviderUserInfo tests LDAP user info retrieval | |
| func TestLDAPProviderUserInfo(t *testing.T) { | |
| 	if testing.Short() { | |
| 		t.Skip("Skipping LDAP integration test in short mode") | |
| 	} | |
| 
 | |
| 	provider := NewLDAPProvider("test-ldap") | |
| 	config := &LDAPConfig{ | |
| 		Server:     "ldap://localhost:389", | |
| 		BaseDN:     "DC=example,DC=com", | |
| 		BindDN:     "CN=admin,DC=example,DC=com", | |
| 		BindPass:   "password", | |
| 		UserFilter: "(sAMAccountName=%s)", | |
| 		Attributes: map[string]string{ | |
| 			"email":       "mail", | |
| 			"displayName": "displayName", | |
| 			"department":  "department", | |
| 		}, | |
| 	} | |
| 
 | |
| 	err := provider.Initialize(config) | |
| 	require.NoError(t, err) | |
| 
 | |
| 	t.Run("get user info", func(t *testing.T) { | |
| 		identity, err := provider.GetUserInfo(context.Background(), "testuser") | |
| 		if err != nil { | |
| 			t.Skip("LDAP server not available for testing") | |
| 		} | |
| 
 | |
| 		assert.NoError(t, err) | |
| 		assert.Equal(t, "testuser", identity.UserID) | |
| 		assert.Equal(t, "test-ldap", identity.Provider) | |
| 		assert.NotEmpty(t, identity.Email) | |
| 		assert.NotEmpty(t, identity.DisplayName) | |
| 	}) | |
| 
 | |
| 	t.Run("get user info with empty username", func(t *testing.T) { | |
| 		_, err := provider.GetUserInfo(context.Background(), "") | |
| 		assert.Error(t, err) | |
| 	}) | |
| 
 | |
| 	t.Run("get user info for non-existent user", func(t *testing.T) { | |
| 		_, err := provider.GetUserInfo(context.Background(), "nonexistent") | |
| 		assert.Error(t, err) | |
| 	}) | |
| } | |
| 
 | |
| // TestLDAPAttributeMapping tests LDAP attribute mapping | |
| func TestLDAPAttributeMapping(t *testing.T) { | |
| 	provider := NewLDAPProvider("test-ldap") | |
| 	config := &LDAPConfig{ | |
| 		Server: "ldap://localhost:389", | |
| 		BaseDN: "DC=example,DC=com", | |
| 		Attributes: map[string]string{ | |
| 			"email":       "mail", | |
| 			"displayName": "cn", | |
| 			"department":  "departmentNumber", | |
| 			"groups":      "memberOf", | |
| 		}, | |
| 	} | |
| 
 | |
| 	err := provider.Initialize(config) | |
| 	require.NoError(t, err) | |
| 
 | |
| 	t.Run("map LDAP attributes to identity", func(t *testing.T) { | |
| 		ldapAttrs := map[string][]string{ | |
| 			"mail":             {"user@example.com"}, | |
| 			"cn":               {"John Doe"}, | |
| 			"departmentNumber": {"IT"}, | |
| 			"memberOf": { | |
| 				"CN=Users,OU=Groups,DC=example,DC=com", | |
| 				"CN=Developers,OU=Groups,DC=example,DC=com", | |
| 			}, | |
| 		} | |
| 
 | |
| 		identity := provider.mapLDAPAttributes("testuser", ldapAttrs) | |
| 
 | |
| 		assert.Equal(t, "testuser", identity.UserID) | |
| 		assert.Equal(t, "user@example.com", identity.Email) | |
| 		assert.Equal(t, "John Doe", identity.DisplayName) | |
| 		assert.Equal(t, "test-ldap", identity.Provider) | |
| 
 | |
| 		// Check groups | |
| 		assert.Contains(t, identity.Groups, "CN=Users,OU=Groups,DC=example,DC=com") | |
| 		assert.Contains(t, identity.Groups, "CN=Developers,OU=Groups,DC=example,DC=com") | |
| 
 | |
| 		// Check attributes | |
| 		assert.Equal(t, "IT", identity.Attributes["department"]) | |
| 	}) | |
| } | |
| 
 | |
| // TestLDAPGroupFiltering tests LDAP group filtering and role mapping | |
| func TestLDAPGroupFiltering(t *testing.T) { | |
| 	provider := NewLDAPProvider("test-ldap") | |
| 	config := &LDAPConfig{ | |
| 		Server: "ldap://localhost:389", | |
| 		BaseDN: "DC=example,DC=com", | |
| 		RoleMapping: &providers.RoleMapping{ | |
| 			Rules: []providers.MappingRule{ | |
| 				{ | |
| 					Claim: "groups", | |
| 					Value: "*Admins*", | |
| 					Role:  "arn:seaweed:iam::role/AdminRole", | |
| 				}, | |
| 				{ | |
| 					Claim: "groups", | |
| 					Value: "*Users*", | |
| 					Role:  "arn:seaweed:iam::role/UserRole", | |
| 				}, | |
| 			}, | |
| 			DefaultRole: "arn:seaweed:iam::role/GuestRole", | |
| 		}, | |
| 	} | |
| 
 | |
| 	err := provider.Initialize(config) | |
| 	require.NoError(t, err) | |
| 
 | |
| 	tests := []struct { | |
| 		name           string | |
| 		groups         []string | |
| 		expectedRole   string | |
| 		expectedClaims map[string]interface{} | |
| 	}{ | |
| 		{ | |
| 			name:         "admin user", | |
| 			groups:       []string{"CN=Admins,OU=Groups,DC=example,DC=com", "CN=Users,OU=Groups,DC=example,DC=com"}, | |
| 			expectedRole: "arn:seaweed:iam::role/AdminRole", | |
| 		}, | |
| 		{ | |
| 			name:         "regular user", | |
| 			groups:       []string{"CN=Users,OU=Groups,DC=example,DC=com"}, | |
| 			expectedRole: "arn:seaweed:iam::role/UserRole", | |
| 		}, | |
| 		{ | |
| 			name:         "guest user", | |
| 			groups:       []string{"CN=Guests,OU=Groups,DC=example,DC=com"}, | |
| 			expectedRole: "arn:seaweed:iam::role/GuestRole", | |
| 		}, | |
| 	} | |
| 
 | |
| 	for _, tt := range tests { | |
| 		t.Run(tt.name, func(t *testing.T) { | |
| 			identity := &providers.ExternalIdentity{ | |
| 				UserID:   "testuser", | |
| 				Groups:   tt.groups, | |
| 				Provider: "test-ldap", | |
| 			} | |
| 
 | |
| 			role := provider.mapUserToRole(identity) | |
| 			assert.Equal(t, tt.expectedRole, role) | |
| 		}) | |
| 	} | |
| } | |
| 
 | |
| // TestLDAPConnectionPool tests LDAP connection pooling | |
| func TestLDAPConnectionPool(t *testing.T) { | |
| 	if testing.Short() { | |
| 		t.Skip("Skipping LDAP connection pool test in short mode") | |
| 	} | |
| 
 | |
| 	provider := NewLDAPProvider("test-ldap") | |
| 	config := &LDAPConfig{ | |
| 		Server:         "ldap://localhost:389", | |
| 		BaseDN:         "DC=example,DC=com", | |
| 		BindDN:         "CN=admin,DC=example,DC=com", | |
| 		BindPass:       "password", | |
| 		MaxConnections: 5, | |
| 		ConnTimeout:    30, | |
| 	} | |
| 
 | |
| 	err := provider.Initialize(config) | |
| 	require.NoError(t, err) | |
| 
 | |
| 	t.Run("connection pool management", func(t *testing.T) { | |
| 		// Test that multiple concurrent requests work | |
| 		// This would require actual LDAP server for full testing | |
| 		pool := provider.getConnectionPool() | |
| 		assert.NotNil(t, pool) | |
| 
 | |
| 		// Test connection acquisition and release | |
| 		conn, err := provider.getConnection() | |
| 		if err != nil { | |
| 			t.Skip("LDAP server not available") | |
| 		} | |
| 
 | |
| 		assert.NotNil(t, conn) | |
| 		provider.releaseConnection(conn) | |
| 	}) | |
| } | |
| 
 | |
| // MockLDAPServer for unit testing (without external dependencies) | |
| type MockLDAPServer struct { | |
| 	users map[string]map[string][]string | |
| } | |
| 
 | |
| func NewMockLDAPServer() *MockLDAPServer { | |
| 	return &MockLDAPServer{ | |
| 		users: map[string]map[string][]string{ | |
| 			"testuser": { | |
| 				"mail":       {"testuser@example.com"}, | |
| 				"cn":         {"Test User"}, | |
| 				"department": {"Engineering"}, | |
| 				"memberOf":   {"CN=Users,OU=Groups,DC=example,DC=com"}, | |
| 			}, | |
| 			"admin": { | |
| 				"mail":       {"admin@example.com"}, | |
| 				"cn":         {"Administrator"}, | |
| 				"department": {"IT"}, | |
| 				"memberOf":   {"CN=Admins,OU=Groups,DC=example,DC=com", "CN=Users,OU=Groups,DC=example,DC=com"}, | |
| 			}, | |
| 		}, | |
| 	} | |
| } | |
| 
 | |
| func (m *MockLDAPServer) Authenticate(username, password string) bool { | |
| 	_, exists := m.users[username] | |
| 	return exists && password == "password" // Mock authentication | |
| } | |
| 
 | |
| func (m *MockLDAPServer) GetUserAttributes(username string) (map[string][]string, error) { | |
| 	if attrs, exists := m.users[username]; exists { | |
| 		return attrs, nil | |
| 	} | |
| 	return nil, fmt.Errorf("user not found: %s", username) | |
| }
 |