From a8744482c668e07a61798573ec8e17bd3f6009f9 Mon Sep 17 00:00:00 2001 From: Jannis Mattheis Date: Sat, 11 Apr 2026 21:43:59 +0200 Subject: [PATCH] fix: enforce elevated authentication --- api/application.go | 2 + api/client.go | 4 ++ api/user.go | 14 ++++ auth/authentication.go | 30 ++++++--- auth/authentication_test.go | 124 +++++++++++++++++++++++++++++++++--- docs/spec.json | 11 +++- router/router.go | 31 +++------ 7 files changed, 175 insertions(+), 41 deletions(-) diff --git a/api/application.go b/api/application.go index 2527842..a984b0f 100644 --- a/api/application.go +++ b/api/application.go @@ -151,6 +151,8 @@ func (a *ApplicationAPI) GetApplications(ctx *gin.Context) { // // Delete an application. // +// Requires elevated authentication. +// // --- // consumes: [application/json] // produces: [application/json] diff --git a/api/client.go b/api/client.go index 3736f31..52e78a3 100644 --- a/api/client.go +++ b/api/client.go @@ -193,6 +193,8 @@ func (a *ClientAPI) GetClients(ctx *gin.Context) { // // Delete a client. // +// Requires elevated authentication. +// // --- // consumes: [application/json] // produces: [application/json] @@ -242,6 +244,8 @@ func (a *ClientAPI) DeleteClient(ctx *gin.Context) { // // Elevate a client session. // +// Requires elevated authentication. +// // --- // consumes: [application/json] // produces: [application/json] diff --git a/api/user.go b/api/user.go index 61c0966..f5272dd 100644 --- a/api/user.go +++ b/api/user.go @@ -69,6 +69,8 @@ type UserAPI struct { // // Return all users. // +// Requires elevated authentication. +// // --- // produces: [application/json] // security: [clientTokenAuthorizationHeader: [], clientTokenHeader: [], clientTokenQuery: [], basicAuth: []] @@ -105,6 +107,8 @@ func (a *UserAPI) GetUsers(ctx *gin.Context) { // // Return the current user. // +// Requires elevated authentication. +// // --- // produces: [application/json] // security: [clientTokenAuthorizationHeader: [], clientTokenHeader: [], clientTokenQuery: [], basicAuth: []] @@ -147,6 +151,8 @@ func (a *UserAPI) GetCurrentUser(ctx *gin.Context) { // With enabled registration: non admin users can be created without authentication. // With disabled registrations: users can only be created by admin users. // +// Requires elevated authentication. +// // --- // consumes: [application/json] // produces: [application/json] @@ -233,6 +239,8 @@ func (a *UserAPI) CreateUser(ctx *gin.Context) { // // Get a user. // +// Requires elevated authentication. +// // --- // consumes: [application/json] // produces: [application/json] @@ -284,6 +292,8 @@ func (a *UserAPI) GetUserByID(ctx *gin.Context) { // // Deletes a user. // +// Requires elevated authentication. +// // --- // produces: [application/json] // security: [clientTokenAuthorizationHeader: [], clientTokenHeader: [], clientTokenQuery: [], basicAuth: []] @@ -344,6 +354,8 @@ func (a *UserAPI) DeleteUserByID(ctx *gin.Context) { // // Update the password of the current user. // +// Requires elevated authentication. +// // --- // consumes: [application/json] // produces: [application/json] @@ -387,6 +399,8 @@ func (a *UserAPI) ChangePassword(ctx *gin.Context) { // // Update a user. // +// Requires elevated authentication. +// // --- // consumes: [application/json] // produces: [application/json] diff --git a/auth/authentication.go b/auth/authentication.go index 1dcd67d..6e7a9b7 100644 --- a/auth/authentication.go +++ b/auth/authentication.go @@ -15,6 +15,7 @@ type authState int const ( authStateSkip authState = iota authStateForbidden + authStateNotElevated authStateOk ) @@ -23,6 +24,8 @@ const ( cookieName = "gotify-client-token" ) +var timeNow = time.Now + // The Database interface for encapsulating database access. type Database interface { GetApplicationByToken(token string) (*model.Application, error) @@ -39,16 +42,20 @@ type Auth struct { SecureCookie bool } -// RequireAdmin returns a gin middleware which requires a client token or basic authentication header to be supplied -// with the request. Also the authenticated user must be an administrator. +// RequireElevatedAdmin requires an elevated client token or basic auth, the user must be an admin. func (a *Auth) RequireAdmin(ctx *gin.Context) { - a.evaluateOr401(ctx, a.user(true), a.client(true)) + a.evaluateOr401(ctx, a.user(true), a.client(true, true)) } // RequireClient returns a gin middleware which requires a client token or basic authentication header to be supplied // with the request. func (a *Auth) RequireClient(ctx *gin.Context) { - a.evaluateOr401(ctx, a.user(false), a.client(false)) + a.evaluateOr401(ctx, a.user(false), a.client(false, false)) +} + +// RequireElevatedClient requires an elevated client token or basic auth. +func (a *Auth) RequireElevatedClient(ctx *gin.Context) { + a.evaluateOr401(ctx, a.user(false), a.client(false, true)) } // RequireApplicationToken returns a gin middleware which requires an application token to be supplied with the request. @@ -69,7 +76,7 @@ func (a *Auth) RequireApplicationToken(ctx *gin.Context) { } func (a *Auth) Optional(ctx *gin.Context) { - if !a.evaluate(ctx, a.user(false), a.client(false)) { + if !a.evaluate(ctx, a.user(false), a.client(false, false)) { ctx.Next() } } @@ -85,6 +92,9 @@ func (a *Auth) evaluate(ctx *gin.Context, funcs ...func(ctx *gin.Context) (authS case authStateForbidden: a.abort403(ctx) return true + case authStateNotElevated: + ctx.AbortWithError(403, errors.New("session not elevated, use basic auth or call /client:elevate")) + return true case authStateOk: ctx.Next() return true @@ -127,7 +137,7 @@ func (a *Auth) user(requireAdmin bool) func(ctx *gin.Context) (authState, error) } } -func (a *Auth) client(requireAdmin bool) func(ctx *gin.Context) (authState, error) { +func (a *Auth) client(requireAdmin, requireElevated bool) func(ctx *gin.Context) (authState, error) { return func(ctx *gin.Context) (authState, error) { token, isCookie := a.readTokenFromRequest(ctx) if token == "" { @@ -142,7 +152,7 @@ func (a *Auth) client(requireAdmin bool) func(ctx *gin.Context) (authState, erro } RegisterClient(ctx, client) - now := time.Now() + now := timeNow() if client.LastUsed == nil || client.LastUsed.Add(5*time.Minute).Before(now) { if err := a.DB.UpdateClientTokensLastUsed([]string{client.Token}, &now); err != nil { return authStateSkip, err @@ -160,6 +170,10 @@ func (a *Auth) client(requireAdmin bool) func(ctx *gin.Context) (authState, erro } } + if requireElevated && (client.ElevatedUntil == nil || !now.Before(*client.ElevatedUntil)) { + return authStateNotElevated, nil + } + return authStateOk, nil } } @@ -178,7 +192,7 @@ func (a *Auth) application(ctx *gin.Context) (authState, error) { } RegisterApplication(ctx, app) - now := time.Now() + now := timeNow() if app.LastUsed == nil || app.LastUsed.Add(5*time.Minute).Before(now) { if err := a.DB.UpdateApplicationTokenLastUsed(app.Token, &now); err != nil { return authStateSkip, err diff --git a/auth/authentication_test.go b/auth/authentication_test.go index 154a67a..f47fd98 100644 --- a/auth/authentication_test.go +++ b/auth/authentication_test.go @@ -5,6 +5,7 @@ import ( "net/http" "net/http/httptest" "testing" + "time" "github.com/gin-gonic/gin" "github.com/gotify/server/v2/auth/password" @@ -30,12 +31,22 @@ func (s *AuthenticationSuite) SetupSuite() { s.DB = testdb.NewDB(s.T()) s.auth = &Auth{DB: s.DB} + now := time.Date(2025, 1, 1, 12, 0, 0, 0, time.UTC) + timeNow = func() time.Time { return now } + + elevated := now.Add(time.Hour) + expired := now.Add(-time.Hour) + s.DB.CreateUser(&model.User{ Name: "existing", Pass: password.CreatePassword("pw", 5), Admin: false, Applications: []model.Application{{Token: "apptoken", Name: "backup server1", Description: "irrelevant"}}, - Clients: []model.Client{{Token: "clienttoken", Name: "android phone1"}}, + Clients: []model.Client{ + {Token: "clienttoken", Name: "android phone1"}, + {Token: "clienttoken_elevated", Name: "elevated phone1", ElevatedUntil: &elevated}, + {Token: "clienttoken_expired", Name: "expired phone1", ElevatedUntil: &expired}, + }, }) s.DB.CreateUser(&model.User{ @@ -43,11 +54,15 @@ func (s *AuthenticationSuite) SetupSuite() { Pass: password.CreatePassword("pw", 5), Admin: true, Applications: []model.Application{{Token: "apptoken_admin", Name: "backup server2", Description: "irrelevant"}}, - Clients: []model.Client{{Token: "clienttoken_admin", Name: "android phone2"}}, + Clients: []model.Client{ + {Token: "clienttoken_admin", Name: "android phone2"}, + {Token: "clienttoken_admin_elevated", Name: "elevated phone2", ElevatedUntil: &elevated}, + }, }) } func (s *AuthenticationSuite) TearDownSuite() { + timeNow = time.Now s.DB.Close() } @@ -56,27 +71,53 @@ func (s *AuthenticationSuite) TestQueryToken() { s.assertQueryRequest("token", "ergerogerg", s.auth.RequireApplicationToken, 401) s.assertQueryRequest("token", "ergerogerg", s.auth.RequireClient, 401) s.assertQueryRequest("token", "ergerogerg", s.auth.RequireAdmin, 401) + s.assertQueryRequest("token", "ergerogerg", s.auth.RequireElevatedClient, 401) // not existing key s.assertQueryRequest("tokenx", "clienttoken", s.auth.RequireApplicationToken, 401) s.assertQueryRequest("tokenx", "clienttoken", s.auth.RequireClient, 401) s.assertQueryRequest("tokenx", "clienttoken", s.auth.RequireAdmin, 401) + s.assertQueryRequest("tokenx", "clienttoken", s.auth.RequireElevatedClient, 401) // apptoken s.assertQueryRequest("token", "apptoken", s.auth.RequireApplicationToken, 200) s.assertQueryRequest("token", "apptoken", s.auth.RequireClient, 401) s.assertQueryRequest("token", "apptoken", s.auth.RequireAdmin, 401) + s.assertQueryRequest("token", "apptoken", s.auth.RequireElevatedClient, 401) s.assertQueryRequest("token", "apptoken_admin", s.auth.RequireApplicationToken, 200) s.assertQueryRequest("token", "apptoken_admin", s.auth.RequireClient, 401) s.assertQueryRequest("token", "apptoken_admin", s.auth.RequireAdmin, 401) + s.assertQueryRequest("token", "apptoken_admin", s.auth.RequireElevatedClient, 401) - // clienttoken + // clienttoken (non-admin, not elevated) s.assertQueryRequest("token", "clienttoken", s.auth.RequireApplicationToken, 401) s.assertQueryRequest("token", "clienttoken", s.auth.RequireClient, 200) s.assertQueryRequest("token", "clienttoken", s.auth.RequireAdmin, 403) + s.assertQueryRequest("token", "clienttoken", s.auth.RequireElevatedClient, 403) + + // clienttoken_elevated (non-admin, elevated) + s.assertQueryRequest("token", "clienttoken_elevated", s.auth.RequireApplicationToken, 401) + s.assertQueryRequest("token", "clienttoken_elevated", s.auth.RequireClient, 200) + s.assertQueryRequest("token", "clienttoken_elevated", s.auth.RequireAdmin, 403) + s.assertQueryRequest("token", "clienttoken_elevated", s.auth.RequireElevatedClient, 200) + + // clienttoken_expired (non-admin, elevation expired) + s.assertQueryRequest("token", "clienttoken_expired", s.auth.RequireApplicationToken, 401) + s.assertQueryRequest("token", "clienttoken_expired", s.auth.RequireClient, 200) + s.assertQueryRequest("token", "clienttoken_expired", s.auth.RequireAdmin, 403) + s.assertQueryRequest("token", "clienttoken_expired", s.auth.RequireElevatedClient, 403) + + // clienttoken_admin (not elevated) s.assertQueryRequest("token", "clienttoken_admin", s.auth.RequireApplicationToken, 401) s.assertQueryRequest("token", "clienttoken_admin", s.auth.RequireClient, 200) - s.assertQueryRequest("token", "clienttoken_admin", s.auth.RequireAdmin, 200) + s.assertQueryRequest("token", "clienttoken_admin", s.auth.RequireAdmin, 403) + s.assertQueryRequest("token", "clienttoken_admin", s.auth.RequireElevatedClient, 403) + + // clienttoken_admin_elevated + s.assertQueryRequest("token", "clienttoken_admin_elevated", s.auth.RequireApplicationToken, 401) + s.assertQueryRequest("token", "clienttoken_admin_elevated", s.auth.RequireClient, 200) + s.assertQueryRequest("token", "clienttoken_admin_elevated", s.auth.RequireAdmin, 200) + s.assertQueryRequest("token", "clienttoken_admin_elevated", s.auth.RequireElevatedClient, 200) } func (s *AuthenticationSuite) assertQueryRequest(key, value string, f fMiddleware, code int) (ctx *gin.Context) { @@ -101,27 +142,47 @@ func (s *AuthenticationSuite) TestHeaderApiKeyToken() { s.assertHeaderRequest("X-Gotify-Key", "ergerogerg", s.auth.RequireApplicationToken, 401) s.assertHeaderRequest("X-Gotify-Key", "ergerogerg", s.auth.RequireClient, 401) s.assertHeaderRequest("X-Gotify-Key", "ergerogerg", s.auth.RequireAdmin, 401) + s.assertHeaderRequest("X-Gotify-Key", "ergerogerg", s.auth.RequireElevatedClient, 401) // not existing key s.assertHeaderRequest("X-Gotify-Keyx", "clienttoken", s.auth.RequireApplicationToken, 401) s.assertHeaderRequest("X-Gotify-Keyx", "clienttoken", s.auth.RequireClient, 401) s.assertHeaderRequest("X-Gotify-Keyx", "clienttoken", s.auth.RequireAdmin, 401) + s.assertHeaderRequest("X-Gotify-Keyx", "clienttoken", s.auth.RequireElevatedClient, 401) // apptoken s.assertHeaderRequest("X-Gotify-Key", "apptoken", s.auth.RequireApplicationToken, 200) s.assertHeaderRequest("X-Gotify-Key", "apptoken", s.auth.RequireClient, 401) s.assertHeaderRequest("X-Gotify-Key", "apptoken", s.auth.RequireAdmin, 401) + s.assertHeaderRequest("X-Gotify-Key", "apptoken", s.auth.RequireElevatedClient, 401) s.assertHeaderRequest("X-Gotify-Key", "apptoken_admin", s.auth.RequireApplicationToken, 200) s.assertHeaderRequest("X-Gotify-Key", "apptoken_admin", s.auth.RequireClient, 401) s.assertHeaderRequest("X-Gotify-Key", "apptoken_admin", s.auth.RequireAdmin, 401) + s.assertHeaderRequest("X-Gotify-Key", "apptoken_admin", s.auth.RequireElevatedClient, 401) - // clienttoken + // clienttoken (non-admin, not elevated) s.assertHeaderRequest("X-Gotify-Key", "clienttoken", s.auth.RequireApplicationToken, 401) s.assertHeaderRequest("X-Gotify-Key", "clienttoken", s.auth.RequireClient, 200) s.assertHeaderRequest("X-Gotify-Key", "clienttoken", s.auth.RequireAdmin, 403) + s.assertHeaderRequest("X-Gotify-Key", "clienttoken", s.auth.RequireElevatedClient, 403) + + // clienttoken_elevated (non-admin, elevated) + s.assertHeaderRequest("X-Gotify-Key", "clienttoken_elevated", s.auth.RequireApplicationToken, 401) + s.assertHeaderRequest("X-Gotify-Key", "clienttoken_elevated", s.auth.RequireClient, 200) + s.assertHeaderRequest("X-Gotify-Key", "clienttoken_elevated", s.auth.RequireAdmin, 403) + s.assertHeaderRequest("X-Gotify-Key", "clienttoken_elevated", s.auth.RequireElevatedClient, 200) + + // clienttoken_admin (not elevated) s.assertHeaderRequest("X-Gotify-Key", "clienttoken_admin", s.auth.RequireApplicationToken, 401) s.assertHeaderRequest("X-Gotify-Key", "clienttoken_admin", s.auth.RequireClient, 200) - s.assertHeaderRequest("X-Gotify-Key", "clienttoken_admin", s.auth.RequireAdmin, 200) + s.assertHeaderRequest("X-Gotify-Key", "clienttoken_admin", s.auth.RequireAdmin, 403) + s.assertHeaderRequest("X-Gotify-Key", "clienttoken_admin", s.auth.RequireElevatedClient, 403) + + // clienttoken_admin_elevated + s.assertHeaderRequest("X-Gotify-Key", "clienttoken_admin_elevated", s.auth.RequireApplicationToken, 401) + s.assertHeaderRequest("X-Gotify-Key", "clienttoken_admin_elevated", s.auth.RequireClient, 200) + s.assertHeaderRequest("X-Gotify-Key", "clienttoken_admin_elevated", s.auth.RequireAdmin, 200) + s.assertHeaderRequest("X-Gotify-Key", "clienttoken_admin_elevated", s.auth.RequireElevatedClient, 200) } func (s *AuthenticationSuite) TestAuthorizationHeaderApiKeyToken() { @@ -129,58 +190,84 @@ func (s *AuthenticationSuite) TestAuthorizationHeaderApiKeyToken() { s.assertHeaderRequest("Authorization", "Bearer ergerogerg", s.auth.RequireApplicationToken, 401) s.assertHeaderRequest("Authorization", "Bearer ergerogerg", s.auth.RequireClient, 401) s.assertHeaderRequest("Authorization", "Bearer ergerogerg", s.auth.RequireAdmin, 401) + s.assertHeaderRequest("Authorization", "Bearer ergerogerg", s.auth.RequireElevatedClient, 401) // no authentication schema s.assertHeaderRequest("Authorization", "ergerogerg", s.auth.RequireApplicationToken, 401) s.assertHeaderRequest("Authorization", "ergerogerg", s.auth.RequireClient, 401) s.assertHeaderRequest("Authorization", "ergerogerg", s.auth.RequireAdmin, 401) + s.assertHeaderRequest("Authorization", "ergerogerg", s.auth.RequireElevatedClient, 401) // wrong authentication schema s.assertHeaderRequest("Authorization", "ApiKeyx clienttoken", s.auth.RequireApplicationToken, 401) s.assertHeaderRequest("Authorization", "ApiKeyx clienttoken", s.auth.RequireClient, 401) s.assertHeaderRequest("Authorization", "ApiKeyx clienttoken", s.auth.RequireAdmin, 401) + s.assertHeaderRequest("Authorization", "ApiKeyx clienttoken", s.auth.RequireElevatedClient, 401) // Authorization Bearer apptoken s.assertHeaderRequest("Authorization", "Bearer apptoken", s.auth.RequireApplicationToken, 200) s.assertHeaderRequest("Authorization", "Bearer apptoken", s.auth.RequireClient, 401) s.assertHeaderRequest("Authorization", "Bearer apptoken", s.auth.RequireAdmin, 401) + s.assertHeaderRequest("Authorization", "Bearer apptoken", s.auth.RequireElevatedClient, 401) s.assertHeaderRequest("Authorization", "Bearer apptoken_admin", s.auth.RequireApplicationToken, 200) s.assertHeaderRequest("Authorization", "Bearer apptoken_admin", s.auth.RequireClient, 401) s.assertHeaderRequest("Authorization", "Bearer apptoken_admin", s.auth.RequireAdmin, 401) + s.assertHeaderRequest("Authorization", "Bearer apptoken_admin", s.auth.RequireElevatedClient, 401) - // Authorization Bearer clienttoken + // Authorization Bearer clienttoken (non-admin, not elevated) s.assertHeaderRequest("Authorization", "Bearer clienttoken", s.auth.RequireApplicationToken, 401) s.assertHeaderRequest("Authorization", "Bearer clienttoken", s.auth.RequireClient, 200) s.assertHeaderRequest("Authorization", "Bearer clienttoken", s.auth.RequireAdmin, 403) + s.assertHeaderRequest("Authorization", "Bearer clienttoken", s.auth.RequireElevatedClient, 403) + + // Authorization Bearer clienttoken_elevated (non-admin, elevated) + s.assertHeaderRequest("Authorization", "Bearer clienttoken_elevated", s.auth.RequireApplicationToken, 401) + s.assertHeaderRequest("Authorization", "Bearer clienttoken_elevated", s.auth.RequireClient, 200) + s.assertHeaderRequest("Authorization", "Bearer clienttoken_elevated", s.auth.RequireAdmin, 403) + s.assertHeaderRequest("Authorization", "Bearer clienttoken_elevated", s.auth.RequireElevatedClient, 200) + + // Authorization bearer clienttoken_admin (not elevated) s.assertHeaderRequest("Authorization", "bearer clienttoken_admin", s.auth.RequireApplicationToken, 401) s.assertHeaderRequest("Authorization", "bearer clienttoken_admin", s.auth.RequireClient, 200) - s.assertHeaderRequest("Authorization", "bearer clienttoken_admin", s.auth.RequireAdmin, 200) + s.assertHeaderRequest("Authorization", "bearer clienttoken_admin", s.auth.RequireAdmin, 403) + s.assertHeaderRequest("Authorization", "bearer clienttoken_admin", s.auth.RequireElevatedClient, 403) + + // Authorization Bearer clienttoken_admin_elevated + s.assertHeaderRequest("Authorization", "Bearer clienttoken_admin_elevated", s.auth.RequireApplicationToken, 401) + s.assertHeaderRequest("Authorization", "Bearer clienttoken_admin_elevated", s.auth.RequireClient, 200) + s.assertHeaderRequest("Authorization", "Bearer clienttoken_admin_elevated", s.auth.RequireAdmin, 200) + s.assertHeaderRequest("Authorization", "Bearer clienttoken_admin_elevated", s.auth.RequireElevatedClient, 200) } func (s *AuthenticationSuite) TestBasicAuth() { s.assertHeaderRequest("Authorization", "Basic ergerogerg", s.auth.RequireApplicationToken, 401) s.assertHeaderRequest("Authorization", "Basic ergerogerg", s.auth.RequireClient, 401) s.assertHeaderRequest("Authorization", "Basic ergerogerg", s.auth.RequireAdmin, 401) + s.assertHeaderRequest("Authorization", "Basic ergerogerg", s.auth.RequireElevatedClient, 401) // user existing:pw s.assertHeaderRequest("Authorization", "Basic ZXhpc3Rpbmc6cHc=", s.auth.RequireApplicationToken, 403) s.assertHeaderRequest("Authorization", "Basic ZXhpc3Rpbmc6cHc=", s.auth.RequireClient, 200) s.assertHeaderRequest("Authorization", "Basic ZXhpc3Rpbmc6cHc=", s.auth.RequireAdmin, 403) + s.assertHeaderRequest("Authorization", "Basic ZXhpc3Rpbmc6cHc=", s.auth.RequireElevatedClient, 200) // user admin:pw s.assertHeaderRequest("Authorization", "Basic YWRtaW46cHc=", s.auth.RequireApplicationToken, 403) s.assertHeaderRequest("Authorization", "Basic YWRtaW46cHc=", s.auth.RequireClient, 200) s.assertHeaderRequest("Authorization", "Basic YWRtaW46cHc=", s.auth.RequireAdmin, 200) + s.assertHeaderRequest("Authorization", "Basic YWRtaW46cHc=", s.auth.RequireElevatedClient, 200) // user admin:pwx s.assertHeaderRequest("Authorization", "Basic YWRtaW46cHd4", s.auth.RequireApplicationToken, 401) s.assertHeaderRequest("Authorization", "Basic YWRtaW46cHd4", s.auth.RequireClient, 401) s.assertHeaderRequest("Authorization", "Basic YWRtaW46cHd4", s.auth.RequireAdmin, 401) + s.assertHeaderRequest("Authorization", "Basic YWRtaW46cHd4", s.auth.RequireElevatedClient, 401) // user notexisting:pw s.assertHeaderRequest("Authorization", "Basic bm90ZXhpc3Rpbmc6cHc=", s.auth.RequireApplicationToken, 401) s.assertHeaderRequest("Authorization", "Basic bm90ZXhpc3Rpbmc6cHc=", s.auth.RequireClient, 401) s.assertHeaderRequest("Authorization", "Basic bm90ZXhpc3Rpbmc6cHc=", s.auth.RequireAdmin, 401) + s.assertHeaderRequest("Authorization", "Basic bm90ZXhpc3Rpbmc6cHc=", s.auth.RequireElevatedClient, 401) } func (s *AuthenticationSuite) TestOptionalAuth() { @@ -216,18 +303,35 @@ func (s *AuthenticationSuite) TestCookieToken() { s.assertCookieRequest("ergerogerg", s.auth.RequireApplicationToken, 401) s.assertCookieRequest("ergerogerg", s.auth.RequireClient, 401) s.assertCookieRequest("ergerogerg", s.auth.RequireAdmin, 401) + s.assertCookieRequest("ergerogerg", s.auth.RequireElevatedClient, 401) // apptoken s.assertCookieRequest("apptoken", s.auth.RequireApplicationToken, 200) s.assertCookieRequest("apptoken", s.auth.RequireClient, 401) s.assertCookieRequest("apptoken", s.auth.RequireAdmin, 401) + s.assertCookieRequest("apptoken", s.auth.RequireElevatedClient, 401) - // clienttoken + // clienttoken (non-admin, not elevated) s.assertCookieRequest("clienttoken", s.auth.RequireApplicationToken, 401) s.assertCookieRequest("clienttoken", s.auth.RequireClient, 200) s.assertCookieRequest("clienttoken", s.auth.RequireAdmin, 403) + s.assertCookieRequest("clienttoken", s.auth.RequireElevatedClient, 403) + + // clienttoken_elevated (non-admin, elevated) + s.assertCookieRequest("clienttoken_elevated", s.auth.RequireApplicationToken, 401) + s.assertCookieRequest("clienttoken_elevated", s.auth.RequireClient, 200) + s.assertCookieRequest("clienttoken_elevated", s.auth.RequireAdmin, 403) + s.assertCookieRequest("clienttoken_elevated", s.auth.RequireElevatedClient, 200) + + // clienttoken_admin (not elevated) s.assertCookieRequest("clienttoken_admin", s.auth.RequireClient, 200) - s.assertCookieRequest("clienttoken_admin", s.auth.RequireAdmin, 200) + s.assertCookieRequest("clienttoken_admin", s.auth.RequireAdmin, 403) + s.assertCookieRequest("clienttoken_admin", s.auth.RequireElevatedClient, 403) + + // clienttoken_admin_elevated + s.assertCookieRequest("clienttoken_admin_elevated", s.auth.RequireClient, 200) + s.assertCookieRequest("clienttoken_admin_elevated", s.auth.RequireAdmin, 200) + s.assertCookieRequest("clienttoken_admin_elevated", s.auth.RequireElevatedClient, 200) } func (s *AuthenticationSuite) assertCookieRequest(token string, f fMiddleware, code int) (ctx *gin.Context) { diff --git a/docs/spec.json b/docs/spec.json index 01c3086..32ea034 100644 --- a/docs/spec.json +++ b/docs/spec.json @@ -231,6 +231,7 @@ "basicAuth": [] } ], + "description": "Requires elevated authentication.", "consumes": [ "application/json" ], @@ -1075,6 +1076,7 @@ "basicAuth": [] } ], + "description": "Requires elevated authentication.", "consumes": [ "application/json" ], @@ -1143,6 +1145,7 @@ "basicAuth": [] } ], + "description": "Requires elevated authentication.", "consumes": [ "application/json" ], @@ -1206,6 +1209,7 @@ "basicAuth": [] } ], + "description": "Requires elevated authentication.", "produces": [ "application/json" ], @@ -1252,6 +1256,7 @@ "basicAuth": [] } ], + "description": "Requires elevated authentication.", "consumes": [ "application/json" ], @@ -2079,6 +2084,7 @@ "basicAuth": [] } ], + "description": "Requires elevated authentication.", "produces": [ "application/json" ], @@ -2126,7 +2132,7 @@ "basicAuth": [] } ], - "description": "With enabled registration: non admin users can be created without authentication.\nWith disabled registrations: users can only be created by admin users.", + "description": "With enabled registration: non admin users can be created without authentication.\nWith disabled registrations: users can only be created by admin users.\n\nRequires elevated authentication.", "consumes": [ "application/json" ], @@ -2193,6 +2199,7 @@ "basicAuth": [] } ], + "description": "Requires elevated authentication.", "consumes": [ "application/json" ], @@ -2262,6 +2269,7 @@ "basicAuth": [] } ], + "description": "Requires elevated authentication.", "consumes": [ "application/json" ], @@ -2340,6 +2348,7 @@ "basicAuth": [] } ], + "description": "Requires elevated authentication.", "produces": [ "application/json" ], diff --git a/router/router.go b/router/router.go index f13bc2e..2950b42 100644 --- a/router/router.go +++ b/router/router.go @@ -186,21 +186,14 @@ func Create(db *database.GormDatabase, vInfo *model.VersionInfo, conf *config.Co app := clientAuth.Group("/application") { app.GET("", applicationHandler.GetApplications) - app.POST("", applicationHandler.CreateApplication) - app.POST("/:id/image", applicationHandler.UploadApplicationImage) - app.DELETE("/:id/image", applicationHandler.RemoveApplicationImage) - app.PUT("/:id", applicationHandler.UpdateApplication) - app.DELETE("/:id", applicationHandler.DeleteApplication) - tokenMessage := app.Group("/:id/message") { tokenMessage.GET("", messageHandler.GetMessagesWithApplication) - tokenMessage.DELETE("", messageHandler.DeleteMessageWithApplication) } } @@ -208,43 +201,37 @@ func Create(db *database.GormDatabase, vInfo *model.VersionInfo, conf *config.Co client := clientAuth.Group("/client") { client.GET("", clientHandler.GetClients) - client.POST("", clientHandler.CreateClient) - - client.DELETE("/:id", clientHandler.DeleteClient) - client.PUT("/:id", clientHandler.UpdateClient) } - client.POST("/client:elevate", clientHandler.ElevateClient) message := clientAuth.Group("/message") { message.GET("", messageHandler.GetMessages) - message.DELETE("", messageHandler.DeleteMessages) - message.DELETE("/:id", messageHandler.DeleteMessage) } clientAuth.GET("/stream", streamHandler.Handle) - clientAuth.GET("current/user", userHandler.GetCurrentUser) - - clientAuth.POST("current/user/password", userHandler.ChangePassword) - clientAuth.POST("/auth/logout", sessionHandler.Logout) } + clientElevated := g.Group("") + { + clientElevated.Use(authentication.RequireElevatedClient) + clientElevated.POST("/client:elevate", clientHandler.ElevateClient) + clientElevated.DELETE("/application/:id", applicationHandler.DeleteApplication) + clientElevated.DELETE("/client/:id", clientHandler.DeleteClient) + clientElevated.POST("/current/user/password", userHandler.ChangePassword) + } + authAdmin := g.Group("/user") { authAdmin.Use(authentication.RequireAdmin) - authAdmin.GET("", userHandler.GetUsers) - authAdmin.DELETE("/:id", userHandler.DeleteUserByID) - authAdmin.GET("/:id", userHandler.GetUserByID) - authAdmin.POST("/:id", userHandler.UpdateUserByID) } return g, streamHandler.Close