1package radius
2
3import (
4	"context"
5	"fmt"
6	"strings"
7
8	"github.com/hashicorp/vault/sdk/framework"
9	"github.com/hashicorp/vault/sdk/helper/policyutil"
10	"github.com/hashicorp/vault/sdk/logical"
11)
12
13func pathUsersList(b *backend) *framework.Path {
14	return &framework.Path{
15		Pattern: "users/?$",
16
17		Callbacks: map[logical.Operation]framework.OperationFunc{
18			logical.ListOperation: b.pathUserList,
19		},
20
21		HelpSynopsis:    pathUserHelpSyn,
22		HelpDescription: pathUserHelpDesc,
23	}
24}
25
26func pathUsers(b *backend) *framework.Path {
27	return &framework.Path{
28		Pattern: `users/(?P<name>.+)`,
29		Fields: map[string]*framework.FieldSchema{
30			"name": &framework.FieldSchema{
31				Type:        framework.TypeString,
32				Description: "Name of the RADIUS user.",
33			},
34
35			"policies": &framework.FieldSchema{
36				Type:        framework.TypeCommaStringSlice,
37				Description: "Comma-separated list of policies associated to the user.",
38			},
39		},
40
41		Callbacks: map[logical.Operation]framework.OperationFunc{
42			logical.DeleteOperation: b.pathUserDelete,
43			logical.ReadOperation:   b.pathUserRead,
44			logical.UpdateOperation: b.pathUserWrite,
45			logical.CreateOperation: b.pathUserWrite,
46		},
47
48		ExistenceCheck: b.userExistenceCheck,
49
50		HelpSynopsis:    pathUserHelpSyn,
51		HelpDescription: pathUserHelpDesc,
52	}
53}
54
55func (b *backend) userExistenceCheck(ctx context.Context, req *logical.Request, data *framework.FieldData) (bool, error) {
56	userEntry, err := b.user(ctx, req.Storage, data.Get("name").(string))
57	if err != nil {
58		return false, err
59	}
60
61	return userEntry != nil, nil
62}
63
64func (b *backend) user(ctx context.Context, s logical.Storage, username string) (*UserEntry, error) {
65	if username == "" {
66		return nil, fmt.Errorf("missing username")
67	}
68
69	entry, err := s.Get(ctx, "user/"+strings.ToLower(username))
70	if err != nil {
71		return nil, err
72	}
73	if entry == nil {
74		return nil, nil
75	}
76
77	var result UserEntry
78	if err := entry.DecodeJSON(&result); err != nil {
79		return nil, err
80	}
81
82	return &result, nil
83}
84
85func (b *backend) pathUserDelete(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
86	err := req.Storage.Delete(ctx, "user/"+d.Get("name").(string))
87	if err != nil {
88		return nil, err
89	}
90
91	return nil, nil
92}
93
94func (b *backend) pathUserRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
95	user, err := b.user(ctx, req.Storage, d.Get("name").(string))
96	if err != nil {
97		return nil, err
98	}
99	if user == nil {
100		return nil, nil
101	}
102
103	return &logical.Response{
104		Data: map[string]interface{}{
105			"policies": user.Policies,
106		},
107	}, nil
108}
109
110func (b *backend) pathUserWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
111
112	var policies = policyutil.ParsePolicies(d.Get("policies"))
113	for _, policy := range policies {
114		if policy == "root" {
115			return logical.ErrorResponse("root policy cannot be granted by an auth method"), nil
116		}
117	}
118
119	// Store it
120	entry, err := logical.StorageEntryJSON("user/"+d.Get("name").(string), &UserEntry{
121		Policies: policies,
122	})
123	if err != nil {
124		return nil, err
125	}
126	if err := req.Storage.Put(ctx, entry); err != nil {
127		return nil, err
128	}
129
130	return nil, nil
131}
132
133func (b *backend) pathUserList(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
134	users, err := req.Storage.List(ctx, "user/")
135	if err != nil {
136		return nil, err
137	}
138	return logical.ListResponse(users), nil
139}
140
141type UserEntry struct {
142	Policies []string
143}
144
145const pathUserHelpSyn = `
146Manage users allowed to authenticate.
147`
148
149const pathUserHelpDesc = `
150This endpoint allows you to create, read, update, and delete configuration
151for RADIUS users that are allowed to authenticate, and associate policies to
152them.
153
154Deleting a user will not revoke auth for prior authenticated users.
155To do this, do a revoke token by path on "auth/radius/login/<username>"
156for the usernames you want revoked.
157`
158