blob: 8e7e2efe83334f4980997f08d056f12aad781fa3 [file] [log] [blame]
khenaidoo59ce9dd2019-11-11 13:05:32 -05001// Copyright 2015 The etcd Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package client
16
17import (
18 "bytes"
19 "context"
20 "encoding/json"
21 "net/http"
22 "net/url"
23 "path"
24)
25
26var (
27 defaultV2AuthPrefix = "/v2/auth"
28)
29
30type User struct {
31 User string `json:"user"`
32 Password string `json:"password,omitempty"`
33 Roles []string `json:"roles"`
34 Grant []string `json:"grant,omitempty"`
35 Revoke []string `json:"revoke,omitempty"`
36}
37
38// userListEntry is the user representation given by the server for ListUsers
39type userListEntry struct {
40 User string `json:"user"`
41 Roles []Role `json:"roles"`
42}
43
44type UserRoles struct {
45 User string `json:"user"`
46 Roles []Role `json:"roles"`
47}
48
49func v2AuthURL(ep url.URL, action string, name string) *url.URL {
50 if name != "" {
51 ep.Path = path.Join(ep.Path, defaultV2AuthPrefix, action, name)
52 return &ep
53 }
54 ep.Path = path.Join(ep.Path, defaultV2AuthPrefix, action)
55 return &ep
56}
57
58// NewAuthAPI constructs a new AuthAPI that uses HTTP to
59// interact with etcd's general auth features.
60func NewAuthAPI(c Client) AuthAPI {
61 return &httpAuthAPI{
62 client: c,
63 }
64}
65
66type AuthAPI interface {
67 // Enable auth.
68 Enable(ctx context.Context) error
69
70 // Disable auth.
71 Disable(ctx context.Context) error
72}
73
74type httpAuthAPI struct {
75 client httpClient
76}
77
78func (s *httpAuthAPI) Enable(ctx context.Context) error {
79 return s.enableDisable(ctx, &authAPIAction{"PUT"})
80}
81
82func (s *httpAuthAPI) Disable(ctx context.Context) error {
83 return s.enableDisable(ctx, &authAPIAction{"DELETE"})
84}
85
86func (s *httpAuthAPI) enableDisable(ctx context.Context, req httpAction) error {
87 resp, body, err := s.client.Do(ctx, req)
88 if err != nil {
89 return err
90 }
91 if err = assertStatusCode(resp.StatusCode, http.StatusOK, http.StatusCreated); err != nil {
92 var sec authError
93 err = json.Unmarshal(body, &sec)
94 if err != nil {
95 return err
96 }
97 return sec
98 }
99 return nil
100}
101
102type authAPIAction struct {
103 verb string
104}
105
106func (l *authAPIAction) HTTPRequest(ep url.URL) *http.Request {
107 u := v2AuthURL(ep, "enable", "")
108 req, _ := http.NewRequest(l.verb, u.String(), nil)
109 return req
110}
111
112type authError struct {
113 Message string `json:"message"`
114 Code int `json:"-"`
115}
116
117func (e authError) Error() string {
118 return e.Message
119}
120
121// NewAuthUserAPI constructs a new AuthUserAPI that uses HTTP to
122// interact with etcd's user creation and modification features.
123func NewAuthUserAPI(c Client) AuthUserAPI {
124 return &httpAuthUserAPI{
125 client: c,
126 }
127}
128
129type AuthUserAPI interface {
130 // AddUser adds a user.
131 AddUser(ctx context.Context, username string, password string) error
132
133 // RemoveUser removes a user.
134 RemoveUser(ctx context.Context, username string) error
135
136 // GetUser retrieves user details.
137 GetUser(ctx context.Context, username string) (*User, error)
138
139 // GrantUser grants a user some permission roles.
140 GrantUser(ctx context.Context, username string, roles []string) (*User, error)
141
142 // RevokeUser revokes some permission roles from a user.
143 RevokeUser(ctx context.Context, username string, roles []string) (*User, error)
144
145 // ChangePassword changes the user's password.
146 ChangePassword(ctx context.Context, username string, password string) (*User, error)
147
148 // ListUsers lists the users.
149 ListUsers(ctx context.Context) ([]string, error)
150}
151
152type httpAuthUserAPI struct {
153 client httpClient
154}
155
156type authUserAPIAction struct {
157 verb string
158 username string
159 user *User
160}
161
162type authUserAPIList struct{}
163
164func (list *authUserAPIList) HTTPRequest(ep url.URL) *http.Request {
165 u := v2AuthURL(ep, "users", "")
166 req, _ := http.NewRequest("GET", u.String(), nil)
167 req.Header.Set("Content-Type", "application/json")
168 return req
169}
170
171func (l *authUserAPIAction) HTTPRequest(ep url.URL) *http.Request {
172 u := v2AuthURL(ep, "users", l.username)
173 if l.user == nil {
174 req, _ := http.NewRequest(l.verb, u.String(), nil)
175 return req
176 }
177 b, err := json.Marshal(l.user)
178 if err != nil {
179 panic(err)
180 }
181 body := bytes.NewReader(b)
182 req, _ := http.NewRequest(l.verb, u.String(), body)
183 req.Header.Set("Content-Type", "application/json")
184 return req
185}
186
187func (u *httpAuthUserAPI) ListUsers(ctx context.Context) ([]string, error) {
188 resp, body, err := u.client.Do(ctx, &authUserAPIList{})
189 if err != nil {
190 return nil, err
191 }
192 if err = assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
193 var sec authError
194 err = json.Unmarshal(body, &sec)
195 if err != nil {
196 return nil, err
197 }
198 return nil, sec
199 }
200
201 var userList struct {
202 Users []userListEntry `json:"users"`
203 }
204
205 if err = json.Unmarshal(body, &userList); err != nil {
206 return nil, err
207 }
208
209 ret := make([]string, 0, len(userList.Users))
210 for _, u := range userList.Users {
211 ret = append(ret, u.User)
212 }
213 return ret, nil
214}
215
216func (u *httpAuthUserAPI) AddUser(ctx context.Context, username string, password string) error {
217 user := &User{
218 User: username,
219 Password: password,
220 }
221 return u.addRemoveUser(ctx, &authUserAPIAction{
222 verb: "PUT",
223 username: username,
224 user: user,
225 })
226}
227
228func (u *httpAuthUserAPI) RemoveUser(ctx context.Context, username string) error {
229 return u.addRemoveUser(ctx, &authUserAPIAction{
230 verb: "DELETE",
231 username: username,
232 })
233}
234
235func (u *httpAuthUserAPI) addRemoveUser(ctx context.Context, req *authUserAPIAction) error {
236 resp, body, err := u.client.Do(ctx, req)
237 if err != nil {
238 return err
239 }
240 if err = assertStatusCode(resp.StatusCode, http.StatusOK, http.StatusCreated); err != nil {
241 var sec authError
242 err = json.Unmarshal(body, &sec)
243 if err != nil {
244 return err
245 }
246 return sec
247 }
248 return nil
249}
250
251func (u *httpAuthUserAPI) GetUser(ctx context.Context, username string) (*User, error) {
252 return u.modUser(ctx, &authUserAPIAction{
253 verb: "GET",
254 username: username,
255 })
256}
257
258func (u *httpAuthUserAPI) GrantUser(ctx context.Context, username string, roles []string) (*User, error) {
259 user := &User{
260 User: username,
261 Grant: roles,
262 }
263 return u.modUser(ctx, &authUserAPIAction{
264 verb: "PUT",
265 username: username,
266 user: user,
267 })
268}
269
270func (u *httpAuthUserAPI) RevokeUser(ctx context.Context, username string, roles []string) (*User, error) {
271 user := &User{
272 User: username,
273 Revoke: roles,
274 }
275 return u.modUser(ctx, &authUserAPIAction{
276 verb: "PUT",
277 username: username,
278 user: user,
279 })
280}
281
282func (u *httpAuthUserAPI) ChangePassword(ctx context.Context, username string, password string) (*User, error) {
283 user := &User{
284 User: username,
285 Password: password,
286 }
287 return u.modUser(ctx, &authUserAPIAction{
288 verb: "PUT",
289 username: username,
290 user: user,
291 })
292}
293
294func (u *httpAuthUserAPI) modUser(ctx context.Context, req *authUserAPIAction) (*User, error) {
295 resp, body, err := u.client.Do(ctx, req)
296 if err != nil {
297 return nil, err
298 }
299 if err = assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
300 var sec authError
301 err = json.Unmarshal(body, &sec)
302 if err != nil {
303 return nil, err
304 }
305 return nil, sec
306 }
307 var user User
308 if err = json.Unmarshal(body, &user); err != nil {
309 var userR UserRoles
310 if urerr := json.Unmarshal(body, &userR); urerr != nil {
311 return nil, err
312 }
313 user.User = userR.User
314 for _, r := range userR.Roles {
315 user.Roles = append(user.Roles, r.Role)
316 }
317 }
318 return &user, nil
319}