diff --git a/README.md b/README.md index 88fd1ff..10da1e0 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,15 @@ if err := cl.Connect(); err != nil { } ``` +### 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)) +``` + + ## Contributing 1. Create new PR from `main` branch diff --git a/adc.go b/adc.go index 914e643..de4a422 100644 --- a/adc.go +++ b/adc.go @@ -4,7 +4,6 @@ package adc import ( "crypto/tls" "errors" - "fmt" "strings" "time" @@ -19,6 +18,7 @@ const ( type Client struct { cfg *Config ldapCl ldap.Client + logger Logger } // Creates new client and populate provided config and options. @@ -29,6 +29,7 @@ func New(cfg *Config, opts ...Option) *Client { Users: DefaultUsersConfigs(), Groups: DefaultGroupsConfigs(), }, + logger: &nopLogger{}, } // Apply options @@ -42,6 +43,12 @@ func New(cfg *Config, opts ...Option) *Client { return cl } +// Client logger interface. +type Logger interface { + Debug(args ...interface{}) + Debugf(template string, args ...interface{}) +} + type Option func(*Client) // Specifies ldap client for AD client. @@ -49,6 +56,11 @@ func WithLdapClient(l ldap.Client) Option { return func(cl *Client) { cl.ldapCl = l } } +// Specifies custom logger for client. +func WithLogger(l Logger) Option { + return func(cl *Client) { cl.logger = l } +} + func (cl *Client) Config() *Config { return cl.cfg } @@ -57,7 +69,7 @@ func (cl *Client) Config() *Config { func (cl *Client) Connect() error { conn, err := cl.connect(cl.cfg.Bind) if err != nil { - return fmt.Errorf("can't connect: %s", err.Error()) + return err } cl.ldapCl = conn return nil diff --git a/group.go b/group.go index 77e21d1..c00a0ef 100644 --- a/group.go +++ b/group.go @@ -148,6 +148,7 @@ func (g *Group) MembersId() []string { return result } +// Adds provided accounts IDs to provided group members. Returns number of addedd accounts. func (cl *Client) AddGroupMembers(groupId string, membersIds ...string) (int, error) { group, err := cl.GetGroup(&GetGroupequest{Id: groupId}) if err != nil { @@ -158,27 +159,41 @@ func (cl *Client) AddGroupMembers(groupId string, membersIds ...string) (int, er } ch := make(chan string, len(membersIds)) + errCh := make(chan error, len(membersIds)) wg := &sync.WaitGroup{} + for _, id := range membersIds { wg.Add(1) - go func(userId string, ch chan<- string, wg *sync.WaitGroup) { + go func(userId string, ch chan<- string, errCh chan<- error, wg *sync.WaitGroup) { defer wg.Done() user, err := cl.GetUser(&GetUserRequest{Id: userId}) if err != nil { + errCh <- fmt.Errorf("can't get account '%s': %s", userId, err.Error()) return } if user == nil { + cl.logger.Debugf("Account '%s' being added to '%s' wasn't found", + userId, groupId) return } if user.IsGroupMember(groupId) { + cl.logger.Debugf("The adding account '%s' is already a member of the group '%s'", + userId, groupId) return } ch <- user.DN - }(id, ch, wg) + }(id, ch, errCh, wg) } wg.Wait() + close(errCh) close(ch) + for err := range errCh { + if err != nil { + return 0, err + } + } + var toAdd []string for dn := range ch { toAdd = append(toAdd, dn) @@ -188,6 +203,10 @@ func (cl *Client) AddGroupMembers(groupId string, membersIds ...string) (int, er } newMembers := popAddGroupMembers(group, toAdd) + + cl.logger.Debugf("Adding new group members to '%s'; Old count: %d; New count: %d", + groupId, len(group.MembersId()), len(newMembers)) + if err := cl.updateAttribute(group.DN, "member", newMembers); err != nil { return 0, err } @@ -205,6 +224,7 @@ func popAddGroupMembers(g *Group, toAdd []string) []string { return result } +// Deletes provided accounts IDs from provided group members. Returns number of deleted from group members. func (cl *Client) DeleteGroupMembers(groupId string, membersIds ...string) (int, error) { group, err := cl.GetGroup(&GetGroupequest{Id: groupId}) if err != nil { @@ -215,27 +235,41 @@ func (cl *Client) DeleteGroupMembers(groupId string, membersIds ...string) (int, } ch := make(chan string, len(membersIds)) + errCh := make(chan error, len(membersIds)) wg := &sync.WaitGroup{} + for _, id := range membersIds { wg.Add(1) - go func(userId string, ch chan<- string, wg *sync.WaitGroup) { + go func(userId string, ch chan<- string, errCh chan<- error, wg *sync.WaitGroup) { defer wg.Done() user, err := cl.GetUser(&GetUserRequest{Id: userId}) if err != nil { + errCh <- fmt.Errorf("can't get account '%s': %s", userId, err.Error()) return } if user == nil { + cl.logger.Debugf("Account '%s' being deleted from '%s' wasn't found", + userId, groupId) return } if !user.IsGroupMember(groupId) { + cl.logger.Debugf("The deleting account '%s' already isn't a member of the group '%s'", + userId, groupId) return } ch <- user.DN - }(id, ch, wg) + }(id, ch, errCh, wg) } wg.Wait() + close(errCh) close(ch) + for err := range errCh { + if err != nil { + return 0, err + } + } + var toDel []string for dn := range ch { toDel = append(toDel, dn) @@ -245,6 +279,10 @@ func (cl *Client) DeleteGroupMembers(groupId string, membersIds ...string) (int, } newMembers := popDelGroupMembers(group, toDel) + + cl.logger.Debugf("Deleting members from group '%s'; Old count: %d; New count: %d", + groupId, len(group.MembersId()), len(newMembers)) + if err := cl.updateAttribute(group.DN, "member", newMembers); err != nil { return 0, err } diff --git a/group_test.go b/group_test.go index 7c30588..fd486d1 100644 --- a/group_test.go +++ b/group_test.go @@ -108,14 +108,16 @@ func Test_AddGroupMembers(t *testing.T) { added, err := cl.AddGroupMembers("group1", "userFake") require.NoError(t, err) require.Equal(t, 0, added) + // Error user - added, err = cl.AddGroupMembers("group1", "user2") - require.NoError(t, err) - require.Equal(t, 0, added) + _, err = cl.AddGroupMembers("group1", "user2") + require.Error(t, err) + // Already member user added, err = cl.AddGroupMembers("group1", "user1") require.NoError(t, err) require.Equal(t, 0, added) + // Ok user added, err = cl.AddGroupMembers("group1", "user3") require.NoError(t, err) @@ -152,14 +154,16 @@ func Test_DeleteGroupMembers(t *testing.T) { deleted, err := cl.DeleteGroupMembers("group1", "userFake") require.NoError(t, err) require.Equal(t, 0, deleted) + // Error user - deleted, err = cl.DeleteGroupMembers("group1", "user2") - require.NoError(t, err) - require.Equal(t, 0, deleted) + _, err = cl.DeleteGroupMembers("group1", "user2") + require.Error(t, err) + // Already not member user deleted, err = cl.DeleteGroupMembers("group1", "user3") require.NoError(t, err) require.Equal(t, 0, deleted) + // Ok user deleted, err = cl.DeleteGroupMembers("group1", "user1") require.NoError(t, err) diff --git a/mock.go b/mock.go index 6cf2204..5206747 100644 --- a/mock.go +++ b/mock.go @@ -9,6 +9,12 @@ import ( "github.com/go-ldap/ldap/v3" ) +// Dummy not operational logger. +type nopLogger struct{} + +func (l *nopLogger) Debug(args ...interface{}) {} +func (l *nopLogger) Debugf(template string, args ...interface{}) {} + // Mock client. Implements ldap client interface. type mockClient struct { }