influxdb/http/user_resource_mapping_servi...

340 lines
8.1 KiB
Go

package http
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"path"
"github.com/influxdata/platform"
kerrors "github.com/influxdata/platform/kit/errors"
"github.com/julienschmidt/httprouter"
)
// TODO(jm): how is basepath going to be populated?
type UserResourceMappingService struct {
Addr string
Token string
InsecureSkipVerify bool
BasePath string
}
type userResourceResponse struct {
Links map[string]string `json:"links"`
platform.UserResourceMapping
}
func newUserResourceResponse(u *platform.UserResourceMapping) *userResourceResponse {
return &userResourceResponse{
Links: map[string]string{
"user": fmt.Sprintf("/api/v2/users/%s", u.UserID),
"resource": fmt.Sprintf("/api/v2/%ss/%s", u.ResourceType, u.ResourceID),
},
UserResourceMapping: *u,
}
}
type userResourcesResponse struct {
UserResourceMappings []*userResourceResponse `json:"userResourceMappings"`
}
func newUserResourcesResponse(opt platform.FindOptions, f platform.UserResourceMappingFilter, ms []*platform.UserResourceMapping) *userResourcesResponse {
rs := make([]*userResourceResponse, 0, len(ms))
for _, m := range ms {
rs = append(rs, newUserResourceResponse(m))
}
return &userResourcesResponse{
UserResourceMappings: rs,
}
}
// newPostMemberHandler returns a handler func for a POST to /members or /owners endpoints
func newPostMemberHandler(s platform.UserResourceMappingService, resourceType platform.ResourceType, userType platform.UserType) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
req, err := decodePostMemberRequest(ctx, r)
if err != nil {
EncodeError(ctx, err, w)
return
}
mapping := &platform.UserResourceMapping{
ResourceID: req.ResourceID,
ResourceType: resourceType,
UserID: req.MemberID,
UserType: userType,
}
if err := s.CreateUserResourceMapping(ctx, mapping); err != nil {
EncodeError(ctx, err, w)
return
}
if err := encodeResponse(ctx, w, http.StatusCreated, newUserResourceResponse(mapping)); err != nil {
EncodeError(ctx, err, w)
return
}
}
}
type postMemberRequest struct {
MemberID platform.ID
ResourceID platform.ID
}
func decodePostMemberRequest(ctx context.Context, r *http.Request) (*postMemberRequest, error) {
params := httprouter.ParamsFromContext(ctx)
id := params.ByName("id")
if id == "" {
return nil, kerrors.InvalidDataf("url missing id")
}
var rid platform.ID
if err := rid.DecodeFromString(id); err != nil {
return nil, err
}
u := &platform.User{}
if err := json.NewDecoder(r.Body).Decode(u); err != nil {
return nil, err
}
if !u.ID.Valid() {
return nil, kerrors.InvalidDataf("user id missing or invalid")
}
return &postMemberRequest{
MemberID: u.ID,
ResourceID: rid,
}, nil
}
// newPostMemberHandler returns a handler func for a GET to /members or /owners endpoints
func newGetMembersHandler(s platform.UserResourceMappingService, userType platform.UserType) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
req, err := decodeGetMembersRequest(ctx, r)
if err != nil {
EncodeError(ctx, err, w)
return
}
filter := platform.UserResourceMappingFilter{
ResourceID: req.ResourceID,
UserType: platform.Member,
}
opts := platform.FindOptions{}
mappings, _, err := s.FindUserResourceMappings(ctx, filter)
if err != nil {
EncodeError(ctx, err, w)
return
}
if err := encodeResponse(ctx, w, http.StatusOK, newUserResourcesResponse(opts, filter, mappings)); err != nil {
EncodeError(ctx, err, w)
return
}
}
}
type getMembersRequest struct {
MemberID platform.ID
ResourceID platform.ID
}
func decodeGetMembersRequest(ctx context.Context, r *http.Request) (*getMembersRequest, error) {
params := httprouter.ParamsFromContext(ctx)
id := params.ByName("id")
if id == "" {
return nil, kerrors.InvalidDataf("url missing id")
}
var i platform.ID
if err := i.DecodeFromString(id); err != nil {
return nil, err
}
req := &getMembersRequest{
ResourceID: i,
}
return req, nil
}
// newDeleteMemberHandler returns a handler func for a DELETE to /members or /owners endpoints
func newDeleteMemberHandler(s platform.UserResourceMappingService, userType platform.UserType) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
req, err := decodeDeleteMemberRequest(ctx, r)
if err != nil {
EncodeError(ctx, err, w)
return
}
if err := s.DeleteUserResourceMapping(ctx, req.ResourceID, req.MemberID); err != nil {
EncodeError(ctx, err, w)
return
}
w.WriteHeader(http.StatusNoContent)
}
}
type deleteMemberRequest struct {
MemberID platform.ID
ResourceID platform.ID
}
func decodeDeleteMemberRequest(ctx context.Context, r *http.Request) (*deleteMemberRequest, error) {
params := httprouter.ParamsFromContext(ctx)
id := params.ByName("id")
if id == "" {
return nil, kerrors.InvalidDataf("url missing resource id")
}
var rid platform.ID
if err := rid.DecodeFromString(id); err != nil {
return nil, err
}
id = params.ByName("userID")
if id == "" {
return nil, kerrors.InvalidDataf("url missing member id")
}
var mid platform.ID
if err := mid.DecodeFromString(id); err != nil {
return nil, err
}
return &deleteMemberRequest{
MemberID: mid,
ResourceID: rid,
}, nil
}
func (s *UserResourceMappingService) FindUserResourceMappings(ctx context.Context, filter platform.UserResourceMappingFilter, opt ...platform.FindOptions) ([]*platform.UserResourceMapping, int, error) {
url, err := newURL(s.Addr, s.BasePath)
if err != nil {
return nil, 0, err
}
query := url.Query()
// this is not how this is going to work, lol
if filter.ResourceID.Valid() {
query.Add("resourceID", filter.ResourceID.String())
}
if filter.UserID.Valid() {
query.Add("userID", filter.UserID.String())
}
if filter.UserType != "" {
query.Add("userType", string(filter.UserType))
}
req, err := http.NewRequest("GET", url.String(), nil)
if err != nil {
return nil, 0, err
}
req.URL.RawQuery = query.Encode()
SetToken(s.Token, req)
hc := newClient(url.Scheme, s.InsecureSkipVerify)
resp, err := hc.Do(req)
if err != nil {
return nil, 0, err
}
if err := CheckError(resp); err != nil {
return nil, 0, err
}
// TODO(jm): make this actually work
return nil, 0, nil
}
func (s *UserResourceMappingService) CreateUserResourceMapping(ctx context.Context, m *platform.UserResourceMapping) error {
if !m.ResourceID.Valid() {
return kerrors.InvalidDataf("resource ID is required")
}
if !m.UserID.Valid() {
return kerrors.InvalidDataf("user ID is required")
}
if m.UserType == "" {
return kerrors.InvalidDataf("user type is required")
}
url, err := newURL(s.Addr, resourceIDPath(s.BasePath, m.ResourceID))
if err != nil {
return err
}
octets, err := json.Marshal(m)
if err != nil {
return err
}
req, err := http.NewRequest("POST", url.String(), bytes.NewReader(octets))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
SetToken(s.Token, req)
hc := newClient(url.Scheme, s.InsecureSkipVerify)
resp, err := hc.Do(req)
if err != nil {
return err
}
// TODO(jsternberg): Should this check for a 201 explicitly?
if err := CheckError(resp); err != nil {
return err
}
if err := json.NewDecoder(resp.Body).Decode(m); err != nil {
return err
}
return nil
}
func (s *UserResourceMappingService) DeleteUserResourceMapping(ctx context.Context, resourceID platform.ID, userID platform.ID) error {
url, err := newURL(s.Addr, memberIDPath(s.BasePath, resourceID, userID))
if err != nil {
return err
}
req, err := http.NewRequest("DELETE", url.String(), nil)
if err != nil {
return err
}
SetToken(s.Token, req)
hc := newClient(url.Scheme, s.InsecureSkipVerify)
resp, err := hc.Do(req)
if err != nil {
return err
}
return CheckError(resp)
}
func resourceIDPath(basePath string, resourceID platform.ID) string {
return path.Join(basePath, resourceID.String())
}
func memberIDPath(basePath string, resourceID platform.ID, memberID platform.ID) string {
return path.Join(basePath, resourceID.String(), "members", memberID.String())
}