diff --git a/Makefile b/Makefile index 0d3dd4750..2cf250107 100644 --- a/Makefile +++ b/Makefile @@ -61,7 +61,7 @@ test.crm: $(GOTEST) test.rbac: $(GOTEST) $(GOTEST) -covermode count -coverprofile .cover.out -v ./rbac/... - $(GO) tool cover -func=.cover.out + $(GO) tool cover -func=.cover.out | grep --color "^\|[^0-9]0.0%" vet: $(GO) vet `cd ${GOPATH}/src/; find $(PKG) -type f -name '*.go' -and -not -path '*vendor*'|xargs -n1 dirname|uniq` diff --git a/rbac/flags_test.go b/rbac/flags_test.go new file mode 100644 index 000000000..9882f7580 --- /dev/null +++ b/rbac/flags_test.go @@ -0,0 +1,37 @@ +package rbac + +import ( + "testing" +) + +func TestFlags(t *testing.T) { + c := configuration{} + mustFail(t, c.validate()) + c.auth = "a" + mustFail(t, c.validate()) + c.tenant = "a" + mustFail(t, c.validate()) + c.baseURL = "a" + must(t, c.validate()) +} + +/* imported below from main_test.go because of different package namespace */ + +func assert(t *testing.T, ok bool, format string, args ...interface{}) bool { + if !ok { + t.Fatalf(format, args...) + } + return ok +} + +func must(t *testing.T, err error, message ...string) { + if len(message) > 0 { + assert(t, err == nil, message[0]+": %+v", err) + return + } + assert(t, err == nil, "Error: %+v", err) +} + +func mustFail(t *testing.T, err error) { + assert(t, err != nil, "Expected error, got nil") +} diff --git a/rbac/main_test.go b/rbac/main_test.go index fa638da1b..3e5c690fc 100644 --- a/rbac/main_test.go +++ b/rbac/main_test.go @@ -19,7 +19,19 @@ func getClient() (*rbac.Client, error) { func assert(t *testing.T, ok bool, format string, args ...interface{}) bool { if !ok { - t.Errorf(format, args...) + t.Fatalf(format, args...) } return ok } + +func must(t *testing.T, err error, message ...string) { + if len(message) > 0 { + assert(t, err == nil, message[0]+": %+v", err) + return + } + assert(t, err == nil, "Error: %+v", err) +} + +func mustFail(t *testing.T, err error) { + assert(t, err != nil, "Expected error, got nil") +} diff --git a/rbac/resources.go b/rbac/resources.go index a45dad4f4..251d490f8 100644 --- a/rbac/resources.go +++ b/rbac/resources.go @@ -12,19 +12,45 @@ type ( *Client } + ResourcesRole struct { + Rolepath string `json:"role"` + Operation string `json:"operation"` + } + ResourcesInterface interface { Create(resourceID string, operations []string) error Get(resourceID string) (*types.Resource, error) Delete(resourceID string) error + + Grant(resourceID string, rolepath string, operations []string) error + GrantMultiple(resourceID string, roles []ResourcesRole) error + + CheckAccess(resourceID string, operation string, sessionID string) error } ) const ( - resourcesCreate = "/resources/%s" - resourcesGet = "/resources/%s" - resourcesDelete = "/resources/%s" + resourcesCreate = "/resources/%s" + resourcesGet = "/resources/%s" + resourcesDelete = "/resources/%s" + resourcesGrant = "/resources/%s/grantPermission" + resourcesCheckAccess = "/resources/%s/checkAccess?operation=%s&session=%s" ) +func (u *Resources) CheckAccess(resourceID string, operation string, sessionID string) error { + resp, err := u.Client.Get(fmt.Sprintf(resourcesCheckAccess, resourceID, operation, sessionID)) + if err != nil { + return errors.Wrap(err, "request failed") + } + defer resp.Body.Close() + switch resp.StatusCode { + case 200: + return nil + default: + return toError(resp) + } +} + func (u *Resources) Create(resourceID string, operations []string) error { body := struct { Operations []string `json:"operations"` @@ -43,6 +69,32 @@ func (u *Resources) Create(resourceID string, operations []string) error { } } +func (u *Resources) Grant(resourceID string, rolepath string, operations []string) error { + body := make([]ResourcesRole, len(operations)) + for index, operation := range operations { + body[index] = ResourcesRole{rolepath, operation} + } + return u.GrantMultiple(resourceID, body) +} + +func (u *Resources) GrantMultiple(resourceID string, roles []ResourcesRole) error { + body := struct { + Permissions []ResourcesRole `json:"permissions"` + }{roles} + + resp, err := u.Client.Patch(fmt.Sprintf(resourcesGrant, resourceID), body) + if err != nil { + return errors.Wrap(err, "request failed") + } + defer resp.Body.Close() + switch resp.StatusCode { + case 200: + return nil + default: + return toError(resp) + } +} + func (u *Resources) Get(resourceID string) (*types.Resource, error) { resp, err := u.Client.Get(fmt.Sprintf(resourcesGet, resourceID)) if err != nil { diff --git a/rbac/resources_test.go b/rbac/resources_test.go index 2ad7e3f94..0a89214e6 100644 --- a/rbac/resources_test.go +++ b/rbac/resources_test.go @@ -6,34 +6,26 @@ import ( func TestResources(t *testing.T) { rbac, err := getClient() - if err != nil { - t.Errorf("Unexpected error when creating RBAC instance: %+v", err) - } + assert(t, err == nil, "Error when creating RBAC instance: %+v", err) rbac.Debug("debug") + roles := rbac.Roles() resources := rbac.Resources() + + roles.Delete("test-role") resources.Delete("test-resource") - if err := resources.Create("test-resource", []string{"view", "edit", "delete"}); err != nil { - t.Errorf("Error when creating test-resource, %+v", err) - return - } + must(t, roles.Create("test-role"), "Error when creating test-role") + must(t, resources.Create("test-resource", []string{"view", "edit", "delete"}), "Error when creating test-resource") + must(t, resources.Grant("test-resource", "test-role", []string{"view", "edit"}), "Error when granting permissions to role on resource") - // test get resources + // test get resources (not implemented) @todo if false { - _, err := resources.Get("test-resource") - if err != nil { - t.Errorf("Error when retrieving test-resource, %+v", err) - return - } + res, err := resources.Get("test-resource") + must(t, err, "Error when retrieving test-resource") + assert(t, res != nil, "Expected non-nil test-resource") } - if err := resources.Delete("test-resource"); err != nil { - t.Errorf("Unexpected error deleting a resource, %+v", err) - return - } - - if err := resources.Delete("test-resource"); err == nil { - t.Errorf("Expected error when deleting unexistant resource, got none") - } + must(t, resources.Delete("test-resource"), "Error deleting a resource") + mustFail(t, resources.Delete("test-resource")) } diff --git a/rbac/roles.go b/rbac/roles.go index 1e2aaabd5..8471d59c1 100644 --- a/rbac/roles.go +++ b/rbac/roles.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/crusttech/crust/rbac/types" "github.com/pkg/errors" + "strings" ) type ( @@ -14,8 +15,9 @@ type ( RolesInterface interface { Create(rolepath string) error - Delete(rolepath string) error + CreateNested(rolepaths ...string) error Get(rolepath string) (*types.Role, error) + Delete(rolepath string) error } ) @@ -26,6 +28,9 @@ const ( ) func (u *Roles) Create(rolepath string) error { + if rolepath == "" { + return errors.New("tried creating empty role") + } resp, err := u.Client.Post(fmt.Sprintf(rolesCreate, rolepath), nil) if err != nil { return errors.Wrap(err, "request failed") @@ -39,6 +44,13 @@ func (u *Roles) Create(rolepath string) error { } } +func (u *Roles) CreateNested(rolepaths ...string) error { + if len(rolepaths) == 0 { + return errors.New("tried creating empty role") + } + return u.Create(strings.Join(rolepaths, "/")) +} + func (u *Roles) Get(rolepath string) (*types.Role, error) { resp, err := u.Client.Get(fmt.Sprintf(rolesDelete, rolepath)) if err != nil { @@ -54,6 +66,13 @@ func (u *Roles) Get(rolepath string) (*types.Role, error) { } } +func (u *Roles) GetNested(rolepaths ...string) (*types.Role, error) { + if len(rolepaths) == 0 { + return nil, errors.New("tried creating empty role") + } + return u.Get(strings.Join(rolepaths, "/")) +} + func (u *Roles) Delete(rolepath string) error { resp, err := u.Client.Delete(fmt.Sprintf(rolesDelete, rolepath)) if err != nil { diff --git a/rbac/roles_test.go b/rbac/roles_test.go index 14f829898..0c8828911 100644 --- a/rbac/roles_test.go +++ b/rbac/roles_test.go @@ -6,43 +6,54 @@ import ( func TestRoles(t *testing.T) { rbac, err := getClient() - if err != nil { - t.Errorf("Unexpected error when creating RBAC instance: %+v", err) - } + assert(t, err == nil, "Error when creating RBAC instance: %+v", err) rbac.Debug("info") roles := rbac.Roles() roles.Delete("test-role") - if err := roles.Create("test-role"); err != nil { - t.Errorf("Error when creating test-role: %+v", err) - return - } + err = roles.Create("test-role") + assert(t, err == nil, "Error when creating test-role: %+v", err) - if err := roles.Create("test-role/nested/role"); err == nil { - t.Errorf("Expected error when creating deep nested role, got nil") - return - } + err = roles.Create("test-role/nested/role") + assert(t, err != nil, "Expected error when creating deep nested role, got nil") - if err := roles.Create("test-role/nested"); err != nil { - t.Errorf("Expected error when creating deep nested role, got nil") - return - } + err = roles.Create("test-role/nested") + assert(t, err == nil, "Error when creating deep nested role, got %+v", err) + + err = roles.CreateNested("test-role", "nested", "role") + assert(t, err == nil, "Error when creating deep nested role, got %+v", err) + + err = roles.CreateNested() + assert(t, err != nil, "Expected non-nil error") { role, err := roles.Get("test-role") - if !assert(t, err == nil, "Unexpected error when getting role, %+v", err) { - return - } + assert(t, err == nil, "Error when getting role, %+v", err) assert(t, role.Name == "test-role", "Unexpected role name, test-role != '%s'", role.Name) } - if err := roles.Delete("test-role"); err != nil { - t.Errorf("Error when deleting test-role: %+v", err) - return + { + role, err := roles.Get("test-role/nested/role") + assert(t, err == nil, "Error when getting role, %+v", err) + assert(t, role.Name == "test-role/nested/role", "Unexpected role name, test != '%s'", role.Name) } - if err := roles.Delete("non-existant"); err == nil { - t.Errorf("Expected error on deleting a non-existant role") + { + role, err := roles.GetNested() + assert(t, role == nil, "Expected role=nil, got %+v", role) + assert(t, err != nil, "Expected non-nil error") } + + { + role, err := roles.GetNested("test-role", "nested") + assert(t, err == nil, "Error when getting role, %+v", err) + assert(t, role.Name == "test-role/nested", "Unexpected role name, test != '%s'", role.Name) + } + + err = roles.Delete("test-role") + assert(t, nil == err, "Error when deleting test-role: %+v", err) + + err = roles.Delete("non-existant") + assert(t, err != nil, "Expected error on deleting a non-existant role") } diff --git a/rbac/sessions.go b/rbac/sessions.go index 0b5582560..54436131a 100644 --- a/rbac/sessions.go +++ b/rbac/sessions.go @@ -57,7 +57,10 @@ func (u *Sessions) Get(sessionID string) (*types.Session, error) { defer resp.Body.Close() switch resp.StatusCode { case 200: - session := &types.Session{} + // @todo: fix session get response to return ID too + session := &types.Session{ + ID: sessionID, + } return session, errors.Wrap(json.NewDecoder(resp.Body).Decode(session), "decoding json failed") default: return nil, toError(resp) diff --git a/rbac/sessions_test.go b/rbac/sessions_test.go index 578f72a9c..0c1a0d1ad 100644 --- a/rbac/sessions_test.go +++ b/rbac/sessions_test.go @@ -6,97 +6,67 @@ import ( func TestSessions(t *testing.T) { rbac, err := getClient() - if err != nil { - t.Errorf("Unexpected error when creating RBAC instance: %+v", err) - } + assert(t, err == nil, "Error when creating RBAC instance: %+v", err) rbac.Debug("info") sessions := rbac.Sessions() users := rbac.Users() roles := rbac.Roles() + resources := rbac.Resources() // clean up data users.Delete("test-user") sessions.Delete("test-session") roles.Delete("test-role") + resources.Delete("test-resource") - if err := roles.Create("test-role"); err != nil { - t.Errorf("Unexpected error when creating test-role, %+v", err) - return - } - - if err := users.Create("test-user", "test-password"); err != nil { - t.Errorf("Unexpected error when creating test-user, %+v", err) - return - } - - if err := users.AddRole("test-user", "test-role"); err != nil { - t.Errorf("Unexpected error when assigning test-role to test-user, %+v", err) - return - } - - if err := sessions.Create("test-session", "test-user", "test-role"); err != nil { - t.Errorf("Unexpected error when creating test-session, %+v", err) - return - } + must(t, roles.Create("test-role"), "Error when creating test-role") + must(t, users.Create("test-user", "test-password"), "Error when creating test-user") + must(t, users.AddRole("test-user", "test-role"), "Error when assigning test-role to test-user") + must(t, sessions.Create("test-session", "test-user", "test-role"), "Error when creating test-session") + must(t, resources.Create("test-resource", []string{"view", "edit", "delete"}), "Error when creating test-resource") + must(t, resources.Grant("test-resource", "test-role", []string{"view", "edit"}), "Error when granting permissions to role on resource") // check role is created { session, err := sessions.Get("test-session") - if !assert(t, err == nil, "Unexpected error when getting test-session, %+v", err) { - return - } - // @todo: DAASI should return session ID from a get-query as well - // assert(t, session.ID == "test-session", "Unexpected returned Session ID, test-session != '%s'", session.ID) - assert(t, session.Username == "test-user", "Unexpected returned user, test-user != '%s'", session.Username) - if !assert(t, len(session.Roles) == 1, "Expected one session role, got %+v", session.Roles) { - return - } + must(t, err, "Error when getting test-session") + assert(t, session.ID == "test-session", "Unexpected Session ID, test-session != '%s'", session.ID) + assert(t, session.Username == "test-user", "Unexpected user, test-user != '%s'", session.Username) + assert(t, len(session.Roles) == 1, "Expected one session role, got %+v", session.Roles) assert(t, session.Roles[0] == "test-role", "Unexpected session role, test-role != '%s'", session.Roles[0]) } - if err := sessions.DeactivateRole("test-session", "test-role"); err != nil { - t.Errorf("Unexpected error when deactivating session role, %+v", err) - return + // check user has permissions from role + { + must(t, resources.CheckAccess("test-resource", "view", "test-session"), "User has permission, but CheckAccess reports error") + mustFail(t, resources.CheckAccess("test-resource", "delete", "test-session")) } + must(t, sessions.DeactivateRole("test-session", "test-role"), "Error when deactivating session role") + // check role is deactivated { session, err := sessions.Get("test-session") - if !assert(t, err == nil, "Unexpected error when getting test-session, %+v", err) { - return - } - // @todo: DAASI should return session ID from a get-query as well - // assert(t, session.ID == "test-session", "Unexpected returned Session ID, test-session != '%s'", session.ID) - assert(t, session.Username == "test-user", "Unexpected returned user, test-user != '%s'", session.Username) - if !assert(t, len(session.Roles) == 0, "Expected one session role, got %+v", session.Roles) { - return - } + must(t, err, "Error when getting test-session") + assert(t, session.ID == "test-session", "Unexpected Session ID, test-session != '%s'", session.ID) + assert(t, session.Username == "test-user", "Unexpected user, test-user != '%s'", session.Username) + assert(t, len(session.Roles) == 0, "Expected one session role, got %+v", session.Roles) } - if err := sessions.ActivateRole("test-session", "test-role"); err != nil { - t.Errorf("Unexpected error when deactivating session role, %+v", err) - return - } + must(t, sessions.ActivateRole("test-session", "test-role"), "Error when deactivating session role") // check role is activated { session, err := sessions.Get("test-session") - if !assert(t, err == nil, "Unexpected error when getting test-session, %+v", err) { - return - } - // @todo: DAASI should return session ID from a get-query as well - // assert(t, session.ID == "test-session", "Unexpected returned Session ID, test-session != '%s'", session.ID) - assert(t, session.Username == "test-user", "Unexpected returned user, test-user != '%s'", session.Username) - if !assert(t, len(session.Roles) == 1, "Expected one session role, got %+v", session.Roles) { - return - } + must(t, err, "Error when getting test-session") + assert(t, session.ID == "test-session", "Unexpected Session ID, test-session != '%s'", session.ID) + assert(t, session.Username == "test-user", "Unexpected user, test-user != '%s'", session.Username) + assert(t, len(session.Roles) == 1, "Expected one session role, got %+v", session.Roles) assert(t, session.Roles[0] == "test-role", "Unexpected session role, test-role != '%s'", session.Roles[0]) } - if err := sessions.Delete("test-session"); err != nil { - t.Errorf("Unexpected error when deleting test-session, %+v", err) - } + must(t, sessions.Delete("test-session"), "Error when deleting test-session") - // Write tests (need users, roles) + // @todo: Write tests (need users, roles) } diff --git a/rbac/users_test.go b/rbac/users_test.go index dbf204120..1cdf09f03 100644 --- a/rbac/users_test.go +++ b/rbac/users_test.go @@ -6,9 +6,7 @@ import ( func TestUsers(t *testing.T) { rbac, err := getClient() - if err != nil { - t.Errorf("Unexpected error when creating RBAC instance: %+v", err) - } + assert(t, err == nil, "Error when creating RBAC instance: %+v", err) rbac.Debug("info") users := rbac.Users() @@ -18,19 +16,19 @@ func TestUsers(t *testing.T) { roles.Delete("test-role") if err := roles.Create("test-role"); err != nil { - t.Errorf("Unexpected error when creating test-role, %+v", err) + t.Fatalf("Error when creating test-role, %+v", err) return } if err := users.Create("test-user", "test-password"); err != nil { - t.Errorf("Error when creating test-user: %+v", err) + t.Fatalf("Error when creating test-user: %+v", err) return } // check if we inherited some roles (should be empty) { user, err := users.Get("test-user") - if !assert(t, err == nil, "Unexpected error when retrieving test-user 1, %+v", err) { + if !assert(t, err == nil, "Error when retrieving test-user 1, %+v", err) { return } assert(t, user.Username == "test-user", "Unexpected username, test-user != '%s'", user.Username) @@ -38,14 +36,14 @@ func TestUsers(t *testing.T) { } if err := users.AddRole("test-user", "test-role"); err != nil { - t.Errorf("Unexpected error when assigning test-role to test-user 2, %+v", err) + t.Fatalf("Error when assigning test-role to test-user 2, %+v", err) return } // check if we inherited some roles (should be empty) { user, err := users.Get("test-user") - if !assert(t, err == nil, "Unexpected error when retrieving test-user 3, %+v", err) { + if !assert(t, err == nil, "Error when retrieving test-user 3, %+v", err) { return } assert(t, user.Username == "test-user", "Unexpected username, test-user != '%s'", user.Username) @@ -56,14 +54,14 @@ func TestUsers(t *testing.T) { } if err := users.RemoveRole("test-user", "test-role"); err != nil { - t.Errorf("Unexpected error when deassigning test-role to test-user, %+v", err) + t.Fatalf("Error when deassigning test-role to test-user, %+v", err) return } // check roles are empty after de-assign { user, err := users.Get("test-user") - if !assert(t, err == nil, "Unexpected error when retrieving test-user 4, %+v", err) { + if !assert(t, err == nil, "Error when retrieving test-user 4, %+v", err) { return } assert(t, user.Username == "test-user", "Unexpected username, test-user != '%s'", user.Username) @@ -71,16 +69,16 @@ func TestUsers(t *testing.T) { } if err := users.Delete("test-user"); err != nil { - t.Errorf("Error when deleting test-user: %+v", err) + t.Fatalf("Error when deleting test-user: %+v", err) return } if _, err := users.Get("test-user"); err == nil { - t.Errorf("Expected error on retrieving a non-existant user") + t.Fatalf("Expected error on retrieving a non-existant user") return } if err := users.Delete("test-user"); err == nil { - t.Errorf("Expected error on deleting a non-existant user") + t.Fatalf("Expected error on deleting a non-existant user") } } diff --git a/rbac/util.go b/rbac/util.go index 5b3c9f9ed..e9260c689 100644 --- a/rbac/util.go +++ b/rbac/util.go @@ -8,8 +8,8 @@ import ( func toError(resp *http.Response) error { body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return errors.Errorf("unexpected response (%d)", resp.StatusCode) + if body == nil || err != nil { + return errors.Errorf("unexpected response (%d, %s)", resp.StatusCode, err) } return errors.New(string(body)) }