3
0

upd(rbac): add resource grants and checkaccess

This commit is contained in:
Tit Petric
2018-08-16 18:49:18 +02:00
parent 44e3bf1dc7
commit f1337bc487
11 changed files with 220 additions and 126 deletions

View File

@@ -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`

37
rbac/flags_test.go Normal file
View File

@@ -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")
}

View File

@@ -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")
}

View File

@@ -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 {

View File

@@ -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"))
}

View File

@@ -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 {

View File

@@ -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")
}

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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")
}
}

View File

@@ -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))
}