| // Copyright 2015 The etcd Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package client |
| |
| import ( |
| "bytes" |
| "context" |
| "encoding/json" |
| "net/http" |
| "net/url" |
| "path" |
| ) |
| |
| var ( |
| defaultV2AuthPrefix = "/v2/auth" |
| ) |
| |
| type User struct { |
| User string `json:"user"` |
| Password string `json:"password,omitempty"` |
| Roles []string `json:"roles"` |
| Grant []string `json:"grant,omitempty"` |
| Revoke []string `json:"revoke,omitempty"` |
| } |
| |
| // userListEntry is the user representation given by the server for ListUsers |
| type userListEntry struct { |
| User string `json:"user"` |
| Roles []Role `json:"roles"` |
| } |
| |
| type UserRoles struct { |
| User string `json:"user"` |
| Roles []Role `json:"roles"` |
| } |
| |
| func v2AuthURL(ep url.URL, action string, name string) *url.URL { |
| if name != "" { |
| ep.Path = path.Join(ep.Path, defaultV2AuthPrefix, action, name) |
| return &ep |
| } |
| ep.Path = path.Join(ep.Path, defaultV2AuthPrefix, action) |
| return &ep |
| } |
| |
| // NewAuthAPI constructs a new AuthAPI that uses HTTP to |
| // interact with etcd's general auth features. |
| func NewAuthAPI(c Client) AuthAPI { |
| return &httpAuthAPI{ |
| client: c, |
| } |
| } |
| |
| type AuthAPI interface { |
| // Enable auth. |
| Enable(ctx context.Context) error |
| |
| // Disable auth. |
| Disable(ctx context.Context) error |
| } |
| |
| type httpAuthAPI struct { |
| client httpClient |
| } |
| |
| func (s *httpAuthAPI) Enable(ctx context.Context) error { |
| return s.enableDisable(ctx, &authAPIAction{"PUT"}) |
| } |
| |
| func (s *httpAuthAPI) Disable(ctx context.Context) error { |
| return s.enableDisable(ctx, &authAPIAction{"DELETE"}) |
| } |
| |
| func (s *httpAuthAPI) enableDisable(ctx context.Context, req httpAction) error { |
| resp, body, err := s.client.Do(ctx, req) |
| if err != nil { |
| return err |
| } |
| if err = assertStatusCode(resp.StatusCode, http.StatusOK, http.StatusCreated); err != nil { |
| var sec authError |
| err = json.Unmarshal(body, &sec) |
| if err != nil { |
| return err |
| } |
| return sec |
| } |
| return nil |
| } |
| |
| type authAPIAction struct { |
| verb string |
| } |
| |
| func (l *authAPIAction) HTTPRequest(ep url.URL) *http.Request { |
| u := v2AuthURL(ep, "enable", "") |
| req, _ := http.NewRequest(l.verb, u.String(), nil) |
| return req |
| } |
| |
| type authError struct { |
| Message string `json:"message"` |
| Code int `json:"-"` |
| } |
| |
| func (e authError) Error() string { |
| return e.Message |
| } |
| |
| // NewAuthUserAPI constructs a new AuthUserAPI that uses HTTP to |
| // interact with etcd's user creation and modification features. |
| func NewAuthUserAPI(c Client) AuthUserAPI { |
| return &httpAuthUserAPI{ |
| client: c, |
| } |
| } |
| |
| type AuthUserAPI interface { |
| // AddUser adds a user. |
| AddUser(ctx context.Context, username string, password string) error |
| |
| // RemoveUser removes a user. |
| RemoveUser(ctx context.Context, username string) error |
| |
| // GetUser retrieves user details. |
| GetUser(ctx context.Context, username string) (*User, error) |
| |
| // GrantUser grants a user some permission roles. |
| GrantUser(ctx context.Context, username string, roles []string) (*User, error) |
| |
| // RevokeUser revokes some permission roles from a user. |
| RevokeUser(ctx context.Context, username string, roles []string) (*User, error) |
| |
| // ChangePassword changes the user's password. |
| ChangePassword(ctx context.Context, username string, password string) (*User, error) |
| |
| // ListUsers lists the users. |
| ListUsers(ctx context.Context) ([]string, error) |
| } |
| |
| type httpAuthUserAPI struct { |
| client httpClient |
| } |
| |
| type authUserAPIAction struct { |
| verb string |
| username string |
| user *User |
| } |
| |
| type authUserAPIList struct{} |
| |
| func (list *authUserAPIList) HTTPRequest(ep url.URL) *http.Request { |
| u := v2AuthURL(ep, "users", "") |
| req, _ := http.NewRequest("GET", u.String(), nil) |
| req.Header.Set("Content-Type", "application/json") |
| return req |
| } |
| |
| func (l *authUserAPIAction) HTTPRequest(ep url.URL) *http.Request { |
| u := v2AuthURL(ep, "users", l.username) |
| if l.user == nil { |
| req, _ := http.NewRequest(l.verb, u.String(), nil) |
| return req |
| } |
| b, err := json.Marshal(l.user) |
| if err != nil { |
| panic(err) |
| } |
| body := bytes.NewReader(b) |
| req, _ := http.NewRequest(l.verb, u.String(), body) |
| req.Header.Set("Content-Type", "application/json") |
| return req |
| } |
| |
| func (u *httpAuthUserAPI) ListUsers(ctx context.Context) ([]string, error) { |
| resp, body, err := u.client.Do(ctx, &authUserAPIList{}) |
| if err != nil { |
| return nil, err |
| } |
| if err = assertStatusCode(resp.StatusCode, http.StatusOK); err != nil { |
| var sec authError |
| err = json.Unmarshal(body, &sec) |
| if err != nil { |
| return nil, err |
| } |
| return nil, sec |
| } |
| |
| var userList struct { |
| Users []userListEntry `json:"users"` |
| } |
| |
| if err = json.Unmarshal(body, &userList); err != nil { |
| return nil, err |
| } |
| |
| ret := make([]string, 0, len(userList.Users)) |
| for _, u := range userList.Users { |
| ret = append(ret, u.User) |
| } |
| return ret, nil |
| } |
| |
| func (u *httpAuthUserAPI) AddUser(ctx context.Context, username string, password string) error { |
| user := &User{ |
| User: username, |
| Password: password, |
| } |
| return u.addRemoveUser(ctx, &authUserAPIAction{ |
| verb: "PUT", |
| username: username, |
| user: user, |
| }) |
| } |
| |
| func (u *httpAuthUserAPI) RemoveUser(ctx context.Context, username string) error { |
| return u.addRemoveUser(ctx, &authUserAPIAction{ |
| verb: "DELETE", |
| username: username, |
| }) |
| } |
| |
| func (u *httpAuthUserAPI) addRemoveUser(ctx context.Context, req *authUserAPIAction) error { |
| resp, body, err := u.client.Do(ctx, req) |
| if err != nil { |
| return err |
| } |
| if err = assertStatusCode(resp.StatusCode, http.StatusOK, http.StatusCreated); err != nil { |
| var sec authError |
| err = json.Unmarshal(body, &sec) |
| if err != nil { |
| return err |
| } |
| return sec |
| } |
| return nil |
| } |
| |
| func (u *httpAuthUserAPI) GetUser(ctx context.Context, username string) (*User, error) { |
| return u.modUser(ctx, &authUserAPIAction{ |
| verb: "GET", |
| username: username, |
| }) |
| } |
| |
| func (u *httpAuthUserAPI) GrantUser(ctx context.Context, username string, roles []string) (*User, error) { |
| user := &User{ |
| User: username, |
| Grant: roles, |
| } |
| return u.modUser(ctx, &authUserAPIAction{ |
| verb: "PUT", |
| username: username, |
| user: user, |
| }) |
| } |
| |
| func (u *httpAuthUserAPI) RevokeUser(ctx context.Context, username string, roles []string) (*User, error) { |
| user := &User{ |
| User: username, |
| Revoke: roles, |
| } |
| return u.modUser(ctx, &authUserAPIAction{ |
| verb: "PUT", |
| username: username, |
| user: user, |
| }) |
| } |
| |
| func (u *httpAuthUserAPI) ChangePassword(ctx context.Context, username string, password string) (*User, error) { |
| user := &User{ |
| User: username, |
| Password: password, |
| } |
| return u.modUser(ctx, &authUserAPIAction{ |
| verb: "PUT", |
| username: username, |
| user: user, |
| }) |
| } |
| |
| func (u *httpAuthUserAPI) modUser(ctx context.Context, req *authUserAPIAction) (*User, error) { |
| resp, body, err := u.client.Do(ctx, req) |
| if err != nil { |
| return nil, err |
| } |
| if err = assertStatusCode(resp.StatusCode, http.StatusOK); err != nil { |
| var sec authError |
| err = json.Unmarshal(body, &sec) |
| if err != nil { |
| return nil, err |
| } |
| return nil, sec |
| } |
| var user User |
| if err = json.Unmarshal(body, &user); err != nil { |
| var userR UserRoles |
| if urerr := json.Unmarshal(body, &userR); urerr != nil { |
| return nil, err |
| } |
| user.User = userR.User |
| for _, r := range userR.Roles { |
| user.Roles = append(user.Roles, r.Role) |
| } |
| } |
| return &user, nil |
| } |