mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-07-20 20:44:51 -04:00
allow static secret access to global notification endpoints
Signed-off-by: jkoberg <jkoberg@owncloud.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
Enhancement: Allow calling the global notifications endpoints when knowing a static secret
|
||||
|
||||
The global notifications POST and DELETE endpoints (used only for deprovision notifications at the moment) can now be called by adding a static secret to the header. Admins can still call this endpoint without knowing the secret
|
||||
|
||||
https://github.com/owncloud/ocis/pull/6946
|
||||
@@ -28,7 +28,9 @@ type Config struct {
|
||||
Events Events `yaml:"events"`
|
||||
Persistence Persistence `yaml:"persistence"`
|
||||
|
||||
DisableSSE bool `yaml:"disable_sse" env:"USERLOG_DISABLE_SSE" desc:"Disables server-sent events (sse). When disabled, clients will no longer be able to connect to the sse endpoint."`
|
||||
DisableSSE bool `yaml:"disable_sse" env:"OCIS_DISABLE_SSE,USERLOG_DISABLE_SSE" desc:"Disables server-sent events (sse). When disabled, clients will no longer be able to connect to the sse endpoint."`
|
||||
|
||||
GlobalNotificationsSecret string `yaml:"global_notifications_secret" env:"USERLOG_GLOBAL_NOTIFICATIONS_SECRET" desc:"The secret to secure the global notifications endpoint. Only users knowing that secret and system admins can call the global notifications POST/DELETE endpoints"`
|
||||
|
||||
Context context.Context `yaml:"-"`
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/cs3org/reva/v2/pkg/appctx"
|
||||
revactx "github.com/cs3org/reva/v2/pkg/ctx"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/log"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/roles"
|
||||
"github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode"
|
||||
settings "github.com/owncloud/ocis/v2/services/settings/pkg/service/v0"
|
||||
@@ -202,41 +204,60 @@ type PostEventsRequest struct {
|
||||
Data map[string]string `json:"data"`
|
||||
}
|
||||
|
||||
// RequireAdmin middleware is used to require the user in context to be an admin / have account management permissions
|
||||
func RequireAdmin(rm *roles.Manager, logger log.Logger) func(next http.HandlerFunc) http.HandlerFunc {
|
||||
// RequireAdminOrSecret middleware allows only requests if the requesting user is an admin or knows the static secret
|
||||
func RequireAdminOrSecret(rm *roles.Manager, secret string) func(http.HandlerFunc) http.HandlerFunc {
|
||||
return func(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
u, ok := revactx.ContextGetUser(r.Context())
|
||||
if !ok || u.GetId().GetOpaqueId() == "" {
|
||||
logger.Error().Str("userid", u.Id.OpaqueId).Msg("user not in context")
|
||||
errorcode.AccessDenied.Render(w, r, http.StatusInternalServerError, "")
|
||||
return
|
||||
}
|
||||
// get roles from context
|
||||
roleIDs, ok := roles.ReadRoleIDsFromContext(r.Context())
|
||||
if !ok {
|
||||
logger.Debug().Str("userid", u.Id.OpaqueId).Msg("No roles in context, contacting settings service")
|
||||
var err error
|
||||
roleIDs, err = rm.FindRoleIDsForUser(r.Context(), u.Id.OpaqueId)
|
||||
if err != nil {
|
||||
logger.Err(err).Str("userid", u.Id.OpaqueId).Msg("failed to get roles for user")
|
||||
errorcode.AccessDenied.Render(w, r, http.StatusInternalServerError, "")
|
||||
return
|
||||
}
|
||||
if len(roleIDs) == 0 {
|
||||
logger.Err(err).Str("userid", u.Id.OpaqueId).Msg("user has no roles")
|
||||
errorcode.AccessDenied.Render(w, r, http.StatusInternalServerError, "")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// check if permission is present in roles of the authenticated account
|
||||
if rm.FindPermissionByID(r.Context(), roleIDs, settings.AccountManagementPermissionID) != nil {
|
||||
// allow bypassing admin requirement by sending the correct secret
|
||||
if secret != "" && r.Header.Get("secret") == secret {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
errorcode.AccessDenied.Render(w, r, http.StatusNotFound, "Forbidden")
|
||||
isadmin, err := isAdmin(r.Context(), rm)
|
||||
if err != nil {
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "")
|
||||
return
|
||||
}
|
||||
|
||||
if isadmin {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, "Not found")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// isAdmin determines if the user in the context is an admin / has account management permissions
|
||||
func isAdmin(ctx context.Context, rm *roles.Manager) (bool, error) {
|
||||
logger := appctx.GetLogger(ctx)
|
||||
|
||||
u, ok := revactx.ContextGetUser(ctx)
|
||||
uid := u.GetId().GetOpaqueId()
|
||||
if !ok || uid == "" {
|
||||
logger.Error().Str("userid", uid).Msg("user not in context")
|
||||
return false, errors.New("no user in context")
|
||||
}
|
||||
// get roles from context
|
||||
roleIDs, ok := roles.ReadRoleIDsFromContext(ctx)
|
||||
if !ok {
|
||||
logger.Debug().Str("userid", uid).Msg("No roles in context, contacting settings service")
|
||||
var err error
|
||||
roleIDs, err = rm.FindRoleIDsForUser(ctx, uid)
|
||||
if err != nil {
|
||||
logger.Err(err).Str("userid", uid).Msg("failed to get roles for user")
|
||||
return false, err
|
||||
}
|
||||
|
||||
if len(roleIDs) == 0 {
|
||||
logger.Err(err).Str("userid", uid).Msg("user has no roles")
|
||||
return false, errors.New("user has no roles")
|
||||
}
|
||||
}
|
||||
|
||||
// check if permission is present in roles of the authenticated account
|
||||
return rm.FindPermissionByID(ctx, roleIDs, settings.AccountManagementPermissionID) != nil, nil
|
||||
}
|
||||
|
||||
@@ -98,8 +98,8 @@ func NewUserlogService(opts ...Option) (*UserlogService, error) {
|
||||
ul.m.Route("/ocs/v2.php/apps/notifications/api/v1/notifications", func(r chi.Router) {
|
||||
r.Get("/", ul.HandleGetEvents)
|
||||
r.Delete("/", ul.HandleDeleteEvents)
|
||||
r.Post("/global", RequireAdmin(&m, ul.log)(ul.HandlePostGlobalEvent))
|
||||
r.Delete("/global", RequireAdmin(&m, ul.log)(ul.HandleDeleteGlobalEvent))
|
||||
r.Post("/global", RequireAdminOrSecret(&m, o.Config.GlobalNotificationsSecret)(ul.HandlePostGlobalEvent))
|
||||
r.Delete("/global", RequireAdminOrSecret(&m, o.Config.GlobalNotificationsSecret)(ul.HandleDeleteGlobalEvent))
|
||||
|
||||
if !ul.cfg.DisableSSE {
|
||||
r.Get("/sse", ul.HandleSSE)
|
||||
|
||||
Reference in New Issue
Block a user