mirror of https://github.com/grafana/grafana.git
				
				
				
			
		
			
				
	
	
		
			493 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			493 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
| package ossaccesscontrol
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"testing"
 | |
| 
 | |
| 	"github.com/grafana/grafana/pkg/infra/log"
 | |
| 	"github.com/grafana/grafana/pkg/infra/usagestats"
 | |
| 	"github.com/grafana/grafana/pkg/models"
 | |
| 	"github.com/grafana/grafana/pkg/services/accesscontrol"
 | |
| 	"github.com/grafana/grafana/pkg/setting"
 | |
| 	"github.com/stretchr/testify/assert"
 | |
| 	"github.com/stretchr/testify/require"
 | |
| )
 | |
| 
 | |
| func setupTestEnv(t testing.TB) *OSSAccessControlService {
 | |
| 	t.Helper()
 | |
| 
 | |
| 	cfg := setting.NewCfg()
 | |
| 	cfg.FeatureToggles = map[string]bool{"accesscontrol": true}
 | |
| 	ac := ProvideService(cfg, &usagestats.UsageStatsMock{T: t})
 | |
| 	return ac
 | |
| }
 | |
| 
 | |
| func removeRoleHelper(role string) {
 | |
| 	delete(accesscontrol.FixedRoles, role)
 | |
| 
 | |
| 	// Compute new grants removing any appearance of the role in the list
 | |
| 	replaceGrants := map[string][]string{}
 | |
| 
 | |
| 	for builtInRole, grants := range accesscontrol.FixedRoleGrants {
 | |
| 		newGrants := make([]string, len(grants))
 | |
| 		for _, r := range grants {
 | |
| 			if r != role {
 | |
| 				newGrants = append(newGrants, r)
 | |
| 			}
 | |
| 		}
 | |
| 		replaceGrants[builtInRole] = newGrants
 | |
| 	}
 | |
| 
 | |
| 	// Replace grants
 | |
| 	for br, grants := range replaceGrants {
 | |
| 		accesscontrol.FixedRoleGrants[br] = grants
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type evaluatingPermissionsTestCase struct {
 | |
| 	desc       string
 | |
| 	user       userTestCase
 | |
| 	endpoints  []endpointTestCase
 | |
| 	evalResult bool
 | |
| }
 | |
| 
 | |
| type userTestCase struct {
 | |
| 	name           string
 | |
| 	orgRole        models.RoleType
 | |
| 	isGrafanaAdmin bool
 | |
| }
 | |
| 
 | |
| type endpointTestCase struct {
 | |
| 	evaluator accesscontrol.Evaluator
 | |
| }
 | |
| 
 | |
| func TestEvaluatingPermissions(t *testing.T) {
 | |
| 	testCases := []evaluatingPermissionsTestCase{
 | |
| 		{
 | |
| 			desc: "should successfully evaluate access to the endpoint",
 | |
| 			user: userTestCase{
 | |
| 				name:           "testuser",
 | |
| 				orgRole:        "Grafana Admin",
 | |
| 				isGrafanaAdmin: false,
 | |
| 			},
 | |
| 			endpoints: []endpointTestCase{
 | |
| 				{evaluator: accesscontrol.EvalPermission(accesscontrol.ActionUsersDisable, accesscontrol.ScopeGlobalUsersAll)},
 | |
| 				{evaluator: accesscontrol.EvalPermission(accesscontrol.ActionUsersEnable, accesscontrol.ScopeGlobalUsersAll)},
 | |
| 			},
 | |
| 			evalResult: true,
 | |
| 		},
 | |
| 		{
 | |
| 			desc: "should restrict access to the unauthorized endpoints",
 | |
| 			user: userTestCase{
 | |
| 				name:           "testuser",
 | |
| 				orgRole:        models.ROLE_VIEWER,
 | |
| 				isGrafanaAdmin: false,
 | |
| 			},
 | |
| 			endpoints: []endpointTestCase{
 | |
| 				{evaluator: accesscontrol.EvalPermission(accesscontrol.ActionUsersCreate, accesscontrol.ScopeGlobalUsersAll)},
 | |
| 			},
 | |
| 			evalResult: false,
 | |
| 		},
 | |
| 	}
 | |
| 	for _, tc := range testCases {
 | |
| 		t.Run(tc.desc, func(t *testing.T) {
 | |
| 			ac := setupTestEnv(t)
 | |
| 
 | |
| 			user := &models.SignedInUser{
 | |
| 				UserId:         1,
 | |
| 				OrgId:          1,
 | |
| 				Name:           tc.user.name,
 | |
| 				OrgRole:        tc.user.orgRole,
 | |
| 				IsGrafanaAdmin: tc.user.isGrafanaAdmin,
 | |
| 			}
 | |
| 
 | |
| 			for _, endpoint := range tc.endpoints {
 | |
| 				result, err := ac.Evaluate(context.Background(), user, endpoint.evaluator)
 | |
| 				require.NoError(t, err)
 | |
| 				assert.Equal(t, tc.evalResult, result)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestUsageMetrics(t *testing.T) {
 | |
| 	tests := []struct {
 | |
| 		name          string
 | |
| 		enabled       bool
 | |
| 		expectedValue int
 | |
| 	}{
 | |
| 		{
 | |
| 			name:          "Expecting metric with value 0",
 | |
| 			enabled:       false,
 | |
| 			expectedValue: 0,
 | |
| 		},
 | |
| 		{
 | |
| 			name:          "Expecting metric with value 1",
 | |
| 			enabled:       true,
 | |
| 			expectedValue: 1,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tt := range tests {
 | |
| 		t.Run(tt.name, func(t *testing.T) {
 | |
| 			cfg := setting.NewCfg()
 | |
| 			if tt.enabled {
 | |
| 				cfg.FeatureToggles = map[string]bool{"accesscontrol": true}
 | |
| 			}
 | |
| 
 | |
| 			s := ProvideService(cfg, &usagestats.UsageStatsMock{T: t})
 | |
| 			report, err := s.UsageStats.GetUsageReport(context.Background())
 | |
| 			assert.Nil(t, err)
 | |
| 
 | |
| 			assert.Equal(t, tt.expectedValue, report.Metrics["stats.oss.accesscontrol.enabled.count"])
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type assignmentTestCase struct {
 | |
| 	role         accesscontrol.RoleDTO
 | |
| 	builtInRoles []string
 | |
| }
 | |
| 
 | |
| func TestOSSAccessControlService_RegisterFixedRole(t *testing.T) {
 | |
| 	tests := []struct {
 | |
| 		name string
 | |
| 		runs []assignmentTestCase
 | |
| 	}{
 | |
| 		{
 | |
| 			name: "Successfully register role no assignments",
 | |
| 			runs: []assignmentTestCase{
 | |
| 				{
 | |
| 					role: accesscontrol.RoleDTO{
 | |
| 						Version: 1,
 | |
| 						Name:    "fixed:test:test",
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "Successfully ignore overwriting existing role",
 | |
| 			runs: []assignmentTestCase{
 | |
| 				{
 | |
| 					role: accesscontrol.RoleDTO{
 | |
| 						Version: 1,
 | |
| 						Name:    "fixed:test:test",
 | |
| 					},
 | |
| 				},
 | |
| 				{
 | |
| 					role: accesscontrol.RoleDTO{
 | |
| 						Version: 1,
 | |
| 						Name:    "fixed:test:test",
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "Successfully register and assign role",
 | |
| 			runs: []assignmentTestCase{
 | |
| 				{
 | |
| 					role: accesscontrol.RoleDTO{
 | |
| 						Version: 1,
 | |
| 						Name:    "fixed:test:test",
 | |
| 					},
 | |
| 					builtInRoles: []string{"Viewer", "Editor", "Admin"},
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "Successfully ignore unchanged assignment",
 | |
| 			runs: []assignmentTestCase{
 | |
| 				{
 | |
| 					role: accesscontrol.RoleDTO{
 | |
| 						Version: 1,
 | |
| 						Name:    "fixed:test:test",
 | |
| 					},
 | |
| 					builtInRoles: []string{"Viewer"},
 | |
| 				},
 | |
| 				{
 | |
| 					role: accesscontrol.RoleDTO{
 | |
| 						Version: 2,
 | |
| 						Name:    "fixed:test:test",
 | |
| 					},
 | |
| 					builtInRoles: []string{"Viewer"},
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "Successfully add a new assignment",
 | |
| 			runs: []assignmentTestCase{
 | |
| 				{
 | |
| 					role: accesscontrol.RoleDTO{
 | |
| 						Version: 1,
 | |
| 						Name:    "fixed:test:test",
 | |
| 					},
 | |
| 					builtInRoles: []string{"Viewer"},
 | |
| 				},
 | |
| 				{
 | |
| 					role: accesscontrol.RoleDTO{
 | |
| 						Version: 1,
 | |
| 						Name:    "fixed:test:test",
 | |
| 					},
 | |
| 					builtInRoles: []string{"Editor"},
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	// Check all runs performed so far to get the number of assignments seeder
 | |
| 	// should have recorded
 | |
| 	getTotalAssignCount := func(curRunIdx int, runs []assignmentTestCase) int {
 | |
| 		builtIns := map[string]struct{}{}
 | |
| 		for i := 0; i < curRunIdx+1; i++ {
 | |
| 			for _, br := range runs[i].builtInRoles {
 | |
| 				builtIns[br] = struct{}{}
 | |
| 			}
 | |
| 		}
 | |
| 		return len(builtIns)
 | |
| 	}
 | |
| 
 | |
| 	for _, tc := range tests {
 | |
| 		t.Run(tc.name, func(t *testing.T) {
 | |
| 			ac := &OSSAccessControlService{
 | |
| 				Cfg:        setting.NewCfg(),
 | |
| 				UsageStats: &usagestats.UsageStatsMock{T: t},
 | |
| 				Log:        log.New("accesscontrol-test"),
 | |
| 			}
 | |
| 
 | |
| 			for i, run := range tc.runs {
 | |
| 				// Remove any inserted role after the test case has been run
 | |
| 				t.Cleanup(func() { removeRoleHelper(run.role.Name) })
 | |
| 
 | |
| 				ac.registerFixedRole(run.role, run.builtInRoles)
 | |
| 
 | |
| 				// Check role has been registered
 | |
| 				storedRole, ok := accesscontrol.FixedRoles[run.role.Name]
 | |
| 				assert.True(t, ok, "role should have been registered")
 | |
| 
 | |
| 				// Check registered role has not been altered
 | |
| 				assert.Equal(t, run.role, storedRole, "role should not have been altered")
 | |
| 
 | |
| 				// Check assignments
 | |
| 				// Count number of times the role has been assigned
 | |
| 				assignCnt := 0
 | |
| 				for _, grants := range accesscontrol.FixedRoleGrants {
 | |
| 					for _, r := range grants {
 | |
| 						if r == run.role.Name {
 | |
| 							assignCnt++
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 				assert.Equal(t, getTotalAssignCount(i, tc.runs), assignCnt,
 | |
| 					"assignments should only be added, never removed")
 | |
| 
 | |
| 				for _, br := range run.builtInRoles {
 | |
| 					assigns, ok := accesscontrol.FixedRoleGrants[br]
 | |
| 					assert.True(t, ok,
 | |
| 						fmt.Sprintf("role %s should have been assigned to %s", run.role.Name, br))
 | |
| 					assert.Contains(t, assigns, run.role.Name,
 | |
| 						fmt.Sprintf("role %s should have been assigned to %s", run.role.Name, br))
 | |
| 				}
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestOSSAccessControlService_DeclareFixedRoles(t *testing.T) {
 | |
| 	tests := []struct {
 | |
| 		name          string
 | |
| 		registrations []accesscontrol.RoleRegistration
 | |
| 		wantErr       bool
 | |
| 		err           error
 | |
| 	}{
 | |
| 		{
 | |
| 			name:    "should work with empty list",
 | |
| 			wantErr: false,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "should add registration",
 | |
| 			registrations: []accesscontrol.RoleRegistration{
 | |
| 				{
 | |
| 					Role: accesscontrol.RoleDTO{
 | |
| 						Version: 1,
 | |
| 						Name:    "fixed:test:test",
 | |
| 					},
 | |
| 					Grants: []string{"Admin"},
 | |
| 				},
 | |
| 			},
 | |
| 			wantErr: false,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "should fail registration invalid role name",
 | |
| 			registrations: []accesscontrol.RoleRegistration{
 | |
| 				{
 | |
| 					Role: accesscontrol.RoleDTO{
 | |
| 						Version: 1,
 | |
| 						Name:    "custom:test:test",
 | |
| 					},
 | |
| 					Grants: []string{"Admin"},
 | |
| 				},
 | |
| 			},
 | |
| 			wantErr: true,
 | |
| 			err:     accesscontrol.ErrFixedRolePrefixMissing,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "should fail registration invalid builtin role assignment",
 | |
| 			registrations: []accesscontrol.RoleRegistration{
 | |
| 				{
 | |
| 					Role: accesscontrol.RoleDTO{
 | |
| 						Version: 1,
 | |
| 						Name:    "fixed:test:test",
 | |
| 					},
 | |
| 					Grants: []string{"WrongAdmin"},
 | |
| 				},
 | |
| 			},
 | |
| 			wantErr: true,
 | |
| 			err:     accesscontrol.ErrInvalidBuiltinRole,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "should add multiple registrations at once",
 | |
| 			registrations: []accesscontrol.RoleRegistration{
 | |
| 				{
 | |
| 					Role: accesscontrol.RoleDTO{
 | |
| 						Version: 1,
 | |
| 						Name:    "fixed:test:test",
 | |
| 					},
 | |
| 					Grants: []string{"Admin"},
 | |
| 				},
 | |
| 				{
 | |
| 					Role: accesscontrol.RoleDTO{
 | |
| 						Version: 1,
 | |
| 						Name:    "fixed:test2:test2",
 | |
| 					},
 | |
| 					Grants: []string{"Admin"},
 | |
| 				},
 | |
| 			},
 | |
| 			wantErr: false,
 | |
| 		},
 | |
| 	}
 | |
| 	for _, tt := range tests {
 | |
| 		t.Run(tt.name, func(t *testing.T) {
 | |
| 			ac := &OSSAccessControlService{
 | |
| 				Cfg:           setting.NewCfg(),
 | |
| 				UsageStats:    &usagestats.UsageStatsMock{T: t},
 | |
| 				Log:           log.New("accesscontrol-test"),
 | |
| 				registrations: accesscontrol.RegistrationList{},
 | |
| 			}
 | |
| 			ac.Cfg.FeatureToggles = map[string]bool{"accesscontrol": true}
 | |
| 
 | |
| 			// Test
 | |
| 			err := ac.DeclareFixedRoles(tt.registrations...)
 | |
| 			if tt.wantErr {
 | |
| 				require.Error(t, err)
 | |
| 				assert.ErrorIs(t, err, tt.err)
 | |
| 				return
 | |
| 			}
 | |
| 			require.NoError(t, err)
 | |
| 
 | |
| 			registrationCnt := 0
 | |
| 			ac.registrations.Range(func(registration accesscontrol.RoleRegistration) bool {
 | |
| 				registrationCnt++
 | |
| 				return true
 | |
| 			})
 | |
| 			assert.Equal(t, len(tt.registrations), registrationCnt,
 | |
| 				"expected service registration list to contain all test registrations")
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestOSSAccessControlService_RegisterFixedRoles(t *testing.T) {
 | |
| 	tests := []struct {
 | |
| 		name          string
 | |
| 		token         models.Licensing
 | |
| 		registrations []accesscontrol.RoleRegistration
 | |
| 		wantErr       bool
 | |
| 	}{
 | |
| 		{
 | |
| 			name: "should work with empty list",
 | |
| 		},
 | |
| 		{
 | |
| 			name: "should register and assign role",
 | |
| 			registrations: []accesscontrol.RoleRegistration{
 | |
| 				{
 | |
| 					Role: accesscontrol.RoleDTO{
 | |
| 						Version: 1,
 | |
| 						Name:    "fixed:test:test",
 | |
| 					},
 | |
| 					Grants: []string{"Admin"},
 | |
| 				},
 | |
| 			},
 | |
| 			wantErr: false,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "should register and assign multiple roles",
 | |
| 			registrations: []accesscontrol.RoleRegistration{
 | |
| 				{
 | |
| 					Role: accesscontrol.RoleDTO{
 | |
| 						Version: 1,
 | |
| 						Name:    "fixed:test:test",
 | |
| 					},
 | |
| 					Grants: []string{"Admin"},
 | |
| 				},
 | |
| 				{
 | |
| 					Role: accesscontrol.RoleDTO{
 | |
| 						Version: 1,
 | |
| 						Name:    "fixed:test2:test2",
 | |
| 					},
 | |
| 					Grants: []string{"Admin"},
 | |
| 				},
 | |
| 			},
 | |
| 			wantErr: false,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tt := range tests {
 | |
| 		cfg := setting.NewCfg()
 | |
| 		cfg.FeatureToggles = map[string]bool{"accesscontrol": true}
 | |
| 
 | |
| 		t.Run(tt.name, func(t *testing.T) {
 | |
| 			// Remove any inserted role after the test case has been run
 | |
| 			t.Cleanup(func() {
 | |
| 				for _, registration := range tt.registrations {
 | |
| 					removeRoleHelper(registration.Role.Name)
 | |
| 				}
 | |
| 			})
 | |
| 
 | |
| 			// Setup
 | |
| 			ac := &OSSAccessControlService{
 | |
| 				Cfg:           setting.NewCfg(),
 | |
| 				UsageStats:    &usagestats.UsageStatsMock{T: t},
 | |
| 				Log:           log.New("accesscontrol-test"),
 | |
| 				registrations: accesscontrol.RegistrationList{},
 | |
| 			}
 | |
| 			ac.Cfg.FeatureToggles = map[string]bool{"accesscontrol": true}
 | |
| 			ac.registrations.Append(tt.registrations...)
 | |
| 
 | |
| 			// Test
 | |
| 			err := ac.RegisterFixedRoles()
 | |
| 			if tt.wantErr {
 | |
| 				require.Error(t, err)
 | |
| 				return
 | |
| 			}
 | |
| 			require.NoError(t, err)
 | |
| 
 | |
| 			// Check
 | |
| 			for _, registration := range tt.registrations {
 | |
| 				role, ok := accesscontrol.FixedRoles[registration.Role.Name]
 | |
| 				assert.True(t, ok,
 | |
| 					fmt.Sprintf("role %s should have been registered", registration.Role.Name))
 | |
| 				assert.NotNil(t, role,
 | |
| 					fmt.Sprintf("role %s should have been registered", registration.Role.Name))
 | |
| 
 | |
| 				for _, br := range registration.Grants {
 | |
| 					rolesWithGrant, ok := accesscontrol.FixedRoleGrants[br]
 | |
| 					assert.True(t, ok,
 | |
| 						fmt.Sprintf("role %s should have been assigned to %s", registration.Role.Name, br))
 | |
| 					assert.Contains(t, rolesWithGrant, registration.Role.Name,
 | |
| 						fmt.Sprintf("role %s should have been assigned to %s", registration.Role.Name, br))
 | |
| 				}
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 |