318 lines
7.9 KiB
Go
318 lines
7.9 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
|
|
"github.com/bouk/httprouter"
|
|
"github.com/influxdata/chronograf"
|
|
)
|
|
|
|
// NewSourceUser adds user to source
|
|
func (h *Service) NewSourceUser(w http.ResponseWriter, r *http.Request) {
|
|
var req userRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
invalidJSON(w, h.Logger)
|
|
return
|
|
}
|
|
|
|
if err := req.ValidCreate(); err != nil {
|
|
invalidData(w, err, h.Logger)
|
|
return
|
|
}
|
|
|
|
ctx := r.Context()
|
|
srcID, ts, err := h.sourcesSeries(ctx, w, r)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
store := ts.Users(ctx)
|
|
user := &chronograf.User{
|
|
Name: req.Username,
|
|
Passwd: req.Password,
|
|
Permissions: req.Permissions,
|
|
Roles: req.Roles,
|
|
}
|
|
|
|
res, err := store.Add(ctx, user)
|
|
if err != nil {
|
|
Error(w, http.StatusBadRequest, err.Error(), h.Logger)
|
|
return
|
|
}
|
|
|
|
if err != nil {
|
|
Error(w, http.StatusBadRequest, err.Error(), h.Logger)
|
|
return
|
|
}
|
|
|
|
su := newUserResponse(srcID, res.Name).WithPermissions(res.Permissions)
|
|
if _, hasRoles := h.hasRoles(ctx, ts); hasRoles {
|
|
su.WithRoles(srcID, res.Roles)
|
|
}
|
|
w.Header().Add("Location", su.Links.Self)
|
|
encodeJSON(w, http.StatusCreated, su, h.Logger)
|
|
}
|
|
|
|
// SourceUsers retrieves all users from source.
|
|
func (h *Service) SourceUsers(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
srcID, ts, err := h.sourcesSeries(ctx, w, r)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
store := ts.Users(ctx)
|
|
users, err := store.All(ctx)
|
|
if err != nil {
|
|
Error(w, http.StatusBadRequest, err.Error(), h.Logger)
|
|
return
|
|
}
|
|
|
|
_, hasRoles := h.hasRoles(ctx, ts)
|
|
ur := make([]userResponse, len(users))
|
|
for i, u := range users {
|
|
usr := newUserResponse(srcID, u.Name).WithPermissions(u.Permissions)
|
|
if hasRoles {
|
|
usr.WithRoles(srcID, u.Roles)
|
|
}
|
|
ur[i] = *usr
|
|
}
|
|
|
|
res := usersResponse{
|
|
Users: ur,
|
|
}
|
|
|
|
encodeJSON(w, http.StatusOK, res, h.Logger)
|
|
}
|
|
|
|
// SourceUserID retrieves a user with ID from store.
|
|
func (h *Service) SourceUserID(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
uid := httprouter.GetParamFromContext(ctx, "uid")
|
|
|
|
srcID, ts, err := h.sourcesSeries(ctx, w, r)
|
|
if err != nil {
|
|
return
|
|
}
|
|
store := ts.Users(ctx)
|
|
u, err := store.Get(ctx, uid)
|
|
if err != nil {
|
|
Error(w, http.StatusBadRequest, err.Error(), h.Logger)
|
|
return
|
|
}
|
|
|
|
res := newUserResponse(srcID, u.Name).WithPermissions(u.Permissions)
|
|
if _, hasRoles := h.hasRoles(ctx, ts); hasRoles {
|
|
res.WithRoles(srcID, u.Roles)
|
|
}
|
|
encodeJSON(w, http.StatusOK, res, h.Logger)
|
|
}
|
|
|
|
// RemoveSourceUser removes the user from the InfluxDB source
|
|
func (h *Service) RemoveSourceUser(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
uid := httprouter.GetParamFromContext(ctx, "uid")
|
|
|
|
_, store, err := h.sourceUsersStore(ctx, w, r)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if err := store.Delete(ctx, &chronograf.User{Name: uid}); err != nil {
|
|
Error(w, http.StatusBadRequest, err.Error(), h.Logger)
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
// UpdateSourceUser changes the password or permissions of a source user
|
|
func (h *Service) UpdateSourceUser(w http.ResponseWriter, r *http.Request) {
|
|
var req userRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
invalidJSON(w, h.Logger)
|
|
return
|
|
}
|
|
if err := req.ValidUpdate(); err != nil {
|
|
invalidData(w, err, h.Logger)
|
|
return
|
|
}
|
|
|
|
ctx := r.Context()
|
|
uid := httprouter.GetParamFromContext(ctx, "uid")
|
|
srcID, ts, err := h.sourcesSeries(ctx, w, r)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
user := &chronograf.User{
|
|
Name: uid,
|
|
Passwd: req.Password,
|
|
Permissions: req.Permissions,
|
|
Roles: req.Roles,
|
|
}
|
|
store := ts.Users(ctx)
|
|
|
|
if err := store.Update(ctx, user); err != nil {
|
|
Error(w, http.StatusBadRequest, err.Error(), h.Logger)
|
|
return
|
|
}
|
|
|
|
u, err := store.Get(ctx, uid)
|
|
if err != nil {
|
|
Error(w, http.StatusBadRequest, err.Error(), h.Logger)
|
|
return
|
|
}
|
|
|
|
res := newUserResponse(srcID, u.Name).WithPermissions(u.Permissions)
|
|
if _, hasRoles := h.hasRoles(ctx, ts); hasRoles {
|
|
res.WithRoles(srcID, u.Roles)
|
|
}
|
|
w.Header().Add("Location", res.Links.Self)
|
|
encodeJSON(w, http.StatusOK, res, h.Logger)
|
|
}
|
|
|
|
func (h *Service) sourcesSeries(ctx context.Context, w http.ResponseWriter, r *http.Request) (int, chronograf.TimeSeries, error) {
|
|
srcID, err := paramID("id", r)
|
|
if err != nil {
|
|
Error(w, http.StatusUnprocessableEntity, err.Error(), h.Logger)
|
|
return 0, nil, err
|
|
}
|
|
|
|
src, err := h.SourcesStore.Get(ctx, srcID)
|
|
if err != nil {
|
|
notFound(w, srcID, h.Logger)
|
|
return 0, nil, err
|
|
}
|
|
|
|
ts, err := h.TimeSeries(src)
|
|
if err != nil {
|
|
msg := fmt.Sprintf("Unable to connect to source %d: %v", srcID, err)
|
|
Error(w, http.StatusBadRequest, msg, h.Logger)
|
|
return 0, nil, err
|
|
}
|
|
|
|
if err = ts.Connect(ctx, &src); err != nil {
|
|
msg := fmt.Sprintf("Unable to connect to source %d: %v", srcID, err)
|
|
Error(w, http.StatusBadRequest, msg, h.Logger)
|
|
return 0, nil, err
|
|
}
|
|
return srcID, ts, nil
|
|
}
|
|
|
|
func (h *Service) sourceUsersStore(ctx context.Context, w http.ResponseWriter, r *http.Request) (int, chronograf.UsersStore, error) {
|
|
srcID, ts, err := h.sourcesSeries(ctx, w, r)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
|
|
store := ts.Users(ctx)
|
|
return srcID, store, nil
|
|
}
|
|
|
|
// hasRoles checks if the influx source has roles or not
|
|
func (h *Service) hasRoles(ctx context.Context, ts chronograf.TimeSeries) (chronograf.RolesStore, bool) {
|
|
store, err := ts.Roles(ctx)
|
|
if err != nil {
|
|
return nil, false
|
|
}
|
|
return store, true
|
|
}
|
|
|
|
type userRequest struct {
|
|
Username string `json:"name,omitempty"` // Username for new account
|
|
Password string `json:"password,omitempty"` // Password for new account
|
|
Permissions chronograf.Permissions `json:"permissions,omitempty"` // Optional permissions
|
|
Roles []chronograf.Role `json:"roles,omitempty"` // Optional roles
|
|
}
|
|
|
|
func (r *userRequest) ValidCreate() error {
|
|
if r.Username == "" {
|
|
return fmt.Errorf("Username required")
|
|
}
|
|
if r.Password == "" {
|
|
return fmt.Errorf("Password required")
|
|
}
|
|
return validPermissions(&r.Permissions)
|
|
}
|
|
|
|
type usersResponse struct {
|
|
Users []userResponse `json:"users"`
|
|
}
|
|
|
|
func (r *userRequest) ValidUpdate() error {
|
|
if r.Password == "" && len(r.Permissions) == 0 && len(r.Roles) == 0 {
|
|
return fmt.Errorf("No fields to update")
|
|
}
|
|
return validPermissions(&r.Permissions)
|
|
}
|
|
|
|
type userResponse struct {
|
|
Name string // Username for new account
|
|
Permissions chronograf.Permissions // Account's permissions
|
|
Roles []roleResponse // Roles if source uses them
|
|
Links selfLinks // Links are URI locations related to user
|
|
hasPermissions bool
|
|
hasRoles bool
|
|
}
|
|
|
|
func (u *userResponse) MarshalJSON() ([]byte, error) {
|
|
res := map[string]interface{}{
|
|
"name": u.Name,
|
|
"links": u.Links,
|
|
}
|
|
if u.hasRoles {
|
|
res["roles"] = u.Roles
|
|
}
|
|
if u.hasPermissions {
|
|
res["permissions"] = u.Permissions
|
|
}
|
|
return json.Marshal(res)
|
|
}
|
|
|
|
// newUserResponse creates an HTTP JSON response for a user w/o roles
|
|
func newUserResponse(srcID int, name string) *userResponse {
|
|
self := newSelfLinks(srcID, "users", name)
|
|
return &userResponse{
|
|
Name: name,
|
|
Links: self,
|
|
}
|
|
}
|
|
|
|
func (u *userResponse) WithPermissions(perms chronograf.Permissions) *userResponse {
|
|
u.hasPermissions = true
|
|
if perms == nil {
|
|
perms = make(chronograf.Permissions, 0)
|
|
}
|
|
u.Permissions = perms
|
|
return u
|
|
}
|
|
|
|
// WithRoles adds roles to the HTTP JSON response for a user
|
|
func (u *userResponse) WithRoles(srcID int, roles []chronograf.Role) *userResponse {
|
|
u.hasRoles = true
|
|
rr := make([]roleResponse, len(roles))
|
|
for i, role := range roles {
|
|
rr[i] = newRoleResponse(srcID, &role)
|
|
}
|
|
u.Roles = rr
|
|
return u
|
|
}
|
|
|
|
type selfLinks struct {
|
|
Self string `json:"self"` // Self link mapping to this resource
|
|
}
|
|
|
|
func newSelfLinks(id int, parent, resource string) selfLinks {
|
|
httpAPISrcs := "/chronograf/v1/sources"
|
|
u := &url.URL{Path: resource}
|
|
encodedResource := u.String()
|
|
return selfLinks{
|
|
Self: fmt.Sprintf("%s/%d/%s/%s", httpAPISrcs, id, parent, encodedResource),
|
|
}
|
|
}
|