diff --git a/instantlaunches/main.go b/instantlaunches/main.go index a55f4be..d093a93 100644 --- a/instantlaunches/main.go +++ b/instantlaunches/main.go @@ -229,8 +229,14 @@ func New(db *sqlx.DB, group *echo.Group, init *Init) *App { instance.Group.DELETE("/:id", instance.DeleteInstantLaunchHandler) instance.Group.GET("/:id/full", instance.FullInstantLaunchHandler) instance.Group.GET("/:id/metadata", instance.GetMetadataHandler) - instance.Group.POST("/:id/metadata", instance.AddOrUpdateMetadataHandler) - instance.Group.PUT("/:id/metadata", instance.SetAllMetadataHandler) + + iladmin := instance.Group.Group("/admin") + iladmin.PUT("/", instance.AdminAddInstantLaunchHandler) + iladmin.PUT("", instance.AdminAddInstantLaunchHandler) + iladmin.POST("/:id", instance.AdminUpdateInstantLaunchHandler) + iladmin.DELETE("/:id", instance.AdminDeleteInstantLaunchHandler) + iladmin.POST("/:id/metadata", instance.AdminAddOrUpdateMetadataHandler) + iladmin.PUT("/:id/metadata", instance.AdminSetAllMetadataHandler) return instance } diff --git a/instantlaunches/metadata.go b/instantlaunches/metadata.go index 8583713..87640c1 100644 --- a/instantlaunches/metadata.go +++ b/instantlaunches/metadata.go @@ -195,12 +195,12 @@ func (a *App) GetMetadataHandler(c echo.Context) error { return c.Blob(resp.StatusCode, resp.Header.Get("content-type"), body) } -// AddOrUpdateMetadataHandler adds or updates one or more AVUs on an instant -// launch. -func (a *App) AddOrUpdateMetadataHandler(c echo.Context) error { +// AdminAddOrUpdateMetadataHandler adds or updates one or more AVUs on an instant +// launch as an admin +func (a *App) AdminAddOrUpdateMetadataHandler(c echo.Context) error { ctx := c.Request().Context() - log.Debug("in AddOrUpdateMetadataHandler") + log.Debug("in AdminAddOrUpdateMetadataHandler") id := c.Param("id") if id == "" { @@ -260,12 +260,12 @@ func (a *App) AddOrUpdateMetadataHandler(c echo.Context) error { return c.Blob(resp.StatusCode, resp.Header.Get("content-type"), body) } -// SetAllMetadataHandler sets all of the AVUs associated with an instant -// launch to the set contained in the body of the request. -func (a *App) SetAllMetadataHandler(c echo.Context) error { +// AdminSetAllMetadataHandler sets all of the AVUs associated with an instant +// launch to the set contained in the body of the request (as an admin). +func (a *App) AdminSetAllMetadataHandler(c echo.Context) error { ctx := c.Request().Context() - log.Debug("in SetAllMetadataHandler") + log.Debug("in AdminSetAllMetadataHandler") id := c.Param("id") if id == "" { diff --git a/instantlaunches/mgmt.go b/instantlaunches/mgmt.go index 2e4a8bf..30179b1 100644 --- a/instantlaunches/mgmt.go +++ b/instantlaunches/mgmt.go @@ -4,13 +4,62 @@ import ( "database/sql" "fmt" "net/http" + "regexp" "strings" "github.com/labstack/echo/v4" ) -// AddInstantLaunchHandler is the HTTP handler for adding a new instant launch. +// suffixUsername takes a possibly-already-suffixed username, strips any suffix, and adds the provided one, to ensure proper suffixing +func suffixUsername(username, suffix string) string { + re, _ := regexp.Compile(`@.*$`) + return fmt.Sprintf("%s@%s", re.ReplaceAllString(username, ""), strings.Trim(suffix, "@")) +} + +// checkUserMatches ensures that `first` and `second` match when both suffixed the same. +func checkUserMatches(first, second, suffix string) bool { + return (suffixUsername(first, suffix) == suffixUsername(second, suffix)) +} + +// AddInstantLaunchHandler is the HTTP handler for adding a new instant launch +// as a regular user func (a *App) AddInstantLaunchHandler(c echo.Context) error { + ctx := c.Request().Context() + user := c.QueryParam("user") + if user == "" { + return echo.NewHTTPError(http.StatusBadRequest, "user query parameter must be set") + } + + il, err := NewInstantLaunchFromJSON(c.Request().Body) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "cannot parse JSON") + } + + if il.AddedBy == "" { + return echo.NewHTTPError(http.StatusBadRequest, "username was not set") + } + + if !checkUserMatches(il.AddedBy, user, a.UserSuffix) { + return echo.NewHTTPError(http.StatusBadRequest, "not authorized to create instant launches as another user") + } + + if !strings.HasSuffix(il.AddedBy, a.UserSuffix) { + il.AddedBy = suffixUsername(il.AddedBy, a.UserSuffix) + } + + newil, err := a.AddInstantLaunch(ctx, il.QuickLaunchID, il.AddedBy) + if err != nil { + if err == sql.ErrNoRows { + return echo.NewHTTPError(http.StatusNotFound, err.Error()) + } + return err + } + + return c.JSON(http.StatusOK, newil) +} + +// AdminAddInstantLaunchHandler is the HTTP handler for adding a new instant launch as an admin. +func (a *App) AdminAddInstantLaunchHandler(c echo.Context) error { ctx := c.Request().Context() il, err := NewInstantLaunchFromJSON(c.Request().Body) if err != nil { @@ -77,8 +126,49 @@ func (a *App) FullInstantLaunchHandler(c echo.Context) error { return c.JSON(http.StatusOK, il) } -// UpdateInstantLaunchHandler is the HTTP handler for updating an instant launch. +// UpdateInstantLaunchHandler is the HTTP handler for updating an instant launch as a regular user func (a *App) UpdateInstantLaunchHandler(c echo.Context) error { + ctx := c.Request().Context() + user := c.QueryParam("user") + if user == "" { + return echo.NewHTTPError(http.StatusBadRequest, "user query parameter must be set") + } + + id := c.Param("id") + if id == "" { + return echo.NewHTTPError(http.StatusNotFound, "id is missing") + } + + il, err := a.GetInstantLaunch(ctx, id) + if err != nil { + if err == sql.ErrNoRows { + return echo.NewHTTPError(http.StatusNotFound, err.Error()) + } + return err + } + + updated, err := NewInstantLaunchFromJSON(c.Request().Body) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "cannot parse JSON") + } + + if !checkUserMatches(il.AddedBy, user, a.UserSuffix) || !checkUserMatches(il.AddedBy, updated.AddedBy, a.UserSuffix) { + return echo.NewHTTPError(http.StatusBadRequest, "not authorized to edit other users' instant launches") + } + + newvalue, err := a.UpdateInstantLaunch(ctx, id, updated.QuickLaunchID) + if err != nil { + if err == sql.ErrNoRows { + return echo.NewHTTPError(http.StatusNotFound, err.Error()) + } + return err + } + + return c.JSON(http.StatusOK, newvalue) +} + +// AdminUpdateInstantLaunchHandler is the HTTP handler for updating an instant launch as an admin. +func (a *App) AdminUpdateInstantLaunchHandler(c echo.Context) error { ctx := c.Request().Context() id := c.Param("id") if id == "" { @@ -102,17 +192,44 @@ func (a *App) UpdateInstantLaunchHandler(c echo.Context) error { } // DeleteInstantLaunchHandler is the HTTP handler for deleting an Instant Launch -// based on its UUID. +// based on its UUID as a regular user func (a *App) DeleteInstantLaunchHandler(c echo.Context) error { ctx := c.Request().Context() + user := c.QueryParam("user") + if user == "" { + return echo.NewHTTPError(http.StatusBadRequest, "user query parameter must be set") + } + id := c.Param("id") if id == "" { return echo.NewHTTPError(http.StatusNotFound, "id is missing") } - err := a.DeleteInstantLaunch(ctx, id) - return err + il, err := a.GetInstantLaunch(ctx, id) + if err != nil { + if err == sql.ErrNoRows { + return echo.NewHTTPError(http.StatusNotFound, err.Error()) + } + return err + } + + if !checkUserMatches(il.AddedBy, user, a.UserSuffix) { + return echo.NewHTTPError(http.StatusBadRequest, "not authorized to delete other users' instant launches") + } + + return a.DeleteInstantLaunch(ctx, id) +} + +// AdminDeleteInstantLaunchHandler is the HTTP handler for deleting an Instant Launch +// based on its UUID as an admin +func (a *App) AdminDeleteInstantLaunchHandler(c echo.Context) error { + ctx := c.Request().Context() + id := c.Param("id") + if id == "" { + return echo.NewHTTPError(http.StatusNotFound, "id is missing") + } + return a.DeleteInstantLaunch(ctx, id) } // ListInstantLaunchesHandler is the HTTP handler for listing all of the