Add limited dashboard view (#1738)

This commit is contained in:
pascal-fischer 2024-03-27 16:11:45 +01:00 committed by GitHub
parent 68b377a28c
commit ea2d060f93
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 466 additions and 41 deletions

View File

@ -85,7 +85,8 @@ type AccountManager interface {
GetAllPATs(accountID string, initiatorUserID string, targetUserID string) ([]*PersonalAccessToken, error)
UpdatePeerSSHKey(peerID string, sshKey string) error
GetUsersFromAccount(accountID, userID string) ([]*UserInfo, error)
GetGroup(accountId, groupID string) (*Group, error)
GetGroup(accountId, groupID, userID string) (*Group, error)
GetAllGroups(accountID, userID string) ([]*Group, error)
GetGroupByName(groupName, accountID string) (*Group, error)
SaveGroup(accountID, userID string, group *Group) error
DeleteGroup(accountId, userId, groupID string) error
@ -162,6 +163,9 @@ type Settings struct {
// Applies to all peers that have Peer.LoginExpirationEnabled set to true.
PeerLoginExpiration time.Duration
// RegularUsersViewBlocked allows to block regular users from viewing even their own peers and some UI elements
RegularUsersViewBlocked bool
// GroupsPropagationEnabled allows to propagate auto groups from the user to the peer
GroupsPropagationEnabled bool
@ -188,6 +192,7 @@ func (s *Settings) Copy() *Settings {
JWTGroupsClaimName: s.JWTGroupsClaimName,
GroupsPropagationEnabled: s.GroupsPropagationEnabled,
JWTAllowGroups: s.JWTAllowGroups,
RegularUsersViewBlocked: s.RegularUsersViewBlocked,
}
if s.Extra != nil {
settings.Extra = s.Extra.Copy()
@ -226,6 +231,10 @@ type Account struct {
Settings *Settings `gorm:"embedded;embeddedPrefix:settings_"`
}
type UserPermissions struct {
DashboardView string `json:"dashboard_view"`
}
type UserInfo struct {
ID string `json:"id"`
Email string `json:"email"`
@ -239,6 +248,7 @@ type UserInfo struct {
LastLogin time.Time `json:"last_login"`
Issued string `json:"issued"`
IntegrationReference IntegrationReference `json:"-"`
Permissions UserPermissions `json:"permissions"`
}
// getRoutesToSync returns the enabled routes for the peer ID and the routes
@ -1885,6 +1895,7 @@ func newAccountWithId(accountID, userID, domain string) *Account {
PeerLoginExpirationEnabled: true,
PeerLoginExpiration: DefaultPeerLoginExpiration,
GroupsPropagationEnabled: true,
RegularUsersViewBlocked: true,
},
}

View File

@ -63,7 +63,7 @@ func (g *Group) Copy() *Group {
}
// GetGroup object of the peers
func (am *DefaultAccountManager) GetGroup(accountID, groupID string) (*Group, error) {
func (am *DefaultAccountManager) GetGroup(accountID, groupID, userID string) (*Group, error) {
unlock := am.Store.AcquireAccountLock(accountID)
defer unlock()
@ -72,6 +72,15 @@ func (am *DefaultAccountManager) GetGroup(accountID, groupID string) (*Group, er
return nil, err
}
user, err := account.FindUser(userID)
if err != nil {
return nil, err
}
if !user.HasAdminPower() && !user.IsServiceUser && account.Settings.RegularUsersViewBlocked {
return nil, status.Errorf(status.PermissionDenied, "groups are blocked for users")
}
group, ok := account.Groups[groupID]
if ok {
return group, nil
@ -80,6 +89,33 @@ func (am *DefaultAccountManager) GetGroup(accountID, groupID string) (*Group, er
return nil, status.Errorf(status.NotFound, "group with ID %s not found", groupID)
}
// GetAllGroups returns all groups in an account
func (am *DefaultAccountManager) GetAllGroups(accountID string, userID string) ([]*Group, error) {
unlock := am.Store.AcquireAccountLock(accountID)
defer unlock()
account, err := am.Store.GetAccount(accountID)
if err != nil {
return nil, err
}
user, err := account.FindUser(userID)
if err != nil {
return nil, err
}
if !user.HasAdminPower() && !user.IsServiceUser && account.Settings.RegularUsersViewBlocked {
return nil, status.Errorf(status.PermissionDenied, "groups are blocked for users")
}
groups := make([]*Group, 0, len(account.Groups))
for _, item := range account.Groups {
groups = append(groups, item)
}
return groups, nil
}
// GetGroupByName filters all groups in an account by name and returns the one with the most peers
func (am *DefaultAccountManager) GetGroupByName(groupName, accountID string) (*Group, error) {
unlock := am.Store.AcquireAccountLock(accountID)

View File

@ -76,6 +76,7 @@ func (h *AccountsHandler) UpdateAccount(w http.ResponseWriter, r *http.Request)
settings := &server.Settings{
PeerLoginExpirationEnabled: req.Settings.PeerLoginExpirationEnabled,
PeerLoginExpiration: time.Duration(float64(time.Second.Nanoseconds()) * float64(req.Settings.PeerLoginExpiration)),
RegularUsersViewBlocked: req.Settings.RegularUsersViewBlocked,
}
if req.Settings.Extra != nil {
@ -143,6 +144,7 @@ func toAccountResponse(account *server.Account) *api.Account {
JwtGroupsEnabled: &account.Settings.JWTGroupsEnabled,
JwtGroupsClaimName: &account.Settings.JWTGroupsClaimName,
JwtAllowGroups: &jwtAllowGroups,
RegularUsersViewBlocked: account.Settings.RegularUsersViewBlocked,
}
if account.Settings.Extra != nil {

View File

@ -69,6 +69,7 @@ func TestAccounts_AccountsHandler(t *testing.T) {
Settings: &server.Settings{
PeerLoginExpirationEnabled: false,
PeerLoginExpiration: time.Hour,
RegularUsersViewBlocked: true,
},
}, adminUser)
@ -96,6 +97,7 @@ func TestAccounts_AccountsHandler(t *testing.T) {
JwtGroupsClaimName: sr(""),
JwtGroupsEnabled: br(false),
JwtAllowGroups: &[]string{},
RegularUsersViewBlocked: true,
},
expectedArray: true,
expectedID: accountID,
@ -114,6 +116,7 @@ func TestAccounts_AccountsHandler(t *testing.T) {
JwtGroupsClaimName: sr(""),
JwtGroupsEnabled: br(false),
JwtAllowGroups: &[]string{},
RegularUsersViewBlocked: false,
},
expectedArray: false,
expectedID: accountID,
@ -123,7 +126,7 @@ func TestAccounts_AccountsHandler(t *testing.T) {
expectedBody: true,
requestType: http.MethodPut,
requestPath: "/api/accounts/" + accountID,
requestBody: bytes.NewBufferString("{\"settings\": {\"peer_login_expiration\": 15552000,\"peer_login_expiration_enabled\": false,\"jwt_groups_enabled\":true,\"jwt_groups_claim_name\":\"roles\",\"jwt_allow_groups\":[\"test\"]}}"),
requestBody: bytes.NewBufferString("{\"settings\": {\"peer_login_expiration\": 15552000,\"peer_login_expiration_enabled\": false,\"jwt_groups_enabled\":true,\"jwt_groups_claim_name\":\"roles\",\"jwt_allow_groups\":[\"test\"],\"regular_users_view_blocked\":true}}"),
expectedStatus: http.StatusOK,
expectedSettings: api.AccountSettings{
PeerLoginExpiration: 15552000,
@ -132,6 +135,7 @@ func TestAccounts_AccountsHandler(t *testing.T) {
JwtGroupsClaimName: sr("roles"),
JwtGroupsEnabled: br(true),
JwtAllowGroups: &[]string{"test"},
RegularUsersViewBlocked: true,
},
expectedArray: false,
expectedID: accountID,
@ -141,7 +145,7 @@ func TestAccounts_AccountsHandler(t *testing.T) {
expectedBody: true,
requestType: http.MethodPut,
requestPath: "/api/accounts/" + accountID,
requestBody: bytes.NewBufferString("{\"settings\": {\"peer_login_expiration\": 554400,\"peer_login_expiration_enabled\": true,\"jwt_groups_enabled\":true,\"jwt_groups_claim_name\":\"groups\",\"groups_propagation_enabled\":true}}"),
requestBody: bytes.NewBufferString("{\"settings\": {\"peer_login_expiration\": 554400,\"peer_login_expiration_enabled\": true,\"jwt_groups_enabled\":true,\"jwt_groups_claim_name\":\"groups\",\"groups_propagation_enabled\":true,\"regular_users_view_blocked\":true}}"),
expectedStatus: http.StatusOK,
expectedSettings: api.AccountSettings{
PeerLoginExpiration: 554400,
@ -150,6 +154,7 @@ func TestAccounts_AccountsHandler(t *testing.T) {
JwtGroupsClaimName: sr("groups"),
JwtGroupsEnabled: br(true),
JwtAllowGroups: &[]string{},
RegularUsersViewBlocked: true,
},
expectedArray: false,
expectedID: accountID,

View File

@ -54,6 +54,10 @@ components:
description: Period of time after which peer login expires (seconds).
type: integer
example: 43200
regular_users_view_blocked:
description: Allows blocking regular users from viewing parts of the system.
type: boolean
example: true
groups_propagation_enabled:
description: Allows propagate the new user auto groups to peers that belongs to the user
type: boolean
@ -77,6 +81,7 @@ components:
required:
- peer_login_expiration_enabled
- peer_login_expiration
- regular_users_view_blocked
AccountExtraSettings:
type: object
properties:
@ -144,6 +149,8 @@ components:
description: How user was issued by API or Integration
type: string
example: api
permissions:
$ref: '#/components/schemas/UserPermissions'
required:
- id
- email
@ -152,6 +159,14 @@ components:
- auto_groups
- status
- is_blocked
UserPermissions:
type: object
properties:
dashboard_view:
description: User's permission to view the dashboard
type: string
enum: [ "limited", "blocked", "full" ]
example: limited
UserRequest:
type: object
properties:
@ -589,8 +604,6 @@ components:
type: string
enum: ["api", "integration", "jwt"]
example: api
type: string
example: api
required:
- id
- name

View File

@ -69,6 +69,20 @@ const (
GeoLocationCheckActionDeny GeoLocationCheckAction = "deny"
)
// Defines values for GroupIssued.
const (
GroupIssuedApi GroupIssued = "api"
GroupIssuedIntegration GroupIssued = "integration"
GroupIssuedJwt GroupIssued = "jwt"
)
// Defines values for GroupMinimumIssued.
const (
GroupMinimumIssuedApi GroupMinimumIssued = "api"
GroupMinimumIssuedIntegration GroupMinimumIssued = "integration"
GroupMinimumIssuedJwt GroupMinimumIssued = "jwt"
)
// Defines values for NameserverNsType.
const (
NameserverNsTypeUdp NameserverNsType = "udp"
@ -129,6 +143,13 @@ const (
UserStatusInvited UserStatus = "invited"
)
// Defines values for UserPermissionsDashboardView.
const (
UserPermissionsDashboardViewBlocked UserPermissionsDashboardView = "blocked"
UserPermissionsDashboardViewFull UserPermissionsDashboardView = "full"
UserPermissionsDashboardViewLimited UserPermissionsDashboardView = "limited"
)
// AccessiblePeer defines model for AccessiblePeer.
type AccessiblePeer struct {
// DnsLabel Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud
@ -186,6 +207,9 @@ type AccountSettings struct {
// PeerLoginExpirationEnabled Enables or disables peer login expiration globally. After peer's login has expired the user has to log in (authenticate). Applies only to peers that were added by a user (interactive SSO login).
PeerLoginExpirationEnabled bool `json:"peer_login_expiration_enabled"`
// RegularUsersViewBlocked Allows blocking regular users from viewing parts of the system.
RegularUsersViewBlocked bool `json:"regular_users_view_blocked"`
}
// Checks List of objects that perform the actual checks
@ -283,8 +307,8 @@ type Group struct {
// Id Group ID
Id string `json:"id"`
// Issued How group was issued by API or from JWT token
Issued *string `json:"issued,omitempty"`
// Issued How the group was issued (api, integration, jwt)
Issued *GroupIssued `json:"issued,omitempty"`
// Name Group Name identifier
Name string `json:"name"`
@ -296,13 +320,16 @@ type Group struct {
PeersCount int `json:"peers_count"`
}
// GroupIssued How the group was issued (api, integration, jwt)
type GroupIssued string
// GroupMinimum defines model for GroupMinimum.
type GroupMinimum struct {
// Id Group ID
Id string `json:"id"`
// Issued How group was issued by API or from JWT token
Issued *string `json:"issued,omitempty"`
// Issued How the group was issued (api, integration, jwt)
Issued *GroupMinimumIssued `json:"issued,omitempty"`
// Name Group Name identifier
Name string `json:"name"`
@ -311,6 +338,9 @@ type GroupMinimum struct {
PeersCount int `json:"peers_count"`
}
// GroupMinimumIssued How the group was issued (api, integration, jwt)
type GroupMinimumIssued string
// GroupRequest defines model for GroupRequest.
type GroupRequest struct {
// Name Group name identifier
@ -1072,7 +1102,8 @@ type User struct {
LastLogin *time.Time `json:"last_login,omitempty"`
// Name User's name from idp provider
Name string `json:"name"`
Name string `json:"name"`
Permissions *UserPermissions `json:"permissions,omitempty"`
// Role User's NetBird account role
Role string `json:"role"`
@ -1102,6 +1133,15 @@ type UserCreateRequest struct {
Role string `json:"role"`
}
// UserPermissions defines model for UserPermissions.
type UserPermissions struct {
// DashboardView User's permission to view the dashboard
DashboardView *UserPermissionsDashboardView `json:"dashboard_view,omitempty"`
}
// UserPermissionsDashboardView User's permission to view the dashboard
type UserPermissionsDashboardView string
// UserRequest defines model for UserRequest.
type UserRequest struct {
// AutoGroups Group IDs to auto-assign to peers registered by this user

View File

@ -35,19 +35,25 @@ func NewGroupsHandler(accountManager server.AccountManager, authCfg AuthCfg) *Gr
// GetAllGroups list for the account
func (h *GroupsHandler) GetAllGroups(w http.ResponseWriter, r *http.Request) {
claims := h.claimsExtractor.FromRequestContext(r)
account, _, err := h.accountManager.GetAccountFromToken(claims)
account, user, err := h.accountManager.GetAccountFromToken(claims)
if err != nil {
log.Error(err)
http.Redirect(w, r, "/", http.StatusInternalServerError)
return
}
var groups []*api.Group
for _, g := range account.Groups {
groups = append(groups, toGroupResponse(account, g))
groups, err := h.accountManager.GetAllGroups(account.Id, user.Id)
if err != nil {
util.WriteError(err, w)
return
}
util.WriteJSONObject(w, groups)
groupsResponse := make([]*api.Group, 0, len(groups))
for _, group := range groups {
groupsResponse = append(groupsResponse, toGroupResponse(account, group))
}
util.WriteJSONObject(w, groupsResponse)
}
// UpdateGroup handles update to a group identified by a given ID
@ -207,7 +213,7 @@ func (h *GroupsHandler) DeleteGroup(w http.ResponseWriter, r *http.Request) {
// GetGroup returns a group
func (h *GroupsHandler) GetGroup(w http.ResponseWriter, r *http.Request) {
claims := h.claimsExtractor.FromRequestContext(r)
account, _, err := h.accountManager.GetAccountFromToken(claims)
account, user, err := h.accountManager.GetAccountFromToken(claims)
if err != nil {
util.WriteError(err, w)
return
@ -221,7 +227,7 @@ func (h *GroupsHandler) GetGroup(w http.ResponseWriter, r *http.Request) {
return
}
group, err := h.accountManager.GetGroup(account.Id, groupID)
group, err := h.accountManager.GetGroup(account.Id, groupID, user.Id)
if err != nil {
util.WriteError(err, w)
return
@ -239,7 +245,7 @@ func toGroupResponse(account *server.Account, group *server.Group) *api.Group {
gr := api.Group{
Id: group.ID,
Name: group.Name,
Issued: &group.Issued,
Issued: (*api.GroupIssued)(&group.Issued),
}
for _, pid := range group.Peers {

View File

@ -37,7 +37,7 @@ func initGroupTestData(user *server.User, groups ...*server.Group) *GroupsHandle
}
return nil
},
GetGroupFunc: func(_, groupID string) (*server.Group, error) {
GetGroupFunc: func(_, groupID, _ string) (*server.Group, error) {
if groupID != "idofthegroup" {
return nil, status.Errorf(status.NotFound, "not found")
}
@ -187,7 +187,7 @@ func TestWriteGroup(t *testing.T) {
expectedGroup: &api.Group{
Id: "id-was-set",
Name: "Default POSTed Group",
Issued: &groupIssuedAPI,
Issued: (*api.GroupIssued)(&groupIssuedAPI),
},
},
{
@ -209,7 +209,7 @@ func TestWriteGroup(t *testing.T) {
expectedGroup: &api.Group{
Id: "id-existed",
Name: "Default POSTed Group",
Issued: &groupIssuedAPI,
Issued: (*api.GroupIssued)(&groupIssuedAPI),
},
},
{
@ -240,7 +240,7 @@ func TestWriteGroup(t *testing.T) {
expectedGroup: &api.Group{
Id: "id-jwt-group",
Name: "changed",
Issued: &groupIssuedJWT,
Issued: (*api.GroupIssued)(&groupIssuedJWT),
},
},
}

View File

@ -288,5 +288,8 @@ func toUserResponse(user *server.UserInfo, currenUserID string) *api.User {
IsBlocked: user.IsBlocked,
LastLogin: &user.LastLogin,
Issued: &user.Issued,
Permissions: &api.UserPermissions{
DashboardView: (*api.UserPermissionsDashboardView)(&user.Permissions.DashboardView),
},
}
}

View File

@ -105,7 +105,7 @@ func initUsersTestData() *UsersHandler {
return nil, status.Errorf(status.NotFound, "user with ID %s does not exists", userID)
}
info, err := update.Copy().ToUserInfo(nil)
info, err := update.Copy().ToUserInfo(nil, &server.Settings{RegularUsersViewBlocked: false})
if err != nil {
return nil, err
}

View File

@ -31,7 +31,8 @@ type MockAccountManager struct {
GetNetworkMapFunc func(peerKey string) (*server.NetworkMap, error)
GetPeerNetworkFunc func(peerKey string) (*server.Network, error)
AddPeerFunc func(setupKey string, userId string, peer *nbpeer.Peer) (*nbpeer.Peer, *server.NetworkMap, error)
GetGroupFunc func(accountID, groupID string) (*server.Group, error)
GetGroupFunc func(accountID, groupID, userID string) (*server.Group, error)
GetAllGroupsFunc func(accountID, userID string) ([]*server.Group, error)
GetGroupByNameFunc func(accountID, groupName string) (*server.Group, error)
SaveGroupFunc func(accountID, userID string, group *server.Group) error
DeleteGroupFunc func(accountID, userId, groupID string) error
@ -92,6 +93,22 @@ type MockAccountManager struct {
GetIdpManagerFunc func() idp.Manager
}
// GetGroup mock implementation of GetGroup from server.AccountManager interface
func (am *MockAccountManager) GetGroup(accountId, groupID, userID string) (*server.Group, error) {
if am.GetGroupFunc != nil {
return am.GetGroupFunc(accountId, groupID, userID)
}
return nil, status.Errorf(codes.Unimplemented, "method GetGroup is not implemented")
}
// GetAllGroups mock implementation of GetAllGroups from server.AccountManager interface
func (am *MockAccountManager) GetAllGroups(accountID, userID string) ([]*server.Group, error) {
if am.GetAllGroupsFunc != nil {
return am.GetAllGroupsFunc(accountID, userID)
}
return nil, status.Errorf(codes.Unimplemented, "method GetAllGroups is not implemented")
}
// GetUsersFromAccount mock implementation of GetUsersFromAccount from server.AccountManager interface
func (am *MockAccountManager) GetUsersFromAccount(accountID string, userID string) ([]*server.UserInfo, error) {
if am.GetUsersFromAccountFunc != nil {
@ -243,14 +260,6 @@ func (am *MockAccountManager) AddPeer(
return nil, nil, status.Errorf(codes.Unimplemented, "method AddPeer is not implemented")
}
// GetGroup mock implementation of GetGroup from server.AccountManager interface
func (am *MockAccountManager) GetGroup(accountID, groupID string) (*server.Group, error) {
if am.GetGroupFunc != nil {
return am.GetGroupFunc(accountID, groupID)
}
return nil, status.Errorf(codes.Unimplemented, "method GetGroup is not implemented")
}
// GetGroupByName mock implementation of GetGroupByName from server.AccountManager interface
func (am *MockAccountManager) GetGroupByName(accountID, groupName string) (*server.Group, error) {
if am.GetGroupFunc != nil {

View File

@ -54,6 +54,11 @@ func (am *DefaultAccountManager) GetPeers(accountID, userID string) ([]*nbpeer.P
peers := make([]*nbpeer.Peer, 0)
peersMap := make(map[string]*nbpeer.Peer)
if !user.HasAdminPower() && !user.IsServiceUser && account.Settings.RegularUsersViewBlocked {
return peers, nil
}
for _, peer := range account.Peers {
if !(user.HasAdminPower() || user.IsServiceUser) && user.Id != peer.UserID {
// only display peers that belong to the current user if the current user is not an admin
@ -738,6 +743,10 @@ func (am *DefaultAccountManager) GetPeer(accountID, peerID, userID string) (*nbp
return nil, err
}
if !user.HasAdminPower() && !user.IsServiceUser && account.Settings.RegularUsersViewBlocked {
return nil, status.Errorf(status.Internal, "user %s has no access to his own peer %s under account %s", userID, peerID, accountID)
}
peer := account.GetPeer(peerID)
if peer == nil {
return nil, status.Errorf(status.NotFound, "peer with %s not found under account %s", peerID, accountID)

View File

@ -4,9 +4,8 @@ import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/rs/xid"
"github.com/stretchr/testify/assert"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
@ -392,6 +391,8 @@ func TestDefaultAccountManager_GetPeer(t *testing.T) {
Id: someUser,
Role: UserRoleUser,
}
account.Settings.RegularUsersViewBlocked = false
err = manager.Store.SaveAccount(account)
if err != nil {
t.Fatal(err)
@ -480,3 +481,153 @@ func TestDefaultAccountManager_GetPeer(t *testing.T) {
}
assert.NotNil(t, peer)
}
func TestDefaultAccountManager_GetPeers(t *testing.T) {
testCases := []struct {
name string
role UserRole
limitedViewSettings bool
isServiceUser bool
expectedPeerCount int
}{
{
name: "Regular user, no limited view settings, not a service user",
role: UserRoleUser,
limitedViewSettings: false,
isServiceUser: false,
expectedPeerCount: 1,
},
{
name: "Service user, no limited view settings",
role: UserRoleUser,
limitedViewSettings: false,
isServiceUser: true,
expectedPeerCount: 2,
},
{
name: "Regular user, limited view settings",
role: UserRoleUser,
limitedViewSettings: true,
isServiceUser: false,
expectedPeerCount: 0,
},
{
name: "Service user, limited view settings",
role: UserRoleUser,
limitedViewSettings: true,
isServiceUser: true,
expectedPeerCount: 2,
},
{
name: "Admin, no limited view settings, not a service user",
role: UserRoleAdmin,
limitedViewSettings: false,
isServiceUser: false,
expectedPeerCount: 2,
},
{
name: "Admin service user, no limited view settings",
role: UserRoleAdmin,
limitedViewSettings: false,
isServiceUser: true,
expectedPeerCount: 2,
},
{
name: "Admin, limited view settings",
role: UserRoleAdmin,
limitedViewSettings: true,
isServiceUser: false,
expectedPeerCount: 2,
},
{
name: "Admin Service user, limited view settings",
role: UserRoleAdmin,
limitedViewSettings: true,
isServiceUser: true,
expectedPeerCount: 2,
},
{
name: "Owner, no limited view settings",
role: UserRoleOwner,
limitedViewSettings: true,
isServiceUser: false,
expectedPeerCount: 2,
},
{
name: "Owner, limited view settings",
role: UserRoleOwner,
limitedViewSettings: true,
isServiceUser: false,
expectedPeerCount: 2,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
manager, err := createManager(t)
if err != nil {
t.Fatal(err)
return
}
// account with an admin and a regular user
accountID := "test_account"
adminUser := "account_creator"
someUser := "some_user"
account := newAccountWithId(accountID, adminUser, "")
account.Users[someUser] = &User{
Id: someUser,
Role: testCase.role,
IsServiceUser: testCase.isServiceUser,
}
account.Policies = []*Policy{}
account.Settings.RegularUsersViewBlocked = testCase.limitedViewSettings
err = manager.Store.SaveAccount(account)
if err != nil {
t.Fatal(err)
return
}
peerKey1, err := wgtypes.GeneratePrivateKey()
if err != nil {
t.Fatal(err)
return
}
peerKey2, err := wgtypes.GeneratePrivateKey()
if err != nil {
t.Fatal(err)
return
}
_, _, err = manager.AddPeer("", someUser, &nbpeer.Peer{
Key: peerKey1.PublicKey().String(),
Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer-1"},
})
if err != nil {
t.Errorf("expecting peer to be added, got failure %v", err)
return
}
_, _, err = manager.AddPeer("", adminUser, &nbpeer.Peer{
Key: peerKey2.PublicKey().String(),
Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer-2"},
})
if err != nil {
t.Errorf("expecting peer to be added, got failure %v", err)
return
}
peers, err := manager.GetPeers(accountID, someUser)
if err != nil {
t.Fatal(err)
return
}
assert.NotNil(t, peers)
assert.Len(t, peers, testCase.expectedPeerCount)
})
}
}

View File

@ -339,6 +339,10 @@ func (am *DefaultAccountManager) ListSetupKeys(accountID, userID string) ([]*Set
return nil, err
}
if !user.HasAdminPower() && !user.IsServiceUser {
return nil, status.Errorf(status.Unauthorized, "only users with admin power can view policies")
}
keys := make([]*SetupKey, 0, len(account.SetupKeys))
for _, key := range account.SetupKeys {
var k *SetupKey
@ -368,6 +372,10 @@ func (am *DefaultAccountManager) GetSetupKey(accountID, userID, keyID string) (*
return nil, err
}
if !user.HasAdminPower() && !user.IsServiceUser {
return nil, status.Errorf(status.Unauthorized, "only users with admin power can view policies")
}
var foundKey *SetupKey
for _, key := range account.SetupKeys {
if key.Id == keyID {

View File

@ -166,6 +166,37 @@ func TestDefaultAccountManager_CreateSetupKey(t *testing.T) {
}
func TestGetSetupKeys(t *testing.T) {
manager, err := createManager(t)
if err != nil {
t.Fatal(err)
}
userID := "testingUser"
account, err := manager.GetOrCreateAccountByUser(userID, "")
if err != nil {
t.Fatal(err)
}
err = manager.SaveGroup(account.Id, userID, &Group{
ID: "group_1",
Name: "group_name_1",
Peers: []string{},
})
if err != nil {
t.Fatal(err)
}
err = manager.SaveGroup(account.Id, userID, &Group{
ID: "group_2",
Name: "group_name_2",
Peers: []string{},
})
if err != nil {
t.Fatal(err)
}
}
func TestGenerateDefaultSetupKey(t *testing.T) {
expectedName := "Default key"
expectedRevoke := false

View File

@ -113,12 +113,20 @@ func (u *User) HasAdminPower() bool {
}
// ToUserInfo converts a User object to a UserInfo object.
func (u *User) ToUserInfo(userData *idp.UserData) (*UserInfo, error) {
func (u *User) ToUserInfo(userData *idp.UserData, settings *Settings) (*UserInfo, error) {
autoGroups := u.AutoGroups
if autoGroups == nil {
autoGroups = []string{}
}
dashboardViewPermissions := "full"
if !u.HasAdminPower() {
dashboardViewPermissions = "limited"
if settings.RegularUsersViewBlocked {
dashboardViewPermissions = "blocked"
}
}
if userData == nil {
return &UserInfo{
ID: u.Id,
@ -131,6 +139,9 @@ func (u *User) ToUserInfo(userData *idp.UserData) (*UserInfo, error) {
IsBlocked: u.Blocked,
LastLogin: u.LastLogin,
Issued: u.Issued,
Permissions: UserPermissions{
DashboardView: dashboardViewPermissions,
},
}, nil
}
if userData.ID != u.Id {
@ -153,6 +164,9 @@ func (u *User) ToUserInfo(userData *idp.UserData) (*UserInfo, error) {
IsBlocked: u.Blocked,
LastLogin: u.LastLogin,
Issued: u.Issued,
Permissions: UserPermissions{
DashboardView: dashboardViewPermissions,
},
}, nil
}
@ -358,7 +372,7 @@ func (am *DefaultAccountManager) inviteNewUser(accountID, userID string, invite
am.StoreEvent(userID, newUser.Id, accountID, activity.UserInvited, nil)
return newUser.ToUserInfo(idpUser)
return newUser.ToUserInfo(idpUser, account.Settings)
}
// GetUser looks up a user by provided authorization claims.
@ -905,9 +919,9 @@ func (am *DefaultAccountManager) SaveOrAddUser(accountID, initiatorUserID string
if err != nil {
return nil, err
}
return newUser.ToUserInfo(userData)
return newUser.ToUserInfo(userData, account.Settings)
}
return newUser.ToUserInfo(nil)
return newUser.ToUserInfo(nil, account.Settings)
}
// GetOrCreateAccountByUser returns an existing account for a given user id or creates a new one if doesn't exist
@ -998,7 +1012,7 @@ func (am *DefaultAccountManager) GetUsersFromAccount(accountID, userID string) (
// if user is not an admin then show only current user and do not show other users
continue
}
info, err := accountUser.ToUserInfo(nil)
info, err := accountUser.ToUserInfo(nil, account.Settings)
if err != nil {
return nil, err
}
@ -1015,7 +1029,7 @@ func (am *DefaultAccountManager) GetUsersFromAccount(accountID, userID string) (
var info *UserInfo
if queriedUser, contains := findUserInIDPUserdata(localUser.Id, queriedUsers); contains {
info, err = localUser.ToUserInfo(queriedUser)
info, err = localUser.ToUserInfo(queriedUser, account.Settings)
if err != nil {
return nil, err
}
@ -1024,6 +1038,15 @@ func (am *DefaultAccountManager) GetUsersFromAccount(accountID, userID string) (
if localUser.IsServiceUser {
name = localUser.ServiceUserName
}
dashboardViewPermissions := "full"
if !localUser.HasAdminPower() {
dashboardViewPermissions = "limited"
if account.Settings.RegularUsersViewBlocked {
dashboardViewPermissions = "blocked"
}
}
info = &UserInfo{
ID: localUser.Id,
Email: "",
@ -1033,6 +1056,7 @@ func (am *DefaultAccountManager) GetUsersFromAccount(accountID, userID string) (
Status: string(UserStatusActive),
IsServiceUser: localUser.IsServiceUser,
NonDeletable: localUser.NonDeletable,
Permissions: UserPermissions{DashboardView: dashboardViewPermissions},
}
}
userInfos = append(userInfos, info)

View File

@ -709,6 +709,83 @@ func TestDefaultAccountManager_ListUsers(t *testing.T) {
assert.Equal(t, 2, regular)
}
func TestDefaultAccountManager_ListUsers_DashboardPermissions(t *testing.T) {
testCases := []struct {
name string
role UserRole
limitedViewSettings bool
expectedDashboardPermissions string
}{
{
name: "Regular user, no limited view settings",
role: UserRoleUser,
limitedViewSettings: false,
expectedDashboardPermissions: "limited",
},
{
name: "Admin user, no limited view settings",
role: UserRoleAdmin,
limitedViewSettings: false,
expectedDashboardPermissions: "full",
},
{
name: "Owner, no limited view settings",
role: UserRoleOwner,
limitedViewSettings: false,
expectedDashboardPermissions: "full",
},
{
name: "Regular user, limited view settings",
role: UserRoleUser,
limitedViewSettings: true,
expectedDashboardPermissions: "blocked",
},
{
name: "Admin user, limited view settings",
role: UserRoleAdmin,
limitedViewSettings: true,
expectedDashboardPermissions: "full",
},
{
name: "Owner, limited view settings",
role: UserRoleOwner,
limitedViewSettings: true,
expectedDashboardPermissions: "full",
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
store := newStore(t)
account := newAccountWithId(mockAccountID, mockUserID, "")
account.Users["normal_user1"] = NewUser("normal_user1", testCase.role, false, false, "", []string{}, UserIssuedAPI)
account.Settings.RegularUsersViewBlocked = testCase.limitedViewSettings
delete(account.Users, mockUserID)
err := store.SaveAccount(account)
if err != nil {
t.Fatalf("Error when saving account: %s", err)
}
am := DefaultAccountManager{
Store: store,
eventStore: &activity.InMemoryEventStore{},
}
users, err := am.ListUsers(mockAccountID)
if err != nil {
t.Fatalf("Error when checking user role: %s", err)
}
assert.Equal(t, 1, len(users))
userInfo, _ := users[0].ToUserInfo(nil, account.Settings)
assert.Equal(t, testCase.expectedDashboardPermissions, userInfo.Permissions.DashboardView)
})
}
}
func TestDefaultAccountManager_ExternalCache(t *testing.T) {
store := newStore(t)
account := newAccountWithId(mockAccountID, mockUserID, "")