mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-21 22:29:08 +02:00
merge conflict and onDenied handler
This commit is contained in:
@@ -1932,7 +1932,7 @@ func (am *DefaultAccountManager) onPeersInvalidated(ctx context.Context, account
|
||||
peers := []*nbpeer.Peer{}
|
||||
log.WithContext(ctx).Debugf("invalidating peers %v for account %s", peerIDs, accountID)
|
||||
for _, peerID := range peerIDs {
|
||||
peer, err := am.GetPeer(ctx, accountID, peerID, activity.SystemInitiator)
|
||||
peer, err := am.Store.GetPeerByID(ctx, store.LockingStrengthNone, accountID, peerID)
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Errorf("failed to get invalidated peer %s for account %s: %v", peerID, accountID, err)
|
||||
continue
|
||||
|
||||
@@ -129,7 +129,7 @@ func NewAPIHandler(ctx context.Context, router *mux.Router, accountManager accou
|
||||
agentnetworkhandlers.RegisterEndpoints(agentNetworkManager, router)
|
||||
}
|
||||
instance.AddEndpoints(instanceManager, accountManager, router)
|
||||
instance.AddVersionEndpoint(instanceManager, router, permissionsManager)
|
||||
instance.AddVersionEndpoint(instanceManager, router)
|
||||
if serviceManager != nil && reverseProxyDomainManager != nil {
|
||||
reverseproxymanager.RegisterEndpoints(serviceManager, *reverseProxyDomainManager, reverseProxyAccessLogsManager, permissionsManager, router)
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/management/internals/modules/permissions"
|
||||
"github.com/netbirdio/netbird/management/internals/modules/permissions/modules"
|
||||
"github.com/netbirdio/netbird/management/internals/modules/permissions/operations"
|
||||
"github.com/netbirdio/netbird/management/server/account"
|
||||
nbinstance "github.com/netbirdio/netbird/management/server/instance"
|
||||
"github.com/netbirdio/netbird/shared/auth"
|
||||
@@ -36,12 +34,12 @@ func AddEndpoints(instanceManager nbinstance.Manager, accountManager account.Man
|
||||
}
|
||||
|
||||
// AddVersionEndpoint registers the authenticated version endpoint.
|
||||
func AddVersionEndpoint(instanceManager nbinstance.Manager, router *mux.Router, permissionsManager permissions.Manager) {
|
||||
func AddVersionEndpoint(instanceManager nbinstance.Manager, router *mux.Router) {
|
||||
h := &handler{
|
||||
instanceManager: instanceManager,
|
||||
}
|
||||
|
||||
router.HandleFunc("/instance/version", permissionsManager.WithPermission(modules.Settings, operations.Read, h.getVersionInfo)).Methods("GET", "OPTIONS")
|
||||
router.HandleFunc("/instance/version", permissions.WrapHandler(h.getVersionInfo)).Methods("GET", "OPTIONS")
|
||||
}
|
||||
|
||||
// getInstanceStatus returns the instance status including whether setup is required.
|
||||
|
||||
@@ -15,10 +15,8 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
|
||||
"github.com/netbirdio/netbird/management/internals/modules/permissions"
|
||||
"github.com/netbirdio/netbird/management/internals/modules/permissions/modules"
|
||||
"github.com/netbirdio/netbird/management/internals/modules/permissions/operations"
|
||||
"github.com/netbirdio/netbird/management/server/account"
|
||||
nbcontext "github.com/netbirdio/netbird/management/server/context"
|
||||
"github.com/netbirdio/netbird/management/server/idp"
|
||||
nbinstance "github.com/netbirdio/netbird/management/server/instance"
|
||||
"github.com/netbirdio/netbird/management/server/mock_server"
|
||||
@@ -550,17 +548,11 @@ func TestSetup_PAT_CreatePATFails_Rollback(t *testing.T) {
|
||||
|
||||
func TestGetVersionInfo_Success(t *testing.T) {
|
||||
manager := &mockInstanceManager{}
|
||||
ctrl := gomock.NewController(t)
|
||||
permissionsManager := permissions.NewMockManager(ctrl)
|
||||
permissionsManager.EXPECT().WithPermission(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(module modules.Module, operation operations.Operation, handler func(w http.ResponseWriter, r *http.Request, userAuth *auth.UserAuth), authErrHandler ...permissions.AuthErrorHandler) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
handler(w, r, &auth.UserAuth{})
|
||||
}
|
||||
}).AnyTimes()
|
||||
router := mux.NewRouter()
|
||||
AddVersionEndpoint(manager, router, permissionsManager)
|
||||
AddVersionEndpoint(manager, router)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/instance/version", nil)
|
||||
req = nbcontext.SetUserAuthInRequest(req, auth.UserAuth{})
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(rec, req)
|
||||
@@ -585,17 +577,11 @@ func TestGetVersionInfo_Error(t *testing.T) {
|
||||
return nil, errors.New("failed to fetch versions")
|
||||
},
|
||||
}
|
||||
ctrl := gomock.NewController(t)
|
||||
permissionsManager := permissions.NewMockManager(ctrl)
|
||||
permissionsManager.EXPECT().WithPermission(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(module modules.Module, operation operations.Operation, handler func(w http.ResponseWriter, r *http.Request, userAuth *auth.UserAuth), authErrHandler ...permissions.AuthErrorHandler) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
handler(w, r, &auth.UserAuth{})
|
||||
}
|
||||
}).AnyTimes()
|
||||
router := mux.NewRouter()
|
||||
AddVersionEndpoint(manager, router, permissionsManager)
|
||||
AddVersionEndpoint(manager, router)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/instance/version", nil)
|
||||
req = nbcontext.SetUserAuthInRequest(req, auth.UserAuth{})
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
@@ -34,11 +34,11 @@ type Handler struct {
|
||||
|
||||
func AddEndpoints(accountManager account.Manager, router *mux.Router, networkMapController network_map.Controller, permissionsManager permissions.Manager) {
|
||||
peersHandler := NewHandler(accountManager, networkMapController, permissionsManager)
|
||||
router.HandleFunc("/peers", permissionsManager.WithPermission(modules.Peers, operations.Read, peersHandler.GetAllPeers)).Methods("GET", "OPTIONS")
|
||||
router.HandleFunc("/peers/{peerId}", permissionsManager.WithPermission(modules.Peers, operations.Read, peersHandler.GetPeer)).Methods("GET", "OPTIONS")
|
||||
router.HandleFunc("/peers", permissionsManager.WithPermission(modules.Peers, operations.Read, peersHandler.GetAllPeers, peersHandler.getOwnPeers)).Methods("GET", "OPTIONS")
|
||||
router.HandleFunc("/peers/{peerId}", permissionsManager.WithPermission(modules.Peers, operations.Read, peersHandler.GetPeer, peersHandler.getOwnPeer)).Methods("GET", "OPTIONS")
|
||||
router.HandleFunc("/peers/{peerId}", permissionsManager.WithPermission(modules.Peers, operations.Update, peersHandler.UpdatePeer)).Methods("PUT", "OPTIONS")
|
||||
router.HandleFunc("/peers/{peerId}", permissionsManager.WithPermission(modules.Peers, operations.Delete, peersHandler.DeletePeer)).Methods("DELETE", "OPTIONS")
|
||||
router.HandleFunc("/peers/{peerId}/accessible-peers", permissionsManager.WithPermission(modules.Peers, operations.Read, peersHandler.GetAccessiblePeers)).Methods("GET", "OPTIONS")
|
||||
router.HandleFunc("/peers/{peerId}/accessible-peers", permissionsManager.WithPermission(modules.Peers, operations.Read, peersHandler.GetAccessiblePeers, peersHandler.getOwnAccessiblePeers)).Methods("GET", "OPTIONS")
|
||||
router.HandleFunc("/peers/{peerId}/temporary-access", permissionsManager.WithPermission(modules.Peers, operations.Create, peersHandler.CreateTemporaryAccess)).Methods("POST", "OPTIONS")
|
||||
router.HandleFunc("/peers/{peerId}/jobs", permissionsManager.WithPermission(modules.RemoteJobs, operations.Read, peersHandler.ListJobs)).Methods("GET", "OPTIONS")
|
||||
router.HandleFunc("/peers/{peerId}/jobs", permissionsManager.WithPermission(modules.RemoteJobs, operations.Create, peersHandler.CreateJob)).Methods("POST", "OPTIONS")
|
||||
@@ -128,19 +128,46 @@ func (h *Handler) GetJob(w http.ResponseWriter, r *http.Request, userAuth *auth.
|
||||
|
||||
// GetPeer handles GET request for a single peer
|
||||
func (h *Handler) GetPeer(w http.ResponseWriter, r *http.Request, userAuth *auth.UserAuth) {
|
||||
vars := mux.Vars(r)
|
||||
peerID := vars["peerId"]
|
||||
peer, ok := h.peerFromRequest(w, r, userAuth)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
h.writePeer(w, r, userAuth, peer)
|
||||
}
|
||||
|
||||
func (h *Handler) getOwnPeer(w http.ResponseWriter, r *http.Request, userAuth *auth.UserAuth) bool {
|
||||
peer, ok := h.peerFromRequest(w, r, userAuth)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
if peer.UserID != userAuth.UserId {
|
||||
util.WriteError(r.Context(), status.Errorf(status.NotFound, "peer not found"), w)
|
||||
return true
|
||||
}
|
||||
|
||||
h.writePeer(w, r, userAuth, peer)
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *Handler) peerFromRequest(w http.ResponseWriter, r *http.Request, userAuth *auth.UserAuth) (*nbpeer.Peer, bool) {
|
||||
peerID := mux.Vars(r)["peerId"]
|
||||
if len(peerID) == 0 {
|
||||
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "invalid peer ID"), w)
|
||||
return
|
||||
return nil, false
|
||||
}
|
||||
|
||||
peer, err := h.accountManager.GetPeer(r.Context(), userAuth.AccountId, peerID, userAuth.UserId)
|
||||
if err != nil {
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return peer, true
|
||||
}
|
||||
|
||||
func (h *Handler) writePeer(w http.ResponseWriter, r *http.Request, userAuth *auth.UserAuth, peer *nbpeer.Peer) {
|
||||
if peer.ProxyMeta.Embedded {
|
||||
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "not allowed to read peer"), w)
|
||||
return
|
||||
@@ -154,7 +181,7 @@ func (h *Handler) GetPeer(w http.ResponseWriter, r *http.Request, userAuth *auth
|
||||
|
||||
dnsDomain := h.networkMapController.GetDNSDomain(settings)
|
||||
|
||||
grps, _ := h.accountManager.GetPeerGroups(r.Context(), userAuth.AccountId, peerID)
|
||||
grps, _ := h.accountManager.GetPeerGroups(r.Context(), userAuth.AccountId, peer.ID)
|
||||
grpsInfoMap := groups.ToGroupsInfoMap(grps, 0)
|
||||
|
||||
validPeers, invalidPeers, err := h.accountManager.GetValidatedPeers(r.Context(), userAuth.AccountId)
|
||||
@@ -167,7 +194,7 @@ func (h *Handler) GetPeer(w http.ResponseWriter, r *http.Request, userAuth *auth
|
||||
_, valid := validPeers[peer.ID]
|
||||
reason := invalidPeers[peer.ID]
|
||||
|
||||
util.WriteJSONObject(r.Context(), w, toSinglePeerResponse(peer, grpsInfoMap[peerID], dnsDomain, valid, reason))
|
||||
util.WriteJSONObject(r.Context(), w, toSinglePeerResponse(peer, grpsInfoMap[peer.ID], dnsDomain, valid, reason))
|
||||
}
|
||||
|
||||
// UpdatePeer handles PUT request to update a peer
|
||||
@@ -280,10 +307,19 @@ func (h *Handler) DeletePeer(w http.ResponseWriter, r *http.Request, userAuth *a
|
||||
|
||||
// GetAllPeers returns a list of all peers associated with a provided account
|
||||
func (h *Handler) GetAllPeers(w http.ResponseWriter, r *http.Request, userAuth *auth.UserAuth) {
|
||||
h.listPeers(w, r, userAuth, true)
|
||||
}
|
||||
|
||||
func (h *Handler) getOwnPeers(w http.ResponseWriter, r *http.Request, userAuth *auth.UserAuth) bool {
|
||||
h.listPeers(w, r, userAuth, false)
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *Handler) listPeers(w http.ResponseWriter, r *http.Request, userAuth *auth.UserAuth, all bool) {
|
||||
nameFilter := r.URL.Query().Get("name")
|
||||
ipFilter := r.URL.Query().Get("ip")
|
||||
|
||||
peers, err := h.accountManager.GetPeers(r.Context(), userAuth.AccountId, userAuth.UserId, nameFilter, ipFilter, true)
|
||||
peers, err := h.accountManager.GetPeers(r.Context(), userAuth.AccountId, userAuth.UserId, nameFilter, ipFilter, all)
|
||||
if err != nil {
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
@@ -354,40 +390,36 @@ func (h *Handler) GetAccessiblePeers(w http.ResponseWriter, r *http.Request, use
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.accountManager.GetUserByID(r.Context(), userAuth.UserId)
|
||||
if err != nil {
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
account, err := h.accountManager.GetAccountByID(r.Context(), userAuth.AccountId, activity.SystemInitiator)
|
||||
if err != nil {
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if user is an admin/service user through their role
|
||||
isAdmin := user.Role == types.UserRoleAdmin || user.Role == types.UserRoleOwner
|
||||
h.writeAccessiblePeers(w, r, account, peerID)
|
||||
}
|
||||
|
||||
if !isAdmin && !user.IsServiceUser && !userAuth.IsChild {
|
||||
if account.Settings.RegularUsersViewBlocked {
|
||||
util.WriteJSONObject(r.Context(), w, []api.AccessiblePeer{})
|
||||
return
|
||||
}
|
||||
func (h *Handler) getOwnAccessiblePeers(w http.ResponseWriter, r *http.Request, userAuth *auth.UserAuth) bool {
|
||||
peerID := mux.Vars(r)["peerId"]
|
||||
|
||||
peer, ok := account.Peers[peerID]
|
||||
if !ok {
|
||||
util.WriteError(r.Context(), status.Errorf(status.NotFound, "peer not found"), w)
|
||||
return
|
||||
}
|
||||
|
||||
if peer.UserID != user.Id {
|
||||
util.WriteJSONObject(r.Context(), w, []api.AccessiblePeer{})
|
||||
return
|
||||
}
|
||||
account, err := h.accountManager.GetAccountByID(r.Context(), userAuth.AccountId, activity.SystemInitiator)
|
||||
if err != nil {
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return true
|
||||
}
|
||||
|
||||
validPeers, _, err := h.accountManager.GetValidatedPeers(r.Context(), userAuth.AccountId)
|
||||
peer, ok := account.Peers[peerID]
|
||||
if account.Settings.RegularUsersViewBlocked || !ok || peer.UserID != userAuth.UserId {
|
||||
util.WriteJSONObject(r.Context(), w, []api.AccessiblePeer{})
|
||||
return true
|
||||
}
|
||||
|
||||
h.writeAccessiblePeers(w, r, account, peerID)
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *Handler) writeAccessiblePeers(w http.ResponseWriter, r *http.Request, account *types.Account, peerID string) {
|
||||
validPeers, _, err := h.accountManager.GetValidatedPeers(r.Context(), account.Id)
|
||||
if err != nil {
|
||||
log.WithContext(r.Context()).Errorf("failed to list approved peers: %v", err)
|
||||
util.WriteError(r.Context(), fmt.Errorf("internal error"), w)
|
||||
@@ -409,9 +441,18 @@ func (h *Handler) CreateTemporaryAccess(w http.ResponseWriter, r *http.Request,
|
||||
return
|
||||
}
|
||||
|
||||
var req api.PeerTemporaryAccessRequest
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
allowed, _, err := h.permissionsManager.ValidateUserPermissions(r.Context(), userAuth.AccountId, userAuth.UserId, modules.Policies, operations.Create)
|
||||
if err != nil {
|
||||
util.WriteError(r.Context(), status.NewPermissionValidationError(err), w)
|
||||
return
|
||||
}
|
||||
if !allowed {
|
||||
util.WriteError(r.Context(), status.NewPermissionDeniedError(), w)
|
||||
return
|
||||
}
|
||||
|
||||
var req api.PeerTemporaryAccessRequest
|
||||
if err = json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -501,7 +501,7 @@ func TestGetAccessiblePeers(t *testing.T) {
|
||||
})
|
||||
|
||||
router := mux.NewRouter()
|
||||
router.HandleFunc("/api/peers/{peerId}/accessible-peers", permissions.WrapHandler(p.GetAccessiblePeers)).Methods("GET")
|
||||
router.HandleFunc("/api/peers/{peerId}/accessible-peers", permissions.WithPermission(p.permissionsManager, modules.Peers, operations.Read, p.GetAccessiblePeers, p.getOwnAccessiblePeers)).Methods("GET")
|
||||
router.ServeHTTP(recorder, req)
|
||||
|
||||
res := recorder.Result()
|
||||
|
||||
@@ -7,8 +7,6 @@ import (
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/netbirdio/netbird/management/internals/modules/permissions"
|
||||
"github.com/netbirdio/netbird/management/internals/modules/permissions/modules"
|
||||
"github.com/netbirdio/netbird/management/internals/modules/permissions/operations"
|
||||
"github.com/netbirdio/netbird/management/server/account"
|
||||
"github.com/netbirdio/netbird/management/server/geolocation"
|
||||
"github.com/netbirdio/netbird/shared/auth"
|
||||
@@ -30,8 +28,8 @@ type geolocationsHandler struct {
|
||||
|
||||
func AddLocationsEndpoints(accountManager account.Manager, locationManager geolocation.Geolocation, permissionsManager permissions.Manager, router *mux.Router) {
|
||||
locationHandler := newGeolocationsHandlerHandler(accountManager, locationManager, permissionsManager)
|
||||
router.HandleFunc("/locations/countries", permissionsManager.WithPermission(modules.Policies, operations.Read, locationHandler.getAllCountries)).Methods("GET", "OPTIONS")
|
||||
router.HandleFunc("/locations/countries/{country}/cities", permissionsManager.WithPermission(modules.Policies, operations.Read, locationHandler.getCitiesByCountry)).Methods("GET", "OPTIONS")
|
||||
router.HandleFunc("/locations/countries", permissions.WrapHandler(locationHandler.getAllCountries)).Methods("GET", "OPTIONS")
|
||||
router.HandleFunc("/locations/countries/{country}/cities", permissions.WrapHandler(locationHandler.getCitiesByCountry)).Methods("GET", "OPTIONS")
|
||||
}
|
||||
|
||||
// newGeolocationsHandlerHandler creates a new Geolocations handler
|
||||
|
||||
@@ -414,10 +414,6 @@ type testServiceManager struct {
|
||||
store store.Store
|
||||
}
|
||||
|
||||
func (m *testServiceManager) DeleteAllServices(ctx context.Context, accountID, userID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *testServiceManager) GetAllServices(_ context.Context, _, _ string) ([]*service.Service, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -28,14 +28,14 @@ type handler struct {
|
||||
func AddEndpoints(accountManager account.Manager, router *mux.Router, permissionsManager permissions.Manager) {
|
||||
userHandler := newHandler(accountManager)
|
||||
router.HandleFunc("/users", permissionsManager.WithPermission(modules.Users, operations.Read, userHandler.getAllUsers, userHandler.getOwnUser)).Methods("GET", "OPTIONS")
|
||||
router.HandleFunc("/users/current", permissionsManager.WithPermission(modules.Users, operations.Read, userHandler.getCurrentUser, userHandler.getCurrentUserFallback)).Methods("GET", "OPTIONS")
|
||||
router.HandleFunc("/users/current", permissions.WrapHandler(userHandler.getCurrentUser)).Methods("GET", "OPTIONS")
|
||||
router.HandleFunc("/users/{userId}", permissionsManager.WithPermission(modules.Users, operations.Update, userHandler.updateUser)).Methods("PUT", "OPTIONS")
|
||||
router.HandleFunc("/users/{userId}", permissionsManager.WithPermission(modules.Users, operations.Delete, userHandler.deleteUser)).Methods("DELETE", "OPTIONS")
|
||||
router.HandleFunc("/users", permissionsManager.WithPermission(modules.Users, operations.Create, userHandler.createUser)).Methods("POST", "OPTIONS")
|
||||
router.HandleFunc("/users/{userId}/invite", permissionsManager.WithPermission(modules.Users, operations.Create, userHandler.inviteUser)).Methods("POST", "OPTIONS")
|
||||
router.HandleFunc("/users/{userId}/approve", permissionsManager.WithPermission(modules.Users, operations.Update, userHandler.approveUser)).Methods("POST", "OPTIONS")
|
||||
router.HandleFunc("/users/{userId}/reject", permissionsManager.WithPermission(modules.Users, operations.Delete, userHandler.rejectUser)).Methods("DELETE", "OPTIONS")
|
||||
router.HandleFunc("/users/{userId}/password", permissionsManager.WithPermission(modules.Users, operations.Update, userHandler.changePassword)).Methods("PUT", "OPTIONS")
|
||||
router.HandleFunc("/users/{userId}/password", permissionsManager.WithPermission(modules.Users, operations.Update, userHandler.changePassword, userHandler.changeOwnPassword)).Methods("PUT", "OPTIONS")
|
||||
addUsersTokensEndpoint(accountManager, router, permissionsManager)
|
||||
}
|
||||
|
||||
@@ -405,28 +405,16 @@ func (h *handler) changePassword(w http.ResponseWriter, r *http.Request, userAut
|
||||
util.WriteJSONObject(r.Context(), w, util.EmptyObject{})
|
||||
}
|
||||
|
||||
func (h *handler) getCurrentUserFallback(w http.ResponseWriter, r *http.Request, userAuth *auth.UserAuth, err error) bool {
|
||||
s, ok := status.FromError(err)
|
||||
if !ok || s.ErrorType != status.PermissionDenied {
|
||||
func (h *handler) changeOwnPassword(w http.ResponseWriter, r *http.Request, userAuth *auth.UserAuth) bool {
|
||||
if mux.Vars(r)["userId"] != userAuth.UserId {
|
||||
return false
|
||||
}
|
||||
|
||||
user, userErr := h.accountManager.GetCurrentUserInfo(r.Context(), *userAuth)
|
||||
if userErr != nil {
|
||||
util.WriteError(r.Context(), userErr, w)
|
||||
return true
|
||||
}
|
||||
|
||||
util.WriteJSONObject(r.Context(), w, toUserWithPermissionsResponse(user, userAuth.UserId))
|
||||
h.changePassword(w, r, userAuth)
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *handler) getOwnUser(w http.ResponseWriter, r *http.Request, userAuth *auth.UserAuth, err error) bool {
|
||||
s, ok := status.FromError(err)
|
||||
if !ok || s.ErrorType != status.PermissionDenied {
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *handler) getOwnUser(w http.ResponseWriter, r *http.Request, userAuth *auth.UserAuth) bool {
|
||||
if r.URL.Query().Get("service_user") != "" {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -26,31 +26,37 @@ func Test_Peers_GetAll(t *testing.T) {
|
||||
name string
|
||||
userId string
|
||||
expectResponse bool
|
||||
expectedPeers int
|
||||
}{
|
||||
{
|
||||
name: "Regular user",
|
||||
userId: testing_tools.TestUserId,
|
||||
expectResponse: false,
|
||||
expectResponse: true,
|
||||
expectedPeers: 1,
|
||||
},
|
||||
{
|
||||
name: "Admin user",
|
||||
userId: testing_tools.TestAdminId,
|
||||
expectResponse: true,
|
||||
expectedPeers: 2,
|
||||
},
|
||||
{
|
||||
name: "Owner user",
|
||||
userId: testing_tools.TestOwnerId,
|
||||
expectResponse: true,
|
||||
expectedPeers: 2,
|
||||
},
|
||||
{
|
||||
name: "Regular service user",
|
||||
userId: testing_tools.TestServiceUserId,
|
||||
expectResponse: false,
|
||||
expectResponse: true,
|
||||
expectedPeers: 0,
|
||||
},
|
||||
{
|
||||
name: "Admin service user",
|
||||
userId: testing_tools.TestServiceAdminId,
|
||||
expectResponse: true,
|
||||
expectedPeers: 2,
|
||||
},
|
||||
{
|
||||
name: "Blocked user",
|
||||
@@ -88,7 +94,7 @@ func Test_Peers_GetAll(t *testing.T) {
|
||||
t.Fatalf("Sent content is not in correct json format; %v", err)
|
||||
}
|
||||
|
||||
assert.GreaterOrEqual(t, len(got), 2, "Expected at least 2 peers")
|
||||
assert.Len(t, got, user.expectedPeers, "regular users must only see their own peers")
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
@@ -99,17 +105,36 @@ func Test_Peers_GetAll(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Peers_GetById_RegularUser(t *testing.T) {
|
||||
tt := []struct {
|
||||
name string
|
||||
peerId string
|
||||
expectedStatus int
|
||||
}{
|
||||
{"Own peer", testing_tools.TestPeerId, http.StatusOK},
|
||||
{"Peer of another user", testPeerId2, http.StatusNotFound},
|
||||
{"Non-existing peer", "nonExistingPeerId", http.StatusNotFound},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
apiHandler, _, _ := channel.BuildApiBlackBoxWithDBState(t, "../testdata/peers_integration.sql", nil, false)
|
||||
|
||||
req := testing_tools.BuildRequest(t, []byte{}, http.MethodGet, "/api/peers/"+tc.peerId, testing_tools.TestUserId)
|
||||
recorder := httptest.NewRecorder()
|
||||
apiHandler.ServeHTTP(recorder, req)
|
||||
|
||||
assert.Equal(t, tc.expectedStatus, recorder.Code, "unexpected status, body: %s", recorder.Body.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Peers_GetById(t *testing.T) {
|
||||
users := []struct {
|
||||
name string
|
||||
userId string
|
||||
expectResponse bool
|
||||
}{
|
||||
{
|
||||
name: "Regular user",
|
||||
userId: testing_tools.TestUserId,
|
||||
expectResponse: false,
|
||||
},
|
||||
{
|
||||
name: "Admin user",
|
||||
userId: testing_tools.TestAdminId,
|
||||
@@ -120,6 +145,11 @@ func Test_Peers_GetById(t *testing.T) {
|
||||
userId: testing_tools.TestOwnerId,
|
||||
expectResponse: true,
|
||||
},
|
||||
{
|
||||
name: "Auditor user",
|
||||
userId: testing_tools.TestAuditorId,
|
||||
expectResponse: true,
|
||||
},
|
||||
{
|
||||
name: "Regular service user",
|
||||
userId: testing_tools.TestServiceUserId,
|
||||
@@ -508,7 +538,7 @@ func Test_Peers_GetAccessiblePeers(t *testing.T) {
|
||||
{
|
||||
name: "Regular user",
|
||||
userId: testing_tools.TestUserId,
|
||||
expectResponse: false,
|
||||
expectResponse: true,
|
||||
},
|
||||
{
|
||||
name: "Admin user",
|
||||
@@ -523,7 +553,7 @@ func Test_Peers_GetAccessiblePeers(t *testing.T) {
|
||||
{
|
||||
name: "Regular service user",
|
||||
userId: testing_tools.TestServiceUserId,
|
||||
expectResponse: false,
|
||||
expectResponse: true,
|
||||
},
|
||||
{
|
||||
name: "Admin service user",
|
||||
|
||||
@@ -26,6 +26,7 @@ func Test_Users_GetAll(t *testing.T) {
|
||||
{"Regular user", testing_tools.TestUserId, true},
|
||||
{"Admin user", testing_tools.TestAdminId, true},
|
||||
{"Owner user", testing_tools.TestOwnerId, true},
|
||||
{"Auditor user", testing_tools.TestAuditorId, true},
|
||||
{"Regular service user", testing_tools.TestServiceUserId, false},
|
||||
{"Admin service user", testing_tools.TestServiceAdminId, true},
|
||||
{"Blocked user", testing_tools.BlockedUserId, false},
|
||||
@@ -62,6 +63,49 @@ func Test_Users_GetAll(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Users_GetAll_ReadOnlyRoleSeesAllUsers(t *testing.T) {
|
||||
apiHandler, _, _ := channel.BuildApiBlackBoxWithDBState(t, "../testdata/users_integration.sql", nil, false)
|
||||
|
||||
req := testing_tools.BuildRequest(t, []byte{}, http.MethodGet, "/api/users", testing_tools.TestAuditorId)
|
||||
recorder := httptest.NewRecorder()
|
||||
apiHandler.ServeHTTP(recorder, req)
|
||||
|
||||
content, _ := testing_tools.ReadResponse(t, recorder, http.StatusOK, true)
|
||||
|
||||
got := []api.User{}
|
||||
if err := json.Unmarshal(content, &got); err != nil {
|
||||
t.Fatalf("Sent content is not in correct json format; %v", err)
|
||||
}
|
||||
|
||||
assert.Greater(t, len(got), 1, "auditor must see every user of the account, not only themselves")
|
||||
}
|
||||
|
||||
func Test_Users_ChangePassword(t *testing.T) {
|
||||
tt := []struct {
|
||||
name string
|
||||
userId string
|
||||
targetUserId string
|
||||
expectedStatus int
|
||||
}{
|
||||
{"Regular user changes own password", testing_tools.TestUserId, testing_tools.TestUserId, http.StatusPreconditionFailed},
|
||||
{"Regular user changes another user's password", testing_tools.TestUserId, testing_tools.TestAdminId, http.StatusForbidden},
|
||||
{"Admin changes another user's password", testing_tools.TestAdminId, testing_tools.TestUserId, http.StatusPreconditionFailed},
|
||||
}
|
||||
|
||||
body := []byte(`{"old_password":"OldPass123!","new_password":"NewPass456!"}`)
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
apiHandler, _, _ := channel.BuildApiBlackBoxWithDBState(t, "../testdata/users_integration.sql", nil, false)
|
||||
|
||||
req := testing_tools.BuildRequest(t, body, http.MethodPut, "/api/users/"+tc.targetUserId+"/password", tc.userId)
|
||||
recorder := httptest.NewRecorder()
|
||||
apiHandler.ServeHTTP(recorder, req)
|
||||
|
||||
assert.Equal(t, tc.expectedStatus, recorder.Code, "unexpected status, body: %s", recorder.Body.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Users_GetAll_ServiceUsers(t *testing.T) {
|
||||
users := []struct {
|
||||
name string
|
||||
|
||||
@@ -7,6 +7,7 @@ INSERT INTO accounts VALUES('testAccountId','','2024-10-02 16:01:38.000000000+00
|
||||
INSERT INTO users VALUES('testUserId','testAccountId','user',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,'');
|
||||
INSERT INTO users VALUES('testAdminId','testAccountId','admin',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,'');
|
||||
INSERT INTO users VALUES('testOwnerId','testAccountId','owner',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,'');
|
||||
INSERT INTO users VALUES('testAuditorId','testAccountId','auditor',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,'');
|
||||
INSERT INTO users VALUES('testServiceUserId','testAccountId','user',1,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,'');
|
||||
INSERT INTO users VALUES('testServiceAdminId','testAccountId','admin',1,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,'');
|
||||
INSERT INTO users VALUES('blockedUserId','testAccountId','admin',0,0,'','[]',1,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,'');
|
||||
|
||||
@@ -8,6 +8,7 @@ INSERT INTO accounts VALUES('testAccountId','','2024-10-02 16:01:38.000000000+00
|
||||
INSERT INTO users VALUES('testUserId','testAccountId','user',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,'');
|
||||
INSERT INTO users VALUES('testAdminId','testAccountId','admin',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,'');
|
||||
INSERT INTO users VALUES('testOwnerId','testAccountId','owner',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,'');
|
||||
INSERT INTO users VALUES('testAuditorId','testAccountId','auditor',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,'');
|
||||
INSERT INTO users VALUES('testServiceUserId','testAccountId','user',1,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,'');
|
||||
INSERT INTO users VALUES('testServiceAdminId','testAccountId','admin',1,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,'');
|
||||
INSERT INTO users VALUES('blockedUserId','testAccountId','admin',0,0,'','[]',1,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,'');
|
||||
|
||||
@@ -10,6 +10,7 @@ INSERT INTO accounts VALUES('testAccountId','','2024-10-02 16:01:38.000000000+00
|
||||
INSERT INTO users VALUES('testUserId','testAccountId','user',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,'');
|
||||
INSERT INTO users VALUES('testAdminId','testAccountId','admin',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,'');
|
||||
INSERT INTO users VALUES('testOwnerId','testAccountId','owner',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,'');
|
||||
INSERT INTO users VALUES('testAuditorId','testAccountId','auditor',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,'');
|
||||
INSERT INTO users VALUES('testServiceUserId','testAccountId','user',1,0,'testServiceUser','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,'');
|
||||
INSERT INTO users VALUES('testServiceAdminId','testAccountId','admin',1,0,'testServiceAdmin','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,'');
|
||||
INSERT INTO users VALUES('blockedUserId','testAccountId','admin',0,0,'','[]',1,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,'');
|
||||
|
||||
@@ -298,7 +298,7 @@ func mockValidateAndParseToken(_ context.Context, token string) (auth.UserAuth,
|
||||
userAuth := auth.UserAuth{}
|
||||
|
||||
switch token {
|
||||
case "testUserId", "testAdminId", "testOwnerId", "testServiceUserId", "testServiceAdminId", "blockedUserId":
|
||||
case "testUserId", "testAdminId", "testOwnerId", "testAuditorId", "testServiceUserId", "testServiceAdminId", "blockedUserId":
|
||||
userAuth.UserId = token
|
||||
userAuth.AccountId = "testAccountId"
|
||||
userAuth.Domain = "test.com"
|
||||
|
||||
@@ -32,6 +32,7 @@ const (
|
||||
TestUserId = "testUserId"
|
||||
TestAdminId = "testAdminId"
|
||||
TestOwnerId = "testOwnerId"
|
||||
TestAuditorId = "testAuditorId"
|
||||
TestServiceUserId = "testServiceUserId"
|
||||
TestServiceAdminId = "testServiceAdminId"
|
||||
BlockedUserId = "blockedUserId"
|
||||
|
||||
@@ -132,15 +132,15 @@ func (am *DefaultAccountManager) GetIdentityProvider(ctx context.Context, accoun
|
||||
|
||||
// CreateIdentityProvider creates a new identity provider
|
||||
func (am *DefaultAccountManager) CreateIdentityProvider(ctx context.Context, accountID, userID string, idpConfig *types.IdentityProvider) (*types.IdentityProvider, error) {
|
||||
if err := validateIdentityProviderConfig(ctx, idpConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
embeddedManager, ok := am.idpManager.(*idp.EmbeddedIdPManager)
|
||||
if !ok {
|
||||
return nil, status.Errorf(status.Internal, "identity provider management requires embedded IdP")
|
||||
}
|
||||
|
||||
if err := validateIdentityProviderConfig(ctx, idpConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Generate ID if not provided
|
||||
if idpConfig.ID == "" {
|
||||
idpConfig.ID = generateIdentityProviderID(idpConfig.Type)
|
||||
@@ -161,15 +161,15 @@ func (am *DefaultAccountManager) CreateIdentityProvider(ctx context.Context, acc
|
||||
|
||||
// UpdateIdentityProvider updates an existing identity provider
|
||||
func (am *DefaultAccountManager) UpdateIdentityProvider(ctx context.Context, accountID, idpID, userID string, idpConfig *types.IdentityProvider) (*types.IdentityProvider, error) {
|
||||
if err := validateIdentityProviderConfig(ctx, idpConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
embeddedManager, ok := am.idpManager.(*idp.EmbeddedIdPManager)
|
||||
if !ok {
|
||||
return nil, status.Errorf(status.Internal, "identity provider management requires embedded IdP")
|
||||
}
|
||||
|
||||
if err := validateIdentityProviderConfig(ctx, idpConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
idpConfig.ID = idpID
|
||||
idpConfig.AccountID = accountID
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ func (am *DefaultAccountManager) GetPeers(ctx context.Context, accountID, userID
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if all || user.IsAdminOrServiceUser() {
|
||||
if all || user.HasAdminPower() {
|
||||
return am.Store.GetAccountPeers(ctx, store.LockingStrengthNone, accountID, nameFilter, ipFilter)
|
||||
}
|
||||
|
||||
@@ -1453,25 +1453,10 @@ func peerLoginExpired(ctx context.Context, peer *nbpeer.Peer, settings *types.Se
|
||||
return false
|
||||
}
|
||||
|
||||
// GetPeer returns a peer visible to the user within an account.
|
||||
// Users with "peers:read" permission can access any peer. Otherwise, users can access only their own peer.
|
||||
// GetPeer returns a peer within an account. Callers are expected to have passed the
|
||||
// "peers:read" permission check at the HTTP layer.
|
||||
func (am *DefaultAccountManager) GetPeer(ctx context.Context, accountID, peerID, userID string) (*nbpeer.Peer, error) {
|
||||
peer, err := am.Store.GetPeerByID(ctx, store.LockingStrengthNone, accountID, peerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthNone, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// if admin or user owns this peer, return peer
|
||||
if user.IsAdminOrServiceUser() || peer.UserID == userID {
|
||||
return peer, nil
|
||||
}
|
||||
|
||||
return nil, status.Errorf(status.Internal, "user %s has no access to peer %s under account %s", userID, peer.ID, accountID)
|
||||
return am.Store.GetPeerByID(ctx, store.LockingStrengthNone, accountID, peerID)
|
||||
}
|
||||
|
||||
// UpdateAccountPeers updates all peers that belong to an account.
|
||||
|
||||
@@ -553,7 +553,7 @@ func TestDefaultAccountManager_GetPeer(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
// the user can see its own peer
|
||||
// authorization is enforced at the HTTP layer, the manager returns any peer of the account
|
||||
peer, err := manager.GetPeer(context.Background(), accountID, peer1.ID, someUser)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -561,12 +561,13 @@ func TestDefaultAccountManager_GetPeer(t *testing.T) {
|
||||
}
|
||||
assert.NotNil(t, peer)
|
||||
|
||||
// the user can NOT see peer2 because it is not owned by them.
|
||||
// Regular users only see peers they directly own.
|
||||
_, err = manager.GetPeer(context.Background(), accountID, peer2.ID, someUser)
|
||||
assert.Error(t, err)
|
||||
peer, err = manager.GetPeer(context.Background(), accountID, peer2.ID, someUser)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
assert.NotNil(t, peer)
|
||||
|
||||
// admin users can always access all the peers
|
||||
peer, err = manager.GetPeer(context.Background(), accountID, peer1.ID, adminUser)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -602,7 +603,7 @@ func TestDefaultAccountManager_GetPeers(t *testing.T) {
|
||||
role: types.UserRoleUser,
|
||||
limitedViewSettings: false,
|
||||
isServiceUser: true,
|
||||
expectedPeerCount: 2,
|
||||
expectedPeerCount: 1,
|
||||
},
|
||||
{
|
||||
name: "Regular user, limited view settings",
|
||||
@@ -616,7 +617,7 @@ func TestDefaultAccountManager_GetPeers(t *testing.T) {
|
||||
role: types.UserRoleUser,
|
||||
limitedViewSettings: true,
|
||||
isServiceUser: true,
|
||||
expectedPeerCount: 2,
|
||||
expectedPeerCount: 0,
|
||||
},
|
||||
{
|
||||
name: "Admin, no limited view settings, not a service user",
|
||||
|
||||
@@ -16,6 +16,8 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/idp/dex"
|
||||
"github.com/netbirdio/netbird/management/internals/modules/permissions/modules"
|
||||
"github.com/netbirdio/netbird/management/internals/modules/permissions/operations"
|
||||
"github.com/netbirdio/netbird/management/server/account"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"github.com/netbirdio/netbird/management/server/affectedpeers"
|
||||
@@ -965,8 +967,12 @@ func (am *DefaultAccountManager) GetOrCreateAccountByUser(ctx context.Context, u
|
||||
// GetUsersFromAccount performs a batched request for users from IDP by account ID apply filter on what data to return
|
||||
// based on provided user role.
|
||||
func (am *DefaultAccountManager) GetUsersFromAccount(ctx context.Context, accountID, initiatorUserID string) (map[string]*types.UserInfo, error) {
|
||||
allowed, ctx, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, initiatorUserID, modules.Users, operations.Read)
|
||||
if err != nil {
|
||||
return nil, status.NewPermissionValidationError(err)
|
||||
}
|
||||
|
||||
var user *types.User
|
||||
var err error
|
||||
if initiatorUserID != activity.SystemInitiator {
|
||||
result, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthNone, initiatorUserID)
|
||||
if err != nil {
|
||||
@@ -976,11 +982,8 @@ func (am *DefaultAccountManager) GetUsersFromAccount(ctx context.Context, accoun
|
||||
}
|
||||
|
||||
accountUsers := []*types.User{}
|
||||
|
||||
hasFullAccess := initiatorUserID == activity.SystemInitiator || user.HasAdminPower() || user.IsServiceUser
|
||||
|
||||
switch {
|
||||
case hasFullAccess:
|
||||
case allowed:
|
||||
start := time.Now()
|
||||
accountUsers, err = am.Store.GetAccountUsers(ctx, store.LockingStrengthNone, accountID)
|
||||
if err != nil {
|
||||
@@ -1465,8 +1468,10 @@ func (am *DefaultAccountManager) GetCurrentUserInfo(ctx context.Context, userAut
|
||||
return nil, status.NewPermissionDeniedError()
|
||||
}
|
||||
|
||||
// Permission checks are now handled by the HTTP middleware via WithPermission wrapper
|
||||
// User account association is already validated above by GetUserByUserID
|
||||
ctx, err = am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
settings, err := am.Store.GetAccountSettings(ctx, store.LockingStrengthNone, accountID)
|
||||
if err != nil {
|
||||
|
||||
@@ -1414,8 +1414,8 @@ func TestUser_GetUsersFromAccount_ForUser(t *testing.T) {
|
||||
t.Fatalf("Error when getting users from account: %s", err)
|
||||
}
|
||||
|
||||
// Service users should see all users
|
||||
assert.Equal(t, 2, len(users))
|
||||
// Service users follow their role like any other user, a role user only sees themselves
|
||||
assert.Equal(t, 1, len(users))
|
||||
}
|
||||
|
||||
func TestDefaultAccountManager_SaveUser(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user