mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-24 07:39:07 +02:00
merge conflict and onDenied handler
This commit is contained in:
@@ -21,12 +21,13 @@ import (
|
||||
"github.com/netbirdio/netbird/shared/management/status"
|
||||
)
|
||||
|
||||
// AuthErrorHandler is called when an auth error occurs during permission validation.
|
||||
// If it returns true, the error is considered handled and the default error response is skipped.
|
||||
type AuthErrorHandler func(w http.ResponseWriter, r *http.Request, userAuth *auth.UserAuth, err error) bool
|
||||
// PermissionDeniedHandler is called when the user's role does not grant the requested operation.
|
||||
// It is not called for validation failures such as blocked users or foreign accounts.
|
||||
// If it returns true, the request is considered handled and the default 403 response is skipped.
|
||||
type PermissionDeniedHandler func(w http.ResponseWriter, r *http.Request, userAuth *auth.UserAuth) bool
|
||||
|
||||
type Manager interface {
|
||||
WithPermission(module modules.Module, operation operations.Operation, handlerFunc func(w http.ResponseWriter, r *http.Request, auth *auth.UserAuth), authErrHandler ...AuthErrorHandler) http.HandlerFunc
|
||||
WithPermission(module modules.Module, operation operations.Operation, handlerFunc func(w http.ResponseWriter, r *http.Request, auth *auth.UserAuth), onDenied ...PermissionDeniedHandler) http.HandlerFunc
|
||||
ValidateUserPermissions(ctx context.Context, accountID, userID string, module modules.Module, operation operations.Operation) (bool, context.Context, error)
|
||||
ValidateRoleModuleAccess(ctx context.Context, accountID string, role roles.RolePermissions, module modules.Module, operation operations.Operation) bool
|
||||
ValidateAccountAccess(ctx context.Context, accountID string, user *types.User, allowOwnerAndAdmin bool) (context.Context, error)
|
||||
@@ -45,18 +46,30 @@ func NewManager(store store.Store) Manager {
|
||||
}
|
||||
}
|
||||
|
||||
// WithPermission wraps an HTTP handler with permission checking logic.
|
||||
// An optional AuthErrorHandler can be provided to intercept auth errors before the default response is written.
|
||||
// The wrapped handler receives a request whose context is enriched by the permission validation.
|
||||
func (m *managerImpl) WithPermission(
|
||||
module modules.Module,
|
||||
operation operations.Operation,
|
||||
handlerFunc func(w http.ResponseWriter, r *http.Request, auth *auth.UserAuth),
|
||||
authErrHandler ...AuthErrorHandler,
|
||||
onDenied ...PermissionDeniedHandler,
|
||||
) http.HandlerFunc {
|
||||
var onAuthErr AuthErrorHandler
|
||||
if len(authErrHandler) > 0 {
|
||||
onAuthErr = authErrHandler[0]
|
||||
return WithPermission(m, module, operation, handlerFunc, onDenied...)
|
||||
}
|
||||
|
||||
// WithPermission wraps an HTTP handler with permission checking performed by the given manager.
|
||||
// Implementations embedding another Manager must route their own WithPermission through this
|
||||
// function so that their ValidateUserPermissions override is the one consulted.
|
||||
// An optional PermissionDeniedHandler can serve a reduced, self-scoped response when the role denies the operation.
|
||||
// The wrapped handler receives a request whose context is enriched by the permission validation.
|
||||
func WithPermission(
|
||||
m Manager,
|
||||
module modules.Module,
|
||||
operation operations.Operation,
|
||||
handlerFunc func(w http.ResponseWriter, r *http.Request, auth *auth.UserAuth),
|
||||
onDenied ...PermissionDeniedHandler,
|
||||
) http.HandlerFunc {
|
||||
var deniedHandler PermissionDeniedHandler
|
||||
if len(onDenied) > 0 {
|
||||
deniedHandler = onDenied[0]
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -68,23 +81,19 @@ func (m *managerImpl) WithPermission(
|
||||
}
|
||||
|
||||
allowed, ctx, err := m.ValidateUserPermissions(r.Context(), userAuth.AccountId, userAuth.UserId, module, operation)
|
||||
enriched := r.WithContext(ctx)
|
||||
if err != nil {
|
||||
if onAuthErr != nil && onAuthErr(w, enriched, &userAuth, err) {
|
||||
return
|
||||
}
|
||||
log.WithContext(ctx).Errorf("failed to validate permissions for user %s on account %s: %v", userAuth.UserId, userAuth.AccountId, err)
|
||||
util.WriteError(ctx, status.NewPermissionValidationError(err), w)
|
||||
return
|
||||
}
|
||||
|
||||
enriched := r.WithContext(ctx)
|
||||
if !allowed {
|
||||
permErr := status.NewPermissionDeniedError()
|
||||
if onAuthErr != nil && onAuthErr(w, enriched, &userAuth, permErr) {
|
||||
if deniedHandler != nil && deniedHandler(w, enriched, &userAuth) {
|
||||
return
|
||||
}
|
||||
log.WithContext(ctx).Tracef("user %s on account %s is not allowed to %s in %s", userAuth.UserId, userAuth.AccountId, operation, module)
|
||||
util.WriteError(ctx, permErr, w)
|
||||
util.WriteError(ctx, status.NewPermissionDeniedError(), w)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -120,10 +120,10 @@ func (mr *MockManagerMockRecorder) ValidateUserPermissions(ctx, accountID, userI
|
||||
}
|
||||
|
||||
// WithPermission mocks base method.
|
||||
func (m *MockManager) WithPermission(module modules.Module, operation operations.Operation, handlerFunc func(http.ResponseWriter, *http.Request, *auth.UserAuth), authErrHandler ...AuthErrorHandler) http.HandlerFunc {
|
||||
func (m *MockManager) WithPermission(module modules.Module, operation operations.Operation, handlerFunc func(http.ResponseWriter, *http.Request, *auth.UserAuth), onDenied ...PermissionDeniedHandler) http.HandlerFunc {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []any{module, operation, handlerFunc}
|
||||
for _, a := range authErrHandler {
|
||||
for _, a := range onDenied {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "WithPermission", varargs...)
|
||||
@@ -132,8 +132,8 @@ func (m *MockManager) WithPermission(module modules.Module, operation operations
|
||||
}
|
||||
|
||||
// WithPermission indicates an expected call of WithPermission.
|
||||
func (mr *MockManagerMockRecorder) WithPermission(module, operation, handlerFunc any, authErrHandler ...any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) WithPermission(module, operation, handlerFunc any, onDenied ...any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]any{module, operation, handlerFunc}, authErrHandler...)
|
||||
varargs := append([]any{module, operation, handlerFunc}, onDenied...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WithPermission", reflect.TypeOf((*MockManager)(nil).WithPermission), varargs...)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ type Manager interface {
|
||||
CreateService(ctx context.Context, accountID, userID string, service *Service) (*Service, error)
|
||||
UpdateService(ctx context.Context, accountID, userID string, service *Service) (*Service, error)
|
||||
DeleteService(ctx context.Context, accountID, userID, serviceID string) error
|
||||
DeleteAllServices(ctx context.Context, accountID, userID string) error
|
||||
SetCertificateIssuedAt(ctx context.Context, accountID, serviceID string) error
|
||||
SetStatus(ctx context.Context, accountID, serviceID string, status Status) error
|
||||
ReloadAllServicesForAccount(ctx context.Context, accountID string) error
|
||||
|
||||
@@ -85,20 +85,6 @@ func (mr *MockManagerMockRecorder) DeleteAccountCluster(ctx, accountID, userID,
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAccountCluster", reflect.TypeOf((*MockManager)(nil).DeleteAccountCluster), ctx, accountID, userID, clusterAddress)
|
||||
}
|
||||
|
||||
// DeleteAllServices mocks base method.
|
||||
func (m *MockManager) DeleteAllServices(ctx context.Context, accountID, userID string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteAllServices", ctx, accountID, userID)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteAllServices indicates an expected call of DeleteAllServices.
|
||||
func (mr *MockManagerMockRecorder) DeleteAllServices(ctx, accountID, userID any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAllServices", reflect.TypeOf((*MockManager)(nil).DeleteAllServices), ctx, accountID, userID)
|
||||
}
|
||||
|
||||
// DeleteService mocks base method.
|
||||
func (m *MockManager) DeleteService(ctx context.Context, accountID, userID, serviceID string) error {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
@@ -20,15 +20,13 @@ import (
|
||||
)
|
||||
|
||||
type handler struct {
|
||||
manager rpservice.Manager
|
||||
permissionsManager permissions.Manager
|
||||
manager rpservice.Manager
|
||||
}
|
||||
|
||||
// RegisterEndpoints registers all service HTTP endpoints.
|
||||
func RegisterEndpoints(manager rpservice.Manager, domainManager domainmanager.Manager, accessLogsManager accesslogs.Manager, permissionsManager permissions.Manager, router *mux.Router) {
|
||||
h := &handler{
|
||||
manager: manager,
|
||||
permissionsManager: permissionsManager,
|
||||
manager: manager,
|
||||
}
|
||||
|
||||
domainRouter := router.PathPrefix("/reverse-proxies").Subrouter()
|
||||
|
||||
@@ -842,43 +842,6 @@ func (m *Manager) DeleteService(ctx context.Context, accountID, userID, serviceI
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) DeleteAllServices(ctx context.Context, accountID, userID string) error {
|
||||
var services []*service.Service
|
||||
err := m.store.ExecuteInTransaction(ctx, func(transaction store.Store) error {
|
||||
var err error
|
||||
services, err = transaction.GetAccountServices(ctx, store.LockingStrengthUpdate, accountID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, svc := range services {
|
||||
if err = transaction.DeleteServiceTargets(ctx, accountID, svc.ID); err != nil {
|
||||
return fmt.Errorf("failed to delete service targets: %w", err)
|
||||
}
|
||||
|
||||
if err = transaction.DeleteService(ctx, accountID, svc.ID); err != nil {
|
||||
return fmt.Errorf("failed to delete service: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
oidcCfg := m.proxyController.GetOIDCValidationConfig()
|
||||
|
||||
for _, svc := range services {
|
||||
m.accountManager.StoreEvent(ctx, userID, svc.ID, accountID, activity.ServiceDeleted, svc.EventMeta())
|
||||
m.proxyController.SendServiceUpdateToCluster(ctx, accountID, svc.ToProtoMapping(service.Delete, "", oidcCfg), svc.ProxyCluster)
|
||||
}
|
||||
|
||||
m.accountManager.UpdateAccountPeers(ctx, accountID, types.UpdateReason{Resource: types.UpdateResourceService, Operation: types.UpdateOperationDelete})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetCertificateIssuedAt sets the certificate issued timestamp to the current time.
|
||||
// Call this when receiving a gRPC notification that the certificate was issued.
|
||||
func (m *Manager) SetCertificateIssuedAt(ctx context.Context, accountID, serviceID string) error {
|
||||
|
||||
@@ -1052,30 +1052,6 @@ func TestDeleteService_DeletesEphemeralExpose(t *testing.T) {
|
||||
assert.NoError(t, err, "new expose should succeed after API delete")
|
||||
}
|
||||
|
||||
func TestDeleteAllServices_DeletesEphemeralExposes(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
mgr, _ := setupIntegrationTest(t)
|
||||
|
||||
for i := range 3 {
|
||||
_, err := mgr.CreateServiceFromPeer(ctx, testAccountID, testPeerID, &rpservice.ExposeServiceRequest{
|
||||
Port: uint16(8080 + i),
|
||||
Mode: "http",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
count, err := mgr.store.CountEphemeralServicesByPeer(ctx, store.LockingStrengthNone, testAccountID, testPeerID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(3), count, "all ephemeral services should exist")
|
||||
|
||||
err = mgr.DeleteAllServices(ctx, testAccountID, testUserID)
|
||||
require.NoError(t, err)
|
||||
|
||||
count, err = mgr.store.CountEphemeralServicesByPeer(ctx, store.LockingStrengthNone, testAccountID, testPeerID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(0), count, "all ephemeral services should be deleted after DeleteAllServices")
|
||||
}
|
||||
|
||||
func TestRenewServiceFromPeer(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user