3
0

del(rbac): removed pdp implementation, closes #90

This commit is contained in:
Tit Petric 2019-03-14 23:02:19 +01:00
parent 6934917da0
commit d934513dbd
20 changed files with 17 additions and 1150 deletions

View File

@ -6,15 +6,18 @@ import (
context "github.com/SentimensRG/ctx"
"github.com/SentimensRG/ctx/sigctx"
"github.com/namsral/flag"
sub "github.com/crusttech/crust/crm"
"github.com/crusttech/crust/internal/auth"
"github.com/crusttech/crust/internal/rbac"
"github.com/crusttech/crust/internal/subscription"
"github.com/crusttech/crust/internal/version"
)
func main() {
// log to stdout not stderr
log.SetOutput(os.Stdout)
log.SetFlags(log.LstdFlags | log.Lshortfile)
log.Printf("Starting "+os.Args[0]+", version: %v, built on: %v", version.Version, version.BuildTime)
ctx := context.AsContext(sigctx.New())
@ -23,14 +26,9 @@ func main() {
"crm",
sub.Flags,
auth.Flags,
rbac.Flags,
subscription.Flags,
)
// log to stdout not stderr
log.SetOutput(os.Stdout)
log.SetFlags(log.LstdFlags | log.Lshortfile)
if err := sub.Init(); err != nil {
log.Fatalf("Error initializing: %+v", err)
}
@ -42,6 +40,7 @@ func main() {
switch command {
case "help":
flag.PrintDefaults()
default:
// Checks subscription, will os.Exit(1) if there is an error
// Disabled for now, system service is the only one that validates subscription

View File

@ -22,7 +22,6 @@ import (
"github.com/crusttech/crust/internal/config"
"github.com/crusttech/crust/internal/metrics"
"github.com/crusttech/crust/internal/middleware"
"github.com/crusttech/crust/internal/rbac"
"github.com/crusttech/crust/internal/routes"
"github.com/crusttech/crust/internal/subscription"
"github.com/crusttech/crust/internal/version"
@ -62,7 +61,6 @@ func main() {
system.Flags("system")
auth.Flags()
rbac.Flags()
subscription.Flags()
flag.Parse()

View File

@ -6,15 +6,18 @@ import (
context "github.com/SentimensRG/ctx"
"github.com/SentimensRG/ctx/sigctx"
"github.com/namsral/flag"
"github.com/crusttech/crust/internal/auth"
"github.com/crusttech/crust/internal/rbac"
"github.com/crusttech/crust/internal/subscription"
"github.com/crusttech/crust/internal/version"
sub "github.com/crusttech/crust/messaging"
)
func main() {
// log to stdout not stderr
log.SetOutput(os.Stdout)
log.SetFlags(log.LstdFlags | log.Lshortfile)
log.Printf("Starting "+os.Args[0]+", version: %v, built on: %v", version.Version, version.BuildTime)
ctx := context.AsContext(sigctx.New())
@ -23,14 +26,9 @@ func main() {
"messaging",
sub.Flags,
auth.Flags,
rbac.Flags,
subscription.Flags,
)
// log to stdout not stderr
log.SetOutput(os.Stdout)
log.SetFlags(log.LstdFlags | log.Lshortfile)
if err := sub.Init(); err != nil {
log.Fatalf("Error initializing: %+v", err)
}
@ -42,7 +40,7 @@ func main() {
switch command {
case "help":
case "merge-users":
flag.PrintDefaults()
default:
// Checks subscription, will os.Exit(1) if there is an error
// Disabled for now, system service is the only one that validates subscription

View File

@ -8,16 +8,15 @@ import (
systemService "github.com/crusttech/crust/system/service"
"github.com/crusttech/crust/internal/auth"
"github.com/crusttech/crust/internal/rbac"
)
func main() {
flags("system", service.Flags, auth.Flags, rbac.Flags)
// log to stdout not stderr
log.SetOutput(os.Stdout)
log.SetFlags(log.LstdFlags | log.Lshortfile)
flags("system", service.Flags, auth.Flags)
service.InitDatabase()
systemService.Init()

View File

@ -6,15 +6,18 @@ import (
context "github.com/SentimensRG/ctx"
"github.com/SentimensRG/ctx/sigctx"
"github.com/namsral/flag"
"github.com/crusttech/crust/internal/auth"
"github.com/crusttech/crust/internal/rbac"
"github.com/crusttech/crust/internal/subscription"
"github.com/crusttech/crust/internal/version"
sub "github.com/crusttech/crust/system"
)
func main() {
// log to stdout not stderr
log.SetOutput(os.Stdout)
log.SetFlags(log.LstdFlags | log.Lshortfile)
log.Printf("Starting "+os.Args[0]+", version: %v, built on: %v", version.Version, version.BuildTime)
ctx := context.AsContext(sigctx.New())
@ -23,14 +26,9 @@ func main() {
"system",
sub.Flags,
auth.Flags,
rbac.Flags,
subscription.Flags,
)
// log to stdout not stderr
log.SetOutput(os.Stdout)
log.SetFlags(log.LstdFlags | log.Lshortfile)
if err := sub.Init(); err != nil {
log.Fatalf("Error initializing: %+v", err)
}
@ -42,6 +40,7 @@ func main() {
switch command {
case "help":
flag.PrintDefaults()
default:
// Checks subscription, will os.Exit(1) if there is an error
ctx = subscription.Monitor(ctx)

View File

@ -1,42 +0,0 @@
package config
import (
"github.com/namsral/flag"
"github.com/pkg/errors"
)
type (
RBAC struct {
Auth string
Tenant string
BaseURL string
Timeout int
}
)
var rbac *RBAC
func (c *RBAC) Validate() error {
if c.Auth == "" {
return errors.New("No authentication provided for RBAC")
}
if c.Tenant == "" {
return errors.New("No tenant provided for RBAC")
}
if c.BaseURL == "" {
return errors.New("No Base URL provided for RBAC")
}
return nil
}
func (*RBAC) Init(prefix ...string) *RBAC {
if rbac != nil {
return rbac
}
rbac = new(RBAC)
flag.StringVar(&rbac.Auth, "rbac-auth", "username:password", "Credentials to use for RBAC queries")
flag.StringVar(&rbac.Tenant, "rbac-tenant", "", "Tenant ID")
flag.StringVar(&rbac.BaseURL, "rbac-base-url", "", "RBAC Base URL")
flag.IntVar(&rbac.Timeout, "rbac-timeout", 30, "RBAC request timeout (seconds)")
return rbac
}

View File

@ -1,162 +0,0 @@
package rbac
import (
"bytes"
"crypto/tls"
"encoding/base64"
"encoding/json"
"fmt"
"net"
"net/http"
"net/http/httputil"
"strings"
"time"
"github.com/crusttech/crust/internal/config"
)
var _ = tls.Config{}
type (
Client struct {
Transport *http.Transport
Client *http.Client
debugLevel string
config *config.RBAC
}
ClientInterface interface {
Users() *Users
Roles() *Roles
Resources() *Resources
Sessions() *Sessions
}
)
func (c *Client) Users() *Users { return &Users{c} }
func (c *Client) Roles() *Roles { return &Roles{c} }
func (c *Client) Resources() *Resources { return &Resources{c} }
func (c *Client) Sessions() *Sessions { return &Sessions{c} }
var _ ClientInterface = &Client{}
func New() (*Client, error) {
if err := flags.Validate(); err != nil {
return nil, err
}
timeout := time.Duration(flags.Timeout) * time.Second
transport := &http.Transport{
Dial: (&net.Dialer{
Timeout: timeout,
}).Dial,
TLSHandshakeTimeout: timeout,
}
client := &http.Client{
Timeout: timeout,
Transport: transport,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
return &Client{
Transport: transport,
Client: client,
config: flags,
}, nil
}
func (c *Client) Debug(debugLevel string) *Client {
c.debugLevel = debugLevel
return c
}
func (c *Client) Get(url string) (*http.Response, error) {
return c.Request("GET", url, nil)
}
func (c *Client) Post(url string, body interface{}) (*http.Response, error) {
return c.Request("POST", url, body)
}
func (c *Client) Patch(url string, body interface{}) (*http.Response, error) {
return c.Request("PATCH", url, body)
}
func (c *Client) Delete(url string) (*http.Response, error) {
return c.Request("DELETE", url, nil)
}
func (c *Client) Request(method, url string, body interface{}) (*http.Response, error) {
link := strings.TrimRight(c.config.BaseURL, "/") + "/" + strings.TrimLeft(url, "/")
if c.debugLevel == "info" {
fmt.Println("RBAC >>>", method, link)
}
request := func() (*http.Request, error) {
if body != nil {
b, err := json.Marshal(body)
if err != nil {
return nil, err
}
return http.NewRequest(method, link, bytes.NewBuffer(b))
}
return http.NewRequest(method, link, nil)
}
req, err := request()
if err != nil {
return nil, err
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(c.config.Auth)))
req.Header.Add("X-Tenant-Id", c.config.Tenant)
if c.debugLevel == "debug" {
fmt.Println("RBAC >>> (request)")
b, err := httputil.DumpRequestOut(req, true)
if err != nil {
fmt.Println("RBAC >>> Error:", err)
} else {
if b != nil {
fmt.Println(strings.TrimSpace(string(b)))
}
}
fmt.Println("---")
}
resp, err := c.Client.Do(req)
if c.debugLevel == "debug" {
fmt.Println("RBAC <<< (response)")
if err != nil {
fmt.Println("RBAC <<< Error:", err)
} else {
b, err := httputil.DumpResponse(resp, true)
if err != nil {
fmt.Println("RBAC <<< Error:", err)
} else {
if b != nil {
fmt.Println(string(b))
}
}
}
fmt.Println("-----------------")
}
if err != nil {
if c.debugLevel == "info" {
fmt.Println("RBAC <<< Response error", err)
}
return nil, err
}
if c.debugLevel == "info" {
fmt.Println("RBAC <<< Response", resp.StatusCode)
}
return resp, nil
}

View File

@ -1,17 +0,0 @@
package rbac
import (
"fmt"
"github.com/crusttech/crust/internal/config"
)
var flags *config.RBAC
func Flags(prefix ...string) {
flags = new(config.RBAC).Init(prefix...)
}
func Debug() {
fmt.Printf("Debug RBAC flags: %#v", *flags)
}

View File

@ -1,68 +0,0 @@
package rbac
type (
// Permissions is a stateful object
Permissions interface /* for Session, User, Roles, Resource */ {
// Scoped for [Resource]
Grant(permission string) error
Revoke(permission string) error
List() ([]string, error)
// Check permission of stateful object (Session, User, Roles)
CheckAccess(permission string) (bool, error)
}
// Roles is a stateful object
Roles interface /* for Session, User */ {
// Scoped to User
Add(role string) error
Delete(role string) error
// Scoped to Session, User
List() ([]string, error)
ListAuthorized() ([]string, error)
// Scoped to Session
GrantRole(role string) error
RevokeRole(role string) error
// Permissions are scoped to [Session, User]
Permissions(role string) Permissions
}
// Session object holds session state (Create, Load)
Session interface {
// Unscoped functions
Create(userID string, roles ...string) error
Load(sessionID string) error
Delete() error
// User returns User scoped object with global roles/permissions
User() (User, error)
// Roles and Permissions return session scoped objects
Roles() Roles
Permissions() Permissions
}
// Resource is a static object
Resource interface {
Load(resource string) error
Create(resource string) error
Delete(resource string) error
RolePermissions(resource string, role string) Permissions
UserPermissions(resource string, user string) Permissions
}
// Users is a static object
User interface {
Load(user string) error
Create(user string) error
Delete(user string) error
// Roles and Permissions return User scoped objects
Roles(user string) Roles
Permissions(user string) Permissions
}
)

View File

@ -1,48 +0,0 @@
package rbac_test
import (
"testing"
"github.com/joho/godotenv"
"github.com/namsral/flag"
"github.com/crusttech/crust/internal/rbac"
)
var loaded bool
func getClient() (*rbac.Client, error) {
if !loaded {
godotenv.Load("../../.env")
rbac.Flags()
flag.Parse()
loaded = true
}
rbac.Debug()
client, err := rbac.New()
if err != nil {
return nil, err
}
client.Debug("debug")
return client, nil
}
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

@ -1,172 +0,0 @@
package rbac
import (
"encoding/json"
"fmt"
"github.com/pkg/errors"
"github.com/crusttech/crust/internal/rbac/types"
)
type (
Resources struct {
*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, resourceIDs ...string) error
Grant(resourceID, rolepath string, operations []string) error
GrantMultiple(resourceID string, roles []ResourcesRole) error
CheckAccess(resourceID, operation, sessionID string) error
CheckAccessMulti(resourceID, operation, sessionID string) error
CheckAccessMultiDetail(resourceID, operation, sessionID string) error
}
)
const (
resourcesCreate = "/resources/%s"
resourcesGet = "/resources/%s"
resourcesDelete = "/resources/%s"
resourcesGrant = "/resources/%s/grantPermission"
resourcesCheckAccess = "/resources/%s/checkAccess?operation=%s&session=%s"
resourcesCheckAccessMulti = "/resources/%s/checkMultiAccess?operation=%s&session=%s"
resourcesCheckAccessMultiDetail = "/resources/%s/checkMultiAccess/detailed?operation=%s&session=%s"
)
func (u *Resources) CheckAccessMulti(resourceID, operation, sessionID string) error {
resp, err := u.Client.Get(fmt.Sprintf(resourcesCheckAccessMulti, 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) CheckAccessMultiDetail(resourceID, operation, sessionID string) error {
resp, err := u.Client.Get(fmt.Sprintf(resourcesCheckAccessMultiDetail, 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) CheckAccess(resourceID, operation, 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"`
}{operations}
resp, err := u.Client.Post(fmt.Sprintf(resourcesCreate, 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) Grant(resourceID, 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 {
return nil, errors.Wrap(err, "request failed")
}
defer resp.Body.Close()
switch resp.StatusCode {
case 200:
resource := &types.Resource{}
return resource, errors.Wrap(json.NewDecoder(resp.Body).Decode(resource), "decoding json failed")
default:
return nil, toError(resp)
}
}
func (u *Resources) Delete(resourceID string, resourceIDs ...string) error {
deleteResource := func(resourceID string) error {
resp, err := u.Client.Delete(fmt.Sprintf(resourcesDelete, resourceID))
if err != nil {
return errors.Wrap(err, "request failed")
}
defer resp.Body.Close()
switch resp.StatusCode {
case 200:
return nil
default:
return toError(resp)
}
}
if err := deleteResource(resourceID); err != nil {
return err
}
for _, resourceID := range resourceIDs {
if err := deleteResource(resourceID); err != nil {
return err
}
}
return nil
}
var _ ResourcesInterface = &Resources{}

View File

@ -1,29 +0,0 @@
package rbac_test
import (
"testing"
)
func TestResources(t *testing.T) {
rbac, err := getClient()
must(t, err, "Error when creating RBAC instance")
roles := rbac.Roles()
resources := rbac.Resources()
roles.Delete("test-role")
resources.Delete("test-resource")
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")
{
res, err := resources.Get("test-resource")
must(t, err, "Error when retrieving test-resource")
assert(t, res != nil, "Expected non-nil test-resource")
}
must(t, resources.Delete("test-resource"), "Error deleting a resource")
mustFail(t, resources.Delete("test-resource"))
}

View File

@ -1,92 +0,0 @@
package rbac
import (
"encoding/json"
"fmt"
"strings"
"github.com/pkg/errors"
"github.com/crusttech/crust/internal/rbac/types"
)
type (
Roles struct {
*Client
}
RolesInterface interface {
Create(rolepath string) error
CreateNested(rolepaths ...string) error
Get(rolepath string) (*types.Role, error)
Delete(rolepath string) error
}
)
const (
rolesCreate = "/roles/%s"
rolesGet = "/roles/%s"
rolesDelete = "/roles/%s"
)
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")
}
defer resp.Body.Close()
switch resp.StatusCode {
case 200:
return nil
default:
return toError(resp)
}
}
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 {
return nil, errors.Wrap(err, "request failed")
}
defer resp.Body.Close()
switch resp.StatusCode {
case 200:
role := &types.Role{}
return role, errors.Wrap(json.NewDecoder(resp.Body).Decode(role), "decoding json failed")
default:
return nil, toError(resp)
}
}
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 {
return errors.Wrap(err, "request failed")
}
defer resp.Body.Close()
switch resp.StatusCode {
case 200:
return nil
default:
return toError(resp)
}
}
var _ RolesInterface = &Roles{}

View File

@ -1,49 +0,0 @@
package rbac_test
import (
"testing"
"github.com/pkg/errors"
)
func TestRoles(t *testing.T) {
rbac, err := getClient()
must(t, err, "Error when creating RBAC instance")
roles := rbac.Roles()
roles.Delete("test-role")
mustFail(t, roles.CreateNested())
must(t, roles.Create("test-role"), "Error when creating test-role")
mustFail(t, roles.Create("test-role/nested/role"))
must(t, roles.Create("test-role/nested"), "Error when creating deep nested role")
must(t, roles.CreateNested("test-role", "nested", "role"), "Error when creating deep nested role")
{
role, err := roles.Get("test-role")
must(t, err, "Error when getting role")
assert(t, role.Name == "test-role", "%+v", errors.Errorf("Unexpected role name, 'test-role' != '%s'", role.Name))
}
{
role, err := roles.Get("test-role/nested/role")
must(t, err, "Error when getting role")
assert(t, role.Name == "test-role/nested/role", "%+v", errors.Errorf("Unexpected role name, 'test-role/nested/role' != '%s'", role.Name))
}
{
role, err := roles.GetNested()
mustFail(t, err)
assert(t, role == nil, "%+v", errors.Errorf("Expected role=nil, got %#v", role))
}
{
role, err := roles.GetNested("test-role", "nested")
must(t, err, "Error when getting role")
assert(t, role.Name == "test-role/nested", "%+v", errors.Errorf("Unexpected role name, 'test-role/nested' != '%s'", role.Name))
}
must(t, roles.Delete("test-role"), "Error when deleting test-role")
mustFail(t, roles.Delete("non-existant"))
mustFail(t, roles.Delete("test-role"))
}

View File

@ -1,121 +0,0 @@
package rbac
import (
"encoding/json"
"fmt"
"github.com/crusttech/crust/internal/rbac/types"
"github.com/pkg/errors"
)
type (
Sessions struct {
*Client
}
SessionsInterface interface {
Create(sessionID, userID string, roles ...string) error
Get(sessionID string) (*types.Session, error)
Delete(sessionID string) error
ActivateRole(sessionID string, roles ...string) error
DeactivateRole(sessionID string, roles ...string) error
}
)
const (
sessionsCreate = "/sessions/%s"
sessionsGet = "/sessions/%s"
sessionsDelete = "/sessions/%s"
sessionsActivateRole = "/sessions/%s/activateRole"
sessionsDeactivateRole = "/sessions/%s/deactivateRole"
)
func (u *Sessions) Create(sessionID, userID string, roles ...string) error {
body := struct {
UserID string `json:"userid"`
Roles []string `json:"roles,omitempty"`
}{userID, roles}
resp, err := u.Client.Post(fmt.Sprintf(sessionsCreate, sessionID), 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 *Sessions) Get(sessionID string) (*types.Session, error) {
resp, err := u.Client.Get(fmt.Sprintf(sessionsGet, sessionID))
if err != nil {
return nil, errors.Wrap(err, "request failed")
}
defer resp.Body.Close()
switch resp.StatusCode {
case 200:
// @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)
}
}
func (u *Sessions) Delete(sessionID string) error {
resp, err := u.Client.Delete(fmt.Sprintf(sessionsDelete, 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 *Sessions) ActivateRole(sessionID string, roles ...string) error {
body := struct {
Roles []string `json:"roles"`
}{roles}
resp, err := u.Client.Patch(fmt.Sprintf(sessionsActivateRole, sessionID), 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 *Sessions) DeactivateRole(sessionID string, roles ...string) error {
body := struct {
Roles []string `json:"roles"`
}{roles}
resp, err := u.Client.Patch(fmt.Sprintf(sessionsDeactivateRole, sessionID), 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)
}
}
var _ SessionsInterface = &Sessions{}

View File

@ -1,101 +0,0 @@
package rbac_test
import (
"fmt"
"testing"
"github.com/pkg/errors"
)
func TestSessions(t *testing.T) {
rbac, err := getClient()
must(t, err, "Error when creating RBAC instance")
sessions := rbac.Sessions()
users := rbac.Users()
roles := rbac.Roles()
resources := rbac.Resources()
// @todo until users.Get implements getting user by email, we need to delete users at end of the test successful and unsuccessful.
sessions.Delete("test-session")
roles.Delete("test-role")
resources.Delete("test-resource")
resources.Delete("team-1", "team-2", "team-3")
must(t, roles.Create("test-role"), "Error when creating test-role")
user, err := users.Create("test-user@crust.tech", "test-password")
must(t, err, "Error when creating test-user@crust.tech")
assert(t, user != nil, "%+v", errors.New("Expected non-nil user"))
assert(t, user.ID != "", "%+v", errors.New("Expected non-empty user.ID"))
assert(t, user.Username == "test-user@crust.tech", "%+v", errors.Errorf("Expected test-user@crust.tech == %s", user.Username))
must(t, users.AddRole(user.ID, "test-role"), "Error when assigning test-role to test-user@crust.tech")
must(t, sessions.Create("test-session", user.ID, "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")
must(t, err, "Error when getting test-session")
assert(t, session.ID == "test-session", "Unexpected Session ID, test-session != '%s'", session.ID)
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])
}
// check user has permissions from role
{
must(t, resources.CheckAccess("test-resource", "view", "test-session"), "Owner has permission, but CheckAccess reports error")
mustFail(t, resources.CheckAccess("test-resource", "delete", "test-session"))
}
// check multi access
{
for i := 1; i <= 5; i++ {
resources.Delete(fmt.Sprintf("team:%d", i))
must(t, resources.Create(fmt.Sprintf("team:%d", i), []string{"edit"}), fmt.Sprintf("Error when creating team:%d", i))
}
mustFail(t, resources.CheckAccessMulti("team:*", "edit", "test-session"))
resources.Grant("team:4", "test-role", []string{"edit"})
must(t, resources.CheckAccess("team:4", "edit", "test-session"))
must(t, resources.CheckAccessMulti("team:*", "edit", "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")
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)
}
must(t, sessions.ActivateRole("test-session", "test-role"), "Error when deactivating session role")
// check role is activated
{
session, err := sessions.Get("test-session")
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])
}
must(t, sessions.Delete("test-session"), "Error when deleting test-session")
mustFail(t, func() error {
_, err := sessions.Get("test-session")
return err
}())
mustFail(t, sessions.Delete("test-session"))
must(t, users.Delete(user.ID), "Error when deleting test-user")
mustFail(t, func() error {
_, err := users.Get(user.ID)
return err
}())
mustFail(t, users.Delete(user.ID))
}

View File

@ -1,34 +0,0 @@
package types
type (
User struct {
ID string `json:"userid"`
Username string `json:"username"`
AssignedRoles []string `json:"assignedRoles"`
AuthorizedRoles []string `json:"authorizedRoles"`
}
Session struct {
ID string `json:"session"`
UserID string `json:"userid"`
Roles []string `json:"roles"`
}
// @todo: need to list nested roles,
// @todo: don't return users=null - return users: []?
Role struct {
Name string `json:"rolename"`
Users []string `json:"users"`
// key = resource name
Permissions map[string]Operations `json:"permissions"`
}
Operations struct {
Operations []string `json:"operations"`
}
// @todo: read resource information
Resource struct {
}
)

View File

@ -1,121 +0,0 @@
package rbac
import (
"encoding/json"
"fmt"
"github.com/pkg/errors"
"github.com/crusttech/crust/internal/rbac/types"
)
type (
Users struct {
*Client
}
UsersInterface interface {
Create(username, password string) (*types.User, error)
Get(userID string) (*types.User, error)
Delete(userID string) error
AddRole(userID string, roles ...string) error
RemoveRole(userID string, roles ...string) error
}
)
const (
usersCreate = "/users/"
usersGet = "/users/%s"
usersDelete = "/users/%s"
// @todo: plural for users, but singular for sessions
usersAddRole = "/users/%s/assignRoles"
usersRemoveRole = "/users/%s/deassignRoles"
)
func (u *Users) Create(username, password string) (*types.User, error) {
body := struct {
Username string `json:"username"`
Password string `json:"password"`
}{username, password}
resp, err := u.Client.Post(usersCreate, body)
if err != nil {
return nil, errors.Wrap(err, "request failed")
}
defer resp.Body.Close()
switch resp.StatusCode {
case 200:
user := &types.User{}
return user, errors.Wrap(json.NewDecoder(resp.Body).Decode(user), "decoding json failed")
default:
return nil, toError(resp)
}
}
func (u *Users) AddRole(userID string, roles ...string) error {
body := struct {
Roles []string `json:"roles"`
}{roles}
resp, err := u.Client.Patch(fmt.Sprintf(usersAddRole, userID), 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 *Users) RemoveRole(userID string, roles ...string) error {
body := struct {
Roles []string `json:"roles"`
}{roles}
resp, err := u.Client.Patch(fmt.Sprintf(usersRemoveRole, userID), 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 *Users) Get(userID string) (*types.User, error) {
resp, err := u.Client.Get(fmt.Sprintf(usersGet, userID))
if err != nil {
return nil, errors.Wrap(err, "request failed")
}
defer resp.Body.Close()
switch resp.StatusCode {
case 200:
user := &types.User{}
return user, errors.Wrap(json.NewDecoder(resp.Body).Decode(user), "decoding json failed")
default:
return nil, toError(resp)
}
}
func (u *Users) Delete(userID string) error {
resp, err := u.Client.Delete(fmt.Sprintf(usersDelete, userID))
if err != nil {
return errors.Wrap(err, "request failed")
}
defer resp.Body.Close()
switch resp.StatusCode {
case 200:
return nil
default:
return toError(resp)
}
}
var _ UsersInterface = &Users{}

View File

@ -1,55 +0,0 @@
package rbac_test
import (
"testing"
)
func TestUsers(t *testing.T) {
rbac, err := getClient()
must(t, err, "Error when creating RBAC instance")
users := rbac.Users()
roles := rbac.Roles()
// Cleanup data
roles.Delete("test-role")
// @todo until users.Get implements getting user by email, we need to delete users at end of the test successful and unsuccessful.
must(t, roles.Create("test-role"), "Error when creating test-role")
user, err := users.Create("test-user@crust.tech", "test-password")
must(t, err, "Error when creating test-user")
// check if we inherited some roles (should be empty)
{
u1, err := users.Get(user.ID)
must(t, err, "Error when retrieving test-user 1")
assert(t, len(u1.AssignedRoles) == 0, "Unexpected number of roles, expected empty, got %+v", u1.AssignedRoles)
}
must(t, users.AddRole(user.ID, "test-role"), "Error when assigning test-role to test-user")
// check if we inherited some roles (should be empty)
{
u2, err := users.Get(user.ID)
must(t, err, "Error when retrieving test-user 2")
assert(t, len(u2.AssignedRoles) == 1, "Unexpected number of roles, expected 1, got %+v", u2.AssignedRoles)
assert(t, u2.AssignedRoles[0] == "test-role", "Unexpected role name, test-role != '%s'", u2.AssignedRoles[0])
}
must(t, users.RemoveRole(user.ID, "test-role"), "Error when de-assigning test-role to test-user")
// check roles are empty after de-assign
{
u3, err := users.Get(user.ID)
must(t, err, "Error when retrieving test-user 3")
assert(t, len(u3.AssignedRoles) == 0, "Unexpected number of roles, expected empty, got %+v", u3.AssignedRoles)
}
must(t, users.Delete(user.ID), "Error when deleting test-user")
mustFail(t, func() error {
_, err := users.Get(user.ID)
return err
}())
mustFail(t, users.Delete(user.ID))
}

View File

@ -1,15 +0,0 @@
package rbac
import (
"github.com/pkg/errors"
"io/ioutil"
"net/http"
)
func toError(resp *http.Response) error {
body, err := ioutil.ReadAll(resp.Body)
if body == nil || err != nil {
return errors.Errorf("unexpected response (%d, %s)", resp.StatusCode, err)
}
return errors.New(string(body))
}