1package ldap
2
3import (
4	"context"
5	"fmt"
6
7	"github.com/hashicorp/vault/sdk/framework"
8	"github.com/hashicorp/vault/sdk/helper/cidrutil"
9	"github.com/hashicorp/vault/sdk/helper/policyutil"
10	"github.com/hashicorp/vault/sdk/logical"
11)
12
13func pathLogin(b *backend) *framework.Path {
14	return &framework.Path{
15		Pattern: `login/(?P<username>.+)`,
16		Fields: map[string]*framework.FieldSchema{
17			"username": {
18				Type:        framework.TypeString,
19				Description: "DN (distinguished name) to be used for login.",
20			},
21
22			"password": {
23				Type:        framework.TypeString,
24				Description: "Password for this user.",
25			},
26		},
27
28		Callbacks: map[logical.Operation]framework.OperationFunc{
29			logical.UpdateOperation:         b.pathLogin,
30			logical.AliasLookaheadOperation: b.pathLoginAliasLookahead,
31		},
32
33		HelpSynopsis:    pathLoginSyn,
34		HelpDescription: pathLoginDesc,
35	}
36}
37
38func (b *backend) pathLoginAliasLookahead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
39	username := d.Get("username").(string)
40	if username == "" {
41		return nil, fmt.Errorf("missing username")
42	}
43
44	return &logical.Response{
45		Auth: &logical.Auth{
46			Alias: &logical.Alias{
47				Name: username,
48			},
49		},
50	}, nil
51}
52
53func (b *backend) pathLogin(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
54	cfg, err := b.Config(ctx, req)
55	if err != nil {
56		return nil, err
57	}
58	if cfg == nil {
59		return logical.ErrorResponse("auth method not configured"), nil
60	}
61
62	// Check for a CIDR match.
63	if len(cfg.TokenBoundCIDRs) > 0 {
64		if req.Connection == nil {
65			b.Logger().Warn("token bound CIDRs found but no connection information available for validation")
66			return nil, logical.ErrPermissionDenied
67		}
68		if !cidrutil.RemoteAddrIsOk(req.Connection.RemoteAddr, cfg.TokenBoundCIDRs) {
69			return nil, logical.ErrPermissionDenied
70		}
71	}
72
73	username := d.Get("username").(string)
74	password := d.Get("password").(string)
75
76	policies, resp, groupNames, err := b.Login(ctx, req, username, password)
77	// Handle an internal error
78	if err != nil {
79		return nil, err
80	}
81	if resp != nil {
82		// Handle a logical error
83		if resp.IsError() {
84			return resp, nil
85		}
86	} else {
87		resp = &logical.Response{}
88	}
89
90	auth := &logical.Auth{
91		Metadata: map[string]string{
92			"username": username,
93		},
94		InternalData: map[string]interface{}{
95			"password": password,
96		},
97		DisplayName: username,
98		Alias: &logical.Alias{
99			Name: username,
100		},
101	}
102
103	cfg.PopulateTokenAuth(auth)
104
105	// Add in configured policies from mappings
106	if len(policies) > 0 {
107		auth.Policies = append(auth.Policies, policies...)
108	}
109
110	resp.Auth = auth
111
112	for _, groupName := range groupNames {
113		if groupName == "" {
114			continue
115		}
116		resp.Auth.GroupAliases = append(resp.Auth.GroupAliases, &logical.Alias{
117			Name: groupName,
118		})
119	}
120	return resp, nil
121}
122
123func (b *backend) pathLoginRenew(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
124	cfg, err := b.Config(ctx, req)
125	if err != nil {
126		return nil, err
127	}
128	if cfg == nil {
129		return logical.ErrorResponse("auth method not configured"), nil
130	}
131
132	username := req.Auth.Metadata["username"]
133	password := req.Auth.InternalData["password"].(string)
134
135	loginPolicies, resp, groupNames, err := b.Login(ctx, req, username, password)
136	if err != nil || (resp != nil && resp.IsError()) {
137		return resp, err
138	}
139
140	finalPolicies := cfg.TokenPolicies
141	if len(loginPolicies) > 0 {
142		finalPolicies = append(finalPolicies, loginPolicies...)
143	}
144
145	if !policyutil.EquivalentPolicies(finalPolicies, req.Auth.TokenPolicies) {
146		return nil, fmt.Errorf("policies have changed, not renewing")
147	}
148
149	resp.Auth = req.Auth
150	resp.Auth.Period = cfg.TokenPeriod
151	resp.Auth.TTL = cfg.TokenTTL
152	resp.Auth.MaxTTL = cfg.TokenMaxTTL
153
154	// Remove old aliases
155	resp.Auth.GroupAliases = nil
156
157	for _, groupName := range groupNames {
158		resp.Auth.GroupAliases = append(resp.Auth.GroupAliases, &logical.Alias{
159			Name: groupName,
160		})
161	}
162
163	return resp, nil
164}
165
166const pathLoginSyn = `
167Log in with a username and password.
168`
169
170const pathLoginDesc = `
171This endpoint authenticates using a username and password. Please be sure to
172read the note on escaping from the path-help for the 'config' endpoint.
173`
174