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