Skip to content

Commit

Permalink
Merge pull request #24 from dlampsi/feature/refact
Browse files Browse the repository at this point in the history
Refactoring and improvements
  • Loading branch information
dlampsi committed Apr 30, 2024
2 parents 8643d19 + 65b9ac3 commit e263b42
Show file tree
Hide file tree
Showing 14 changed files with 882 additions and 774 deletions.
1 change: 1 addition & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ jobs:
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./coverage.out
fail_ci_if_error: true
8 changes: 3 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@
# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
# vendor/
# Output of the go coverage tool
coverage.out
coverage.html
28 changes: 5 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,18 +71,12 @@ fmt.Printf("Deleted %d users from group members", deleted)

```

### Default config file

By default client initializes with default config file. You can find it in [DefaultUsersConfigs()](config.go) func.

### Check auth by creds
### Custom logger

Custom check authentification for provided credentials:
You can specifiy custom logger for client. Logger must implement `Logger` interface. Provide logger during client init:

```go
if err := cl.CheckAuthByDN("CN=user,DC=company,DC=com", "password"); err != nil {
// Handle bad credentials error
}
cl := New(cfg, adc.WithLogger(myCustomLogger))
```

### Custom search base
Expand Down Expand Up @@ -112,7 +106,7 @@ if err := cl.Connect(); err != nil {
You can parse custom attributes to client config to fetch those attributes during users or groups fetch:
```go
// Append new attributes to existsing user attributes
cl.Config().AppendUsesAttributes("manager")
cl.Config.AppendUsesAttributes("manager")

// Search for a user
user, err := cl.GetUser(adc.GetUserArgs{Id:"userId"})
Expand Down Expand Up @@ -192,30 +186,18 @@ fmt.Println(user)

**Note** that provided `Filter` argument int `GetUserArgs` overwrites `Id` and `Dn` arguments usage.


### Custom logger

You can specifiy custom logger for client. Logger must implement `Logger` interface. Provide logger during client init:

```go
cl := New(cfg, adc.WithLogger(myCustomLogger))
```


### Reconnect

Client has reconnect method, that validates connection to server and reconnects to it with provided ticker interval and retries attempts count.

Exxample for recconect each 5 secconds with 24 retrie attempts:

```go
err := cl.Reconnect(nctx, time.NewTicker(5*time.Second), 24)
if err != nil {
if err := cl.Reconnect(ctx, time.NewTicker(5*time.Second), 24); err != nil {
// Handle error
}
```


## Contributing

1. Create new PR from `main` branch
Expand Down
104 changes: 36 additions & 68 deletions adc.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,115 +12,82 @@ import (
"github.com/go-ldap/ldap/v3"
)

const (
defaultClientTimeout = 10 * time.Second
)

// Active Direcotry client.
type Client struct {
cfg *Config
ldapCl ldap.Client
logger Logger
useMock bool
Config *Config
ldap ldap.Client
logger Logger
mockMode bool
}

// Creates new client and populate provided config and options.
func New(cfg *Config, opts ...Option) *Client {
cl := &Client{
cfg: &Config{
Timeout: defaultClientTimeout,
Users: DefaultUsersConfigs(),
Groups: DefaultGroupsConfigs(),
},
logger: &nopLogger{},
Config: populateConfig(cfg),
logger: newNopLogger(),
}

// Apply options
for _, opt := range opts {
opt(cl)
}

// Populate optional config
cl.popConfig(cfg)

return cl
}

// Client logger interface.
type Logger interface {
Debug(args ...interface{})
Debugf(template string, args ...interface{})
}

type Option func(*Client)

// Specifies custom logger for client.
func WithLogger(l Logger) Option {
return func(cl *Client) { cl.logger = l }
}

// Enables mock ldap interface for client
func withMock() Option {
return func(cl *Client) { cl.useMock = true }
}

func (cl *Client) Config() *Config {
return cl.cfg
}

// Connects to AD server and store connection into client.
func (cl *Client) Connect() error {
conn, err := cl.connect(cl.cfg.Bind)
conn, err := cl.connect()
if err != nil {
return err
return fmt.Errorf("Failed to connect: %w", err)
}
cl.ldapCl = conn
return nil
}

// Connects and bind to LDAP server by provided bind account.
func (cl *Client) connect(bind *BindAccount) (ldap.Client, error) {
conn, err := cl.dial()
if err != nil {
return nil, err
}
if bind != nil {
if err := conn.Bind(bind.DN, bind.Password); err != nil {
return nil, err
if cl.Config.Bind != nil {
if err := conn.Bind(cl.Config.Bind.DN, cl.Config.Bind.Password); err != nil {
return fmt.Errorf("Failed to bind: %w", err)
}
}
return conn, nil

cl.ldap = conn

return nil
}

// Dials ldap server provided in client configuration.
func (cl *Client) dial() (ldap.Client, error) {
if cl.useMock {
return &mockClient{}, nil
func (cl *Client) connect() (ldap.Client, error) {
if cl.mockMode {
return mockConnection()
}
var opts []ldap.DialOpt
if strings.HasPrefix(cl.cfg.URL, "ldaps://") {
opts = append(opts, ldap.DialWithTLSConfig(&tls.Config{InsecureSkipVerify: cl.cfg.InsecureTLS}))

var dialOpts []ldap.DialOpt
if strings.HasPrefix(cl.Config.URL, "ldaps://") {
dialOpts = append(
dialOpts, ldap.DialWithTLSConfig(&tls.Config{InsecureSkipVerify: cl.Config.InsecureTLS}),
)
}
return ldap.DialURL(cl.cfg.URL, opts...)
return ldap.DialURL(cl.Config.URL, dialOpts...)
}

// Closes connection to AD.
func (cl *Client) Disconnect() error {
if cl.ldapCl == nil {
if cl.ldap == nil {
return nil
}
return cl.ldapCl.Close()
return cl.ldap.Close()
}

// Checks connections to AD and tries to reconnect if the connection is lost.
func (cl *Client) Reconnect(ctx context.Context, tickerDuration time.Duration, maxAttempts int) error {
_, connErr := cl.searchEntry(&ldap.SearchRequest{
BaseDN: cl.cfg.SearchBase,
BaseDN: cl.Config.SearchBase,
Scope: ldap.ScopeWholeSubtree,
DerefAliases: ldap.NeverDerefAliases,
TimeLimit: int(cl.cfg.Timeout.Seconds()),
Filter: fmt.Sprintf(cl.cfg.Users.FilterByDn, ldap.EscapeFilter(cl.cfg.Bind.DN)),
Attributes: []string{cl.cfg.Users.IdAttribute},
TimeLimit: int(cl.Config.Timeout.Seconds()),
Filter: fmt.Sprintf(cl.Config.Users.FilterByDn, ldap.EscapeFilter(cl.Config.Bind.DN)),
Attributes: []string{cl.Config.Users.IdAttribute},
})
if connErr == nil {
return nil
Expand Down Expand Up @@ -164,7 +131,7 @@ func (cl *Client) Reconnect(ctx context.Context, tickerDuration time.Duration, m
// Returns nil if no entries found.
// Returns 'ErrTooManyEntriesFound' error if entries more that one.
func (cl *Client) searchEntry(req *ldap.SearchRequest) (*ldap.Entry, error) {
result, err := cl.ldapCl.Search(req)
result, err := cl.ldap.Search(req)
if err != nil {
return nil, err
}
Expand All @@ -179,7 +146,7 @@ func (cl *Client) searchEntry(req *ldap.SearchRequest) (*ldap.Entry, error) {

// SearchEntries Perfroms search for ldap entries.
func (cl *Client) searchEntries(req *ldap.SearchRequest) ([]*ldap.Entry, error) {
result, err := cl.ldapCl.Search(req)
result, err := cl.ldap.Search(req)
if err != nil {
return nil, err
}
Expand All @@ -190,12 +157,13 @@ func (cl *Client) searchEntries(req *ldap.SearchRequest) ([]*ldap.Entry, error)
func (cl *Client) updateAttribute(dn string, attribute string, values []string) error {
mr := ldap.NewModifyRequest(dn, nil)
mr.Replace(attribute, values)
return cl.ldapCl.Modify(mr)
return cl.ldap.Modify(mr)
}

// Tries to authorise in AcitveDirecotry by provided DN and password and return error if failed.
// Use this method to check if user can be authenticated in AD.
func (cl *Client) CheckAuthByDN(dn, password string) error {
conn, err := cl.dial()
conn, err := cl.connect()
if err != nil {
return err
}
Expand Down
Loading

0 comments on commit e263b42

Please sign in to comment.