snapshot of https://github.com/seaweedfs/seaweedfs.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
go: build dev binaries / cleanup (push) Successful in 4s
Codespell / Check for spelling errors (push) Failing after 3s
docker: build dev containers / build-rust-binaries (amd64, x86_64-unknown-linux-musl) (push) Failing after 3s
docker: build dev containers / build-dev-containers (push) Has been skipped
docker: build dev containers / build-rust-binaries (arm64, true, aarch64-unknown-linux-musl) (push) Failing after 3s
End to End / FUSE Mount (push) Failing after 2s
EC Integration Tests / EC Integration Tests (push) Failing after 30s
FUSE DLM Integration Tests / FUSE DLM Integration Tests (push) Failing after 3s
FUSE Volume Server Failover Tests / FUSE Volume Server Failover (push) Failing after 3s
FUSE Integration Tests / FUSE Integration Testing (push) Failing after 2s
FUSE P2P Peer Chunk Sharing Integration Tests / FUSE P2P Peer Chunk Sharing (push) Failing after 3s
go: build binary / Go Vet (push) Failing after 2s
go: build binary / Go Vet 32-bit (push) Failing after 3s
go: build binary / Build (push) Failing after 4s
go: build binary / Build cross-platform (freebsd/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (darwin/arm64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/arm64) (push) Failing after 3s
go: build binary / Test (push) Failing after 2s
go: build binary / Test 32-bit (push) Failing after 3s
helm: lint and test charts / lint-test (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (11) (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (17) (push) Failing after 3s
go: build dev binaries / build_dev_linux_windows (amd64, linux) (push) Failing after 2s
go: build dev binaries / build_dev_linux_windows (amd64, windows) (push) Failing after 3s
go: build dev binaries / build_dev_darwin (amd64, darwin) (push) Has been skipped
go: build dev binaries / build_dev_darwin (arm64, darwin) (push) Has been skipped
Java Client Unit Tests / Java Unit Tests (11) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (17) (push) Failing after 2s
Java Client Unit Tests / Java Unit Tests (21) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (8) (push) Failing after 3s
Kafka Quick Test (Load Test with Schema Registry) / Kafka Client Load Test (Quick) (push) Failing after 3s
Kafka Gateway Tests / Kafka Unit Tests (unit-tests-1) (push) Failing after 11s
Kafka Gateway Tests / Kafka Integration Tests (Critical) (integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka End-to-End Tests (with SMQ) (e2e-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Consumer Group Tests (Highly Isolated) (consumer-group-1) (push) Failing after 2s
Kafka Gateway Tests / Kafka Client Compatibility (with SMQ) (client-compat-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka SMQ Integration Tests (Full Stack) (smq-integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Protocol Tests (Isolated) (protocol-1) (push) Failing after 3s
KMS Tests / KMS Provider Integration Tests (push) Failing after 2s
KMS Tests / S3 KMS End-to-End Tests (push) Failing after 3s
Master Cold Start Tests / Master Cold Start Tests (push) Failing after 4s
Metadata Subscribe Integration Tests / Metadata Subscribe Integration Tests (push) Failing after 3s
Multi-Master Tests / Multi-Master Failover Tests (push) Failing after 3s
Performance / CPU and Heap Profile (push) Failing after 2s
Performance / S3 Read/Write Benchmark (rust volume) (push) Failing after 3s
pjdfstest POSIX Compliance / pjdfstest (push) Failing after 2s
Plugin Worker Integration Tests / Plugin Worker: erasure_coding (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: vacuum (push) Failing after 3s
Rust Volume Server Tests / Rust Integration Tests (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 1) (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 1) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 3) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Distributed Tests (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Performance Tests (push) Failing after 3s
Performance / Throughput Benchmark (go volume) (push) Failing after 3s
Performance / Throughput Benchmark (rust volume) (push) Failing after 2s
Performance / S3 Read/Write Benchmark (go volume) (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: volume_balance (push) Failing after 3s
PostgreSQL Gateway Tests / PostgreSQL Basic Tests (push) Failing after 3s
Rust Volume Server Tests / Rust Unit Tests (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 3) (push) Failing after 3s
Rust Plugin Worker Tests / Rust Plugin Worker Build and Unit Tests (push) Failing after 3s
rust: build dev volume server binaries / cleanup (push) Successful in 3s
S3 ETag and ACL Tests / S3 ETag + ACL Integration Tests (push) Failing after 3s
S3 IAM Integration Tests / IAM Unit Tests (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (advanced) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (basic) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (group) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (policy-enforcement) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (sts) (push) Failing after 4s
S3 Keycloak Integration Tests / S3 Keycloak Integration Tests (push) Failing after 3s
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.11) (push) Failing after 3s
S3 PyArrow Parquet Tests / Go Unit Tests (Implicit Directory) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.12) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-amd64, x86_64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-arm64, aarch64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-linux (linux-amd64, x86_64-unknown-linux-gnu) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.9) (push) Has been cancelled
S3 Proxy Signature Tests / S3 Proxy Signature Verification Tests (push) Has been cancelled
S3 SDK V2 Route Disambiguation Tests / S3 SDK V2 Routing Tests (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (quick) (push) Has been cancelled
S3 SSE Tests / S3 SSE Compatibility Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Metadata Persistence Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Copy Operations Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Multipart Upload Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Performance Test (push) Has been cancelled
S3 SSE Tests / S3 Volume Encryption Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (comprehensive) (push) Has been cancelled
Spark Integration Tests / Spark Integration Tests (push) Failing after 2s
Telemetry Integration Tests / telemetry-integration-test (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 1) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 1) (push) Failing after 3s
Ceph S3 tests / Basic S3 tests (KV store) (push) Has been cancelled
Ceph S3 tests / S3 Versioning & Object Lock tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Unit Tests (push) Failing after 3s
Ceph S3 tests / S3 CORS tests (push) Has been cancelled
Ceph S3 tests / SeaweedFS Custom S3 Copy tests (push) Has been cancelled
Ceph S3 tests / Basic S3 tests (SQL store) (push) Has been cancelled
test s3 over https using aws-cli / awscli-tests (push) Has been cancelled
TLS Rotation Integration Tests / TLS Rotation Integration Tests (push) Has been cancelled
Vacuum Integration Tests / Vacuum Integration Tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Variables Integration Tests (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (advanced-policy) (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (basic-policy) (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (contextual-policy) (push) Failing after 4s
S3 Policy Integration Tests / Trusted Proxy Detection Tests (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Shell Integration Tests (push) Failing after 3s
Samba on FUSE Integration / samba-integration (push) Failing after 2s
SFTP Integration Tests / SFTP Integration Testing (push) Failing after 3s
terraform: validate and test modules / fmt, validate, plan-level tests (push) Failing after 3s
terraform: validate and test modules / local cluster smoke test (real weed) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 3) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 2) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 2) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 3) (push) Failing after 5s
mount: windows conformance / WinFsp conformance (push) Has been cancelled
mount: windows / Mount on Windows (push) Has been cancelled
Some checks failed
go: build dev binaries / cleanup (push) Successful in 4s
Codespell / Check for spelling errors (push) Failing after 3s
docker: build dev containers / build-rust-binaries (amd64, x86_64-unknown-linux-musl) (push) Failing after 3s
docker: build dev containers / build-dev-containers (push) Has been skipped
docker: build dev containers / build-rust-binaries (arm64, true, aarch64-unknown-linux-musl) (push) Failing after 3s
End to End / FUSE Mount (push) Failing after 2s
EC Integration Tests / EC Integration Tests (push) Failing after 30s
FUSE DLM Integration Tests / FUSE DLM Integration Tests (push) Failing after 3s
FUSE Volume Server Failover Tests / FUSE Volume Server Failover (push) Failing after 3s
FUSE Integration Tests / FUSE Integration Testing (push) Failing after 2s
FUSE P2P Peer Chunk Sharing Integration Tests / FUSE P2P Peer Chunk Sharing (push) Failing after 3s
go: build binary / Go Vet (push) Failing after 2s
go: build binary / Go Vet 32-bit (push) Failing after 3s
go: build binary / Build (push) Failing after 4s
go: build binary / Build cross-platform (freebsd/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (darwin/arm64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/arm64) (push) Failing after 3s
go: build binary / Test (push) Failing after 2s
go: build binary / Test 32-bit (push) Failing after 3s
helm: lint and test charts / lint-test (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (11) (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (17) (push) Failing after 3s
go: build dev binaries / build_dev_linux_windows (amd64, linux) (push) Failing after 2s
go: build dev binaries / build_dev_linux_windows (amd64, windows) (push) Failing after 3s
go: build dev binaries / build_dev_darwin (amd64, darwin) (push) Has been skipped
go: build dev binaries / build_dev_darwin (arm64, darwin) (push) Has been skipped
Java Client Unit Tests / Java Unit Tests (11) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (17) (push) Failing after 2s
Java Client Unit Tests / Java Unit Tests (21) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (8) (push) Failing after 3s
Kafka Quick Test (Load Test with Schema Registry) / Kafka Client Load Test (Quick) (push) Failing after 3s
Kafka Gateway Tests / Kafka Unit Tests (unit-tests-1) (push) Failing after 11s
Kafka Gateway Tests / Kafka Integration Tests (Critical) (integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka End-to-End Tests (with SMQ) (e2e-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Consumer Group Tests (Highly Isolated) (consumer-group-1) (push) Failing after 2s
Kafka Gateway Tests / Kafka Client Compatibility (with SMQ) (client-compat-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka SMQ Integration Tests (Full Stack) (smq-integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Protocol Tests (Isolated) (protocol-1) (push) Failing after 3s
KMS Tests / KMS Provider Integration Tests (push) Failing after 2s
KMS Tests / S3 KMS End-to-End Tests (push) Failing after 3s
Master Cold Start Tests / Master Cold Start Tests (push) Failing after 4s
Metadata Subscribe Integration Tests / Metadata Subscribe Integration Tests (push) Failing after 3s
Multi-Master Tests / Multi-Master Failover Tests (push) Failing after 3s
Performance / CPU and Heap Profile (push) Failing after 2s
Performance / S3 Read/Write Benchmark (rust volume) (push) Failing after 3s
pjdfstest POSIX Compliance / pjdfstest (push) Failing after 2s
Plugin Worker Integration Tests / Plugin Worker: erasure_coding (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: vacuum (push) Failing after 3s
Rust Volume Server Tests / Rust Integration Tests (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 1) (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 1) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 3) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Distributed Tests (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Performance Tests (push) Failing after 3s
Performance / Throughput Benchmark (go volume) (push) Failing after 3s
Performance / Throughput Benchmark (rust volume) (push) Failing after 2s
Performance / S3 Read/Write Benchmark (go volume) (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: volume_balance (push) Failing after 3s
PostgreSQL Gateway Tests / PostgreSQL Basic Tests (push) Failing after 3s
Rust Volume Server Tests / Rust Unit Tests (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 3) (push) Failing after 3s
Rust Plugin Worker Tests / Rust Plugin Worker Build and Unit Tests (push) Failing after 3s
rust: build dev volume server binaries / cleanup (push) Successful in 3s
S3 ETag and ACL Tests / S3 ETag + ACL Integration Tests (push) Failing after 3s
S3 IAM Integration Tests / IAM Unit Tests (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (advanced) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (basic) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (group) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (policy-enforcement) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (sts) (push) Failing after 4s
S3 Keycloak Integration Tests / S3 Keycloak Integration Tests (push) Failing after 3s
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.11) (push) Failing after 3s
S3 PyArrow Parquet Tests / Go Unit Tests (Implicit Directory) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.12) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-amd64, x86_64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-arm64, aarch64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-linux (linux-amd64, x86_64-unknown-linux-gnu) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.9) (push) Has been cancelled
S3 Proxy Signature Tests / S3 Proxy Signature Verification Tests (push) Has been cancelled
S3 SDK V2 Route Disambiguation Tests / S3 SDK V2 Routing Tests (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (quick) (push) Has been cancelled
S3 SSE Tests / S3 SSE Compatibility Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Metadata Persistence Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Copy Operations Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Multipart Upload Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Performance Test (push) Has been cancelled
S3 SSE Tests / S3 Volume Encryption Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (comprehensive) (push) Has been cancelled
Spark Integration Tests / Spark Integration Tests (push) Failing after 2s
Telemetry Integration Tests / telemetry-integration-test (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 1) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 1) (push) Failing after 3s
Ceph S3 tests / Basic S3 tests (KV store) (push) Has been cancelled
Ceph S3 tests / S3 Versioning & Object Lock tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Unit Tests (push) Failing after 3s
Ceph S3 tests / S3 CORS tests (push) Has been cancelled
Ceph S3 tests / SeaweedFS Custom S3 Copy tests (push) Has been cancelled
Ceph S3 tests / Basic S3 tests (SQL store) (push) Has been cancelled
test s3 over https using aws-cli / awscli-tests (push) Has been cancelled
TLS Rotation Integration Tests / TLS Rotation Integration Tests (push) Has been cancelled
Vacuum Integration Tests / Vacuum Integration Tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Variables Integration Tests (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (advanced-policy) (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (basic-policy) (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (contextual-policy) (push) Failing after 4s
S3 Policy Integration Tests / Trusted Proxy Detection Tests (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Shell Integration Tests (push) Failing after 3s
Samba on FUSE Integration / samba-integration (push) Failing after 2s
SFTP Integration Tests / SFTP Integration Testing (push) Failing after 3s
terraform: validate and test modules / fmt, validate, plan-level tests (push) Failing after 3s
terraform: validate and test modules / local cluster smoke test (real weed) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 3) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 2) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 2) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 3) (push) Failing after 5s
mount: windows conformance / WinFsp conformance (push) Has been cancelled
mount: windows / Mount on Windows (push) Has been cancelled
This commit is contained in:
571
weed/iam/ldap/ldap_provider.go
Normal file
571
weed/iam/ldap/ldap_provider.go
Normal file
@@ -0,0 +1,571 @@
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||
"github.com/seaweedfs/seaweedfs/weed/iam/providers"
|
||||
)
|
||||
|
||||
// LDAPConfig holds configuration for LDAP provider
|
||||
type LDAPConfig struct {
|
||||
// Server is the LDAP server URL (ldap:// or ldaps://)
|
||||
Server string `json:"server"`
|
||||
|
||||
// BindDN is the DN used to bind for searches (optional for anonymous bind)
|
||||
BindDN string `json:"bindDN,omitempty"`
|
||||
|
||||
// BindPassword is the password for the bind DN
|
||||
BindPassword string `json:"bindPassword,omitempty"`
|
||||
|
||||
// BaseDN is the base DN for user searches
|
||||
BaseDN string `json:"baseDN"`
|
||||
|
||||
// UserFilter is the filter to find users (use %s for username placeholder)
|
||||
// Example: "(uid=%s)" or "(cn=%s)" or "(&(objectClass=person)(uid=%s))"
|
||||
UserFilter string `json:"userFilter"`
|
||||
|
||||
// GroupFilter is the filter to find user groups (use %s for user DN placeholder)
|
||||
// Example: "(member=%s)" or "(memberUid=%s)"
|
||||
GroupFilter string `json:"groupFilter,omitempty"`
|
||||
|
||||
// GroupBaseDN is the base DN for group searches (defaults to BaseDN)
|
||||
GroupBaseDN string `json:"groupBaseDN,omitempty"`
|
||||
|
||||
// Attributes to retrieve from LDAP
|
||||
Attributes LDAPAttributes `json:"attributes,omitempty"`
|
||||
|
||||
// UseTLS enables StartTLS
|
||||
UseTLS bool `json:"useTLS,omitempty"`
|
||||
|
||||
// InsecureSkipVerify skips TLS certificate verification
|
||||
InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty"`
|
||||
|
||||
// ConnectionTimeout is the connection timeout
|
||||
ConnectionTimeout time.Duration `json:"connectionTimeout,omitempty"`
|
||||
|
||||
// PoolSize is the number of connections in the pool (default: 10)
|
||||
PoolSize int `json:"poolSize,omitempty"`
|
||||
|
||||
// Audience is the expected audience for tokens (optional)
|
||||
Audience string `json:"audience,omitempty"`
|
||||
}
|
||||
|
||||
// LDAPAttributes maps LDAP attribute names
|
||||
type LDAPAttributes struct {
|
||||
Email string `json:"email,omitempty"` // Default: mail
|
||||
DisplayName string `json:"displayName,omitempty"` // Default: cn
|
||||
Groups string `json:"groups,omitempty"` // Default: memberOf
|
||||
UID string `json:"uid,omitempty"` // Default: uid
|
||||
}
|
||||
|
||||
// connectionPool manages a pool of LDAP connections for reuse
|
||||
type connectionPool struct {
|
||||
conns chan *ldap.Conn
|
||||
mu sync.Mutex
|
||||
size int
|
||||
closed uint32 // atomic flag: 1 if closed, 0 if open
|
||||
}
|
||||
|
||||
// LDAPProvider implements the IdentityProvider interface for LDAP
|
||||
type LDAPProvider struct {
|
||||
name string
|
||||
config *LDAPConfig
|
||||
initialized bool
|
||||
mu sync.RWMutex
|
||||
pool *connectionPool
|
||||
}
|
||||
|
||||
// NewLDAPProvider creates a new LDAP provider
|
||||
func NewLDAPProvider(name string) *LDAPProvider {
|
||||
return &LDAPProvider{
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns the provider name
|
||||
func (p *LDAPProvider) Name() string {
|
||||
return p.name
|
||||
}
|
||||
|
||||
// Initialize initializes the provider with configuration
|
||||
func (p *LDAPProvider) Initialize(config interface{}) error {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
if p.initialized {
|
||||
return fmt.Errorf("LDAP provider already initialized")
|
||||
}
|
||||
|
||||
cfg := &LDAPConfig{}
|
||||
|
||||
// Check if input is already the correct struct type
|
||||
if c, ok := config.(*LDAPConfig); ok {
|
||||
cfg = c
|
||||
} else {
|
||||
// Parse from map using mapstructure with weak typing and time duration hook
|
||||
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||
DecodeHook: mapstructure.ComposeDecodeHookFunc(
|
||||
mapstructure.StringToTimeDurationHookFunc(),
|
||||
),
|
||||
Result: cfg,
|
||||
TagName: "json",
|
||||
WeaklyTypedInput: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create config decoder: %w", err)
|
||||
}
|
||||
|
||||
if err := decoder.Decode(config); err != nil {
|
||||
return fmt.Errorf("failed to decode LDAP configuration: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate required fields
|
||||
if cfg.Server == "" {
|
||||
return fmt.Errorf("LDAP server URL is required")
|
||||
}
|
||||
if cfg.BaseDN == "" {
|
||||
return fmt.Errorf("LDAP base DN is required")
|
||||
}
|
||||
if cfg.UserFilter == "" {
|
||||
cfg.UserFilter = "(cn=%s)" // Default filter
|
||||
}
|
||||
|
||||
// Warn if BindDN is configured but BindPassword is empty
|
||||
if cfg.BindDN != "" && cfg.BindPassword == "" {
|
||||
glog.Warningf("LDAP provider '%s' configured with BindDN but no BindPassword", p.name)
|
||||
}
|
||||
|
||||
// Warn if InsecureSkipVerify is enabled
|
||||
if cfg.InsecureSkipVerify {
|
||||
glog.Warningf("LDAP provider '%s' has InsecureSkipVerify enabled. Do not use in production.", p.name)
|
||||
}
|
||||
|
||||
// Set default attributes
|
||||
if cfg.Attributes.Email == "" {
|
||||
cfg.Attributes.Email = "mail"
|
||||
}
|
||||
if cfg.Attributes.DisplayName == "" {
|
||||
cfg.Attributes.DisplayName = "cn"
|
||||
}
|
||||
if cfg.Attributes.Groups == "" {
|
||||
cfg.Attributes.Groups = "memberOf"
|
||||
}
|
||||
if cfg.Attributes.UID == "" {
|
||||
cfg.Attributes.UID = "uid"
|
||||
}
|
||||
if cfg.GroupBaseDN == "" {
|
||||
cfg.GroupBaseDN = cfg.BaseDN
|
||||
}
|
||||
if cfg.ConnectionTimeout == 0 {
|
||||
cfg.ConnectionTimeout = 10 * time.Second
|
||||
}
|
||||
|
||||
p.config = cfg
|
||||
|
||||
// Initialize connection pool (default size: 10 connections)
|
||||
poolSize := 10
|
||||
if cfg.PoolSize > 0 {
|
||||
poolSize = cfg.PoolSize
|
||||
}
|
||||
p.pool = &connectionPool{
|
||||
conns: make(chan *ldap.Conn, poolSize),
|
||||
size: poolSize,
|
||||
}
|
||||
|
||||
p.initialized = true
|
||||
|
||||
glog.V(1).Infof("LDAP provider '%s' initialized: server=%s, baseDN=%s",
|
||||
p.name, cfg.Server, cfg.BaseDN)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getConnection gets a connection from the pool or creates a new one
|
||||
func (p *LDAPProvider) getConnection() (*ldap.Conn, error) {
|
||||
// Try to get a connection from the pool (non-blocking)
|
||||
select {
|
||||
case conn := <-p.pool.conns:
|
||||
// Test if connection is still alive
|
||||
if conn != nil && conn.IsClosing() {
|
||||
conn.Close()
|
||||
// Connection is dead, create a new one
|
||||
return p.createConnection()
|
||||
}
|
||||
return conn, nil
|
||||
default:
|
||||
// Pool is empty, create a new connection
|
||||
return p.createConnection()
|
||||
}
|
||||
}
|
||||
|
||||
// returnConnection returns a connection to the pool
|
||||
func (p *LDAPProvider) returnConnection(conn *ldap.Conn) {
|
||||
if conn == nil || conn.IsClosing() {
|
||||
if conn != nil {
|
||||
conn.Close()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Check if pool is closed before attempting to send
|
||||
if atomic.LoadUint32(&p.pool.closed) == 1 {
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
// Try to return to pool (non-blocking)
|
||||
select {
|
||||
case p.pool.conns <- conn:
|
||||
// Successfully returned to pool
|
||||
default:
|
||||
// Pool is full, close the connection
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// createConnection establishes a new connection to the LDAP server
|
||||
func (p *LDAPProvider) createConnection() (*ldap.Conn, error) {
|
||||
var conn *ldap.Conn
|
||||
var err error
|
||||
|
||||
// Create dialer with timeout
|
||||
dialer := &net.Dialer{Timeout: p.config.ConnectionTimeout}
|
||||
|
||||
// Parse server URL
|
||||
if strings.HasPrefix(p.config.Server, "ldaps://") {
|
||||
// LDAPS connection
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: p.config.InsecureSkipVerify,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}
|
||||
conn, err = ldap.DialURL(p.config.Server, ldap.DialWithDialer(dialer), ldap.DialWithTLSConfig(tlsConfig))
|
||||
} else {
|
||||
// LDAP connection
|
||||
conn, err = ldap.DialURL(p.config.Server, ldap.DialWithDialer(dialer))
|
||||
if err == nil && p.config.UseTLS {
|
||||
// StartTLS
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: p.config.InsecureSkipVerify,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}
|
||||
if err = conn.StartTLS(tlsConfig); err != nil {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("failed to start TLS: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to LDAP server: %w", err)
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// Close closes all connections in the pool
|
||||
func (p *LDAPProvider) Close() error {
|
||||
if p.pool == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Atomically mark pool as closed to prevent new connections being returned
|
||||
if !atomic.CompareAndSwapUint32(&p.pool.closed, 0, 1) {
|
||||
// Already closed
|
||||
return nil
|
||||
}
|
||||
|
||||
p.pool.mu.Lock()
|
||||
defer p.pool.mu.Unlock()
|
||||
|
||||
// Now safe to close the channel since closed flag prevents new sends
|
||||
close(p.pool.conns)
|
||||
for conn := range p.pool.conns {
|
||||
if conn != nil {
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Authenticate authenticates a user with username:password credentials
|
||||
func (p *LDAPProvider) Authenticate(ctx context.Context, credentials string) (*providers.ExternalIdentity, error) {
|
||||
p.mu.RLock()
|
||||
if !p.initialized {
|
||||
p.mu.RUnlock()
|
||||
return nil, fmt.Errorf("LDAP provider not initialized")
|
||||
}
|
||||
config := p.config
|
||||
p.mu.RUnlock()
|
||||
|
||||
// Parse credentials (username:password format)
|
||||
parts := strings.SplitN(credentials, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("invalid credentials format (expected username:password)")
|
||||
}
|
||||
username, password := parts[0], parts[1]
|
||||
|
||||
if username == "" || password == "" {
|
||||
return nil, fmt.Errorf("username and password are required")
|
||||
}
|
||||
|
||||
// Get connection from pool
|
||||
conn, err := p.getConnection()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Note: defer returnConnection moved to after rebinding to service account
|
||||
|
||||
// First, bind with service account to search for user
|
||||
if config.BindDN != "" {
|
||||
err = conn.Bind(config.BindDN, config.BindPassword)
|
||||
if err != nil {
|
||||
glog.V(2).Infof("LDAP service bind failed: %v", err)
|
||||
conn.Close() // Close on error, don't return to pool
|
||||
return nil, fmt.Errorf("LDAP service bind failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Search for the user
|
||||
userFilter := fmt.Sprintf(config.UserFilter, ldap.EscapeFilter(username))
|
||||
searchRequest := ldap.NewSearchRequest(
|
||||
config.BaseDN,
|
||||
ldap.ScopeWholeSubtree,
|
||||
ldap.NeverDerefAliases,
|
||||
1, // Size limit
|
||||
int(config.ConnectionTimeout.Seconds()),
|
||||
false,
|
||||
userFilter,
|
||||
[]string{"dn", config.Attributes.Email, config.Attributes.DisplayName, config.Attributes.UID, config.Attributes.Groups},
|
||||
nil,
|
||||
)
|
||||
|
||||
result, err := conn.Search(searchRequest)
|
||||
if err != nil {
|
||||
glog.V(2).Infof("LDAP user search failed: %v", err)
|
||||
conn.Close() // Close on error
|
||||
return nil, fmt.Errorf("LDAP user search failed: %w", err)
|
||||
}
|
||||
|
||||
if len(result.Entries) == 0 {
|
||||
conn.Close() // Close on error
|
||||
return nil, fmt.Errorf("user not found")
|
||||
}
|
||||
if len(result.Entries) > 1 {
|
||||
conn.Close() // Close on error
|
||||
return nil, fmt.Errorf("multiple users found")
|
||||
}
|
||||
|
||||
userEntry := result.Entries[0]
|
||||
userDN := userEntry.DN
|
||||
|
||||
// Bind as the user to verify password
|
||||
err = conn.Bind(userDN, password)
|
||||
if err != nil {
|
||||
glog.V(2).Infof("LDAP user bind failed for %s: %v", username, err)
|
||||
conn.Close() // Close on error, don't return to pool
|
||||
return nil, fmt.Errorf("authentication failed: invalid credentials")
|
||||
}
|
||||
|
||||
// Rebind to service account before returning connection to pool
|
||||
// This prevents pool corruption from authenticated user binds
|
||||
if config.BindDN != "" {
|
||||
if err = conn.Bind(config.BindDN, config.BindPassword); err != nil {
|
||||
glog.V(2).Infof("LDAP rebind to service account failed: %v", err)
|
||||
conn.Close() // Close on error, don't return to pool
|
||||
return nil, fmt.Errorf("LDAP service account rebind failed after successful user authentication (check bindDN %q and its credentials): %w", config.BindDN, err)
|
||||
}
|
||||
}
|
||||
// Now safe to defer return to pool with clean service account binding
|
||||
defer p.returnConnection(conn)
|
||||
|
||||
// Build identity from LDAP attributes
|
||||
identity := &providers.ExternalIdentity{
|
||||
UserID: username,
|
||||
Email: userEntry.GetAttributeValue(config.Attributes.Email),
|
||||
DisplayName: userEntry.GetAttributeValue(config.Attributes.DisplayName),
|
||||
Groups: userEntry.GetAttributeValues(config.Attributes.Groups),
|
||||
Provider: p.name,
|
||||
Attributes: map[string]string{
|
||||
"dn": userDN,
|
||||
"uid": userEntry.GetAttributeValue(config.Attributes.UID),
|
||||
},
|
||||
}
|
||||
|
||||
// If no groups from memberOf, try group search
|
||||
if len(identity.Groups) == 0 && config.GroupFilter != "" {
|
||||
groups, err := p.searchUserGroups(conn, userDN, config)
|
||||
if err != nil {
|
||||
glog.V(2).Infof("Group search failed for %s: %v", username, err)
|
||||
} else {
|
||||
identity.Groups = groups
|
||||
}
|
||||
}
|
||||
|
||||
glog.V(2).Infof("LDAP authentication successful for user: %s, groups: %v", username, identity.Groups)
|
||||
return identity, nil
|
||||
}
|
||||
|
||||
// searchUserGroups searches for groups the user belongs to
|
||||
func (p *LDAPProvider) searchUserGroups(conn *ldap.Conn, userDN string, config *LDAPConfig) ([]string, error) {
|
||||
groupFilter := fmt.Sprintf(config.GroupFilter, ldap.EscapeFilter(userDN))
|
||||
searchRequest := ldap.NewSearchRequest(
|
||||
config.GroupBaseDN,
|
||||
ldap.ScopeWholeSubtree,
|
||||
ldap.NeverDerefAliases,
|
||||
0,
|
||||
int(config.ConnectionTimeout.Seconds()),
|
||||
false,
|
||||
groupFilter,
|
||||
[]string{"cn", "dn"},
|
||||
nil,
|
||||
)
|
||||
|
||||
result, err := conn.Search(searchRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var groups []string
|
||||
for _, entry := range result.Entries {
|
||||
cn := entry.GetAttributeValue("cn")
|
||||
if cn != "" {
|
||||
groups = append(groups, cn)
|
||||
}
|
||||
}
|
||||
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
// GetUserInfo retrieves user information by user ID
|
||||
func (p *LDAPProvider) GetUserInfo(ctx context.Context, userID string) (*providers.ExternalIdentity, error) {
|
||||
p.mu.RLock()
|
||||
if !p.initialized {
|
||||
p.mu.RUnlock()
|
||||
return nil, fmt.Errorf("LDAP provider not initialized")
|
||||
}
|
||||
config := p.config
|
||||
p.mu.RUnlock()
|
||||
|
||||
// Get connection from pool
|
||||
conn, err := p.getConnection()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Note: defer returnConnection moved to after bind
|
||||
|
||||
// Bind with service account
|
||||
if config.BindDN != "" {
|
||||
err = conn.Bind(config.BindDN, config.BindPassword)
|
||||
if err != nil {
|
||||
conn.Close() // Close on bind failure
|
||||
return nil, fmt.Errorf("LDAP service bind failed: %w", err)
|
||||
}
|
||||
}
|
||||
defer p.returnConnection(conn)
|
||||
|
||||
// Search for the user
|
||||
userFilter := fmt.Sprintf(config.UserFilter, ldap.EscapeFilter(userID))
|
||||
searchRequest := ldap.NewSearchRequest(
|
||||
config.BaseDN,
|
||||
ldap.ScopeWholeSubtree,
|
||||
ldap.NeverDerefAliases,
|
||||
1,
|
||||
int(config.ConnectionTimeout.Seconds()),
|
||||
false,
|
||||
userFilter,
|
||||
[]string{"dn", config.Attributes.Email, config.Attributes.DisplayName, config.Attributes.UID, config.Attributes.Groups},
|
||||
nil,
|
||||
)
|
||||
|
||||
result, err := conn.Search(searchRequest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("LDAP user search failed: %w", err)
|
||||
}
|
||||
|
||||
if len(result.Entries) == 0 {
|
||||
return nil, fmt.Errorf("user not found")
|
||||
}
|
||||
if len(result.Entries) > 1 {
|
||||
return nil, fmt.Errorf("multiple users found")
|
||||
}
|
||||
|
||||
userEntry := result.Entries[0]
|
||||
identity := &providers.ExternalIdentity{
|
||||
UserID: userID,
|
||||
Email: userEntry.GetAttributeValue(config.Attributes.Email),
|
||||
DisplayName: userEntry.GetAttributeValue(config.Attributes.DisplayName),
|
||||
Groups: userEntry.GetAttributeValues(config.Attributes.Groups),
|
||||
Provider: p.name,
|
||||
Attributes: map[string]string{
|
||||
"dn": userEntry.DN,
|
||||
"uid": userEntry.GetAttributeValue(config.Attributes.UID),
|
||||
},
|
||||
}
|
||||
|
||||
// If no groups from memberOf, try group search
|
||||
if len(identity.Groups) == 0 && config.GroupFilter != "" {
|
||||
groups, err := p.searchUserGroups(conn, userEntry.DN, config)
|
||||
if err != nil {
|
||||
glog.V(2).Infof("Group search failed for %s: %v", userID, err)
|
||||
} else {
|
||||
identity.Groups = groups
|
||||
}
|
||||
}
|
||||
|
||||
return identity, nil
|
||||
}
|
||||
|
||||
// ValidateToken validates credentials (username:password format) and returns claims
|
||||
func (p *LDAPProvider) ValidateToken(ctx context.Context, token string) (*providers.TokenClaims, error) {
|
||||
identity, err := p.Authenticate(ctx, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.mu.RLock()
|
||||
config := p.config
|
||||
p.mu.RUnlock()
|
||||
|
||||
// If audience is configured, validate it (consistent with OIDC approach)
|
||||
audience := p.name
|
||||
if config.Audience != "" {
|
||||
audience = config.Audience
|
||||
}
|
||||
|
||||
// Populate standard TokenClaims fields for interface compliance
|
||||
now := time.Now()
|
||||
ttl := 1 * time.Hour // Default TTL for LDAP tokens
|
||||
|
||||
return &providers.TokenClaims{
|
||||
Subject: identity.UserID,
|
||||
Issuer: p.name,
|
||||
Audience: audience,
|
||||
IssuedAt: now,
|
||||
ExpiresAt: now.Add(ttl),
|
||||
Claims: map[string]interface{}{
|
||||
"email": identity.Email,
|
||||
"name": identity.DisplayName,
|
||||
"groups": identity.Groups,
|
||||
"dn": identity.Attributes["dn"],
|
||||
"provider": p.name,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// IsInitialized returns whether the provider is initialized
|
||||
func (p *LDAPProvider) IsInitialized() bool {
|
||||
p.mu.RLock()
|
||||
defer p.mu.RUnlock()
|
||||
return p.initialized
|
||||
}
|
||||
186
weed/iam/ldap/mock_provider.go
Normal file
186
weed/iam/ldap/mock_provider.go
Normal file
@@ -0,0 +1,186 @@
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/iam/providers"
|
||||
)
|
||||
|
||||
// MockLDAPProvider is a mock implementation for testing
|
||||
// This is a standalone mock that doesn't depend on production LDAP code
|
||||
type MockLDAPProvider struct {
|
||||
name string
|
||||
initialized bool
|
||||
TestUsers map[string]*providers.ExternalIdentity
|
||||
TestCredentials map[string]string // username -> password
|
||||
}
|
||||
|
||||
// NewMockLDAPProvider creates a mock LDAP provider for testing
|
||||
func NewMockLDAPProvider(name string) *MockLDAPProvider {
|
||||
return &MockLDAPProvider{
|
||||
name: name,
|
||||
initialized: true, // Mock is always initialized
|
||||
TestUsers: make(map[string]*providers.ExternalIdentity),
|
||||
TestCredentials: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns the provider name
|
||||
func (m *MockLDAPProvider) Name() string {
|
||||
return m.name
|
||||
}
|
||||
|
||||
// Initialize initializes the mock provider (no-op for testing)
|
||||
func (m *MockLDAPProvider) Initialize(config interface{}) error {
|
||||
m.initialized = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddTestUser adds a test user with credentials
|
||||
func (m *MockLDAPProvider) AddTestUser(username, password string, identity *providers.ExternalIdentity) {
|
||||
m.TestCredentials[username] = password
|
||||
m.TestUsers[username] = identity
|
||||
}
|
||||
|
||||
// Authenticate authenticates using test data
|
||||
func (m *MockLDAPProvider) Authenticate(ctx context.Context, credentials string) (*providers.ExternalIdentity, error) {
|
||||
if !m.initialized {
|
||||
return nil, fmt.Errorf("provider not initialized")
|
||||
}
|
||||
|
||||
if credentials == "" {
|
||||
return nil, fmt.Errorf("credentials cannot be empty")
|
||||
}
|
||||
|
||||
// Parse credentials (username:password format)
|
||||
parts := strings.SplitN(credentials, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("invalid credentials format (expected username:password)")
|
||||
}
|
||||
|
||||
username, password := parts[0], parts[1]
|
||||
|
||||
// Check test credentials
|
||||
expectedPassword, userExists := m.TestCredentials[username]
|
||||
if !userExists {
|
||||
return nil, fmt.Errorf("user not found")
|
||||
}
|
||||
|
||||
if password != expectedPassword {
|
||||
return nil, fmt.Errorf("invalid credentials")
|
||||
}
|
||||
|
||||
// Return test user identity
|
||||
if identity, exists := m.TestUsers[username]; exists {
|
||||
return identity, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("user identity not found")
|
||||
}
|
||||
|
||||
// GetUserInfo returns test user info
|
||||
func (m *MockLDAPProvider) GetUserInfo(ctx context.Context, userID string) (*providers.ExternalIdentity, error) {
|
||||
if !m.initialized {
|
||||
return nil, fmt.Errorf("provider not initialized")
|
||||
}
|
||||
|
||||
if userID == "" {
|
||||
return nil, fmt.Errorf("user ID cannot be empty")
|
||||
}
|
||||
|
||||
// Check test users
|
||||
if identity, exists := m.TestUsers[userID]; exists {
|
||||
return identity, nil
|
||||
}
|
||||
|
||||
// Return default test user if not found
|
||||
return &providers.ExternalIdentity{
|
||||
UserID: userID,
|
||||
Email: userID + "@test-ldap.com",
|
||||
DisplayName: "Test LDAP User " + userID,
|
||||
Groups: []string{"test-group"},
|
||||
Provider: m.name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ValidateToken validates credentials using test data
|
||||
func (m *MockLDAPProvider) ValidateToken(ctx context.Context, token string) (*providers.TokenClaims, error) {
|
||||
if !m.initialized {
|
||||
return nil, fmt.Errorf("provider not initialized")
|
||||
}
|
||||
|
||||
if token == "" {
|
||||
return nil, fmt.Errorf("token cannot be empty")
|
||||
}
|
||||
|
||||
// Parse credentials (username:password format)
|
||||
parts := strings.SplitN(token, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("invalid token format (expected username:password)")
|
||||
}
|
||||
|
||||
username, password := parts[0], parts[1]
|
||||
|
||||
// Check test credentials
|
||||
expectedPassword, userExists := m.TestCredentials[username]
|
||||
if !userExists {
|
||||
return nil, fmt.Errorf("user not found")
|
||||
}
|
||||
|
||||
if password != expectedPassword {
|
||||
return nil, fmt.Errorf("invalid credentials")
|
||||
}
|
||||
|
||||
// Return test claims
|
||||
identity := m.TestUsers[username]
|
||||
return &providers.TokenClaims{
|
||||
Subject: username,
|
||||
Claims: map[string]interface{}{
|
||||
"ldap_dn": "CN=" + username + ",DC=test,DC=com",
|
||||
"email": identity.Email,
|
||||
"name": identity.DisplayName,
|
||||
"groups": identity.Groups,
|
||||
"provider": m.name,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SetupDefaultTestData configures common test data
|
||||
func (m *MockLDAPProvider) SetupDefaultTestData() {
|
||||
// Add default test user
|
||||
m.AddTestUser("testuser", "testpass", &providers.ExternalIdentity{
|
||||
UserID: "testuser",
|
||||
Email: "testuser@ldap-test.com",
|
||||
DisplayName: "Test LDAP User",
|
||||
Groups: []string{"developers", "users"},
|
||||
Provider: m.name,
|
||||
Attributes: map[string]string{
|
||||
"department": "Engineering",
|
||||
"location": "Test City",
|
||||
},
|
||||
})
|
||||
|
||||
// Add admin test user
|
||||
m.AddTestUser("admin", "adminpass", &providers.ExternalIdentity{
|
||||
UserID: "admin",
|
||||
Email: "admin@ldap-test.com",
|
||||
DisplayName: "LDAP Administrator",
|
||||
Groups: []string{"admins", "users"},
|
||||
Provider: m.name,
|
||||
Attributes: map[string]string{
|
||||
"department": "IT",
|
||||
"role": "administrator",
|
||||
},
|
||||
})
|
||||
|
||||
// Add readonly user
|
||||
m.AddTestUser("readonly", "readpass", &providers.ExternalIdentity{
|
||||
UserID: "readonly",
|
||||
Email: "readonly@ldap-test.com",
|
||||
DisplayName: "Read Only User",
|
||||
Groups: []string{"readonly"},
|
||||
Provider: m.name,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user