From 853cab121191a2cf4dd37c68149fc23b64235464 Mon Sep 17 00:00:00 2001 From: Mike Crute Date: Mon, 22 Nov 2021 18:42:45 -0800 Subject: Add user endpoints --- app/controllers/api_user.go | 181 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 app/controllers/api_user.go (limited to 'app/controllers/api_user.go') diff --git a/app/controllers/api_user.go b/app/controllers/api_user.go new file mode 100644 index 0000000..df667db --- /dev/null +++ b/app/controllers/api_user.go @@ -0,0 +1,181 @@ +package controllers + +import ( + "context" + "net/http" + + "code.crute.us/mcrute/cloud-identity-broker/app/models" + + glecho "code.crute.us/mcrute/golib/echo" + "code.crute.us/mcrute/golib/echo/controller" + "github.com/labstack/echo/v4" +) + +type APIUserHandler struct { + Store models.UserStore +} + +func (h *APIUserHandler) Register(prefix string, r glecho.URLRouter, mw ...echo.MiddlewareFunc) { + // This resource did not exist in the V1 API and thus has no V1 + // representation. We use the default handlers for V1 because otherwise + // requests with V1 Accept headers would result in 406 Unacceptable errors. + gh := &controller.ContentTypeNegotiatingHandler{ + DefaultHandler: h.HandleGet, + Handlers: map[string]echo.HandlerFunc{ + contentTypeV1: h.HandleGet, + contentTypeV2: h.HandleGet, + }, + } + r.GET(prefix, gh.Handle, mw...) + + ph := &controller.ContentTypeNegotiatingHandler{ + DefaultHandler: h.HandlePut, + Handlers: map[string]echo.HandlerFunc{ + contentTypeV1: h.HandlePut, + contentTypeV2: h.HandlePut, + }, + } + r.PUT(prefix, ph.Handle, mw...) + + poh := &controller.ContentTypeNegotiatingHandler{ + DefaultHandler: h.HandlePost, + Handlers: map[string]echo.HandlerFunc{ + contentTypeV1: h.HandlePost, + contentTypeV2: h.HandlePost, + }, + } + r.POST(prefix, poh.Handle, mw...) + + r.DELETE(prefix, h.HandleDelete, mw...) +} + +func (h *APIUserHandler) HandleGet(c echo.Context) error { + u, err := h.Store.Get(context.Background(), c.Param("user")) + if err != nil { + return echo.ErrInternalServerError + } + + return c.JSON(http.StatusOK, u) +} + +func validateKeysAndTokens(in *models.User) error { + for k, v := range in.Keys { + if k != v.KeyId { + return &echo.HTTPError{ + Code: http.StatusBadRequest, + Message: "Key ID must match hash key.", + } + } + + if v.PrivateKey == nil && v.PublicKey == nil { + return &echo.HTTPError{ + Code: http.StatusBadRequest, + Message: "One of public_key or private_key must be set", + } + } + + if v.PrivateKey != nil && v.PublicKey != nil { + return &echo.HTTPError{ + Code: http.StatusBadRequest, + Message: "Only one of public_key or private_key may be set", + } + } + } + + for k, v := range in.AuthTokens { + if k != v.Kind { + return &echo.HTTPError{ + Code: http.StatusBadRequest, + Message: "Token kind must match hash key.", + } + } + } + + return nil +} + +func (h *APIUserHandler) HandlePut(c echo.Context) error { + var in models.User + if err := c.Echo().JSONSerializer.Deserialize(c, &in); err != nil { + return echo.ErrBadRequest + } + + u, err := h.Store.Get(context.Background(), c.Param("user")) + if err != nil { + return echo.ErrInternalServerError + } + + if in.Username != u.Username { + return &echo.HTTPError{ + Code: http.StatusBadRequest, + Message: "Username can not be changed. Create a new user.", + } + } + + if in.Deleted != nil && u.Deleted == nil { + return &echo.HTTPError{ + Code: http.StatusBadRequest, + Message: "Use the DELETE method to delete a record", + } + } + + if err := validateKeysAndTokens(&in); err != nil { + return err + } + + err = h.Store.Put(context.Background(), &in) + if err != nil { + return echo.ErrInternalServerError + } + + return c.String(http.StatusNoContent, "") +} + +func (h *APIUserHandler) HandlePost(c echo.Context) error { + var in models.User + if err := c.Echo().JSONSerializer.Deserialize(c, &in); err != nil { + return echo.ErrBadRequest + } + + _, err := h.Store.Get(context.Background(), c.Param("user")) + if err == nil { + return &echo.HTTPError{ + Code: http.StatusConflict, + Message: "User with username already exists.", + } + } + + if in.Deleted != nil { + return &echo.HTTPError{ + Code: http.StatusBadRequest, + Message: "Can not create deleted user, set Deleted to null", + } + } + + if err := validateKeysAndTokens(&in); err != nil { + return err + } + + err = h.Store.Put(context.Background(), &in) + if err != nil { + return echo.ErrInternalServerError + } + + c.Response().Header().Add("Location", glecho.URLFor(c, "/api/user", in.Username).String()) + + return c.String(http.StatusCreated, "") +} + +func (h *APIUserHandler) HandleDelete(c echo.Context) error { + u, err := h.Store.Get(context.Background(), c.Param("user")) + if err != nil { + return echo.ErrInternalServerError + } + + err = h.Store.Delete(context.Background(), u) + if err != nil { + return echo.ErrInternalServerError + } + + return c.String(http.StatusNoContent, "") +} -- cgit v1.2.3