forked from nerney/dappy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
dappy.go
115 lines (101 loc) · 2.76 KB
/
dappy.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
// Package dappy provides an ldap client for simple ldap authentication.
package dappy
import (
"errors"
"fmt"
"net"
"time"
"gopkg.in/ldap.v3"
)
// Client interface performs ldap auth operation
type Client interface {
Auth(username, password string) error
}
// Config to provide a dappy client.
// All fields are required, except for Filter.
type Config struct {
BaseDN string // base directory, ex. "CN=Users,DC=Company"
ROUser User // the read-only user for initial bind
Host string // the ldap host and port, ex. "ldap.directory.com:389"
Filter string // defaults to "sAMAccountName" for AD
}
// User holds the name and pass required for initial read-only bind.
type User struct {
Name string
Pass string
}
// local struct for implementing Client interface
type client struct {
Config
}
// Auth implementation for the Client interface
func (c client) Auth(username, password string) error {
// establish connection
conn, err := connect(c.Host)
if err != nil {
return err
}
defer conn.Close()
// perform initial read-only bind
if err = conn.Bind(c.ROUser.Name, c.ROUser.Pass); err != nil {
return err
}
// find the user attempting to login
results, err := conn.Search(ldap.NewSearchRequest(
c.BaseDN, ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0, 0, false, fmt.Sprintf("(%v=%v)", c.Filter, username),
[]string{}, nil,
))
if err != nil {
return err
}
if len(results.Entries) < 1 {
return errors.New("not found")
}
// attempt auth
return conn.Bind(results.Entries[0].DN, password)
}
// New dappy client with the provided config
// If the configuration provided is invalid,
// or dappy is unable to connect with the config
// provided, an error will be returned
func New(config Config) (Client, error) {
config, err := validateConfig(config)
if err != nil {
return nil, err
}
c := client{config}
conn, err := connect(c.Host) // test connection
if err != nil {
return nil, err
}
if err = conn.Bind(c.ROUser.Name, c.ROUser.Pass); err != nil {
return nil, err
}
conn.Close()
return c, err
}
// Helper functions
// establishes a connection with an ldap host
// (the caller is expected to Close the connection when finished)
func connect(host string) (*ldap.Conn, error) {
c, err := net.DialTimeout("tcp", host, time.Second*8)
if err != nil {
return nil, err
}
conn := ldap.NewConn(c, false)
conn.Start()
return conn, nil
}
// validates that all required fields were provided
// handles default value for Filter
func validateConfig(config Config) (Config, error) {
if config.BaseDN == "" || config.Host == "" || config.ROUser.Name == "" || config.ROUser.Pass == "" {
return Config{}, errors.New("[CONFIG] The config provided could not be validated")
}
if config.Filter == "" {
config.Filter = "sAMAccountName"
}
return config, nil
}