From 8804379981acac54de2d248a372efd142d0aebcb Mon Sep 17 00:00:00 2001 From: Mitja Zivkovic Date: Tue, 26 Feb 2019 11:09:01 +0100 Subject: [PATCH] add(crm): check service access permissions --- crm/rest/middleware.go | 18 ++++++++++ crm/rest/router.go | 3 +- crm/service/main_test.go | 14 -------- crm/service/permissions.go | 64 +++++++++++++++++++++++++++++++++ crm/service/permissions_test.go | 62 ++++++++++++++++++++++++++++++++ crm/service/service.go | 8 +++++ 6 files changed, 154 insertions(+), 15 deletions(-) create mode 100644 crm/rest/middleware.go create mode 100644 crm/service/permissions.go create mode 100644 crm/service/permissions_test.go diff --git a/crm/rest/middleware.go b/crm/rest/middleware.go new file mode 100644 index 000000000..6f3b1c6fb --- /dev/null +++ b/crm/rest/middleware.go @@ -0,0 +1,18 @@ +package rest + +import ( + "net/http" + + "github.com/crusttech/crust/crm/service" +) + +func middlewareAllowedAccess(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !service.DefaultPermissions.With(r.Context()).CanAccessCompose() { + http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden) + return + } + + next.ServeHTTP(w, r) + }) +} diff --git a/crm/rest/router.go b/crm/rest/router.go index e9f818d9e..ee216edfa 100644 --- a/crm/rest/router.go +++ b/crm/rest/router.go @@ -16,11 +16,12 @@ func MountRoutes() func(chi.Router) { notification = Notification{}.New() ) - // Initialize handers & controllers. + // Initialize handlers & controllers. return func(r chi.Router) { // Protect all _private_ routes r.Group(func(r chi.Router) { r.Use(auth.MiddlewareValidOnly) + r.Use(middlewareAllowedAccess) handlers.NewPage(page).MountRoutes(r) handlers.NewModule(module).MountRoutes(r) diff --git a/crm/service/main_test.go b/crm/service/main_test.go index db3ec59f4..d08cf3c1a 100644 --- a/crm/service/main_test.go +++ b/crm/service/main_test.go @@ -63,20 +63,6 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } -func db() *factory.DB { - return factory.Database.MustGet() -} - -func must(t *testing.T, err error, message ...string) { - prefix := "Error" - if len(message) > 0 { - prefix = message[0] - } - if err != nil { - t.Fatalf(prefix+": %+v", err) - } -} - func assert(t *testing.T, ok bool, format string, args ...interface{}) bool { if !ok { t.Fatalf(format, args...) diff --git a/crm/service/permissions.go b/crm/service/permissions.go new file mode 100644 index 000000000..e38de90b9 --- /dev/null +++ b/crm/service/permissions.go @@ -0,0 +1,64 @@ +package service + +import ( + "context" + + "github.com/crusttech/crust/crm/repository" + internalRules "github.com/crusttech/crust/internal/rules" + systemService "github.com/crusttech/crust/system/service" +) + +type ( + permissions struct { + db db + ctx context.Context + + prm systemService.PermissionsService + } + + // Fallback option + Fallback func() bool + + PermissionsService interface { + With(context.Context) PermissionsService + + CanAccessCompose() bool + } +) + +func Permissions() PermissionsService { + return (&permissions{ + prm: systemService.DefaultPermissions, + }).With(context.Background()) +} + +func (p *permissions) With(ctx context.Context) PermissionsService { + db := repository.DB(ctx) + return &permissions{ + db: db, + ctx: ctx, + + prm: p.prm.With(ctx), + } +} + +func (p *permissions) CanAccessCompose() bool { + return p.checkAccess("compose", "access") +} + +func (p *permissions) checkAccess(resource string, operation string, fbs ...Fallback) bool { + access := p.prm.Check(resource, operation) + switch access { + case internalRules.Allow: + return true + case internalRules.Deny: + return false + default: + for _, fb := range fbs { + if fb() == true { + return true + } + } + return false + } +} diff --git a/crm/service/permissions_test.go b/crm/service/permissions_test.go new file mode 100644 index 000000000..d4006e53a --- /dev/null +++ b/crm/service/permissions_test.go @@ -0,0 +1,62 @@ +package service + +import ( + "context" + "testing" + + "github.com/crusttech/crust/internal/auth" + "github.com/crusttech/crust/internal/rules" + . "github.com/crusttech/crust/internal/test" + + systemService "github.com/crusttech/crust/system/service" + systemTypes "github.com/crusttech/crust/system/types" +) + +func TestPermissions(t *testing.T) { + ctx := context.TODO() + + // Create user with role and add it to context. + userSvc := systemService.User().With(ctx) + user := &systemTypes.User{ + Name: "John Crm Doe", + Username: "johndoe", + SatosaID: "12345", + } + err := user.GeneratePassword("johndoe") + NoError(t, err, "expected no error generating password, got %v", err) + + _, err = userSvc.Create(user) + NoError(t, err, "expected no error creating user, got %v", err) + + roleSvc := systemService.Role().With(ctx) + role := &systemTypes.Role{ + Name: "Test role v1", + } + role, err = roleSvc.Create(role) + NoError(t, err, "expected no error creating role, got %v", err) + + err = roleSvc.MemberAdd(role.ID, user.ID) + NoError(t, err, "expected no error adding user to role, got %v", err) + + // Set Identity. + ctx = auth.SetIdentityToContext(ctx, user) + + // Generate services. + permissionsSvc := Permissions().With(ctx) + systemPermissionSvc := systemService.Permissions().With(ctx) + + // Test `access` to compose service. + ret := permissionsSvc.CanAccessCompose() + Assert(t, ret == false, "expected CanAccessCompose == false, got %v", ret) + + // Add `access` to compose service. + list := []rules.Rule{ + rules.Rule{Resource: "compose", Operation: "access", Value: rules.Allow}, + } + _, err = systemPermissionSvc.Update(role.ID, list) + NoError(t, err, "expected no error, got %v", err) + + // Test `access` to compose service. + ret = permissionsSvc.CanAccessCompose() + Assert(t, ret == true, "expected CanAccessCompose == true, got %v", ret) +} diff --git a/crm/service/service.go b/crm/service/service.go index 4137b9a60..6d94d183b 100644 --- a/crm/service/service.go +++ b/crm/service/service.go @@ -4,6 +4,12 @@ import ( "sync" ) +type ( + db interface { + Transaction(callback func() error) error + } +) + var ( o sync.Once DefaultRecord RecordService @@ -12,6 +18,7 @@ var ( DefaultChart ChartService DefaultPage PageService DefaultNotification NotificationService + DefaultPermissions PermissionsService ) func Init() { @@ -22,5 +29,6 @@ func Init() { DefaultPage = Page() DefaultChart = Chart() DefaultNotification = Notification() + DefaultPermissions = Permissions() }) }