diff --git a/compose/service/access_control.go b/compose/service/access_control.go index 206ff29a3..ca882399b 100644 --- a/compose/service/access_control.go +++ b/compose/service/access_control.go @@ -175,12 +175,16 @@ func (svc accessControl) can(ctx context.Context, res permissionResource, op per } func (svc accessControl) Grant(ctx context.Context, rr ...*permissions.Rule) error { + if !svc.CanGrant(ctx) { + return ErrNoGrantPermissions + } + return svc.permissions.Grant(ctx, svc.Whitelist(), rr...) } func (svc accessControl) FindRulesByRoleID(ctx context.Context, roleID uint64) (permissions.RuleSet, error) { if !svc.CanGrant(ctx) { - return nil, ErrNoGrantPermissions + return nil, ErrNoPermissions } return svc.permissions.FindRulesByRoleID(roleID), nil diff --git a/compose/service/error.go b/compose/service/error.go index 809f0b474..258e767cd 100644 --- a/compose/service/error.go +++ b/compose/service/error.go @@ -11,6 +11,7 @@ type ( const ( ErrInvalidID serviceError = "InvalidID" ErrStaleData serviceError = "StaleData" + ErrNoPermissions serviceError = "NoPermissions" ErrNoGrantPermissions serviceError = "NoGrantPermissions" ErrNoCreatePermissions serviceError = "NoCreatePermissions" ErrNoReadPermissions serviceError = "NoReadPermissions" diff --git a/compose/service/service.go b/compose/service/service.go index 4f53df55b..28edbb34a 100644 --- a/compose/service/service.go +++ b/compose/service/service.go @@ -30,6 +30,8 @@ type ( ) var ( + DefaultStore store.Store + DefaultLogger *zap.Logger // DefaultPermissions Retrieves & stores permissions @@ -64,19 +66,23 @@ func Init(ctx context.Context, log *zap.Logger, c Config) (err error) { DefaultLogger = log.Named("service") - fs, err := store.New(c.Storage.Path) - log.Info("initializing store", zap.String("path", c.Storage.Path), zap.Error(err)) - if err != nil { - return err + if DefaultStore == nil { + DefaultStore, err = store.New(c.Storage.Path) + log.Info("initializing store", zap.String("path", c.Storage.Path), zap.Error(err)) + if err != nil { + return err + } } // Permissions, access control - DefaultPermissions = permissions.Service( - ctx, - DefaultLogger, - permissions.Repository(db, "compose_permission_rules")) - + if DefaultPermissions == nil { + DefaultPermissions = permissions.Service( + ctx, + DefaultLogger, + permissions.Repository(db, "compose_permission_rules")) + } DefaultAccessControl = AccessControl(DefaultPermissions) + DefaultNamespace = Namespace() DefaultModule = Module() diff --git a/internal/permissions/service_alt.go b/internal/permissions/service_alt.go index 5dd4e1455..2c8def52f 100644 --- a/internal/permissions/service_alt.go +++ b/internal/permissions/service_alt.go @@ -2,6 +2,8 @@ package permissions import ( "context" + "fmt" + "strings" ) type ( @@ -49,6 +51,21 @@ func (svc *TestService) ClearGrants() { svc.rules = RuleSet{} } +func (svc *TestService) String() (out string) { + tpl := "%20v\t%-30s\t%-30s\t%v\n" + out = fmt.Sprintf(tpl, "role", "res", "op", "access") + out += strings.Repeat("-", 120) + "\n" + + _ = svc.rules.Walk(func(r *Rule) error { + out += fmt.Sprintf(tpl, r.RoleID, r.Resource, r.Operation, r.Access) + return nil + }) + + out += strings.Repeat("-", 120) + "\n" + + return +} + func NewTestService() *TestService { return &TestService{ service: service{}, diff --git a/messaging/service/access_control.go b/messaging/service/access_control.go index 0683aeb07..3e17466b5 100644 --- a/messaging/service/access_control.go +++ b/messaging/service/access_control.go @@ -215,6 +215,10 @@ func (svc accessControl) can(ctx context.Context, res permissionResource, op per } func (svc accessControl) Grant(ctx context.Context, rr ...*permissions.Rule) error { + if !svc.CanGrant(ctx) { + return ErrNoGrantPermissions + } + return svc.permissions.Grant(ctx, svc.Whitelist(), rr...) } diff --git a/messaging/service/error.go b/messaging/service/error.go index c54115770..9ea455c82 100644 --- a/messaging/service/error.go +++ b/messaging/service/error.go @@ -9,8 +9,9 @@ type ( ) const ( - ErrInvalidID serviceError = "InvalidID" - ErrNoPermissions serviceError = "NoPermissions" + ErrInvalidID serviceError = "InvalidID" + ErrNoPermissions serviceError = "NoPermissions" + ErrNoGrantPermissions serviceError = "NoGrantPermissions" ) func (e serviceError) Error() string { diff --git a/system/service/access_control.go b/system/service/access_control.go index 0fdd12e23..db05047aa 100644 --- a/system/service/access_control.go +++ b/system/service/access_control.go @@ -35,12 +35,12 @@ func (svc accessControl) Effective(ctx context.Context) (ee permissions.Effectiv ee = permissions.EffectiveSet{} ee.Push(types.SystemPermissionResource, "access", svc.CanAccess(ctx)) + ee.Push(types.SystemPermissionResource, "grant", svc.CanGrant(ctx)) ee.Push(types.SystemPermissionResource, "settings.read", svc.CanReadSettings(ctx)) ee.Push(types.SystemPermissionResource, "settings.manage", svc.CanManageSettings(ctx)) ee.Push(types.SystemPermissionResource, "application.create", svc.CanCreateApplication(ctx)) ee.Push(types.SystemPermissionResource, "role.create", svc.CanCreateRole(ctx)) ee.Push(types.SystemPermissionResource, "organisation.create", svc.CanCreateOrganisation(ctx)) - ee.Push(types.SystemPermissionResource, "grant", svc.CanCreateRole(ctx)) return } @@ -151,7 +151,7 @@ func (svc accessControl) can(ctx context.Context, res permissionResource, op per func (svc accessControl) Grant(ctx context.Context, rr ...*permissions.Rule) error { if !svc.CanGrant(ctx) { - return ErrNoPermissions + return ErrNoGrantPermissions } return svc.permissions.Grant(ctx, svc.Whitelist(), rr...) diff --git a/system/service/service.go b/system/service/service.go index cc1bf7f2d..e9260a565 100644 --- a/system/service/service.go +++ b/system/service/service.go @@ -69,10 +69,11 @@ func Init(ctx context.Context, log *zap.Logger, c Config) (err error) { DefaultIntSettings = internalSettings.NewService(internalSettings.NewRepository(repository.DB(ctx), "sys_settings")) - DefaultPermissions = permissions.Service( - ctx, - DefaultLogger, - permissions.Repository(repository.DB(ctx), "sys_permission_rules")) + if DefaultPermissions == nil { + pRepo := permissions.Repository(repository.DB(ctx), "sys_permission_rules") + DefaultPermissions = permissions.Service(ctx, DefaultLogger, pRepo) + } + DefaultAccessControl = AccessControl(DefaultPermissions) DefaultSettings = Settings(ctx, DefaultIntSettings) diff --git a/tests/messaging/permissions_delete_test.go b/tests/messaging/permissions_delete_test.go index 2d4908e92..b09a73b88 100644 --- a/tests/messaging/permissions_delete_test.go +++ b/tests/messaging/permissions_delete_test.go @@ -6,29 +6,39 @@ import ( "testing" "github.com/cortezaproject/corteza-server/internal/permissions" + "github.com/cortezaproject/corteza-server/messaging/types" "github.com/cortezaproject/corteza-server/tests/helpers" ) func TestPermissionsDelete(t *testing.T) { h := newHelper(t) - h.a.Empty(p.FindRulesByRoleID(h.roleID)) + // Make sure our user can grant + h.allow(types.MessagingPermissionResource, "grant") - h.allow("messaging", "access") - h.allow("messaging", "grant") - h.deny("messaging", "channel.group.create") + // New role. + permDelRole := h.roleID + 1 - h.a.Len(p.FindRulesByRoleID(h.roleID), 3) + h.a.Len(p.FindRulesByRoleID(permDelRole), 0) + + // Setup a few fake rules for new roke + h.mockPermissions( + permissions.AllowRule(permDelRole, types.MessagingPermissionResource, "access"), + permissions.DenyRule(permDelRole, types.MessagingPermissionResource, "channel.group.create"), + permissions.DenyRule(permDelRole, types.MessagingPermissionResource, "channel.private.create"), + ) + + h.a.Len(p.FindRulesByRoleID(permDelRole), 3) h.apiInit(). - Delete(fmt.Sprintf("/permissions/%d/rules", h.roleID)). + Delete(fmt.Sprintf("/permissions/%d/rules", permDelRole)). Expect(t). Status(http.StatusOK). Assert(helpers.AssertNoErrors). End() // Make sure everything is deleted - rr, _ := p.FindRulesByRoleID(h.roleID).Filter(func(r *permissions.Rule) (b bool, e error) { + rr, _ := p.FindRulesByRoleID(permDelRole).Filter(func(r *permissions.Rule) (b bool, e error) { return r.Access != permissions.Inherit, nil }) diff --git a/tests/messaging/permissions_update_test.go b/tests/messaging/permissions_update_test.go index 38d96c59e..e2016fc63 100644 --- a/tests/messaging/permissions_update_test.go +++ b/tests/messaging/permissions_update_test.go @@ -10,9 +10,7 @@ import ( func TestPermissionsUpdate(t *testing.T) { h := newHelper(t) - h.allow("messaging", "access") h.allow("messaging", "grant") - h.deny("messaging", "channel.group.create") h.apiInit(). Patch(fmt.Sprintf("/permissions/%d/rules", h.roleID)). diff --git a/tests/system/application_crud_test.go b/tests/system/application_crud_test.go new file mode 100644 index 000000000..7de3af96a --- /dev/null +++ b/tests/system/application_crud_test.go @@ -0,0 +1,21 @@ +package system + +import ( + "testing" +) + +func TestApplicationCreate(t *testing.T) { + t.Skip("to be implemented") +} + +func TestApplicationRead(t *testing.T) { + t.Skip("to be implemented") +} + +func TestApplicationUpdate(t *testing.T) { + t.Skip("to be implemented") +} + +func TestApplicationDelete(t *testing.T) { + t.Skip("to be implemented") +} diff --git a/tests/system/main_test.go b/tests/system/main_test.go new file mode 100644 index 000000000..a2bf1f812 --- /dev/null +++ b/tests/system/main_test.go @@ -0,0 +1,146 @@ +package system + +import ( + "context" + "os" + "testing" + + _ "github.com/joho/godotenv/autoload" + "github.com/steinfletcher/apitest" + "github.com/stretchr/testify/require" + "github.com/titpetric/factory" + + "github.com/go-chi/chi" + "go.uber.org/zap" + + "github.com/cortezaproject/corteza-server/internal/auth" + "github.com/cortezaproject/corteza-server/internal/permissions" + "github.com/cortezaproject/corteza-server/internal/rand" + "github.com/cortezaproject/corteza-server/pkg/api" + "github.com/cortezaproject/corteza-server/pkg/cli" + "github.com/cortezaproject/corteza-server/pkg/logger" + "github.com/cortezaproject/corteza-server/system" + "github.com/cortezaproject/corteza-server/system/rest" + "github.com/cortezaproject/corteza-server/system/service" + "github.com/cortezaproject/corteza-server/system/types" + "github.com/cortezaproject/corteza-server/tests/helpers" +) + +type ( + helper struct { + t *testing.T + a *require.Assertions + + cUser *types.User + roleID uint64 + } +) + +var ( + cfg *cli.Config + r chi.Router + p = permissions.NewTestService() +) + +func InitConfig() { + var err error + + if cfg != nil { + return + } + + helpers.RecursiveDotEnvLoad() + + ctx := context.Background() + log, _ := zap.NewDevelopment() + + cfg = system.Configure() + cfg.Log = log + + cfg.Init() + + auth.SetupDefault(string(rand.Bytes(32)), 10) + + if err = cfg.RootCommandDBSetup.Run(ctx, nil, cfg); err != nil { + panic(err) + } + + logger.SetDefault(log) + service.DefaultPermissions = p + // if service.DefaultStore, err = store.NewWithAfero(afero.NewMemMapFs(), "test"); err != nil { + // panic(err) + // } + + cfg.InitServices(ctx, cfg) +} + +func InitApp() { + InitConfig() + helpers.InitAuth() + + if r != nil { + return + } + + r = chi.NewRouter() + r.Use(api.Base(logger.Default())...) + helpers.BindAuthMiddleware(r) + rest.MountRoutes(r) +} + +func TestMain(m *testing.M) { + InitApp() + os.Exit(m.Run()) +} + +func newHelper(t *testing.T) helper { + h := helper{ + t: t, + a: require.New(t), + roleID: factory.Sonyflake.NextID(), + cUser: &types.User{ + ID: factory.Sonyflake.NextID(), + }, + } + + h.cUser.SetRoles([]uint64{h.roleID}) + + p.ClearGrants() + + // Setup permissions with allowed access to system + h.mockPermissions( + permissions.AllowRule(permissions.EveryoneRoleID, types.SystemPermissionResource, "access"), + ) + + return h +} + +// apitest basics, initialize, set handler, add auth +func (h helper) apiInit() *apitest.APITest { + InitApp() + + return apitest. + New(). + Handler(r). + Intercept(helpers.ReqHeaderAuthBearer(h.cUser)) +} + +func (h helper) mockPermissions(rules ...*permissions.Rule) { + h.a.NoError(p.Grant( + // TestService we use does not have any backend storage, + context.Background(), + // We want to make sure we did not make a mistake with any of the mocked resources or actions + service.DefaultAccessControl.Whitelist(), + rules..., + )) +} + +// Set allow permision for test role +func (h helper) allow(r permissions.Resource, o permissions.Operation) { + h.mockPermissions(permissions.AllowRule(h.roleID, r, o)) +} + +// set deny permission for test role +func (h helper) deny(r permissions.Resource, o permissions.Operation) { + h.mockPermissions(permissions.DenyRule(h.roleID, r, o)) +} diff --git a/tests/system/permissions_delete_test.go b/tests/system/permissions_delete_test.go new file mode 100644 index 000000000..2daaf0fd6 --- /dev/null +++ b/tests/system/permissions_delete_test.go @@ -0,0 +1,46 @@ +package system + +import ( + "fmt" + "net/http" + "testing" + + "github.com/cortezaproject/corteza-server/internal/permissions" + "github.com/cortezaproject/corteza-server/system/types" + "github.com/cortezaproject/corteza-server/tests/helpers" +) + +func TestPermissionsDelete(t *testing.T) { + h := newHelper(t) + + // Make sure our user can grant + h.allow(types.SystemPermissionResource, "grant") + + // New role. + permDelRole := h.roleID + 1 + + h.a.Len(p.FindRulesByRoleID(permDelRole), 0) + + // Setup a few fake rules for new roke + h.mockPermissions( + permissions.AllowRule(permDelRole, types.SystemPermissionResource, "access"), + permissions.DenyRule(permDelRole, types.SystemPermissionResource, "application.create"), + permissions.DenyRule(permDelRole, types.SystemPermissionResource, "user.create"), + ) + + h.a.Len(p.FindRulesByRoleID(permDelRole), 3) + + h.apiInit(). + Delete(fmt.Sprintf("/permissions/%d/rules", permDelRole)). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + End() + + // Make sure everything is deleted + rr, _ := p.FindRulesByRoleID(permDelRole).Filter(func(r *permissions.Rule) (b bool, e error) { + return r.Access != permissions.Inherit, nil + }) + + h.a.Empty(rr) +} diff --git a/tests/system/permissions_effective_test.go b/tests/system/permissions_effective_test.go new file mode 100644 index 000000000..a6f454bfd --- /dev/null +++ b/tests/system/permissions_effective_test.go @@ -0,0 +1,22 @@ +package system + +import ( + "net/http" + "testing" + + "github.com/cortezaproject/corteza-server/system/types" + "github.com/cortezaproject/corteza-server/tests/helpers" +) + +func TestPermissionsEffective(t *testing.T) { + h := newHelper(t) + h.allow(types.SystemPermissionResource, "access") + h.deny(types.SystemPermissionResource, "application.create") + + h.apiInit(). + Get("/permissions/effective"). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + End() +} diff --git a/tests/system/permissions_list_test.go b/tests/system/permissions_list_test.go new file mode 100644 index 000000000..8998b4d54 --- /dev/null +++ b/tests/system/permissions_list_test.go @@ -0,0 +1,22 @@ +package system + +import ( + "net/http" + "testing" + + jsonpath "github.com/steinfletcher/apitest-jsonpath" + + "github.com/cortezaproject/corteza-server/tests/helpers" +) + +func TestPermissionsList(t *testing.T) { + h := newHelper(t) + + h.apiInit(). + Get("/permissions/"). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + Assert(jsonpath.Present(`$.response[? @.resource=="system"]`)). + End() +} diff --git a/tests/system/permissions_read_test.go b/tests/system/permissions_read_test.go new file mode 100644 index 000000000..f62bd1b25 --- /dev/null +++ b/tests/system/permissions_read_test.go @@ -0,0 +1,24 @@ +package system + +import ( + "fmt" + "net/http" + "testing" + + "github.com/cortezaproject/corteza-server/system/types" + "github.com/cortezaproject/corteza-server/tests/helpers" +) + +func TestPermissionsRead(t *testing.T) { + h := newHelper(t) + h.allow(types.SystemPermissionResource, "access") + h.allow(types.SystemPermissionResource, "grant") + h.deny(types.SystemPermissionResource, "application.create") + + h.apiInit(). + Get(fmt.Sprintf("/permissions/%d/rules", h.roleID)). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + End() +} diff --git a/tests/system/permissions_update_test.go b/tests/system/permissions_update_test.go new file mode 100644 index 000000000..b18f670c3 --- /dev/null +++ b/tests/system/permissions_update_test.go @@ -0,0 +1,23 @@ +package system + +import ( + "fmt" + "net/http" + "testing" + + "github.com/cortezaproject/corteza-server/system/types" + "github.com/cortezaproject/corteza-server/tests/helpers" +) + +func TestPermissionsUpdate(t *testing.T) { + h := newHelper(t) + h.allow(types.SystemPermissionResource, "grant") + + h.apiInit(). + Patch(fmt.Sprintf("/permissions/%d/rules", h.roleID)). + JSON(`{"rules":[{"resource":"system","operation":"application.create","access":"allow"}]}`). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + End() +}