Move actions, resource (types), and scopes to packages. (#149)
This commit is contained in:
1 parent
9d1d0ba08e
commit
d8f11af4d7
37 files changed
+721
-460
No files matched your search
+14
-55
@@ -1,65 +1,24 @@
|
||||
package iam
|
||||
|
||||
// Action defines a type for the Actions of Resources
|
||||
// actions are also stored as a lookup db table named iam_action
|
||||
type Action int
|
||||
|
||||
// not using iota intentionally, since the values are stored in the db as well.
|
||||
const (
|
||||
ActionUnknown Action = 0
|
||||
ActionList Action = 1
|
||||
ActionCreate Action = 2
|
||||
ActionUpdate Action = 3
|
||||
ActionRead Action = 4
|
||||
ActionDelete Action = 5
|
||||
ActionAuthen Action = 6
|
||||
ActionAll Action = 7
|
||||
ActionConnect Action = 8
|
||||
)
|
||||
|
||||
var ActionMap = map[string]Action{
|
||||
"unknown": ActionUnknown,
|
||||
"list": ActionList,
|
||||
"create": ActionCreate,
|
||||
"update": ActionUpdate,
|
||||
"read": ActionRead,
|
||||
"delete": ActionDelete,
|
||||
"authen": ActionAuthen,
|
||||
"*": ActionAll,
|
||||
"connect": ActionConnect,
|
||||
}
|
||||
|
||||
func (a Action) String() string {
|
||||
return [...]string{
|
||||
"unknown",
|
||||
"list",
|
||||
"create",
|
||||
"update",
|
||||
"read",
|
||||
"delete",
|
||||
"authen",
|
||||
"*",
|
||||
"connect",
|
||||
}[a]
|
||||
}
|
||||
import "github.com/hashicorp/watchtower/internal/types/action"
|
||||
|
||||
// CrudActions returns a standard set of actions for resources that support a CRUD API
|
||||
func CrudActions() map[string]Action {
|
||||
return map[string]Action{
|
||||
ActionCreate.String(): ActionCreate,
|
||||
ActionUpdate.String(): ActionUpdate,
|
||||
ActionRead.String(): ActionRead,
|
||||
ActionDelete.String(): ActionDelete,
|
||||
func CrudActions() map[string]action.Type {
|
||||
return map[string]action.Type{
|
||||
action.Create.String(): action.Create,
|
||||
action.Update.String(): action.Update,
|
||||
action.Read.String(): action.Read,
|
||||
action.Delete.String(): action.Delete,
|
||||
}
|
||||
}
|
||||
|
||||
// CrudlActions adds list to the standard set of actions for resources that support a CRUD API
|
||||
func CrudlActions() map[string]Action {
|
||||
return map[string]Action{
|
||||
ActionCreate.String(): ActionCreate,
|
||||
ActionUpdate.String(): ActionUpdate,
|
||||
ActionRead.String(): ActionRead,
|
||||
ActionDelete.String(): ActionDelete,
|
||||
ActionList.String(): ActionList,
|
||||
func CrudlActions() map[string]action.Type {
|
||||
return map[string]action.Type{
|
||||
action.Create.String(): action.Create,
|
||||
action.Update.String(): action.Update,
|
||||
action.Read.String(): action.Read,
|
||||
action.Delete.String(): action.Delete,
|
||||
action.List.String(): action.List,
|
||||
}
|
||||
}
|
||||
+14
-13
@@ -3,6 +3,7 @@ package iam
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/watchtower/internal/types/action"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -12,17 +13,17 @@ func Test_CrudActions(t *testing.T) {
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
a := CrudlActions()
|
||||
assert.Equal(a[ActionCreate.String()], ActionCreate)
|
||||
assert.Equal(a[ActionUpdate.String()], ActionUpdate)
|
||||
assert.Equal(a[ActionRead.String()], ActionRead)
|
||||
assert.Equal(a[ActionDelete.String()], ActionDelete)
|
||||
assert.Equal(a[action.Create.String()], action.Create)
|
||||
assert.Equal(a[action.Update.String()], action.Update)
|
||||
assert.Equal(a[action.Read.String()], action.Read)
|
||||
assert.Equal(a[action.Delete.String()], action.Delete)
|
||||
})
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
a := CrudlActions()
|
||||
action, ok := a["invalid"]
|
||||
aType, ok := a["invalid"]
|
||||
assert.Equal(ok, false)
|
||||
assert.Equal(action, ActionUnknown)
|
||||
assert.Equal(aType, action.Unknown)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -32,17 +33,17 @@ func Test_CrudlActions(t *testing.T) {
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
a := CrudlActions()
|
||||
assert.Equal(a[ActionList.String()], ActionList)
|
||||
assert.Equal(a[ActionCreate.String()], ActionCreate)
|
||||
assert.Equal(a[ActionUpdate.String()], ActionUpdate)
|
||||
assert.Equal(a[ActionRead.String()], ActionRead)
|
||||
assert.Equal(a[ActionDelete.String()], ActionDelete)
|
||||
assert.Equal(a[action.List.String()], action.List)
|
||||
assert.Equal(a[action.Create.String()], action.Create)
|
||||
assert.Equal(a[action.Update.String()], action.Update)
|
||||
assert.Equal(a[action.Read.String()], action.Read)
|
||||
assert.Equal(a[action.Delete.String()], action.Delete)
|
||||
})
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
a := CrudlActions()
|
||||
action, ok := a["invalid"]
|
||||
aType, ok := a["invalid"]
|
||||
assert.Equal(ok, false)
|
||||
assert.Equal(action, ActionUnknown)
|
||||
assert.Equal(aType, action.Unknown)
|
||||
})
|
||||
}
|
||||
@@ -6,6 +6,9 @@ import (
|
||||
|
||||
"github.com/hashicorp/watchtower/internal/db"
|
||||
"github.com/hashicorp/watchtower/internal/iam/store"
|
||||
"github.com/hashicorp/watchtower/internal/types/action"
|
||||
"github.com/hashicorp/watchtower/internal/types/resource"
|
||||
"github.com/hashicorp/watchtower/internal/types/scope"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -63,8 +66,8 @@ func (g *Group) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType,
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Group) validScopeTypes() []ScopeType {
|
||||
return []ScopeType{OrganizationScope, ProjectScope}
|
||||
func (u *Group) validScopeTypes() []scope.Type {
|
||||
return []scope.Type{scope.Organization, scope.Project}
|
||||
}
|
||||
|
||||
// GetScope returns the scope for the Group.
|
||||
@@ -73,10 +76,10 @@ func (g *Group) GetScope(ctx context.Context, r db.Reader) (*Scope, error) {
|
||||
}
|
||||
|
||||
// ResourceType returns the type of the Group.
|
||||
func (*Group) ResourceType() ResourceType { return ResourceTypeGroup }
|
||||
func (*Group) ResourceType() resource.Type { return resource.StaticGroup }
|
||||
|
||||
// Actions returns the available actions for Group
|
||||
func (*Group) Actions() map[string]Action {
|
||||
func (*Group) Actions() map[string]action.Type {
|
||||
return CrudActions()
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"github.com/hashicorp/watchtower/internal/db"
|
||||
dbassert "github.com/hashicorp/watchtower/internal/db/assert"
|
||||
"github.com/hashicorp/watchtower/internal/oplog"
|
||||
"github.com/hashicorp/watchtower/internal/types/action"
|
||||
"github.com/hashicorp/watchtower/internal/types/resource"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/proto"
|
||||
@@ -450,17 +452,17 @@ func TestGroup_Actions(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
r := &Group{}
|
||||
a := r.Actions()
|
||||
assert.Equal(a[ActionCreate.String()], ActionCreate)
|
||||
assert.Equal(a[ActionUpdate.String()], ActionUpdate)
|
||||
assert.Equal(a[ActionRead.String()], ActionRead)
|
||||
assert.Equal(a[ActionDelete.String()], ActionDelete)
|
||||
assert.Equal(a[action.Create.String()], action.Create)
|
||||
assert.Equal(a[action.Update.String()], action.Update)
|
||||
assert.Equal(a[action.Read.String()], action.Read)
|
||||
assert.Equal(a[action.Delete.String()], action.Delete)
|
||||
}
|
||||
|
||||
func TestGroup_ResourceType(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
r := &Group{}
|
||||
ty := r.ResourceType()
|
||||
assert.Equal(ty, ResourceTypeGroup)
|
||||
assert.Equal(ty, resource.StaticGroup)
|
||||
}
|
||||
|
||||
func TestGroup_GetScope(t *testing.T) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/watchtower/internal/db"
|
||||
"github.com/hashicorp/watchtower/internal/types/scope"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -36,8 +37,8 @@ func newGroupId() (string, error) {
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func newScopeId(scopeType ScopeType) (string, error) {
|
||||
if scopeType == UnknownScope {
|
||||
func newScopeId(scopeType scope.Type) (string, error) {
|
||||
if scopeType == scope.Unknown {
|
||||
return "", fmt.Errorf("new scope id: unknown is not supported %w", db.ErrInvalidParameter)
|
||||
}
|
||||
id, err := db.NewPublicId(scopeType.Prefix())
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"errors"
|
||||
|
||||
"github.com/hashicorp/watchtower/internal/db"
|
||||
"github.com/hashicorp/watchtower/internal/types/scope"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -28,15 +29,15 @@ func Test_PublicIds(t *testing.T) {
|
||||
assert.True(t, strings.HasPrefix(id, GroupPrefix+"_"))
|
||||
})
|
||||
t.Run("scopes", func(t *testing.T) {
|
||||
id, err := newScopeId(OrganizationScope)
|
||||
id, err := newScopeId(scope.Organization)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, strings.HasPrefix(id, OrganizationScope.Prefix()))
|
||||
assert.True(t, strings.HasPrefix(id, scope.Organization.Prefix()))
|
||||
|
||||
id, err = newScopeId(ProjectScope)
|
||||
id, err = newScopeId(scope.Project)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, strings.HasPrefix(id, ProjectScope.Prefix()))
|
||||
assert.True(t, strings.HasPrefix(id, scope.Project.Prefix()))
|
||||
|
||||
id, err = newScopeId(UnknownScope)
|
||||
id, err = newScopeId(scope.Unknown)
|
||||
require.Error(t, err)
|
||||
assert.Empty(t, id)
|
||||
assert.True(t, errors.Is(err, db.ErrInvalidParameter))
|
||||
|
||||
+13
-12
@@ -10,6 +10,7 @@ import (
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping"
|
||||
"github.com/hashicorp/watchtower/internal/db"
|
||||
"github.com/hashicorp/watchtower/internal/oplog"
|
||||
"github.com/hashicorp/watchtower/internal/types/scope"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -177,27 +178,27 @@ func (r *Repository) delete(ctx context.Context, resource Resource, opt ...Optio
|
||||
|
||||
func (r *Repository) stdMetadata(ctx context.Context, resource Resource) (oplog.Metadata, error) {
|
||||
if s, ok := resource.(*Scope); ok {
|
||||
scope := allocScope()
|
||||
scope.PublicId = s.PublicId
|
||||
scope.Type = s.Type
|
||||
if scope.Type == "" {
|
||||
if err := r.reader.LookupByPublicId(ctx, &scope); err != nil {
|
||||
newScope := allocScope()
|
||||
newScope.PublicId = s.PublicId
|
||||
newScope.Type = s.Type
|
||||
if newScope.Type == "" {
|
||||
if err := r.reader.LookupByPublicId(ctx, &newScope); err != nil {
|
||||
return nil, ErrMetadataScopeNotFound
|
||||
}
|
||||
}
|
||||
switch scope.Type {
|
||||
case OrganizationScope.String():
|
||||
switch newScope.Type {
|
||||
case scope.Organization.String():
|
||||
return oplog.Metadata{
|
||||
"resource-public-id": []string{resource.GetPublicId()},
|
||||
"scope-id": []string{scope.PublicId},
|
||||
"scope-type": []string{scope.Type},
|
||||
"scope-id": []string{newScope.PublicId},
|
||||
"scope-type": []string{newScope.Type},
|
||||
"resource-type": []string{resource.ResourceType().String()},
|
||||
}, nil
|
||||
case ProjectScope.String():
|
||||
case scope.Project.String():
|
||||
return oplog.Metadata{
|
||||
"resource-public-id": []string{resource.GetPublicId()},
|
||||
"scope-id": []string{scope.ParentId},
|
||||
"scope-type": []string{scope.Type},
|
||||
"scope-id": []string{newScope.ParentId},
|
||||
"scope-type": []string{newScope.Type},
|
||||
"resource-type": []string{resource.ResourceType().String()},
|
||||
}, nil
|
||||
default:
|
||||
|
||||
@@ -7,23 +7,24 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/watchtower/internal/db"
|
||||
"github.com/hashicorp/watchtower/internal/types/scope"
|
||||
)
|
||||
|
||||
// CreateScope will create a scope in the repository and return the written
|
||||
// scope. Supported options include: WithPublicId.
|
||||
func (r *Repository) CreateScope(ctx context.Context, scope *Scope, opt ...Option) (*Scope, error) {
|
||||
if scope == nil {
|
||||
func (r *Repository) CreateScope(ctx context.Context, s *Scope, opt ...Option) (*Scope, error) {
|
||||
if s == nil {
|
||||
return nil, fmt.Errorf("create scope: missing scope %w", db.ErrNilParameter)
|
||||
}
|
||||
if scope.Scope == nil {
|
||||
if s.Scope == nil {
|
||||
return nil, fmt.Errorf("create scope: missing scope store %w", db.ErrNilParameter)
|
||||
}
|
||||
if scope.PublicId != "" {
|
||||
if s.PublicId != "" {
|
||||
return nil, fmt.Errorf("create scope: public id not empty: %w", db.ErrInvalidParameter)
|
||||
}
|
||||
opts := getOpts(opt...)
|
||||
var publicId string
|
||||
t := stringToScopeType(scope.Type)
|
||||
t := scope.StringToScopeType(s.Type)
|
||||
if opts.withPublicId != "" {
|
||||
if !strings.HasPrefix(opts.withPublicId, t.Prefix()+"_") {
|
||||
return nil, fmt.Errorf("create scope: passed-in public ID %q has wrong prefix for type %q which uses prefix %q", opts.withPublicId, t.String(), t.Prefix())
|
||||
@@ -36,14 +37,14 @@ func (r *Repository) CreateScope(ctx context.Context, scope *Scope, opt ...Optio
|
||||
return nil, fmt.Errorf("create scope: error generating public id %w for new scope", err)
|
||||
}
|
||||
}
|
||||
s := scope.Clone().(*Scope)
|
||||
s.PublicId = publicId
|
||||
resource, err := r.create(ctx, s)
|
||||
sc := s.Clone().(*Scope)
|
||||
sc.PublicId = publicId
|
||||
resource, err := r.create(ctx, sc)
|
||||
if err != nil {
|
||||
if db.IsUniqueError(err) {
|
||||
return nil, fmt.Errorf("create scope: scope %s/%s already exists: %w", s.PublicId, s.Name, db.ErrNotUnique)
|
||||
return nil, fmt.Errorf("create scope: scope %s/%s already exists: %w", sc.PublicId, sc.Name, db.ErrNotUnique)
|
||||
}
|
||||
return nil, fmt.Errorf("create scope: %w for %s", err, s.PublicId)
|
||||
return nil, fmt.Errorf("create scope: %w for %s", err, sc.PublicId)
|
||||
}
|
||||
return resource.(*Scope), nil
|
||||
}
|
||||
@@ -127,7 +128,7 @@ func (r *Repository) ListProjects(ctx context.Context, withOrganizationId string
|
||||
return nil, fmt.Errorf("list projects: missing organization id %w", db.ErrInvalidParameter)
|
||||
}
|
||||
var projects []*Scope
|
||||
err := r.list(ctx, &projects, "parent_id = ? and type = ?", []interface{}{withOrganizationId, ProjectScope.String()}, opt...)
|
||||
err := r.list(ctx, &projects, "parent_id = ? and type = ?", []interface{}{withOrganizationId, scope.Project.String()}, opt...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list projects: %w", err)
|
||||
}
|
||||
@@ -137,7 +138,7 @@ func (r *Repository) ListProjects(ctx context.Context, withOrganizationId string
|
||||
// ListOrganizations and supports the WithLimit option.
|
||||
func (r *Repository) ListOrganizations(ctx context.Context, opt ...Option) ([]*Scope, error) {
|
||||
var organizations []*Scope
|
||||
err := r.list(ctx, &organizations, "type = ?", []interface{}{OrganizationScope.String()}, opt...)
|
||||
err := r.list(ctx, &organizations, "type = ?", []interface{}{scope.Organization.String()}, opt...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list organizations: %w", err)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/hashicorp/watchtower/internal/db/timestamp"
|
||||
iam_store "github.com/hashicorp/watchtower/internal/iam/store"
|
||||
"github.com/hashicorp/watchtower/internal/oplog"
|
||||
"github.com/hashicorp/watchtower/internal/types/scope"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/proto"
|
||||
@@ -55,7 +56,7 @@ func Test_Repository_CreateScope(t *testing.T) {
|
||||
wrapper := db.TestWrapper(t)
|
||||
repo, err := NewRepository(rw, rw, wrapper)
|
||||
require.NoError(err)
|
||||
publicId, err := newScopeId(OrganizationScope)
|
||||
publicId, err := newScopeId(scope.Organization)
|
||||
require.NoError(err)
|
||||
s, err := NewOrganization()
|
||||
require.NoError(err)
|
||||
@@ -421,7 +422,7 @@ func TestRepository_UpdateScope(t *testing.T) {
|
||||
Scope: &iam_store.Scope{
|
||||
Name: "type" + id,
|
||||
Description: "",
|
||||
Type: ProjectScope.String(),
|
||||
Type: scope.Project.String(),
|
||||
CreateTime: now,
|
||||
UpdateTime: now,
|
||||
PublicId: publicId,
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
iam_store "github.com/hashicorp/watchtower/internal/iam/store"
|
||||
"github.com/hashicorp/watchtower/internal/oplog"
|
||||
"github.com/hashicorp/watchtower/internal/oplog/store"
|
||||
"github.com/hashicorp/watchtower/internal/types/scope"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/proto"
|
||||
@@ -123,7 +124,7 @@ func Test_Repository_create(t *testing.T) {
|
||||
|
||||
s, err := NewOrganization(WithName("fname-" + id))
|
||||
assert.NoError(err)
|
||||
s.PublicId, err = newScopeId(OrganizationScope)
|
||||
s.PublicId, err = newScopeId(scope.Organization)
|
||||
require.NoError(err)
|
||||
retScope, err := repo.create(context.Background(), s)
|
||||
require.NoError(err)
|
||||
|
||||
@@ -6,6 +6,9 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/watchtower/internal/db"
|
||||
"github.com/hashicorp/watchtower/internal/types/action"
|
||||
"github.com/hashicorp/watchtower/internal/types/resource"
|
||||
"github.com/hashicorp/watchtower/internal/types/scope"
|
||||
)
|
||||
|
||||
// Resource declares the shared behavior of IAM Resources
|
||||
@@ -24,51 +27,12 @@ type Resource interface {
|
||||
GetScope(ctx context.Context, r db.Reader) (*Scope, error)
|
||||
|
||||
// Type of Resource (Target, Policy, User, Group, etc)
|
||||
ResourceType() ResourceType
|
||||
ResourceType() resource.Type
|
||||
|
||||
// Actions that can be assigned permissions for
|
||||
// the Resource in Policies. Action String() is key for
|
||||
// the map of Actions returned.
|
||||
Actions() map[string]Action
|
||||
}
|
||||
|
||||
// ResourceType defines the types of resources in the system
|
||||
type ResourceType int
|
||||
|
||||
const (
|
||||
ResourceTypeUnknown ResourceType = 0
|
||||
ResourceTypeScope ResourceType = 1
|
||||
ResourceTypeUser ResourceType = 2
|
||||
ResourceTypeGroup ResourceType = 3
|
||||
ResourceTypeRole ResourceType = 4
|
||||
ResourceTypeOrganization ResourceType = 5
|
||||
ResourceTypeGroupMember ResourceType = 6
|
||||
ResourceTypeGroupUserMember ResourceType = 7
|
||||
ResourceTypeAssignedRole ResourceType = 8
|
||||
ResourceTypeAssignedUserRole ResourceType = 9
|
||||
ResourceTypeAssignedGroupRole ResourceType = 10
|
||||
ResourceTypeRoleGrant ResourceType = 11
|
||||
ResourceTypeAuthMethod ResourceType = 12
|
||||
ResourceTypeProject ResourceType = 13
|
||||
)
|
||||
|
||||
func (r ResourceType) String() string {
|
||||
return [...]string{
|
||||
"unknown",
|
||||
"scope",
|
||||
"user",
|
||||
"group",
|
||||
"role",
|
||||
"organization",
|
||||
"group member",
|
||||
"group user member",
|
||||
"assigned role",
|
||||
"assigned user role",
|
||||
"assigned group role",
|
||||
"role grant",
|
||||
"auth method",
|
||||
"project",
|
||||
}[r]
|
||||
Actions() map[string]action.Type
|
||||
}
|
||||
|
||||
type Clonable interface {
|
||||
@@ -79,7 +43,7 @@ type Clonable interface {
|
||||
type ResourceWithScope interface {
|
||||
GetPublicId() string
|
||||
GetScopeId() string
|
||||
validScopeTypes() []ScopeType
|
||||
validScopeTypes() []scope.Type
|
||||
}
|
||||
|
||||
// LookupScope looks up the resource's scope
|
||||
|
||||
@@ -7,6 +7,9 @@ import (
|
||||
|
||||
"github.com/hashicorp/watchtower/internal/db"
|
||||
"github.com/hashicorp/watchtower/internal/iam/store"
|
||||
"github.com/hashicorp/watchtower/internal/types/action"
|
||||
"github.com/hashicorp/watchtower/internal/types/resource"
|
||||
"github.com/hashicorp/watchtower/internal/types/scope"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -63,8 +66,8 @@ func (role *Role) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Role) validScopeTypes() []ScopeType {
|
||||
return []ScopeType{OrganizationScope, ProjectScope}
|
||||
func (u *Role) validScopeTypes() []scope.Type {
|
||||
return []scope.Type{scope.Organization, scope.Project}
|
||||
}
|
||||
|
||||
// Getscope returns the scope for the Role.
|
||||
@@ -73,10 +76,10 @@ func (role *Role) GetScope(ctx context.Context, r db.Reader) (*Scope, error) {
|
||||
}
|
||||
|
||||
// ResourceType returns the type of the Role.
|
||||
func (*Role) ResourceType() ResourceType { return ResourceTypeRole }
|
||||
func (*Role) ResourceType() resource.Type { return resource.Role }
|
||||
|
||||
// Actions returns the available actions for Role.
|
||||
func (*Role) Actions() map[string]Action {
|
||||
func (*Role) Actions() map[string]action.Type {
|
||||
return CrudActions()
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"github.com/hashicorp/watchtower/internal/db"
|
||||
dbassert "github.com/hashicorp/watchtower/internal/db/assert"
|
||||
"github.com/hashicorp/watchtower/internal/oplog"
|
||||
"github.com/hashicorp/watchtower/internal/types/action"
|
||||
"github.com/hashicorp/watchtower/internal/types/resource"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/proto"
|
||||
@@ -448,17 +450,17 @@ func TestRole_Actions(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
r := &Role{}
|
||||
a := r.Actions()
|
||||
assert.Equal(a[ActionCreate.String()], ActionCreate)
|
||||
assert.Equal(a[ActionUpdate.String()], ActionUpdate)
|
||||
assert.Equal(a[ActionRead.String()], ActionRead)
|
||||
assert.Equal(a[ActionDelete.String()], ActionDelete)
|
||||
assert.Equal(a[action.Create.String()], action.Create)
|
||||
assert.Equal(a[action.Update.String()], action.Update)
|
||||
assert.Equal(a[action.Read.String()], action.Read)
|
||||
assert.Equal(a[action.Delete.String()], action.Delete)
|
||||
}
|
||||
|
||||
func TestRole_ResourceType(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
r := &Role{}
|
||||
ty := r.ResourceType()
|
||||
assert.Equal(ty, ResourceTypeRole)
|
||||
assert.Equal(ty, resource.Role)
|
||||
}
|
||||
|
||||
func TestRole_GetScope(t *testing.T) {
|
||||
|
||||
+21
-54
@@ -7,45 +7,12 @@ import (
|
||||
|
||||
"github.com/hashicorp/watchtower/internal/db"
|
||||
"github.com/hashicorp/watchtower/internal/iam/store"
|
||||
"github.com/hashicorp/watchtower/internal/types/action"
|
||||
"github.com/hashicorp/watchtower/internal/types/resource"
|
||||
"github.com/hashicorp/watchtower/internal/types/scope"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// ScopeType defines the possible types for Scopes
|
||||
type ScopeType uint32
|
||||
|
||||
const (
|
||||
UnknownScope ScopeType = 0
|
||||
OrganizationScope ScopeType = 1
|
||||
ProjectScope ScopeType = 2
|
||||
)
|
||||
|
||||
func (s ScopeType) String() string {
|
||||
return [...]string{
|
||||
"unknown",
|
||||
"organization",
|
||||
"project",
|
||||
}[s]
|
||||
}
|
||||
|
||||
func (s ScopeType) Prefix() string {
|
||||
return [...]string{
|
||||
"unknown",
|
||||
"o",
|
||||
"p",
|
||||
}[s]
|
||||
}
|
||||
|
||||
func stringToScopeType(s string) ScopeType {
|
||||
switch s {
|
||||
case OrganizationScope.String():
|
||||
return OrganizationScope
|
||||
case ProjectScope.String():
|
||||
return ProjectScope
|
||||
default:
|
||||
return UnknownScope
|
||||
}
|
||||
}
|
||||
|
||||
// Scope is used to create a hierarchy of "containers" that encompass the scope of
|
||||
// an IAM resource. Scopes are Organizations and Projects.
|
||||
type Scope struct {
|
||||
@@ -62,14 +29,14 @@ var _ db.VetForWriter = (*Scope)(nil)
|
||||
var _ Clonable = (*Scope)(nil)
|
||||
|
||||
func NewOrganization(opt ...Option) (*Scope, error) {
|
||||
return newScope(OrganizationScope, opt...)
|
||||
return newScope(scope.Organization, opt...)
|
||||
}
|
||||
|
||||
func NewProject(organizationPublicId string, opt ...Option) (*Scope, error) {
|
||||
org := allocScope()
|
||||
org.PublicId = organizationPublicId
|
||||
opt = append(opt, withScope(&org))
|
||||
p, err := newScope(ProjectScope, opt...)
|
||||
p, err := newScope(scope.Project, opt...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating new project: %w", err)
|
||||
}
|
||||
@@ -79,15 +46,15 @@ func NewProject(organizationPublicId string, opt ...Option) (*Scope, error) {
|
||||
// newScope creates a new Scope of the specified ScopeType with options:
|
||||
// WithName specifies the Scope's friendly name. WithDescription specifies the
|
||||
// scope's description. WithScope specifies the Scope's parent
|
||||
func newScope(scopeType ScopeType, opt ...Option) (*Scope, error) {
|
||||
func newScope(scopeType scope.Type, opt ...Option) (*Scope, error) {
|
||||
opts := getOpts(opt...)
|
||||
if scopeType == UnknownScope {
|
||||
if scopeType == scope.Unknown {
|
||||
return nil, fmt.Errorf("new scope: unknown scope type %w", db.ErrInvalidParameter)
|
||||
}
|
||||
if opts.withScope != nil && opts.withScope.PublicId == "" {
|
||||
return nil, fmt.Errorf("new scope: with scope's parent id is missing %w", db.ErrInvalidParameter)
|
||||
}
|
||||
if scopeType == ProjectScope && opts.withScope == nil {
|
||||
if scopeType == scope.Project && opts.withScope == nil {
|
||||
return nil, fmt.Errorf("new scope: project scope is missing its parent %w", db.ErrInvalidParameter)
|
||||
}
|
||||
s := &Scope{
|
||||
@@ -121,7 +88,7 @@ func (s *Scope) Clone() interface{} {
|
||||
// this function is intended to be callled by a db.Writer (Create and Update) to validate
|
||||
// the scope before writing it to the db.
|
||||
func (s *Scope) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
|
||||
if s.Type == UnknownScope.String() {
|
||||
if s.Type == scope.Unknown.String() {
|
||||
return errors.New("unknown scope type for scope write")
|
||||
}
|
||||
if s.PublicId == "" {
|
||||
@@ -139,16 +106,16 @@ func (s *Scope) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType,
|
||||
}
|
||||
}
|
||||
if opType == db.CreateOp {
|
||||
if s.ParentId == "" && s.Type == ProjectScope.String() {
|
||||
if s.ParentId == "" && s.Type == scope.Project.String() {
|
||||
return errors.New("project has no organization")
|
||||
}
|
||||
if s.Type == ProjectScope.String() {
|
||||
if s.Type == scope.Project.String() {
|
||||
parentScope := allocScope()
|
||||
parentScope.PublicId = s.ParentId
|
||||
if err := r.LookupByPublicId(ctx, &parentScope, opt...); err != nil {
|
||||
return fmt.Errorf("unable to verify project's organization scope: %w", err)
|
||||
}
|
||||
if parentScope.Type != OrganizationScope.String() {
|
||||
if parentScope.Type != scope.Organization.String() {
|
||||
return errors.New("project parent scope is not an organization")
|
||||
}
|
||||
}
|
||||
@@ -157,19 +124,19 @@ func (s *Scope) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType,
|
||||
}
|
||||
|
||||
// ResourceType returns the type of scope
|
||||
func (s *Scope) ResourceType() ResourceType {
|
||||
func (s *Scope) ResourceType() resource.Type {
|
||||
switch s.Type {
|
||||
case OrganizationScope.String():
|
||||
return ResourceTypeOrganization
|
||||
case ProjectScope.String():
|
||||
return ResourceTypeProject
|
||||
case scope.Organization.String():
|
||||
return resource.Organization
|
||||
case scope.Project.String():
|
||||
return resource.Project
|
||||
default:
|
||||
return ResourceTypeScope
|
||||
return resource.Scope
|
||||
}
|
||||
}
|
||||
|
||||
// Actions returns the available actions for Scopes
|
||||
func (*Scope) Actions() map[string]Action {
|
||||
func (*Scope) Actions() map[string]action.Type {
|
||||
return CrudActions()
|
||||
}
|
||||
|
||||
@@ -188,9 +155,9 @@ func (s *Scope) GetScope(ctx context.Context, r db.Reader) (*Scope, error) {
|
||||
}
|
||||
// HANDLE_ORG
|
||||
switch s.Type {
|
||||
case OrganizationScope.String():
|
||||
case scope.Organization.String():
|
||||
return nil, nil
|
||||
case ProjectScope.String():
|
||||
case scope.Project.String():
|
||||
var p Scope
|
||||
switch s.ParentId {
|
||||
case "":
|
||||
|
||||
+17
-45
@@ -5,6 +5,9 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/watchtower/internal/db"
|
||||
"github.com/hashicorp/watchtower/internal/types/action"
|
||||
"github.com/hashicorp/watchtower/internal/types/resource"
|
||||
"github.com/hashicorp/watchtower/internal/types/scope"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/proto"
|
||||
@@ -25,7 +28,7 @@ func Test_NewScope(t *testing.T) {
|
||||
s, err := NewOrganization()
|
||||
require.NoError(err)
|
||||
require.NotNil(s.Scope)
|
||||
s.PublicId, err = newScopeId(OrganizationScope)
|
||||
s.PublicId, err = newScopeId(scope.Organization)
|
||||
require.NoError(err)
|
||||
err = w.Create(context.Background(), s)
|
||||
require.NoError(err)
|
||||
@@ -40,7 +43,7 @@ func Test_NewScope(t *testing.T) {
|
||||
})
|
||||
t.Run("unknown-scope", func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
s, err := newScope(UnknownScope)
|
||||
s, err := newScope(scope.Unknown)
|
||||
require.Error(err)
|
||||
require.Nil(s)
|
||||
assert.Equal(err.Error(), "new scope: unknown scope type invalid parameter")
|
||||
@@ -68,7 +71,7 @@ func Test_ScopeCreate(t *testing.T) {
|
||||
s, err := NewOrganization()
|
||||
require.NoError(err)
|
||||
require.NotNil(s.Scope)
|
||||
s.PublicId, err = newScopeId(OrganizationScope)
|
||||
s.PublicId, err = newScopeId(scope.Organization)
|
||||
require.NoError(err)
|
||||
err = w.Create(context.Background(), s)
|
||||
require.NoError(err)
|
||||
@@ -80,7 +83,7 @@ func Test_ScopeCreate(t *testing.T) {
|
||||
s, err := NewOrganization()
|
||||
require.NoError(err)
|
||||
require.NotNil(s.Scope)
|
||||
s.PublicId, err = newScopeId(OrganizationScope)
|
||||
s.PublicId, err = newScopeId(scope.Organization)
|
||||
require.NoError(err)
|
||||
err = w.Create(context.Background(), s)
|
||||
require.NoError(err)
|
||||
@@ -92,7 +95,7 @@ func Test_ScopeCreate(t *testing.T) {
|
||||
require.NotNil(project.Scope)
|
||||
assert.Equal(project.Scope.ParentId, s.PublicId)
|
||||
assert.Equal(project.GetDescription(), id)
|
||||
project.PublicId, err = newScopeId(OrganizationScope)
|
||||
project.PublicId, err = newScopeId(scope.Organization)
|
||||
require.NoError(err)
|
||||
|
||||
err = w.Create(context.Background(), project)
|
||||
@@ -125,7 +128,7 @@ func Test_ScopeUpdate(t *testing.T) {
|
||||
w := db.New(conn)
|
||||
s, _ := TestScopes(t, conn)
|
||||
|
||||
s.Type = ProjectScope.String()
|
||||
s.Type = scope.Project.String()
|
||||
updatedRows, err := w.Update(context.Background(), s, []string{"Type"}, nil)
|
||||
require.Error(err)
|
||||
assert.Equal(0, updatedRows)
|
||||
@@ -155,7 +158,7 @@ func Test_ScopeGetScope(t *testing.T) {
|
||||
|
||||
p := allocScope()
|
||||
p.PublicId = proj.PublicId
|
||||
p.Type = ProjectScope.String()
|
||||
p.Type = scope.Project.String()
|
||||
projectOrg, err = p.GetScope(context.Background(), w)
|
||||
require.NoError(err)
|
||||
assert.Equal(org.PublicId, projectOrg.PublicId)
|
||||
@@ -166,20 +169,20 @@ func TestScope_Actions(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
s := &Scope{}
|
||||
a := s.Actions()
|
||||
assert.Equal(a[ActionCreate.String()], ActionCreate)
|
||||
assert.Equal(a[ActionUpdate.String()], ActionUpdate)
|
||||
assert.Equal(a[ActionRead.String()], ActionRead)
|
||||
assert.Equal(a[ActionDelete.String()], ActionDelete)
|
||||
assert.Equal(a[action.Create.String()], action.Create)
|
||||
assert.Equal(a[action.Update.String()], action.Update)
|
||||
assert.Equal(a[action.Read.String()], action.Read)
|
||||
assert.Equal(a[action.Delete.String()], action.Delete)
|
||||
|
||||
if _, ok := a[ActionList.String()]; ok {
|
||||
t.Errorf("scopes should not include %s as an action", ActionList.String())
|
||||
if _, ok := a[action.List.String()]; ok {
|
||||
t.Errorf("scopes should not include %s as an action", action.List.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestScope_ResourceType(t *testing.T) {
|
||||
o, err := NewOrganization()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, o.ResourceType(), ResourceTypeOrganization)
|
||||
assert.Equal(t, o.ResourceType(), resource.Organization)
|
||||
}
|
||||
|
||||
func TestScope_Clone(t *testing.T) {
|
||||
@@ -205,34 +208,3 @@ func TestScope_Clone(t *testing.T) {
|
||||
assert.True(!proto.Equal(cp.(*Scope).Scope, s2.Scope))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_stringToScopeType(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
s string
|
||||
want ScopeType
|
||||
}{
|
||||
{
|
||||
name: "org",
|
||||
s: "organization",
|
||||
want: OrganizationScope,
|
||||
},
|
||||
{
|
||||
name: "proj",
|
||||
s: "project",
|
||||
want: ProjectScope,
|
||||
},
|
||||
{
|
||||
name: "unknown",
|
||||
s: "org",
|
||||
want: UnknownScope,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
got := stringToScopeType(tt.s)
|
||||
assert.Equal(tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,9 @@ import (
|
||||
|
||||
"github.com/hashicorp/watchtower/internal/db"
|
||||
"github.com/hashicorp/watchtower/internal/iam/store"
|
||||
"github.com/hashicorp/watchtower/internal/types/action"
|
||||
"github.com/hashicorp/watchtower/internal/types/resource"
|
||||
"github.com/hashicorp/watchtower/internal/types/scope"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -64,8 +67,8 @@ func (u *User) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, o
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *User) validScopeTypes() []ScopeType {
|
||||
return []ScopeType{OrganizationScope}
|
||||
func (u *User) validScopeTypes() []scope.Type {
|
||||
return []scope.Type{scope.Organization}
|
||||
}
|
||||
|
||||
// GetScope returns the scope for the User
|
||||
@@ -74,10 +77,10 @@ func (u *User) GetScope(ctx context.Context, r db.Reader) (*Scope, error) {
|
||||
}
|
||||
|
||||
// ResourceType returns the type of the User
|
||||
func (*User) ResourceType() ResourceType { return ResourceTypeUser }
|
||||
func (*User) ResourceType() resource.Type { return resource.User }
|
||||
|
||||
// Actions returns the available actions for Users
|
||||
func (*User) Actions() map[string]Action {
|
||||
func (*User) Actions() map[string]action.Type {
|
||||
return CrudActions()
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/watchtower/internal/db"
|
||||
"github.com/hashicorp/watchtower/internal/types/action"
|
||||
"github.com/hashicorp/watchtower/internal/types/resource"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/proto"
|
||||
@@ -362,18 +364,18 @@ func TestUser_Actions(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
u := &User{}
|
||||
a := u.Actions()
|
||||
assert.Equal(a[ActionCreate.String()], ActionCreate)
|
||||
assert.Equal(a[ActionUpdate.String()], ActionUpdate)
|
||||
assert.Equal(a[ActionRead.String()], ActionRead)
|
||||
assert.Equal(a[ActionDelete.String()], ActionDelete)
|
||||
assert.Equal(a[action.Create.String()], action.Create)
|
||||
assert.Equal(a[action.Update.String()], action.Update)
|
||||
assert.Equal(a[action.Read.String()], action.Read)
|
||||
assert.Equal(a[action.Delete.String()], action.Delete)
|
||||
|
||||
if _, ok := a[ActionList.String()]; ok {
|
||||
t.Errorf("users should not include %s as an action", ActionList.String())
|
||||
if _, ok := a[action.List.String()]; ok {
|
||||
t.Errorf("users should not include %s as an action", action.List.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestUser_ResourceType(t *testing.T) {
|
||||
t.Parallel()
|
||||
u := allocUser()
|
||||
assert.Equal(t, ResourceTypeUser, u.ResourceType())
|
||||
assert.Equal(t, resource.User, u.ResourceType())
|
||||
}
|
||||
+11
-10
@@ -19,7 +19,8 @@ construction is thus synthesizing something reasonable from a set of Grants.
|
||||
*/
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/watchtower/internal/iam"
|
||||
"github.com/hashicorp/watchtower/internal/types/action"
|
||||
"github.com/hashicorp/watchtower/internal/types/resource"
|
||||
)
|
||||
|
||||
// ACL provides an entry point into the permissions engine for determining if an
|
||||
@@ -48,7 +49,7 @@ type Resource struct {
|
||||
Id string
|
||||
|
||||
// Type of resource.
|
||||
Type string
|
||||
Type resource.Type
|
||||
|
||||
// Pin if defined would constrain the resource within the collection of the
|
||||
// pin id.
|
||||
@@ -69,32 +70,32 @@ func NewACL(grants ...Grant) ACL {
|
||||
}
|
||||
|
||||
// Allowed determines if the grants for an ACL allow an action for a resource.
|
||||
func (a ACL) Allowed(resource Resource, action iam.Action) (results ACLResults) {
|
||||
func (a ACL) Allowed(r Resource, aType action.Type) (results ACLResults) {
|
||||
// First, get the grants within the specified scope
|
||||
grants := a.scopeMap[resource.ScopeId]
|
||||
grants := a.scopeMap[r.ScopeId]
|
||||
results.scopeMap = a.scopeMap
|
||||
|
||||
// Now, go through and check the cases indicated above
|
||||
for _, grant := range grants {
|
||||
if !(grant.actions[action] || grant.actions[iam.ActionAll]) {
|
||||
if !(grant.actions[aType] || grant.actions[action.All]) {
|
||||
continue
|
||||
}
|
||||
switch {
|
||||
// type=<resource.type>;actions=<action>
|
||||
case grant.id == "" &&
|
||||
grant.typ == resource.Type:
|
||||
grant.typ == r.Type:
|
||||
results.Allowed = true
|
||||
return
|
||||
|
||||
// id=<resource.id>;actions=<action>
|
||||
case (grant.id == resource.Id || grant.id == "*") &&
|
||||
grant.typ == "":
|
||||
case (grant.id == r.Id || grant.id == "*") &&
|
||||
grant.typ == resource.Unknown:
|
||||
results.Allowed = true
|
||||
return
|
||||
|
||||
// id=<pin>;type=<resource.type>;actions=<action>
|
||||
case grant.id == resource.Pin &&
|
||||
grant.typ == resource.Type:
|
||||
case grant.id == r.Pin &&
|
||||
grant.typ == r.Type:
|
||||
results.Allowed = true
|
||||
return
|
||||
}
|
||||
|
||||
+57
-55
@@ -3,7 +3,9 @@ package perms
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/watchtower/internal/iam"
|
||||
"github.com/hashicorp/watchtower/internal/types/action"
|
||||
"github.com/hashicorp/watchtower/internal/types/resource"
|
||||
"github.com/hashicorp/watchtower/internal/types/scope"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -16,7 +18,7 @@ func Test_ACLAllowed(t *testing.T) {
|
||||
grants []string
|
||||
}
|
||||
type actionAllowed struct {
|
||||
action iam.Action
|
||||
action action.Type
|
||||
allowed bool
|
||||
}
|
||||
type input struct {
|
||||
@@ -30,7 +32,7 @@ func Test_ACLAllowed(t *testing.T) {
|
||||
// A set of common grants to use in the following tests
|
||||
commonGrants := []scopeGrant{
|
||||
{
|
||||
scope: Scope{Type: iam.OrganizationScope, Id: "org_a"},
|
||||
scope: Scope{Type: scope.Organization, Id: "org_a"},
|
||||
grants: []string{
|
||||
"id=a_bar;actions=read,update",
|
||||
"id=a_foo;type=host-catalog;actions=update,delete",
|
||||
@@ -38,7 +40,7 @@ func Test_ACLAllowed(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: Scope{Type: iam.OrganizationScope, Id: "org_b"},
|
||||
scope: Scope{Type: scope.Organization, Id: "org_b"},
|
||||
grants: []string{
|
||||
"type=host-set;actions=list,create",
|
||||
"type=host;actions=*",
|
||||
@@ -46,7 +48,7 @@ func Test_ACLAllowed(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: Scope{Type: iam.OrganizationScope, Id: "org_c"},
|
||||
scope: Scope{Type: scope.Organization, Id: "org_c"},
|
||||
grants: []string{
|
||||
"id={{user.id }};actions=create,update",
|
||||
},
|
||||
@@ -58,19 +60,19 @@ func Test_ACLAllowed(t *testing.T) {
|
||||
tests := []input{
|
||||
{
|
||||
name: "no grants",
|
||||
resource: Resource{ScopeId: "foo", Id: "bar", Type: "typ"},
|
||||
resource: Resource{ScopeId: "foo", Id: "bar", Type: resource.HostCatalog},
|
||||
actionsAllowed: []actionAllowed{
|
||||
{action: iam.ActionCreate},
|
||||
{action: iam.ActionRead},
|
||||
{action: action.Create},
|
||||
{action: action.Read},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no overlap",
|
||||
resource: Resource{ScopeId: "foo", Id: "bar", Type: "typ"},
|
||||
resource: Resource{ScopeId: "foo", Id: "bar", Type: resource.HostCatalog},
|
||||
scopeGrants: commonGrants,
|
||||
actionsAllowed: []actionAllowed{
|
||||
{action: iam.ActionCreate},
|
||||
{action: iam.ActionRead},
|
||||
{action: action.Create},
|
||||
{action: action.Read},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -78,8 +80,8 @@ func Test_ACLAllowed(t *testing.T) {
|
||||
resource: Resource{ScopeId: "org_a", Id: "a_foo"},
|
||||
scopeGrants: commonGrants,
|
||||
actionsAllowed: []actionAllowed{
|
||||
{action: iam.ActionUpdate},
|
||||
{action: iam.ActionDelete},
|
||||
{action: action.Update},
|
||||
{action: action.Delete},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -87,19 +89,19 @@ func Test_ACLAllowed(t *testing.T) {
|
||||
resource: Resource{ScopeId: "org_a", Id: "a_bar"},
|
||||
scopeGrants: commonGrants,
|
||||
actionsAllowed: []actionAllowed{
|
||||
{action: iam.ActionRead, allowed: true},
|
||||
{action: iam.ActionUpdate, allowed: true},
|
||||
{action: iam.ActionDelete},
|
||||
{action: action.Read, allowed: true},
|
||||
{action: action.Update, allowed: true},
|
||||
{action: action.Delete},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "matching scope and id and all action",
|
||||
resource: Resource{ScopeId: "org_b", Type: "host"},
|
||||
resource: Resource{ScopeId: "org_b", Type: resource.Host},
|
||||
scopeGrants: commonGrants,
|
||||
actionsAllowed: []actionAllowed{
|
||||
{action: iam.ActionRead, allowed: true},
|
||||
{action: iam.ActionUpdate, allowed: true},
|
||||
{action: iam.ActionDelete, allowed: true},
|
||||
{action: action.Read, allowed: true},
|
||||
{action: action.Update, allowed: true},
|
||||
{action: action.Delete, allowed: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -107,78 +109,78 @@ func Test_ACLAllowed(t *testing.T) {
|
||||
resource: Resource{ScopeId: "org_b", Id: "id_g"},
|
||||
scopeGrants: commonGrants,
|
||||
actionsAllowed: []actionAllowed{
|
||||
{action: iam.ActionRead},
|
||||
{action: iam.ActionUpdate},
|
||||
{action: iam.ActionDelete},
|
||||
{action: action.Read},
|
||||
{action: action.Update},
|
||||
{action: action.Delete},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "matching scope and not matching type",
|
||||
resource: Resource{ScopeId: "org_a", Type: "host-catalog"},
|
||||
resource: Resource{ScopeId: "org_a", Type: resource.HostCatalog},
|
||||
scopeGrants: commonGrants,
|
||||
actionsAllowed: []actionAllowed{
|
||||
{action: iam.ActionUpdate},
|
||||
{action: iam.ActionDelete},
|
||||
{action: action.Update},
|
||||
{action: action.Delete},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "matching scope and matching type",
|
||||
resource: Resource{ScopeId: "org_a", Type: "host-set"},
|
||||
resource: Resource{ScopeId: "org_a", Type: resource.HostSet},
|
||||
scopeGrants: commonGrants,
|
||||
actionsAllowed: []actionAllowed{
|
||||
{action: iam.ActionList, allowed: true},
|
||||
{action: iam.ActionCreate, allowed: true},
|
||||
{action: iam.ActionDelete},
|
||||
{action: action.List, allowed: true},
|
||||
{action: action.Create, allowed: true},
|
||||
{action: action.Delete},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "matching scope, type, action, bad pin",
|
||||
resource: Resource{ScopeId: "org_a", Id: "a_foo", Type: "host-catalog"},
|
||||
resource: Resource{ScopeId: "org_a", Id: "a_foo", Type: resource.HostCatalog},
|
||||
scopeGrants: commonGrants,
|
||||
actionsAllowed: []actionAllowed{
|
||||
{action: iam.ActionUpdate},
|
||||
{action: iam.ActionDelete},
|
||||
{action: iam.ActionRead},
|
||||
{action: action.Update},
|
||||
{action: action.Delete},
|
||||
{action: action.Read},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "matching scope, type, action, random id and bad pin",
|
||||
resource: Resource{ScopeId: "org_a", Id: "anything", Type: "host-catalog", Pin: "a_bar"},
|
||||
resource: Resource{ScopeId: "org_a", Id: "anything", Type: resource.HostCatalog, Pin: "a_bar"},
|
||||
scopeGrants: commonGrants,
|
||||
actionsAllowed: []actionAllowed{
|
||||
{action: iam.ActionUpdate},
|
||||
{action: iam.ActionDelete},
|
||||
{action: iam.ActionRead},
|
||||
{action: action.Update},
|
||||
{action: action.Delete},
|
||||
{action: action.Read},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "matching scope, type, action, random id and good pin",
|
||||
resource: Resource{ScopeId: "org_a", Id: "anything", Type: "host-catalog", Pin: "a_foo"},
|
||||
resource: Resource{ScopeId: "org_a", Id: "anything", Type: resource.HostCatalog, Pin: "a_foo"},
|
||||
scopeGrants: commonGrants,
|
||||
actionsAllowed: []actionAllowed{
|
||||
{action: iam.ActionUpdate, allowed: true},
|
||||
{action: iam.ActionDelete, allowed: true},
|
||||
{action: iam.ActionRead},
|
||||
{action: action.Update, allowed: true},
|
||||
{action: action.Delete, allowed: true},
|
||||
{action: action.Read},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "wrong scope and matching type",
|
||||
resource: Resource{ScopeId: "org_bad", Type: "host-set"},
|
||||
resource: Resource{ScopeId: "org_bad", Type: resource.HostSet},
|
||||
scopeGrants: commonGrants,
|
||||
actionsAllowed: []actionAllowed{
|
||||
{action: iam.ActionList},
|
||||
{action: iam.ActionCreate},
|
||||
{action: iam.ActionDelete},
|
||||
{action: action.List},
|
||||
{action: action.Create},
|
||||
{action: action.Delete},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "any id",
|
||||
resource: Resource{ScopeId: "org_b", Type: "auth-method"},
|
||||
resource: Resource{ScopeId: "org_b", Type: resource.AuthMethod},
|
||||
scopeGrants: commonGrants,
|
||||
actionsAllowed: []actionAllowed{
|
||||
{action: iam.ActionList},
|
||||
{action: iam.ActionAuthen, allowed: true},
|
||||
{action: iam.ActionDelete},
|
||||
{action: action.List},
|
||||
{action: action.Authen, allowed: true},
|
||||
{action: action.Delete},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -186,9 +188,9 @@ func Test_ACLAllowed(t *testing.T) {
|
||||
resource: Resource{ScopeId: "org_c"},
|
||||
scopeGrants: commonGrants,
|
||||
actionsAllowed: []actionAllowed{
|
||||
{action: iam.ActionList},
|
||||
{action: iam.ActionAuthen},
|
||||
{action: iam.ActionDelete},
|
||||
{action: action.List},
|
||||
{action: action.Authen},
|
||||
{action: action.Delete},
|
||||
},
|
||||
userId: "u_abcd1234",
|
||||
},
|
||||
@@ -197,8 +199,8 @@ func Test_ACLAllowed(t *testing.T) {
|
||||
resource: Resource{ScopeId: "org_c", Id: "u_abcd1234"},
|
||||
scopeGrants: commonGrants,
|
||||
actionsAllowed: []actionAllowed{
|
||||
{action: iam.ActionCreate, allowed: true},
|
||||
{action: iam.ActionUpdate, allowed: true},
|
||||
{action: action.Create, allowed: true},
|
||||
{action: action.Update, allowed: true},
|
||||
},
|
||||
userId: "u_abcd1234",
|
||||
},
|
||||
|
||||
+41
-48
@@ -7,20 +7,9 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/watchtower/internal/iam"
|
||||
)
|
||||
|
||||
const (
|
||||
TypeNone = ""
|
||||
TypeAll = "*"
|
||||
TypeRole = "role"
|
||||
TypeGroup = "group"
|
||||
TypeUser = "user"
|
||||
TypeAuthMethod = "auth-method"
|
||||
TypeHostCatalog = "host-catalog"
|
||||
TypeHostSet = "host-set"
|
||||
TypeHost = "host"
|
||||
TypeTarget = "target"
|
||||
"github.com/hashicorp/watchtower/internal/types/action"
|
||||
"github.com/hashicorp/watchtower/internal/types/resource"
|
||||
"github.com/hashicorp/watchtower/internal/types/scope"
|
||||
)
|
||||
|
||||
// Scope provides an in-memory representation of iam.Scope without the
|
||||
@@ -30,7 +19,7 @@ type Scope struct {
|
||||
Id string
|
||||
|
||||
// Type is the scope's type (org or project)
|
||||
Type iam.ScopeType
|
||||
Type scope.Type
|
||||
}
|
||||
|
||||
// Grant is a Go representation of a parsed grant
|
||||
@@ -42,10 +31,10 @@ type Grant struct {
|
||||
id string
|
||||
|
||||
// The type, if provided
|
||||
typ string
|
||||
typ resource.Type
|
||||
|
||||
// The set of actions being granted
|
||||
actions map[iam.Action]bool
|
||||
actions map[action.Type]bool
|
||||
|
||||
// This is used as a temporary staging area before validating permissions to
|
||||
// allow the same validation code across grant string formats
|
||||
@@ -62,7 +51,7 @@ func (g Grant) clone() *Grant {
|
||||
ret.actionsBeingParsed = append(ret.actionsBeingParsed, g.actionsBeingParsed...)
|
||||
}
|
||||
if g.actions != nil {
|
||||
ret.actions = make(map[iam.Action]bool, len(g.actions))
|
||||
ret.actions = make(map[action.Type]bool, len(g.actions))
|
||||
for action := range g.actions {
|
||||
ret.actions[action] = true
|
||||
}
|
||||
@@ -78,8 +67,8 @@ func (g Grant) CanonicalString() string {
|
||||
builder = append(builder, fmt.Sprintf("id=%s", g.id))
|
||||
}
|
||||
|
||||
if g.typ != TypeNone {
|
||||
builder = append(builder, fmt.Sprintf("type=%s", g.typ))
|
||||
if g.typ != resource.Unknown {
|
||||
builder = append(builder, fmt.Sprintf("type=%s", g.typ.String()))
|
||||
}
|
||||
|
||||
if len(g.actions) > 0 {
|
||||
@@ -100,8 +89,8 @@ func (g Grant) MarshalJSON() ([]byte, error) {
|
||||
if g.id != "" {
|
||||
res["id"] = g.id
|
||||
}
|
||||
if g.typ != "" {
|
||||
res["type"] = g.typ
|
||||
if g.typ != resource.Unknown {
|
||||
res["type"] = g.typ.String()
|
||||
}
|
||||
if len(g.actions) > 0 {
|
||||
actions := make([]string, 0, len(g.actions))
|
||||
@@ -134,7 +123,7 @@ func (g *Grant) unmarshalJSON(data []byte) error {
|
||||
if !ok {
|
||||
return fmt.Errorf("unable to interpret %q as string", "type")
|
||||
}
|
||||
g.typ = strings.ToLower(typ)
|
||||
g.typ = resource.StringToResourceType(typ)
|
||||
}
|
||||
if rawActions, ok := raw["actions"]; ok {
|
||||
interfaceActions, ok := rawActions.([]interface{})
|
||||
@@ -179,7 +168,11 @@ func (g *Grant) unmarshalText(grantString string) error {
|
||||
g.id = strings.ToLower(kv[1])
|
||||
|
||||
case "type":
|
||||
g.typ = strings.ToLower(kv[1])
|
||||
typeString := strings.ToLower(kv[1])
|
||||
g.typ = resource.StringToResourceType(typeString)
|
||||
if g.typ == resource.Unknown {
|
||||
return fmt.Errorf("unknown type specifier %q", typeString)
|
||||
}
|
||||
|
||||
case "actions":
|
||||
actions := strings.Split(kv[1], ",")
|
||||
@@ -205,23 +198,23 @@ func (g *Grant) unmarshalText(grantString string) error {
|
||||
//
|
||||
// The scope must be the org and project where this grant originated, not the
|
||||
// request.
|
||||
func Parse(scope Scope, userId, grantString string) (Grant, error) {
|
||||
func Parse(s Scope, userId, grantString string) (Grant, error) {
|
||||
if len(grantString) == 0 {
|
||||
return Grant{}, errors.New("grant string is empty")
|
||||
}
|
||||
|
||||
switch scope.Type {
|
||||
case iam.ProjectScope, iam.OrganizationScope:
|
||||
switch s.Type {
|
||||
case scope.Project, scope.Organization:
|
||||
default:
|
||||
return Grant{}, errors.New("invalid scope type")
|
||||
}
|
||||
|
||||
if scope.Id == "" {
|
||||
if s.Id == "" {
|
||||
return Grant{}, errors.New("no scope ID provided")
|
||||
}
|
||||
|
||||
grant := Grant{
|
||||
scope: scope,
|
||||
scope: s,
|
||||
}
|
||||
|
||||
switch {
|
||||
@@ -249,7 +242,7 @@ func Parse(scope Scope, userId, grantString string) (Grant, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if grant.id == "" && grant.typ == "" {
|
||||
if grant.id == "" && grant.typ == resource.Unknown {
|
||||
return Grant{}, errors.New(`"id" and "type" cannot both be empty`)
|
||||
}
|
||||
|
||||
@@ -266,16 +259,16 @@ func Parse(scope Scope, userId, grantString string) (Grant, error) {
|
||||
|
||||
func (g Grant) validateType() error {
|
||||
switch g.typ {
|
||||
case TypeNone,
|
||||
TypeAll,
|
||||
TypeRole,
|
||||
TypeGroup,
|
||||
TypeUser,
|
||||
TypeAuthMethod,
|
||||
TypeHostCatalog,
|
||||
TypeHostSet,
|
||||
TypeHost,
|
||||
TypeTarget:
|
||||
case resource.Unknown,
|
||||
resource.All,
|
||||
resource.Role,
|
||||
resource.StaticGroup,
|
||||
resource.User,
|
||||
resource.AuthMethod,
|
||||
resource.HostCatalog,
|
||||
resource.HostSet,
|
||||
resource.Host,
|
||||
resource.Target:
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unknown type specifier %q", g.typ)
|
||||
@@ -286,22 +279,22 @@ func (g *Grant) parseAndValidateActions() error {
|
||||
return errors.New("no actions specified")
|
||||
}
|
||||
|
||||
for _, action := range g.actionsBeingParsed {
|
||||
if action == "" {
|
||||
for _, a := range g.actionsBeingParsed {
|
||||
if a == "" {
|
||||
return errors.New("empty action found")
|
||||
}
|
||||
if g.actions == nil {
|
||||
g.actions = make(map[iam.Action]bool, len(g.actionsBeingParsed))
|
||||
g.actions = make(map[action.Type]bool, len(g.actionsBeingParsed))
|
||||
}
|
||||
if a := iam.ActionMap[action]; a == iam.ActionUnknown {
|
||||
return fmt.Errorf("unknown action %q", action)
|
||||
if am := action.Map[a]; am == action.Unknown {
|
||||
return fmt.Errorf("unknown action %q", a)
|
||||
} else {
|
||||
g.actions[a] = true
|
||||
g.actions[am] = true
|
||||
}
|
||||
}
|
||||
|
||||
if len(g.actions) > 1 && g.actions[iam.ActionAll] {
|
||||
return fmt.Errorf("%q cannot be specified with other actions", iam.ActionAll.String())
|
||||
if len(g.actions) > 1 && g.actions[action.All] {
|
||||
return fmt.Errorf("%q cannot be specified with other actions", action.All.String())
|
||||
}
|
||||
|
||||
g.actionsBeingParsed = nil
|
||||
|
||||
@@ -4,7 +4,9 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/watchtower/internal/iam"
|
||||
"github.com/hashicorp/watchtower/internal/types/action"
|
||||
"github.com/hashicorp/watchtower/internal/types/resource"
|
||||
"github.com/hashicorp/watchtower/internal/types/scope"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -44,8 +46,8 @@ func Test_ActionParsingValidation(t *testing.T) {
|
||||
actionsBeingParsed: []string{"*"},
|
||||
},
|
||||
result: Grant{
|
||||
actions: map[iam.Action]bool{
|
||||
iam.ActionAll: true,
|
||||
actions: map[action.Type]bool{
|
||||
action.All: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -62,14 +64,14 @@ func Test_ActionParsingValidation(t *testing.T) {
|
||||
actionsBeingParsed: []string{"list", "create", "update", "read", "delete", "authen", "connect"},
|
||||
},
|
||||
result: Grant{
|
||||
actions: map[iam.Action]bool{
|
||||
iam.ActionList: true,
|
||||
iam.ActionCreate: true,
|
||||
iam.ActionUpdate: true,
|
||||
iam.ActionRead: true,
|
||||
iam.ActionDelete: true,
|
||||
iam.ActionAuthen: true,
|
||||
iam.ActionConnect: true,
|
||||
actions: map[action.Type]bool{
|
||||
action.List: true,
|
||||
action.Create: true,
|
||||
action.Update: true,
|
||||
action.Read: true,
|
||||
action.Delete: true,
|
||||
action.Authen: true,
|
||||
action.Connect: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -105,14 +107,14 @@ func Test_ValidateType(t *testing.T) {
|
||||
{
|
||||
name: "unknown specifier",
|
||||
input: Grant{
|
||||
typ: "foobar",
|
||||
typ: resource.RoleGrant,
|
||||
},
|
||||
errResult: `unknown type specifier "foobar"`,
|
||||
errResult: `unknown type specifier "role-grant"`,
|
||||
},
|
||||
{
|
||||
name: "valid specifier",
|
||||
input: Grant{
|
||||
typ: TypeHostCatalog,
|
||||
typ: resource.HostCatalog,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -145,7 +147,7 @@ func Test_MarshallingAndCloning(t *testing.T) {
|
||||
name: "empty",
|
||||
input: Grant{
|
||||
scope: Scope{
|
||||
Type: iam.OrganizationScope,
|
||||
Type: scope.Organization,
|
||||
},
|
||||
},
|
||||
jsonOutput: `{}`,
|
||||
@@ -156,29 +158,29 @@ func Test_MarshallingAndCloning(t *testing.T) {
|
||||
input: Grant{
|
||||
id: "baz",
|
||||
scope: Scope{
|
||||
Type: iam.ProjectScope,
|
||||
Type: scope.Project,
|
||||
},
|
||||
typ: TypeGroup,
|
||||
typ: resource.StaticGroup,
|
||||
},
|
||||
jsonOutput: `{"id":"baz","type":"group"}`,
|
||||
canonicalString: `id=baz;type=group`,
|
||||
jsonOutput: `{"id":"baz","type":"static-group"}`,
|
||||
canonicalString: `id=baz;type=static-group`,
|
||||
},
|
||||
{
|
||||
name: "everything",
|
||||
input: Grant{
|
||||
id: "baz",
|
||||
scope: Scope{
|
||||
Type: iam.ProjectScope,
|
||||
Type: scope.Project,
|
||||
},
|
||||
typ: TypeGroup,
|
||||
actions: map[iam.Action]bool{
|
||||
iam.ActionCreate: true,
|
||||
iam.ActionRead: true,
|
||||
typ: resource.StaticGroup,
|
||||
actions: map[action.Type]bool{
|
||||
action.Create: true,
|
||||
action.Read: true,
|
||||
},
|
||||
actionsBeingParsed: []string{"create", "read"},
|
||||
},
|
||||
jsonOutput: `{"actions":["create","read"],"id":"baz","type":"group"}`,
|
||||
canonicalString: `id=baz;type=group;actions=create,read`,
|
||||
jsonOutput: `{"actions":["create","read"],"id":"baz","type":"static-group"}`,
|
||||
canonicalString: `id=baz;type=static-group;actions=create,read`,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -243,7 +245,7 @@ func Test_Unmarshaling(t *testing.T) {
|
||||
{
|
||||
name: "good type",
|
||||
expected: Grant{
|
||||
typ: "host-catalog",
|
||||
typ: resource.HostCatalog,
|
||||
},
|
||||
jsonInput: `{"type":"host-catalog"}`,
|
||||
textInput: `type=host-catalog`,
|
||||
@@ -284,11 +286,10 @@ func Test_Unmarshaling(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
var g Grant
|
||||
if test.jsonInput != "" {
|
||||
err := g.unmarshalJSON([]byte(test.jsonInput))
|
||||
@@ -362,13 +363,13 @@ func Test_Parse(t *testing.T) {
|
||||
expected: Grant{
|
||||
scope: Scope{
|
||||
Id: "scope",
|
||||
Type: iam.OrganizationScope,
|
||||
Type: scope.Organization,
|
||||
},
|
||||
id: "foobar",
|
||||
typ: "host-catalog",
|
||||
actions: map[iam.Action]bool{
|
||||
iam.ActionCreate: true,
|
||||
iam.ActionRead: true,
|
||||
typ: resource.HostCatalog,
|
||||
actions: map[action.Type]bool{
|
||||
action.Create: true,
|
||||
action.Read: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -378,13 +379,13 @@ func Test_Parse(t *testing.T) {
|
||||
expected: Grant{
|
||||
scope: Scope{
|
||||
Id: "scope",
|
||||
Type: iam.OrganizationScope,
|
||||
Type: scope.Organization,
|
||||
},
|
||||
id: "foobar",
|
||||
typ: "host-catalog",
|
||||
actions: map[iam.Action]bool{
|
||||
iam.ActionCreate: true,
|
||||
iam.ActionRead: true,
|
||||
typ: resource.HostCatalog,
|
||||
actions: map[action.Type]bool{
|
||||
action.Create: true,
|
||||
action.Read: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -401,38 +402,37 @@ func Test_Parse(t *testing.T) {
|
||||
expected: Grant{
|
||||
scope: Scope{
|
||||
Id: "scope",
|
||||
Type: iam.OrganizationScope,
|
||||
Type: scope.Organization,
|
||||
},
|
||||
id: "u_abcd1234",
|
||||
actions: map[iam.Action]bool{
|
||||
iam.ActionCreate: true,
|
||||
iam.ActionRead: true,
|
||||
actions: map[action.Type]bool{
|
||||
action.Create: true,
|
||||
action.Read: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
_, err := Parse(Scope{}, "", "")
|
||||
require.Error(err)
|
||||
assert.Equal("grant string is empty", err.Error())
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, "grant string is empty", err.Error())
|
||||
|
||||
_, err = Parse(Scope{}, "", "{}")
|
||||
require.Error(err)
|
||||
assert.Equal("invalid scope type", err.Error())
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, "invalid scope type", err.Error())
|
||||
|
||||
_, err = Parse(Scope{Type: iam.OrganizationScope}, "", "{}")
|
||||
require.Error(err)
|
||||
assert.Equal("no scope ID provided", err.Error())
|
||||
_, err = Parse(Scope{Type: scope.Organization}, "", "{}")
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, "no scope ID provided", err.Error())
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
grant, err := Parse(Scope{Id: "scope", Type: iam.OrganizationScope}, test.userId, test.input)
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
grant, err := Parse(Scope{Id: "scope", Type: scope.Organization}, test.userId, test.input)
|
||||
if test.err != "" {
|
||||
require.Error(err)
|
||||
assert.True(strings.HasPrefix(err.Error(), test.err))
|
||||
assert.True(strings.Contains(err.Error(), test.err))
|
||||
} else {
|
||||
require.NoError(err)
|
||||
assert.Equal(test.expected, grant)
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
pbs "github.com/hashicorp/watchtower/internal/gen/controller/api/services"
|
||||
"github.com/hashicorp/watchtower/internal/iam"
|
||||
"github.com/hashicorp/watchtower/internal/servers/controller/handlers"
|
||||
"github.com/hashicorp/watchtower/internal/types/scope"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/wrapperspb"
|
||||
@@ -347,7 +348,7 @@ func validateAncestors(r ancestorProvider) map[string]string {
|
||||
if r.GetOrgId() == "" {
|
||||
return map[string]string{orgIdFieldName: "Missing organization id."}
|
||||
}
|
||||
if !validId(r.GetOrgId(), iam.OrganizationScope.Prefix()+"_") {
|
||||
if !validId(r.GetOrgId(), scope.Organization.Prefix()+"_") {
|
||||
return map[string]string{orgIdFieldName: "Improperly formatted identifier."}
|
||||
}
|
||||
return map[string]string{}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
pbs "github.com/hashicorp/watchtower/internal/gen/controller/api/services"
|
||||
"github.com/hashicorp/watchtower/internal/iam"
|
||||
"github.com/hashicorp/watchtower/internal/servers/controller/handlers/groups"
|
||||
"github.com/hashicorp/watchtower/internal/types/scope"
|
||||
"google.golang.org/genproto/protobuf/field_mask"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
@@ -155,14 +156,14 @@ func TestList(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Invalid Org Id",
|
||||
req: &pbs.ListGroupsRequest{OrgId: iam.OrganizationScope.Prefix() + "_this is invalid"},
|
||||
req: &pbs.ListGroupsRequest{OrgId: scope.Organization.Prefix() + "_this is invalid"},
|
||||
res: nil,
|
||||
errCode: codes.InvalidArgument,
|
||||
},
|
||||
// TODO: When an org doesn't exist, we should return a 404 instead of an empty list.
|
||||
{
|
||||
name: "Unfound Org",
|
||||
req: &pbs.ListGroupsRequest{OrgId: iam.OrganizationScope.Prefix() + "_DoesntExis"},
|
||||
req: &pbs.ListGroupsRequest{OrgId: scope.Organization.Prefix() + "_DoesntExis"},
|
||||
res: &pbs.ListGroupsResponse{},
|
||||
errCode: codes.OK,
|
||||
},
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
pb "github.com/hashicorp/watchtower/internal/gen/controller/api/resources/hosts"
|
||||
pbs "github.com/hashicorp/watchtower/internal/gen/controller/api/services"
|
||||
"github.com/hashicorp/watchtower/internal/host/static"
|
||||
"github.com/hashicorp/watchtower/internal/iam"
|
||||
"github.com/hashicorp/watchtower/internal/servers/controller/common"
|
||||
"github.com/hashicorp/watchtower/internal/servers/controller/handlers"
|
||||
"github.com/hashicorp/watchtower/internal/types/scope"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/wrapperspb"
|
||||
@@ -391,10 +391,10 @@ func validateAncestors(r ancestorProvider) map[string]string {
|
||||
return badFields
|
||||
}
|
||||
|
||||
if !validId(r.GetOrgId(), iam.OrganizationScope.Prefix()+"_") {
|
||||
if !validId(r.GetOrgId(), scope.Organization.Prefix()+"_") {
|
||||
badFields[orgIdFieldName] = "The field is incorrectly formatted."
|
||||
}
|
||||
if !validId(r.GetProjectId(), iam.ProjectScope.Prefix()+"_") {
|
||||
if !validId(r.GetProjectId(), scope.Project.Prefix()+"_") {
|
||||
badFields[projectIdFieldName] = "The field is incorrectly formatted."
|
||||
}
|
||||
return badFields
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/hashicorp/watchtower/internal/iam"
|
||||
"github.com/hashicorp/watchtower/internal/servers/controller/common"
|
||||
"github.com/hashicorp/watchtower/internal/servers/controller/handlers"
|
||||
"github.com/hashicorp/watchtower/internal/types/scope"
|
||||
"google.golang.org/protobuf/types/known/wrapperspb"
|
||||
)
|
||||
|
||||
@@ -109,7 +110,7 @@ func toProto(in *iam.Scope) *pb.Organization {
|
||||
// * There are no conflicting parameters provided
|
||||
func validateGetRequest(req *pbs.GetOrganizationRequest) error {
|
||||
badFields := make(map[string]string)
|
||||
if !validId(req.GetId(), iam.OrganizationScope.Prefix()+"_") {
|
||||
if !validId(req.GetId(), scope.Organization.Prefix()+"_") {
|
||||
badFields["id"] = "Invalid formatted organization id."
|
||||
}
|
||||
if len(badFields) > 0 {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
pbs "github.com/hashicorp/watchtower/internal/gen/controller/api/services"
|
||||
"github.com/hashicorp/watchtower/internal/iam"
|
||||
"github.com/hashicorp/watchtower/internal/servers/controller/handlers/organizations"
|
||||
"github.com/hashicorp/watchtower/internal/types/scope"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/proto"
|
||||
@@ -77,7 +78,7 @@ func TestGet(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Get a non existing organization",
|
||||
req: &pbs.GetOrganizationRequest{Id: iam.OrganizationScope.Prefix() + "_DoesntExis"},
|
||||
req: &pbs.GetOrganizationRequest{Id: scope.Organization.Prefix() + "_DoesntExis"},
|
||||
res: nil,
|
||||
errCode: codes.NotFound,
|
||||
},
|
||||
@@ -89,7 +90,7 @@ func TestGet(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "space in id",
|
||||
req: &pbs.GetOrganizationRequest{Id: iam.OrganizationScope.Prefix() + "_1 23456789"},
|
||||
req: &pbs.GetOrganizationRequest{Id: scope.Organization.Prefix() + "_1 23456789"},
|
||||
res: nil,
|
||||
errCode: codes.InvalidArgument,
|
||||
},
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/hashicorp/watchtower/internal/iam"
|
||||
"github.com/hashicorp/watchtower/internal/servers/controller/common"
|
||||
"github.com/hashicorp/watchtower/internal/servers/controller/handlers"
|
||||
"github.com/hashicorp/watchtower/internal/types/scope"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/wrapperspb"
|
||||
@@ -246,7 +247,7 @@ func toProto(in *iam.Scope) *pb.Project {
|
||||
// * There are no conflicting parameters provided
|
||||
func validateGetRequest(req *pbs.GetProjectRequest) error {
|
||||
badFields := validateAncestors(req)
|
||||
if !validId(req.GetId(), iam.ProjectScope.Prefix()+"_") {
|
||||
if !validId(req.GetId(), scope.Project.Prefix()+"_") {
|
||||
badFields["id"] = "Invalid formatted project id."
|
||||
}
|
||||
if len(badFields) > 0 {
|
||||
@@ -275,7 +276,7 @@ func validateCreateRequest(req *pbs.CreateProjectRequest) error {
|
||||
|
||||
func validateUpdateRequest(req *pbs.UpdateProjectRequest) error {
|
||||
badFields := validateAncestors(req)
|
||||
if !validId(req.GetId(), iam.ProjectScope.Prefix()+"_") {
|
||||
if !validId(req.GetId(), scope.Project.Prefix()+"_") {
|
||||
badFields["project_id"] = "Improperly formatted path identifier."
|
||||
}
|
||||
if req.GetUpdateMask() == nil {
|
||||
@@ -309,7 +310,7 @@ func validateUpdateRequest(req *pbs.UpdateProjectRequest) error {
|
||||
|
||||
func validateDeleteRequest(req *pbs.DeleteProjectRequest) error {
|
||||
badFields := validateAncestors(req)
|
||||
if !validId(req.GetId(), iam.ProjectScope.Prefix()+"_") {
|
||||
if !validId(req.GetId(), scope.Project.Prefix()+"_") {
|
||||
badFields["id"] = "Incorrectly formatted project."
|
||||
}
|
||||
if len(badFields) > 0 {
|
||||
@@ -343,7 +344,7 @@ func validateAncestors(r ancestorProvider) map[string]string {
|
||||
if r.GetOrgId() == "" {
|
||||
return map[string]string{orgIdFieldName: "Missing organization id."}
|
||||
}
|
||||
if !validId(r.GetOrgId(), iam.OrganizationScope.Prefix()+"_") {
|
||||
if !validId(r.GetOrgId(), scope.Organization.Prefix()+"_") {
|
||||
return map[string]string{orgIdFieldName: "Improperly formatted identifier."}
|
||||
}
|
||||
return map[string]string{}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
pbs "github.com/hashicorp/watchtower/internal/gen/controller/api/services"
|
||||
"github.com/hashicorp/watchtower/internal/iam"
|
||||
"github.com/hashicorp/watchtower/internal/servers/controller/handlers/projects"
|
||||
"github.com/hashicorp/watchtower/internal/types/scope"
|
||||
"google.golang.org/genproto/protobuf/field_mask"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
@@ -172,14 +173,14 @@ func TestList(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Invalid Org Id",
|
||||
req: &pbs.ListProjectsRequest{OrgId: iam.OrganizationScope.Prefix() + "_this is invalid"},
|
||||
req: &pbs.ListProjectsRequest{OrgId: scope.Organization.Prefix() + "_this is invalid"},
|
||||
res: nil,
|
||||
errCode: codes.InvalidArgument,
|
||||
},
|
||||
// TODO: When an org doesn't exist, we should return a 404 instead of an empty list.
|
||||
{
|
||||
name: "Unfound Org",
|
||||
req: &pbs.ListProjectsRequest{OrgId: iam.OrganizationScope.Prefix() + "_DoesntExis"},
|
||||
req: &pbs.ListProjectsRequest{OrgId: scope.Organization.Prefix() + "_DoesntExis"},
|
||||
res: &pbs.ListProjectsResponse{},
|
||||
errCode: codes.OK,
|
||||
},
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
pbs "github.com/hashicorp/watchtower/internal/gen/controller/api/services"
|
||||
"github.com/hashicorp/watchtower/internal/iam"
|
||||
"github.com/hashicorp/watchtower/internal/servers/controller/handlers"
|
||||
"github.com/hashicorp/watchtower/internal/types/scope"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/wrapperspb"
|
||||
@@ -347,7 +348,7 @@ func validateAncestors(r ancestorProvider) map[string]string {
|
||||
if r.GetOrgId() == "" {
|
||||
return map[string]string{orgIdFieldName: "Missing organization id."}
|
||||
}
|
||||
if !validId(r.GetOrgId(), iam.OrganizationScope.Prefix()+"_") {
|
||||
if !validId(r.GetOrgId(), scope.Organization.Prefix()+"_") {
|
||||
return map[string]string{orgIdFieldName: "Improperly formatted identifier."}
|
||||
}
|
||||
return map[string]string{}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
pbs "github.com/hashicorp/watchtower/internal/gen/controller/api/services"
|
||||
"github.com/hashicorp/watchtower/internal/iam"
|
||||
"github.com/hashicorp/watchtower/internal/servers/controller/handlers/roles"
|
||||
"github.com/hashicorp/watchtower/internal/types/scope"
|
||||
"google.golang.org/genproto/protobuf/field_mask"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
@@ -155,14 +156,14 @@ func TestList(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Invalid Org Id",
|
||||
req: &pbs.ListRolesRequest{OrgId: iam.OrganizationScope.Prefix() + "_this is invalid"},
|
||||
req: &pbs.ListRolesRequest{OrgId: scope.Organization.Prefix() + "_this is invalid"},
|
||||
res: nil,
|
||||
errCode: codes.InvalidArgument,
|
||||
},
|
||||
// TODO: When an org doesn't exist, we should return a 404 instead of an empty list.
|
||||
{
|
||||
name: "Unfound Org",
|
||||
req: &pbs.ListRolesRequest{OrgId: iam.OrganizationScope.Prefix() + "_DoesntExis"},
|
||||
req: &pbs.ListRolesRequest{OrgId: scope.Organization.Prefix() + "_DoesntExis"},
|
||||
res: &pbs.ListRolesResponse{},
|
||||
errCode: codes.OK,
|
||||
},
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
pbs "github.com/hashicorp/watchtower/internal/gen/controller/api/services"
|
||||
"github.com/hashicorp/watchtower/internal/iam"
|
||||
"github.com/hashicorp/watchtower/internal/servers/controller/handlers"
|
||||
"github.com/hashicorp/watchtower/internal/types/scope"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/wrapperspb"
|
||||
@@ -316,7 +317,7 @@ func validateAncestors(r ancestorProvider) map[string]string {
|
||||
if r.GetOrgId() == "" {
|
||||
return map[string]string{orgIdFieldName: "Missing organization id."}
|
||||
}
|
||||
if !validId(r.GetOrgId(), iam.OrganizationScope.Prefix()+"_") {
|
||||
if !validId(r.GetOrgId(), scope.Organization.Prefix()+"_") {
|
||||
return map[string]string{orgIdFieldName: "Improperly formatted identifier."}
|
||||
}
|
||||
return map[string]string{}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package action
|
||||
|
||||
// Type defines a type for the Actions of Resources
|
||||
// actions are also stored as a lookup db table named iam_action
|
||||
type Type int
|
||||
|
||||
// not using iota intentionally, since the values are stored in the db as well.
|
||||
const (
|
||||
Unknown Type = 0
|
||||
List Type = 1
|
||||
Create Type = 2
|
||||
Update Type = 3
|
||||
Read Type = 4
|
||||
Delete Type = 5
|
||||
Authen Type = 6
|
||||
All Type = 7
|
||||
Connect Type = 8
|
||||
)
|
||||
|
||||
var Map = map[string]Type{
|
||||
"unknown": Unknown,
|
||||
"list": List,
|
||||
"create": Create,
|
||||
"update": Update,
|
||||
"read": Read,
|
||||
"delete": Delete,
|
||||
"authen": Authen,
|
||||
"*": All,
|
||||
"connect": Connect,
|
||||
}
|
||||
|
||||
func (a Type) String() string {
|
||||
return [...]string{
|
||||
"unknown",
|
||||
"list",
|
||||
"create",
|
||||
"update",
|
||||
"read",
|
||||
"delete",
|
||||
"authen",
|
||||
"*",
|
||||
"connect",
|
||||
}[a]
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAction(t *testing.T) {
|
||||
tests := []struct {
|
||||
action Type
|
||||
want string
|
||||
}{
|
||||
{
|
||||
action: Unknown,
|
||||
want: "unknown",
|
||||
},
|
||||
{
|
||||
action: List,
|
||||
want: "list",
|
||||
},
|
||||
{
|
||||
action: Create,
|
||||
want: "create",
|
||||
},
|
||||
{
|
||||
action: Update,
|
||||
want: "update",
|
||||
},
|
||||
{
|
||||
action: Read,
|
||||
want: "read",
|
||||
},
|
||||
{
|
||||
action: Delete,
|
||||
want: "delete",
|
||||
},
|
||||
{
|
||||
action: Authen,
|
||||
want: "authen",
|
||||
},
|
||||
{
|
||||
action: All,
|
||||
want: "*",
|
||||
},
|
||||
{
|
||||
action: Connect,
|
||||
want: "connect",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.want, func(t *testing.T) {
|
||||
assert.Equalf(t, tt.want, tt.action.String(), "unexpected action string for %s", tt.want)
|
||||
assert.Equalf(t, tt.action, Map[tt.want], "unexpected action from map for %s", tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package resource
|
||||
|
||||
// Type defines the types of resources in the system
|
||||
type Type int
|
||||
|
||||
const (
|
||||
Unknown Type = 0
|
||||
Scope Type = 1
|
||||
User Type = 2
|
||||
StaticGroup Type = 3
|
||||
Role Type = 4
|
||||
Organization Type = 5
|
||||
StaticGroupMember Type = 6
|
||||
StaticGroupUserMember Type = 7
|
||||
AssignedRole Type = 8
|
||||
AssignedUserRole Type = 9
|
||||
AssignedStaticGroupRole Type = 10
|
||||
RoleGrant Type = 11
|
||||
AuthMethod Type = 12
|
||||
Project Type = 13
|
||||
All Type = 14
|
||||
HostCatalog Type = 15
|
||||
HostSet Type = 16
|
||||
Host Type = 17
|
||||
Target Type = 18
|
||||
)
|
||||
|
||||
func (r Type) String() string {
|
||||
return [...]string{
|
||||
"unknown",
|
||||
"scope",
|
||||
"user",
|
||||
"static-group",
|
||||
"role",
|
||||
"organization",
|
||||
"static-group-member",
|
||||
"static-group-user-member",
|
||||
"assigned-role",
|
||||
"assigned-user-role",
|
||||
"assigned-static-group-role",
|
||||
"role-grant",
|
||||
"auth-method",
|
||||
"project",
|
||||
"*",
|
||||
"host-catalog",
|
||||
"host-set",
|
||||
"host",
|
||||
"target",
|
||||
}[r]
|
||||
}
|
||||
|
||||
func StringToResourceType(s string) Type {
|
||||
switch s {
|
||||
case Scope.String():
|
||||
return Scope
|
||||
case User.String():
|
||||
return User
|
||||
case StaticGroup.String():
|
||||
return StaticGroup
|
||||
case Role.String():
|
||||
return Role
|
||||
case Organization.String():
|
||||
return Organization
|
||||
case StaticGroupMember.String():
|
||||
return StaticGroupMember
|
||||
case StaticGroupUserMember.String():
|
||||
return StaticGroupUserMember
|
||||
case AssignedRole.String():
|
||||
return AssignedRole
|
||||
case AssignedUserRole.String():
|
||||
return AssignedUserRole
|
||||
case AssignedStaticGroupRole.String():
|
||||
return AssignedStaticGroupRole
|
||||
case RoleGrant.String():
|
||||
return RoleGrant
|
||||
case AuthMethod.String():
|
||||
return AuthMethod
|
||||
case Project.String():
|
||||
return Project
|
||||
case All.String():
|
||||
return All
|
||||
case HostCatalog.String():
|
||||
return HostCatalog
|
||||
case HostSet.String():
|
||||
return HostSet
|
||||
case Host.String():
|
||||
return Host
|
||||
case Target.String():
|
||||
return Target
|
||||
default:
|
||||
return Unknown
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package resource
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_Resource(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
typeString string
|
||||
want Type
|
||||
}{
|
||||
{
|
||||
typeString: "unknown",
|
||||
want: Unknown,
|
||||
},
|
||||
{
|
||||
typeString: "scope",
|
||||
want: Scope,
|
||||
},
|
||||
{
|
||||
typeString: "user",
|
||||
want: User,
|
||||
},
|
||||
{
|
||||
typeString: "static-group",
|
||||
want: StaticGroup,
|
||||
},
|
||||
{
|
||||
typeString: "role",
|
||||
want: Role,
|
||||
},
|
||||
{
|
||||
typeString: "organization",
|
||||
want: Organization,
|
||||
},
|
||||
{
|
||||
typeString: "static-group-member",
|
||||
want: StaticGroupMember,
|
||||
},
|
||||
{
|
||||
typeString: "static-group-user-member",
|
||||
want: StaticGroupUserMember,
|
||||
},
|
||||
{
|
||||
typeString: "assigned-role",
|
||||
want: AssignedRole,
|
||||
},
|
||||
{
|
||||
typeString: "assigned-user-role",
|
||||
want: AssignedUserRole,
|
||||
},
|
||||
{
|
||||
typeString: "assigned-static-group-role",
|
||||
want: AssignedStaticGroupRole,
|
||||
},
|
||||
{
|
||||
typeString: "role-grant",
|
||||
want: RoleGrant,
|
||||
},
|
||||
{
|
||||
typeString: "auth-method",
|
||||
want: AuthMethod,
|
||||
},
|
||||
{
|
||||
typeString: "project",
|
||||
want: Project,
|
||||
},
|
||||
{
|
||||
typeString: "*",
|
||||
want: All,
|
||||
},
|
||||
{
|
||||
typeString: "host-catalog",
|
||||
want: HostCatalog,
|
||||
},
|
||||
{
|
||||
typeString: "host-set",
|
||||
want: HostSet,
|
||||
},
|
||||
{
|
||||
typeString: "host",
|
||||
want: Host,
|
||||
},
|
||||
{
|
||||
typeString: "target",
|
||||
want: Target,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.typeString, func(t *testing.T) {
|
||||
assert.Equalf(t, tt.want, StringToResourceType(tt.typeString), "unexpected type for %s", tt.typeString)
|
||||
assert.Equalf(t, tt.typeString, tt.want.String(), "unexpected string for %s", tt.typeString)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package scope
|
||||
|
||||
// Type defines the possible types for Scopes
|
||||
type Type uint32
|
||||
|
||||
const (
|
||||
Unknown Type = 0
|
||||
Organization Type = 1
|
||||
Project Type = 2
|
||||
)
|
||||
|
||||
func (s Type) String() string {
|
||||
return [...]string{
|
||||
"unknown",
|
||||
"organization",
|
||||
"project",
|
||||
}[s]
|
||||
}
|
||||
|
||||
func (s Type) Prefix() string {
|
||||
return [...]string{
|
||||
"unknown",
|
||||
"o",
|
||||
"p",
|
||||
}[s]
|
||||
}
|
||||
|
||||
func StringToScopeType(s string) Type {
|
||||
switch s {
|
||||
case Organization.String():
|
||||
return Organization
|
||||
case Project.String():
|
||||
return Project
|
||||
default:
|
||||
return Unknown
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package scope
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_StringToScopeType(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
s string
|
||||
want Type
|
||||
wantPrefix string
|
||||
}{
|
||||
{
|
||||
name: "org",
|
||||
s: "organization",
|
||||
want: Organization,
|
||||
wantPrefix: "o",
|
||||
},
|
||||
{
|
||||
name: "proj",
|
||||
s: "project",
|
||||
want: Project,
|
||||
wantPrefix: "p",
|
||||
},
|
||||
{
|
||||
name: "unknown",
|
||||
s: "org",
|
||||
want: Unknown,
|
||||
wantPrefix: "unknown",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
got := StringToScopeType(tt.s)
|
||||
assert.Equal(tt.want, got)
|
||||
assert.Equalf(tt.wantPrefix, got.Prefix(), "unexpected prefix for %s", tt.s)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user