package controllers import ( "context" "fmt" "net/http" "time" "code.crute.us/mcrute/cloud-identity-broker/app/middleware" "code.crute.us/mcrute/cloud-identity-broker/app/models" "code.crute.us/mcrute/cloud-identity-broker/cloud/aws" glecho "code.crute.us/mcrute/golib/echo" "code.crute.us/mcrute/golib/echo/controller" "github.com/labstack/echo/v4" ) type APIAccountHandler struct { Store models.AccountStore AdminStore models.AccountStore } func (h *APIAccountHandler) 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 *APIAccountHandler) getPrincipalAndAccount(c echo.Context) (*models.User, *models.Account, error) { var err error ctx := context.Background() p, err := middleware.GetAuthorizedPrincipal(c) if err != nil { return nil, nil, echo.ErrUnauthorized } var a *models.Account if p.IsAdmin { a, err = h.AdminStore.GetForUser(ctx, c.Param("account"), p) if err != nil { return nil, nil, echo.NotFoundHandler(c) } } else { a, err = h.Store.GetForUser(ctx, c.Param("account"), p) if err != nil { return nil, nil, echo.NotFoundHandler(c) } } return p, a, nil } func (h *APIAccountHandler) HandleGet(c echo.Context) error { p, a, err := h.getPrincipalAndAccount(c) if err != nil { return err } // These fields are slightly sensitive and give away too many security // details about the account so they should only be visible to users who // can administer the account. if !a.CanBeModifiedBy(p) { a.VaultMaterial = "" a.Users = nil } return c.JSON(http.StatusOK, a) } func (h *APIAccountHandler) HandlePut(c echo.Context) error { var in models.Account if err := c.Echo().JSONSerializer.Deserialize(c, &in); err != nil { return echo.ErrBadRequest } p, a, err := h.getPrincipalAndAccount(c) if err != nil { return err } if !a.CanBeModifiedBy(p) { return echo.ErrForbidden } if in.ShortName != a.ShortName { return &echo.HTTPError{ Code: http.StatusBadRequest, Message: "Account short_name can not be changed. Create a new account.", } } if in.AccountType != a.AccountType { return &echo.HTTPError{ Code: http.StatusBadRequest, Message: "Account type can not be changed. Create a new account.", } } a.AccountNumber = in.AccountNumber a.Name = in.Name a.ConsoleSessionDuration = in.ConsoleSessionDuration a.VaultMaterial = in.VaultMaterial a.DefaultRegion = in.DefaultRegion a.Users = in.Users // PUT-ing Deleted equal to null effectively un-deletes the record a.Deleted = in.Deleted if err := h.Store.Put(context.Background(), a); err != nil { return echo.ErrInternalServerError } return c.String(http.StatusNoContent, "") } func (h *APIAccountHandler) HandlePost(c echo.Context) error { var in models.Account if err := c.Echo().JSONSerializer.Deserialize(c, &in); err != nil { return echo.ErrBadRequest } if _, err := h.AdminStore.Get(context.Background(), in.ShortName); err == nil { return &echo.HTTPError{ Code: http.StatusConflict, Message: "Account with short name already exists. Choose another short name.", } } if in.ConsoleSessionDuration < time.Hour { in.ConsoleSessionDuration = time.Hour } if in.ConsoleSessionDuration > 12*time.Hour { return &echo.HTTPError{ Code: http.StatusBadRequest, Message: "Console duration is greater than the AWS maximum of 12 hours.", } } if in.Deleted != nil { return &echo.HTTPError{ Code: http.StatusBadRequest, Message: "Can not create deleted account, set Deleted to null", } } if err := aws.ValidateVaultMaterial(in.VaultMaterial); err != nil { return &echo.HTTPError{ Code: http.StatusBadRequest, Message: fmt.Sprintf("Unable to access Vault material: %s", err), } } if err := h.Store.Put(context.Background(), &in); err != nil { return echo.ErrInternalServerError } c.Response().Header().Add("Location", glecho.URLFor(c, "/api/account", in.ShortName).String()) return c.String(http.StatusCreated, "") } func (h *APIAccountHandler) HandleDelete(c echo.Context) error { p, a, err := h.getPrincipalAndAccount(c) if err != nil { return err } if a.CanBeModifiedBy(p) { if err := h.Store.Delete(context.Background(), a); err != nil { return echo.ErrInternalServerError } } else { return echo.ErrForbidden } return c.String(http.StatusNoContent, "") }