From 018ce2e53797342256fd84ad3acc3ddbdc73f1bb Mon Sep 17 00:00:00 2001 From: eikendev Date: Sun, 2 Aug 2020 17:06:11 +0200 Subject: [PATCH] Introduce middleware for parsing ID from URI --- api/application.go | 22 +++++++++++++--------- api/middleware.go | 36 ++++++++++++++++++++++++++++++++++++ api/notification.go | 2 +- api/user.go | 26 +++++++++++++++----------- database/user.go | 2 +- model/application.go | 10 ---------- model/user.go | 18 ++++-------------- router/router.go | 8 ++++---- 8 files changed, 74 insertions(+), 50 deletions(-) create mode 100644 api/middleware.go diff --git a/api/application.go b/api/application.go index 6d60662..8fc6eb5 100644 --- a/api/application.go +++ b/api/application.go @@ -40,7 +40,7 @@ func (h *ApplicationHandler) applicationExists(token string) bool { func (h *ApplicationHandler) CreateApplication(ctx *gin.Context) { var createApplication model.CreateApplication - if success := successOrAbort(ctx, http.StatusBadRequest, ctx.Bind(&createApplication)); !success { + if err := ctx.Bind(&createApplication); err != nil { return } @@ -69,13 +69,12 @@ func (h *ApplicationHandler) CreateApplication(ctx *gin.Context) { // DeleteApplication deletes an application with a certain ID. func (h *ApplicationHandler) DeleteApplication(ctx *gin.Context) { - var deleteApplication model.DeleteApplication - - if success := successOrAbort(ctx, http.StatusBadRequest, ctx.BindUri(&deleteApplication)); !success { + id, err := getID(ctx) + if err != nil { return } - application, err := h.DB.GetApplicationByID(deleteApplication.ID) + application, err := h.DB.GetApplicationByID(id) if success := successOrAbort(ctx, http.StatusNotFound, err); !success { return } @@ -99,13 +98,12 @@ func (h *ApplicationHandler) DeleteApplication(ctx *gin.Context) { // UpdateApplication updates an application with a certain ID. func (h *ApplicationHandler) UpdateApplication(ctx *gin.Context) { - var updateApplication model.UpdateApplication - - if success := successOrAbort(ctx, http.StatusBadRequest, ctx.BindUri(&updateApplication)); !success { + id, err := getID(ctx) + if err != nil { return } - application, err := h.DB.GetApplicationByID(updateApplication.ID) + application, err := h.DB.GetApplicationByID(id) if success := successOrAbort(ctx, http.StatusNotFound, err); !success { return } @@ -114,6 +112,12 @@ func (h *ApplicationHandler) UpdateApplication(ctx *gin.Context) { return } + var updateApplication model.UpdateApplication + + if err := ctx.BindUri(&updateApplication); err != nil { + return + } + log.Printf("Updating application %s.\n", application.Name) // TODO: Handle unbound members. diff --git a/api/middleware.go b/api/middleware.go new file mode 100644 index 0000000..0ca0556 --- /dev/null +++ b/api/middleware.go @@ -0,0 +1,36 @@ +package api + +import ( + "errors" + "net/http" + + "github.com/gin-gonic/gin" +) + +type idInURI struct { + ID uint `uri:"id" binding:"required"` +} + +// RequireIDInURI returns a Gin middleware which requires an ID to be supplied in the URI of the request. +func RequireIDInURI() gin.HandlerFunc { + return func(ctx *gin.Context) { + var requestModel idInURI + + if err := ctx.BindUri(&requestModel); err != nil { + return + } + + ctx.Set("id", requestModel.ID) + } +} + +func getID(ctx *gin.Context) (uint, error) { + id, ok := ctx.MustGet("user").(uint) + if !ok { + err := errors.New("an error occured while retrieving ID from context") + ctx.AbortWithError(http.StatusInternalServerError, err) + return 0, err + } + + return id, nil +} diff --git a/api/notification.go b/api/notification.go index 7aad80e..c0c3fff 100644 --- a/api/notification.go +++ b/api/notification.go @@ -31,7 +31,7 @@ type NotificationHandler struct { func (h *NotificationHandler) CreateNotification(ctx *gin.Context) { var notification model.Notification - if success := successOrAbort(ctx, http.StatusBadRequest, ctx.Bind(¬ification)); !success { + if err := ctx.Bind(¬ification); err != nil { return } diff --git a/api/user.go b/api/user.go index bcd7449..2ee3026 100644 --- a/api/user.go +++ b/api/user.go @@ -13,7 +13,7 @@ import ( // The UserDatabase interface for encapsulating database access. type UserDatabase interface { - CreateUser(user model.ExternalUserWithCredentials) (*model.User, error) + CreateUser(user model.CreateUser) (*model.User, error) DeleteUser(user *model.User) error UpdateUser(user *model.User) error GetUserByID(ID uint) (*model.User, error) @@ -57,9 +57,9 @@ func (h *UserHandler) ensureIsNotLastAdmin(ctx *gin.Context) (int, error) { // CreateUser creates a new user. // This method assumes that the requesting user has privileges. func (h *UserHandler) CreateUser(ctx *gin.Context) { - var externalUser model.ExternalUserWithCredentials + var externalUser model.CreateUser - if success := successOrAbort(ctx, http.StatusBadRequest, ctx.Bind(&externalUser)); !success { + if err := ctx.Bind(&externalUser); err != nil { return } @@ -81,13 +81,12 @@ func (h *UserHandler) CreateUser(ctx *gin.Context) { // // This method assumes that the requesting user has privileges. func (h *UserHandler) DeleteUser(ctx *gin.Context) { - var deleteUser model.DeleteUser - - if success := successOrAbort(ctx, http.StatusBadRequest, ctx.BindUri(&deleteUser)); !success { + id, err := getID(ctx) + if err != nil { return } - user, err := h.DB.GetUserByID(deleteUser.ID) + user, err := h.DB.GetUserByID(id) if success := successOrAbort(ctx, http.StatusNotFound, err); !success { return } @@ -125,17 +124,22 @@ func (h *UserHandler) DeleteUser(ctx *gin.Context) { // This method assumes that the requesting user has privileges. If users can later update their own user, make sure they // cannot give themselves privileges. func (h *UserHandler) UpdateUser(ctx *gin.Context) { - var updateUser model.UpdateUser - - if success := successOrAbort(ctx, http.StatusBadRequest, ctx.BindUri(&updateUser)); !success { + id, err := getID(ctx) + if err != nil { return } - user, err := h.DB.GetUserByID(updateUser.ID) + user, err := h.DB.GetUserByID(id) if success := successOrAbort(ctx, http.StatusNotFound, err); !success { return } + var updateUser model.UpdateUser + + if err := ctx.BindUri(&updateUser); err != nil { + return + } + currentUser := authentication.GetUser(ctx) // Last privileged user must not be taken privileges. Assumes that the current user has privileges. diff --git a/database/user.go b/database/user.go index 2c95dca..17e8f13 100644 --- a/database/user.go +++ b/database/user.go @@ -10,7 +10,7 @@ import ( ) // CreateUser creates a user. -func (d *Database) CreateUser(externalUser model.ExternalUserWithCredentials) (*model.User, error) { +func (d *Database) CreateUser(externalUser model.CreateUser) (*model.User, error) { user := externalUser.IntoInternalUser(d.credentialsManager) return user, d.gormdb.Create(user).Error diff --git a/model/application.go b/model/application.go index 7523029..0a32bf4 100644 --- a/model/application.go +++ b/model/application.go @@ -14,17 +14,7 @@ type CreateApplication struct { Name string `form:"name" query:"name" json:"name" binding:"required"` } -type applicationIdentification struct { - ID uint `uri:"id" binding:"required"` -} - -// DeleteApplication is used to process queries for deleting applications. -type DeleteApplication struct { - applicationIdentification -} - // UpdateApplication is used to process queries for updating applications. type UpdateApplication struct { - applicationIdentification Name string `json:"name"` } diff --git a/model/user.go b/model/user.go index a1c55ce..f335a68 100644 --- a/model/user.go +++ b/model/user.go @@ -29,8 +29,8 @@ type UserCredentials struct { Password string `json:"password,omitempty" form:"password" query:"password" binding:"required"` } -// ExternalUserWithCredentials represents a user for external purposes and includes the user's credentials in plaintext. -type ExternalUserWithCredentials struct { +// CreateUser is used to process queries for creating users. +type CreateUser struct { ExternalUser UserCredentials } @@ -47,8 +47,8 @@ func NewUser(cm *credentials.Manager, name, password string, isAdmin bool, matri } } -// IntoInternalUser converts a ExternalUserWithCredentials into a User. -func (u *ExternalUserWithCredentials) IntoInternalUser(cm *credentials.Manager) *User { +// IntoInternalUser converts a CreateUser into a User. +func (u *CreateUser) IntoInternalUser(cm *credentials.Manager) *User { return &User{ Name: u.Name, PasswordHash: cm.CreatePasswordHash(u.Password), @@ -67,18 +67,8 @@ func (u *User) IntoExternalUser() *ExternalUser { } } -type userIdentification struct { - ID uint `uri:"id" binding:"required"` -} - -// DeleteUser is used to process queries for deleting users. -type DeleteUser struct { - userIdentification -} - // UpdateUser is used to process queries for updating users. type UpdateUser struct { - userIdentification Name string `json:"name"` Password string `json:"password"` IsAdmin bool `json:"is_admin"` diff --git a/router/router.go b/router/router.go index b6d490f..cd08a47 100644 --- a/router/router.go +++ b/router/router.go @@ -35,8 +35,8 @@ func Create(debug bool, cm *credentials.Manager, db *database.Database, dp *disp applicationGroup.Use(auth.RequireUser()) { applicationGroup.POST("", applicationHandler.CreateApplication) - applicationGroup.DELETE("/:id", applicationHandler.DeleteApplication) - applicationGroup.PUT("/:id", applicationHandler.UpdateApplication) + applicationGroup.DELETE("/:id", api.RequireIDInURI(), applicationHandler.DeleteApplication) + applicationGroup.PUT("/:id", api.RequireIDInURI(), applicationHandler.UpdateApplication) } r.POST("/message", auth.RequireApplicationToken(), notificationHandler.CreateNotification) @@ -45,8 +45,8 @@ func Create(debug bool, cm *credentials.Manager, db *database.Database, dp *disp userGroup.Use(auth.RequireAdmin()) { userGroup.POST("", userHandler.CreateUser) - userGroup.DELETE("/:id", userHandler.DeleteUser) - userGroup.PUT("/:id", userHandler.UpdateUser) + userGroup.DELETE("/:id", api.RequireIDInURI(), userHandler.DeleteUser) + userGroup.PUT("/:id", api.RequireIDInURI(), userHandler.UpdateUser) } return r