aboutsummaryrefslogtreecommitdiff
path: root/auth_ldap.go
blob: f5c7ada723f9143ca6c27aaaae5a5431f82bf983 (plain) (blame)
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package main

import (
	"errors"
	"fmt"
	"net/http"

	"github.com/go-ldap/ldap/v3"
)

/* Check credentials against LDAP */
type LdapPreAuth struct {
	WithConfig      *Config
	OnWrongPassword ErrorHandler
	OnFailure       ErrorHandler
	OnCreds         CredsHandler
}

func (l LdapPreAuth) WithCreds(username, password string) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		var e *LdapWrongPasswordError

		access_key, secret_key, err := LdapGetS3(l.WithConfig, username, password)

		if err == nil {
			l.OnCreds.WithCreds(access_key, secret_key).ServeHTTP(w, r)
		} else if errors.As(err, &e) {
			l.OnWrongPassword.WithError(err).ServeHTTP(w, r)
		} else {
			l.OnFailure.WithError(e).ServeHTTP(w, r)
		}
	})
}

/**
 * Private logic
 */

type ldapConnector struct {
	conn   *ldap.Conn
	config *Config
	userDn string
}

type LdapError struct {
	Username string
	Err      error
}

func (e *LdapError) Error() string { return "ldap error for " + e.Username + ": " + e.Err.Error() }

type LdapWrongPasswordError struct{ LdapError }

func LdapGetS3(c *Config, username, password string) (access_key, secret_key string, werr error) {
	// 1. Connect to the server
	conn, err := ldapConnect(c)
	if err != nil {
		werr = &LdapError{username, err}
		return
	}
	defer conn.Close()

	// 2. Authenticate with provided credentials
	// @FIXME we should better check the error, it could also be due to an LDAP error
	err = conn.auth(username, password)
	if err != nil {
		werr = &LdapWrongPasswordError{LdapError{username, err}}
		return
	}

	// 3. Fetch user's profile
	profile, err := conn.profile()
	if err != nil {
		werr = &LdapError{username, err}
		return
	}

	// 4. Basic checks upon users' attributes
	access_key = profile.GetAttributeValue("garage_s3_access_key")
	secret_key = profile.GetAttributeValue("garage_s3_secret_key")
	if access_key == "" || secret_key == "" {
		err = errors.New(fmt.Sprintf("Either access key or secret key is missing in LDAP for %s", conn.userDn))
		werr = &LdapError{username, err}
		return
	}

	// 5. Send fetched credentials to the next middleware
	return
}

func ldapConnect(c *Config) (ldapConnector, error) {
	ldapSock, err := ldap.Dial("tcp", c.LdapServer)
	if err != nil {
		return ldapConnector{}, err
	}

	return ldapConnector{
		conn:   ldapSock,
		config: c,
	}, nil
}

func (l *ldapConnector) auth(username, password string) error {
	l.userDn = fmt.Sprintf("%s=%s,%s", l.config.UserNameAttr, username, l.config.UserBaseDN)
	return l.conn.Bind(l.userDn, password)
}

func (l *ldapConnector) profile() (*ldap.Entry, error) {
	searchRequest := ldap.NewSearchRequest(
		l.userDn,
		ldap.ScopeBaseObject,
		ldap.NeverDerefAliases,
		0,
		0,
		false,
		"(objectClass=*)",
		[]string{"garage_s3_access_key", "garage_s3_secret_key"},
		nil)

	sr, err := l.conn.Search(searchRequest)
	if err != nil {
		return nil, err
	}

	if len(sr.Entries) != 1 {
		return nil, errors.New(fmt.Sprintf("Wrong number of LDAP entries, expected 1, got %d", len(sr.Entries)))
	}

	return sr.Entries[0], nil
}

func (l *ldapConnector) Close() {
	if l.conn != nil {
		l.conn.Close()
		l.conn = nil
	}
}