2018-05-14 16:26:38 +00:00
|
|
|
package http
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
2018-05-22 22:05:17 +00:00
|
|
|
"errors"
|
2018-05-14 16:26:38 +00:00
|
|
|
"net/http"
|
2018-05-16 18:59:35 +00:00
|
|
|
"path"
|
2018-05-14 16:26:38 +00:00
|
|
|
|
|
|
|
"go.uber.org/zap"
|
|
|
|
|
|
|
|
"github.com/influxdata/platform"
|
2018-05-22 22:05:17 +00:00
|
|
|
kerrors "github.com/influxdata/platform/kit/errors"
|
2018-05-14 16:26:38 +00:00
|
|
|
"github.com/julienschmidt/httprouter"
|
|
|
|
)
|
|
|
|
|
|
|
|
// AuthorizationHandler represents an HTTP API handler for authorizations.
|
|
|
|
type AuthorizationHandler struct {
|
|
|
|
*httprouter.Router
|
|
|
|
Logger *zap.Logger
|
|
|
|
|
|
|
|
AuthorizationService platform.AuthorizationService
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewAuthorizationHandler returns a new instance of AuthorizationHandler.
|
|
|
|
func NewAuthorizationHandler() *AuthorizationHandler {
|
|
|
|
h := &AuthorizationHandler{
|
|
|
|
Router: httprouter.New(),
|
|
|
|
}
|
|
|
|
|
|
|
|
h.HandlerFunc("POST", "/v1/authorizations", h.handlePostAuthorization)
|
|
|
|
h.HandlerFunc("GET", "/v1/authorizations", h.handleGetAuthorizations)
|
2018-05-16 18:59:35 +00:00
|
|
|
h.HandlerFunc("GET", "/v1/authorizations/:id", h.handleGetAuthorization)
|
|
|
|
h.HandlerFunc("DELETE", "/v1/authorizations/:id", h.handleDeleteAuthorization)
|
2018-05-14 16:26:38 +00:00
|
|
|
return h
|
|
|
|
}
|
|
|
|
|
|
|
|
// handlePostAuthorization is the HTTP handler for the POST /v1/authorizations route.
|
|
|
|
func (h *AuthorizationHandler) handlePostAuthorization(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
|
|
|
|
req, err := decodePostAuthorizationRequest(ctx, r)
|
|
|
|
if err != nil {
|
|
|
|
h.Logger.Info("failed to decode request", zap.String("handler", "postAuthorization"), zap.Error(err))
|
2018-05-22 22:05:17 +00:00
|
|
|
kerrors.EncodeHTTP(ctx, err, w)
|
2018-05-14 16:26:38 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Need to do some validation of req.Authorization.Permissions
|
|
|
|
|
|
|
|
if err := h.AuthorizationService.CreateAuthorization(ctx, req.Authorization); err != nil {
|
|
|
|
// Don't log here, it should already be handled by the service
|
2018-05-22 22:05:17 +00:00
|
|
|
kerrors.EncodeHTTP(ctx, err, w)
|
2018-05-14 16:26:38 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := encodeResponse(ctx, w, http.StatusCreated, req.Authorization); err != nil {
|
|
|
|
h.Logger.Info("failed to encode response", zap.String("handler", "postAuthorization"), zap.Error(err))
|
2018-05-22 22:05:17 +00:00
|
|
|
kerrors.EncodeHTTP(ctx, err, w)
|
2018-05-14 16:26:38 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type postAuthorizationRequest struct {
|
|
|
|
Authorization *platform.Authorization
|
|
|
|
}
|
|
|
|
|
|
|
|
func decodePostAuthorizationRequest(ctx context.Context, r *http.Request) (*postAuthorizationRequest, error) {
|
|
|
|
a := &platform.Authorization{}
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(a); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &postAuthorizationRequest{
|
|
|
|
Authorization: a,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// handleGetAuthorizations is the HTTP handler for the GET /v1/authorizations route.
|
|
|
|
func (h *AuthorizationHandler) handleGetAuthorizations(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
|
|
|
|
req, err := decodeGetAuthorizationsRequest(ctx, r)
|
|
|
|
if err != nil {
|
|
|
|
h.Logger.Info("failed to decode request", zap.String("handler", "getAuthorizations"), zap.Error(err))
|
2018-05-22 22:05:17 +00:00
|
|
|
kerrors.EncodeHTTP(ctx, err, w)
|
2018-05-14 16:26:38 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
as, _, err := h.AuthorizationService.FindAuthorizations(ctx, req.filter)
|
|
|
|
if err != nil {
|
|
|
|
// Don't log here, it should already be handled by the service
|
2018-05-22 22:05:17 +00:00
|
|
|
kerrors.EncodeHTTP(ctx, err, w)
|
2018-05-14 16:26:38 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := encodeResponse(ctx, w, http.StatusOK, as); err != nil {
|
|
|
|
h.Logger.Info("failed to encode response", zap.String("handler", "getAuthorizations"), zap.Error(err))
|
2018-05-22 22:05:17 +00:00
|
|
|
kerrors.EncodeHTTP(ctx, err, w)
|
2018-05-14 16:26:38 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type getAuthorizationsRequest struct {
|
|
|
|
filter platform.AuthorizationFilter
|
|
|
|
}
|
|
|
|
|
|
|
|
func decodeGetAuthorizationsRequest(ctx context.Context, r *http.Request) (*getAuthorizationsRequest, error) {
|
|
|
|
qp := r.URL.Query()
|
|
|
|
|
|
|
|
req := &getAuthorizationsRequest{}
|
|
|
|
|
2018-05-16 18:59:35 +00:00
|
|
|
userID := qp.Get("userID")
|
2018-05-14 16:26:38 +00:00
|
|
|
if userID != "" {
|
2018-05-16 18:59:35 +00:00
|
|
|
req.filter.UserID = &platform.ID{}
|
|
|
|
if err := req.filter.UserID.DecodeFromString(userID); err != nil {
|
2018-05-14 16:26:38 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2018-05-16 18:59:35 +00:00
|
|
|
}
|
2018-05-14 16:26:38 +00:00
|
|
|
|
2018-05-16 18:59:35 +00:00
|
|
|
user := qp.Get("user")
|
|
|
|
if user != "" {
|
|
|
|
req.filter.User = &user
|
|
|
|
}
|
|
|
|
|
|
|
|
authID := qp.Get("id")
|
|
|
|
if authID != "" {
|
|
|
|
req.filter.ID = &platform.ID{}
|
|
|
|
if err := req.filter.ID.DecodeFromString(authID); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-05-14 16:26:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return req, nil
|
|
|
|
}
|
|
|
|
|
2018-05-16 18:59:35 +00:00
|
|
|
// handleGetAuthorization is the HTTP handler for the GET /v1/authorizations/:id route.
|
|
|
|
func (h *AuthorizationHandler) handleGetAuthorization(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
|
|
|
|
req, err := decodeGetAuthorizationRequest(ctx, r)
|
|
|
|
if err != nil {
|
|
|
|
h.Logger.Info("failed to decode request", zap.String("handler", "getAuthorization"), zap.Error(err))
|
2018-05-22 22:05:17 +00:00
|
|
|
kerrors.EncodeHTTP(ctx, err, w)
|
2018-05-16 18:59:35 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
a, err := h.AuthorizationService.FindAuthorizationByID(ctx, req.ID)
|
|
|
|
if err != nil {
|
|
|
|
// Don't log here, it should already be handled by the service
|
2018-05-22 22:05:17 +00:00
|
|
|
kerrors.EncodeHTTP(ctx, err, w)
|
2018-05-16 18:59:35 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := encodeResponse(ctx, w, http.StatusOK, a); err != nil {
|
|
|
|
h.Logger.Info("failed to encode response", zap.String("handler", "getAuthorization"), zap.Error(err))
|
2018-05-22 22:05:17 +00:00
|
|
|
kerrors.EncodeHTTP(ctx, err, w)
|
2018-05-16 18:59:35 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type getAuthorizationRequest struct {
|
|
|
|
ID platform.ID
|
|
|
|
}
|
|
|
|
|
|
|
|
func decodeGetAuthorizationRequest(ctx context.Context, r *http.Request) (*getAuthorizationRequest, error) {
|
|
|
|
params := httprouter.ParamsFromContext(ctx)
|
|
|
|
id := params.ByName("id")
|
|
|
|
if id == "" {
|
2018-05-22 22:05:17 +00:00
|
|
|
return nil, kerrors.InvalidDataf("url missing id")
|
2018-05-16 18:59:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var i platform.ID
|
|
|
|
if err := i.DecodeFromString(id); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &getAuthorizationRequest{
|
|
|
|
ID: i,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// handleDeleteAuthorization is the HTTP handler for the DELETE /v1/authorizations/:id route.
|
|
|
|
func (h *AuthorizationHandler) handleDeleteAuthorization(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
|
|
|
|
req, err := decodeDeleteAuthorizationRequest(ctx, r)
|
|
|
|
if err != nil {
|
|
|
|
h.Logger.Info("failed to decode request", zap.String("handler", "deleteAuthorization"), zap.Error(err))
|
2018-05-22 22:05:17 +00:00
|
|
|
kerrors.EncodeHTTP(ctx, err, w)
|
2018-05-16 18:59:35 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := h.AuthorizationService.DeleteAuthorization(ctx, req.ID); err != nil {
|
|
|
|
// Don't log here, it should already be handled by the service
|
2018-05-22 22:05:17 +00:00
|
|
|
kerrors.EncodeHTTP(ctx, err, w)
|
2018-05-16 18:59:35 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
w.WriteHeader(http.StatusAccepted)
|
|
|
|
}
|
|
|
|
|
|
|
|
type deleteAuthorizationRequest struct {
|
|
|
|
ID platform.ID
|
|
|
|
}
|
|
|
|
|
|
|
|
func decodeDeleteAuthorizationRequest(ctx context.Context, r *http.Request) (*deleteAuthorizationRequest, error) {
|
|
|
|
params := httprouter.ParamsFromContext(ctx)
|
|
|
|
id := params.ByName("id")
|
|
|
|
if id == "" {
|
2018-05-22 22:05:17 +00:00
|
|
|
return nil, kerrors.InvalidDataf("url missing id")
|
2018-05-16 18:59:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var i platform.ID
|
|
|
|
if err := i.DecodeFromString(id); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &deleteAuthorizationRequest{
|
|
|
|
ID: i,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2018-05-14 16:26:38 +00:00
|
|
|
// AuthorizationService connects to Influx via HTTP using tokens to manage authorizations
|
|
|
|
type AuthorizationService struct {
|
|
|
|
Addr string
|
|
|
|
Token string
|
|
|
|
InsecureSkipVerify bool
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ platform.AuthorizationService = (*AuthorizationService)(nil)
|
|
|
|
|
2018-05-16 18:59:35 +00:00
|
|
|
func (s *AuthorizationService) FindAuthorizationByID(ctx context.Context, id platform.ID) (*platform.Authorization, error) {
|
|
|
|
u, err := newURL(s.Addr, authorizationIDPath(id))
|
2018-05-14 16:26:38 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
req, err := http.NewRequest("GET", u.String(), nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
req.Header.Set("Authorization", s.Token)
|
|
|
|
|
|
|
|
hc := newClient(u.Scheme, s.InsecureSkipVerify)
|
|
|
|
resp, err := hc.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-05-16 18:59:35 +00:00
|
|
|
if resp.StatusCode != http.StatusOK {
|
2018-05-22 22:05:17 +00:00
|
|
|
return nil, errors.New(resp.Header.Get("X-Influx-Error"))
|
2018-05-14 16:26:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var b platform.Authorization
|
2018-05-16 18:59:35 +00:00
|
|
|
if err := json.NewDecoder(resp.Body).Decode(&b); err != nil {
|
2018-05-14 16:26:38 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2018-05-16 18:59:35 +00:00
|
|
|
defer resp.Body.Close()
|
2018-05-14 16:26:38 +00:00
|
|
|
|
|
|
|
return &b, nil
|
|
|
|
}
|
|
|
|
|
2018-05-16 18:59:35 +00:00
|
|
|
// FindAuthorizationByToken returns a single authorization by Token.
|
|
|
|
func (s *AuthorizationService) FindAuthorizationByToken(ctx context.Context, token string) (*platform.Authorization, error) {
|
2018-05-22 22:05:17 +00:00
|
|
|
return nil, errors.New("not supported in HTTP authorization service")
|
2018-05-14 16:26:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// FindAuthorizations returns a list of authorizations that match filter and the total count of matching authorizations.
|
|
|
|
// Additional options provide pagination & sorting.
|
|
|
|
func (s *AuthorizationService) FindAuthorizations(ctx context.Context, filter platform.AuthorizationFilter, opt ...platform.FindOptions) ([]*platform.Authorization, int, error) {
|
|
|
|
u, err := newURL(s.Addr, authorizationPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
query := u.Query()
|
|
|
|
|
|
|
|
req, err := http.NewRequest("GET", u.String(), nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
|
2018-05-16 18:59:35 +00:00
|
|
|
if filter.ID != nil {
|
|
|
|
query.Add("id", filter.ID.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
if filter.UserID != nil {
|
|
|
|
query.Add("userID", filter.UserID.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
if filter.User != nil {
|
|
|
|
query.Add("user", *filter.User)
|
|
|
|
}
|
|
|
|
|
2018-05-14 16:26:38 +00:00
|
|
|
req.URL.RawQuery = query.Encode()
|
|
|
|
req.Header.Set("Authorization", s.Token)
|
|
|
|
|
|
|
|
hc := newClient(u.Scheme, s.InsecureSkipVerify)
|
|
|
|
resp, err := hc.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
2018-05-22 22:05:17 +00:00
|
|
|
return nil, 0, errors.New(resp.Header.Get("X-Influx-Error"))
|
2018-05-14 16:26:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var bs []*platform.Authorization
|
2018-05-16 18:59:35 +00:00
|
|
|
if err := json.NewDecoder(resp.Body).Decode(&bs); err != nil {
|
2018-05-14 16:26:38 +00:00
|
|
|
return nil, 0, err
|
|
|
|
}
|
2018-05-16 18:59:35 +00:00
|
|
|
defer resp.Body.Close()
|
2018-05-14 16:26:38 +00:00
|
|
|
|
|
|
|
return bs, len(bs), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
authorizationPath = "/v1/authorizations"
|
|
|
|
)
|
|
|
|
|
|
|
|
// CreateAuthorization creates a new authorization and sets b.ID with the new identifier.
|
2018-05-16 18:59:35 +00:00
|
|
|
func (s *AuthorizationService) CreateAuthorization(ctx context.Context, a *platform.Authorization) error {
|
2018-05-14 16:26:38 +00:00
|
|
|
u, err := newURL(s.Addr, authorizationPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-05-16 18:59:35 +00:00
|
|
|
octets, err := json.Marshal(a)
|
2018-05-14 16:26:38 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
req, err := http.NewRequest("POST", u.String(), bytes.NewReader(octets))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
req.Header.Set("Authorization", s.Token)
|
|
|
|
|
|
|
|
hc := newClient(u.Scheme, s.InsecureSkipVerify)
|
|
|
|
|
|
|
|
resp, err := hc.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: this should really check the error from the headers
|
|
|
|
if resp.StatusCode != http.StatusCreated {
|
2018-05-22 22:05:17 +00:00
|
|
|
return errors.New(resp.Header.Get("X-Influx-Error"))
|
2018-05-14 16:26:38 +00:00
|
|
|
}
|
|
|
|
|
2018-05-16 18:59:35 +00:00
|
|
|
if err := json.NewDecoder(resp.Body).Decode(a); err != nil {
|
2018-05-14 16:26:38 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-05-16 18:59:35 +00:00
|
|
|
// DeleteAuthorization removes a authorization by id.
|
|
|
|
func (s *AuthorizationService) DeleteAuthorization(ctx context.Context, id platform.ID) error {
|
|
|
|
u, err := newURL(s.Addr, authorizationIDPath(id))
|
2018-05-14 16:26:38 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
req, err := http.NewRequest("DELETE", u.String(), nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
req.Header.Set("Authorization", s.Token)
|
|
|
|
|
|
|
|
hc := newClient(u.Scheme, s.InsecureSkipVerify)
|
|
|
|
resp, err := hc.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-05-16 18:59:35 +00:00
|
|
|
switch resp.StatusCode {
|
|
|
|
case http.StatusNoContent, http.StatusAccepted:
|
|
|
|
return nil
|
2018-05-14 16:26:38 +00:00
|
|
|
}
|
|
|
|
|
2018-05-22 22:05:17 +00:00
|
|
|
return errors.New(resp.Header.Get("X-Influx-Error"))
|
2018-05-14 16:26:38 +00:00
|
|
|
}
|
|
|
|
|
2018-05-16 18:59:35 +00:00
|
|
|
func authorizationIDPath(id platform.ID) string {
|
|
|
|
return path.Join(authorizationPath, id.String())
|
2018-05-14 16:26:38 +00:00
|
|
|
}
|