fix REST API permission checks (#209)

This commit is contained in:
Christoph Haas
2024-01-31 21:14:36 +01:00
parent 81e696fc7d
commit 1b4b5ff161
14 changed files with 239 additions and 26 deletions

View File

@@ -44,7 +44,8 @@ func (e authEndpoint) RegisterRoutes(g *gin.RouterGroup, authenticator *authenti
// @Router /auth/providers [get]
func (e authEndpoint) handleExternalLoginProvidersGet() gin.HandlerFunc {
return func(c *gin.Context) {
providers := e.app.Authenticator.GetExternalLoginProviders(c.Request.Context())
ctx := domain.SetUserInfoFromGin(c)
providers := e.app.Authenticator.GetExternalLoginProviders(ctx)
c.JSON(http.StatusOK, model.NewLoginProviderInfos(providers))
}
@@ -69,7 +70,7 @@ func (e authEndpoint) handleSessionInfoGet() gin.HandlerFunc {
var email *string
if currentSession.LoggedIn {
uid := string(currentSession.UserIdentifier)
uid := currentSession.UserIdentifier
f := currentSession.Firstname
l := currentSession.Lastname
e := currentSession.Email
@@ -134,7 +135,8 @@ func (e authEndpoint) handleOauthInitiateGet() gin.HandlerFunc {
return
}
authCodeUrl, state, nonce, err := e.app.Authenticator.OauthLoginStep1(c.Request.Context(), provider)
ctx := domain.SetUserInfoFromGin(c)
authCodeUrl, state, nonce, err := e.app.Authenticator.OauthLoginStep1(ctx, provider)
if err != nil {
if autoRedirect {
redirectToReturn()
@@ -292,7 +294,8 @@ func (e authEndpoint) handleLoginPost() gin.HandlerFunc {
return
}
user, err := e.app.Authenticator.PlainLogin(c.Request.Context(), loginData.Username, loginData.Password)
ctx := domain.SetUserInfoFromGin(c)
user, err := e.app.Authenticator.PlainLogin(ctx, loginData.Username, loginData.Password)
if err != nil {
c.JSON(http.StatusUnauthorized, model.Error{Code: http.StatusUnauthorized, Message: "login failed"})
return

View File

@@ -19,7 +19,7 @@ func (e interfaceEndpoint) GetName() string {
}
func (e interfaceEndpoint) RegisterRoutes(g *gin.RouterGroup, authenticator *authenticationHandler) {
apiGroup := g.Group("/interface", e.authenticator.LoggedIn())
apiGroup := g.Group("/interface", e.authenticator.LoggedIn(ScopeAdmin))
apiGroup.GET("/prepare", e.handlePrepareGet())
apiGroup.GET("/all", e.handleAllGet())
@@ -45,7 +45,8 @@ func (e interfaceEndpoint) RegisterRoutes(g *gin.RouterGroup, authenticator *aut
// @Router /interface/prepare [get]
func (e interfaceEndpoint) handlePrepareGet() gin.HandlerFunc {
return func(c *gin.Context) {
in, err := e.app.PrepareInterface(c.Request.Context())
ctx := domain.SetUserInfoFromGin(c)
in, err := e.app.PrepareInterface(ctx)
if err != nil {
c.JSON(http.StatusInternalServerError, model.Error{
Code: http.StatusInternalServerError, Message: err.Error(),
@@ -68,7 +69,8 @@ func (e interfaceEndpoint) handlePrepareGet() gin.HandlerFunc {
// @Router /interface/all [get]
func (e interfaceEndpoint) handleAllGet() gin.HandlerFunc {
return func(c *gin.Context) {
interfaces, peers, err := e.app.GetAllInterfacesAndPeers(c.Request.Context())
ctx := domain.SetUserInfoFromGin(c)
interfaces, peers, err := e.app.GetAllInterfacesAndPeers(ctx)
if err != nil {
c.JSON(http.StatusInternalServerError, model.Error{
Code: http.StatusInternalServerError, Message: err.Error(),
@@ -92,6 +94,7 @@ func (e interfaceEndpoint) handleAllGet() gin.HandlerFunc {
// @Router /interface/get/{id} [get]
func (e interfaceEndpoint) handleSingleGet() gin.HandlerFunc {
return func(c *gin.Context) {
ctx := domain.SetUserInfoFromGin(c)
id := Base64UrlDecode(c.Param("id"))
if id == "" {
c.JSON(http.StatusBadRequest, model.Error{
@@ -100,7 +103,7 @@ func (e interfaceEndpoint) handleSingleGet() gin.HandlerFunc {
return
}
iface, peers, err := e.app.GetInterfaceAndPeers(c.Request.Context(), domain.InterfaceIdentifier(id))
iface, peers, err := e.app.GetInterfaceAndPeers(ctx, domain.InterfaceIdentifier(id))
if err != nil {
c.JSON(http.StatusInternalServerError, model.Error{
Code: http.StatusInternalServerError, Message: err.Error(),
@@ -124,6 +127,7 @@ func (e interfaceEndpoint) handleSingleGet() gin.HandlerFunc {
// @Router /interface/config/{id} [get]
func (e interfaceEndpoint) handleConfigGet() gin.HandlerFunc {
return func(c *gin.Context) {
ctx := domain.SetUserInfoFromGin(c)
id := Base64UrlDecode(c.Param("id"))
if id == "" {
c.JSON(http.StatusBadRequest, model.Error{
@@ -132,7 +136,7 @@ func (e interfaceEndpoint) handleConfigGet() gin.HandlerFunc {
return
}
config, err := e.app.GetInterfaceConfig(c.Request.Context(), domain.InterfaceIdentifier(id))
config, err := e.app.GetInterfaceConfig(ctx, domain.InterfaceIdentifier(id))
if err != nil {
c.JSON(http.StatusInternalServerError, model.Error{
Code: http.StatusInternalServerError, Message: err.Error(),

View File

@@ -21,11 +21,11 @@ func (e peerEndpoint) GetName() string {
func (e peerEndpoint) RegisterRoutes(g *gin.RouterGroup, authenticator *authenticationHandler) {
apiGroup := g.Group("/peer", e.authenticator.LoggedIn())
apiGroup.GET("/iface/:iface/all", e.handleAllGet())
apiGroup.GET("/iface/:iface/stats", e.handleStatsGet())
apiGroup.GET("/iface/:iface/prepare", e.handlePrepareGet())
apiGroup.POST("/iface/:iface/new", e.handleCreatePost())
apiGroup.POST("/iface/:iface/multiplenew", e.handleCreateMultiplePost())
apiGroup.GET("/iface/:iface/all", e.authenticator.LoggedIn(ScopeAdmin), e.handleAllGet())
apiGroup.GET("/iface/:iface/stats", e.authenticator.LoggedIn(ScopeAdmin), e.handleStatsGet())
apiGroup.GET("/iface/:iface/prepare", e.authenticator.LoggedIn(ScopeAdmin), e.handlePrepareGet())
apiGroup.POST("/iface/:iface/new", e.authenticator.LoggedIn(ScopeAdmin), e.handleCreatePost())
apiGroup.POST("/iface/:iface/multiplenew", e.authenticator.LoggedIn(ScopeAdmin), e.handleCreateMultiplePost())
apiGroup.GET("/config-qr/:id", e.handleQrCodeGet())
apiGroup.POST("/config-mail", e.handleEmailPost())
apiGroup.GET("/config/:id", e.handleConfigGet())
@@ -298,6 +298,8 @@ func (e peerEndpoint) handleDelete() gin.HandlerFunc {
// @Router /peer/config/{id} [get]
func (e peerEndpoint) handleConfigGet() gin.HandlerFunc {
return func(c *gin.Context) {
ctx := domain.SetUserInfoFromGin(c)
id := Base64UrlDecode(c.Param("id"))
if id == "" {
c.JSON(http.StatusBadRequest, model.Error{
@@ -306,7 +308,7 @@ func (e peerEndpoint) handleConfigGet() gin.HandlerFunc {
return
}
config, err := e.app.GetPeerConfig(c.Request.Context(), domain.PeerIdentifier(id))
config, err := e.app.GetPeerConfig(ctx, domain.PeerIdentifier(id))
if err != nil {
c.JSON(http.StatusInternalServerError, model.Error{
Code: http.StatusInternalServerError, Message: err.Error(),
@@ -339,6 +341,7 @@ func (e peerEndpoint) handleConfigGet() gin.HandlerFunc {
// @Router /peer/config-qr/{id} [get]
func (e peerEndpoint) handleQrCodeGet() gin.HandlerFunc {
return func(c *gin.Context) {
ctx := domain.SetUserInfoFromGin(c)
id := Base64UrlDecode(c.Param("id"))
if id == "" {
c.JSON(http.StatusBadRequest, model.Error{
@@ -347,7 +350,7 @@ func (e peerEndpoint) handleQrCodeGet() gin.HandlerFunc {
return
}
config, err := e.app.GetPeerConfigQrCode(c.Request.Context(), domain.PeerIdentifier(id))
config, err := e.app.GetPeerConfigQrCode(ctx, domain.PeerIdentifier(id))
if err != nil {
c.JSON(http.StatusInternalServerError, model.Error{
Code: http.StatusInternalServerError, Message: err.Error(),
@@ -392,11 +395,13 @@ func (e peerEndpoint) handleEmailPost() gin.HandlerFunc {
return
}
ctx := domain.SetUserInfoFromGin(c)
peerIds := make([]domain.PeerIdentifier, len(req.Identifiers))
for i := range req.Identifiers {
peerIds[i] = domain.PeerIdentifier(req.Identifiers[i])
}
err = e.app.SendPeerEmail(c.Request.Context(), req.LinkOnly, peerIds...)
err = e.app.SendPeerEmail(ctx, req.LinkOnly, peerIds...)
if err != nil {
c.JSON(http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
return

View File

@@ -20,13 +20,13 @@ func (e userEndpoint) GetName() string {
func (e userEndpoint) RegisterRoutes(g *gin.RouterGroup, authenticator *authenticationHandler) {
apiGroup := g.Group("/user", e.authenticator.LoggedIn())
apiGroup.GET("/all", e.handleAllGet())
apiGroup.GET("/:id", e.handleSingleGet())
apiGroup.PUT("/:id", e.handleUpdatePut())
apiGroup.DELETE("/:id", e.handleDelete())
apiGroup.POST("/new", e.handleCreatePost())
apiGroup.GET("/:id/peers", e.handlePeersGet())
apiGroup.GET("/:id/stats", e.handleStatsGet())
apiGroup.GET("/all", e.authenticator.LoggedIn(ScopeAdmin), e.handleAllGet())
apiGroup.GET("/:id", e.authenticator.UserIdMatch("id"), e.handleSingleGet())
apiGroup.PUT("/:id", e.authenticator.UserIdMatch("id"), e.handleUpdatePut())
apiGroup.DELETE("/:id", e.authenticator.UserIdMatch("id"), e.handleDelete())
apiGroup.POST("/new", e.authenticator.LoggedIn(ScopeAdmin), e.handleCreatePost())
apiGroup.GET("/:id/peers", e.authenticator.UserIdMatch("id"), e.handlePeersGet())
apiGroup.GET("/:id/stats", e.authenticator.UserIdMatch("id"), e.handleStatsGet())
}
// handleAllGet returns a gorm handler function.

View File

@@ -58,6 +58,31 @@ func (h authenticationHandler) LoggedIn(scopes ...Scope) gin.HandlerFunc {
}
}
// UserIdMatch checks if the user id in the session matches the user id in the request. If not, the request is aborted.
func (h authenticationHandler) UserIdMatch(idParameter string) gin.HandlerFunc {
return func(c *gin.Context) {
session := h.Session.GetData(c)
if session.IsAdmin {
c.Next() // Admins can do everything
return
}
sessionUserId := domain.UserIdentifier(session.UserIdentifier)
requestUserId := domain.UserIdentifier(Base64UrlDecode(c.Param(idParameter)))
if sessionUserId != requestUserId {
// Abort the request with the appropriate error code
c.Abort()
c.JSON(http.StatusForbidden, model.Error{Code: http.StatusForbidden, Message: "not enough permissions"})
return
}
// Continue down the chain to handler etc
c.Next()
}
}
func UserHasScopes(session SessionData, scopes ...Scope) bool {
// No scopes give, so the check should succeed
if len(scopes) == 0 {