diff --git a/internal/iam/action.go b/internal/iam/action.go index d467ffc92..a45635b7d 100644 --- a/internal/iam/action.go +++ b/internal/iam/action.go @@ -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, } } diff --git a/internal/iam/action_test.go b/internal/iam/action_test.go index 81e7e34e3..14876d031 100644 --- a/internal/iam/action_test.go +++ b/internal/iam/action_test.go @@ -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) }) } diff --git a/internal/iam/group.go b/internal/iam/group.go index e3f071aa8..5d298dda1 100644 --- a/internal/iam/group.go +++ b/internal/iam/group.go @@ -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() } diff --git a/internal/iam/group_test.go b/internal/iam/group_test.go index 63b59b7bc..ef3d5e370 100644 --- a/internal/iam/group_test.go +++ b/internal/iam/group_test.go @@ -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) { diff --git a/internal/iam/public_ids.go b/internal/iam/public_ids.go index 44997cef1..edcf5970c 100644 --- a/internal/iam/public_ids.go +++ b/internal/iam/public_ids.go @@ -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()) diff --git a/internal/iam/public_ids_test.go b/internal/iam/public_ids_test.go index 9ee6a0825..aab826c47 100644 --- a/internal/iam/public_ids_test.go +++ b/internal/iam/public_ids_test.go @@ -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)) diff --git a/internal/iam/repository.go b/internal/iam/repository.go index f416fb1b9..8827c4ea5 100644 --- a/internal/iam/repository.go +++ b/internal/iam/repository.go @@ -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: diff --git a/internal/iam/repository_scope.go b/internal/iam/repository_scope.go index 647c86cdf..5bd92229f 100644 --- a/internal/iam/repository_scope.go +++ b/internal/iam/repository_scope.go @@ -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) } diff --git a/internal/iam/repository_scope_test.go b/internal/iam/repository_scope_test.go index 51d6fced2..11fd43759 100644 --- a/internal/iam/repository_scope_test.go +++ b/internal/iam/repository_scope_test.go @@ -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, diff --git a/internal/iam/repository_test.go b/internal/iam/repository_test.go index 984f42fd2..5e7c8aa52 100644 --- a/internal/iam/repository_test.go +++ b/internal/iam/repository_test.go @@ -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) diff --git a/internal/iam/resource.go b/internal/iam/resource.go index 564dce7a7..ac910f581 100644 --- a/internal/iam/resource.go +++ b/internal/iam/resource.go @@ -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 diff --git a/internal/iam/role.go b/internal/iam/role.go index 937bd7bfe..8932cab11 100644 --- a/internal/iam/role.go +++ b/internal/iam/role.go @@ -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() } diff --git a/internal/iam/role_test.go b/internal/iam/role_test.go index 2b797b594..caf688a32 100644 --- a/internal/iam/role_test.go +++ b/internal/iam/role_test.go @@ -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) { diff --git a/internal/iam/scope.go b/internal/iam/scope.go index 43f57c196..c9f47a858 100644 --- a/internal/iam/scope.go +++ b/internal/iam/scope.go @@ -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 "": diff --git a/internal/iam/scope_test.go b/internal/iam/scope_test.go index 9b77b6ef2..b02028fbb 100644 --- a/internal/iam/scope_test.go +++ b/internal/iam/scope_test.go @@ -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) - }) - } -} diff --git a/internal/iam/user.go b/internal/iam/user.go index 6661a10cb..27cb248df 100644 --- a/internal/iam/user.go +++ b/internal/iam/user.go @@ -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() } diff --git a/internal/iam/user_test.go b/internal/iam/user_test.go index 051f3bc46..884a31149 100644 --- a/internal/iam/user_test.go +++ b/internal/iam/user_test.go @@ -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()) } diff --git a/internal/perms/acl.go b/internal/perms/acl.go index cc8b5a8ee..cc8732dc3 100644 --- a/internal/perms/acl.go +++ b/internal/perms/acl.go @@ -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=;actions= case grant.id == "" && - grant.typ == resource.Type: + grant.typ == r.Type: results.Allowed = true return // id=;actions= - 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=;type=;actions= - case grant.id == resource.Pin && - grant.typ == resource.Type: + case grant.id == r.Pin && + grant.typ == r.Type: results.Allowed = true return } diff --git a/internal/perms/acl_test.go b/internal/perms/acl_test.go index 0ba39533b..d0f7766ee 100644 --- a/internal/perms/acl_test.go +++ b/internal/perms/acl_test.go @@ -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", }, diff --git a/internal/perms/grants.go b/internal/perms/grants.go index f34739d1f..76b44eeb8 100644 --- a/internal/perms/grants.go +++ b/internal/perms/grants.go @@ -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 diff --git a/internal/perms/grants_test.go b/internal/perms/grants_test.go index 671d5116a..5b0a5f33a 100644 --- a/internal/perms/grants_test.go +++ b/internal/perms/grants_test.go @@ -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) diff --git a/internal/servers/controller/handlers/groups/group_service.go b/internal/servers/controller/handlers/groups/group_service.go index f03bf9404..c87fae0de 100644 --- a/internal/servers/controller/handlers/groups/group_service.go +++ b/internal/servers/controller/handlers/groups/group_service.go @@ -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{} diff --git a/internal/servers/controller/handlers/groups/group_service_test.go b/internal/servers/controller/handlers/groups/group_service_test.go index 4393b0386..4893ad1d3 100644 --- a/internal/servers/controller/handlers/groups/group_service_test.go +++ b/internal/servers/controller/handlers/groups/group_service_test.go @@ -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, }, diff --git a/internal/servers/controller/handlers/host_catalogs/host_catalog_service.go b/internal/servers/controller/handlers/host_catalogs/host_catalog_service.go index a66f60d9a..41ffb9cdd 100644 --- a/internal/servers/controller/handlers/host_catalogs/host_catalog_service.go +++ b/internal/servers/controller/handlers/host_catalogs/host_catalog_service.go @@ -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 diff --git a/internal/servers/controller/handlers/organizations/organization_service.go b/internal/servers/controller/handlers/organizations/organization_service.go index 246ff4ddb..4c79fa386 100644 --- a/internal/servers/controller/handlers/organizations/organization_service.go +++ b/internal/servers/controller/handlers/organizations/organization_service.go @@ -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 { diff --git a/internal/servers/controller/handlers/organizations/organization_service_test.go b/internal/servers/controller/handlers/organizations/organization_service_test.go index 9c9b7e43d..63884d3dc 100644 --- a/internal/servers/controller/handlers/organizations/organization_service_test.go +++ b/internal/servers/controller/handlers/organizations/organization_service_test.go @@ -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, }, diff --git a/internal/servers/controller/handlers/projects/project_service.go b/internal/servers/controller/handlers/projects/project_service.go index 3aad8fe41..abbcdcdd5 100644 --- a/internal/servers/controller/handlers/projects/project_service.go +++ b/internal/servers/controller/handlers/projects/project_service.go @@ -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{} diff --git a/internal/servers/controller/handlers/projects/project_service_test.go b/internal/servers/controller/handlers/projects/project_service_test.go index d7826eb04..07a01da39 100644 --- a/internal/servers/controller/handlers/projects/project_service_test.go +++ b/internal/servers/controller/handlers/projects/project_service_test.go @@ -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, }, diff --git a/internal/servers/controller/handlers/roles/roles_service.go b/internal/servers/controller/handlers/roles/roles_service.go index 2d2bb4c83..b461d3b7a 100644 --- a/internal/servers/controller/handlers/roles/roles_service.go +++ b/internal/servers/controller/handlers/roles/roles_service.go @@ -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{} diff --git a/internal/servers/controller/handlers/roles/roles_service_test.go b/internal/servers/controller/handlers/roles/roles_service_test.go index 7124fbccd..209e327cd 100644 --- a/internal/servers/controller/handlers/roles/roles_service_test.go +++ b/internal/servers/controller/handlers/roles/roles_service_test.go @@ -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, }, diff --git a/internal/servers/controller/handlers/users/user_service.go b/internal/servers/controller/handlers/users/user_service.go index 8ab42d744..78795f78e 100644 --- a/internal/servers/controller/handlers/users/user_service.go +++ b/internal/servers/controller/handlers/users/user_service.go @@ -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{} diff --git a/internal/types/action/action.go b/internal/types/action/action.go new file mode 100644 index 000000000..900245185 --- /dev/null +++ b/internal/types/action/action.go @@ -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] +} diff --git a/internal/types/action/action_test.go b/internal/types/action/action_test.go new file mode 100644 index 000000000..e0b276866 --- /dev/null +++ b/internal/types/action/action_test.go @@ -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) + }) + } +} diff --git a/internal/types/resource/resource.go b/internal/types/resource/resource.go new file mode 100644 index 000000000..60d091888 --- /dev/null +++ b/internal/types/resource/resource.go @@ -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 + } +} diff --git a/internal/types/resource/resource_test.go b/internal/types/resource/resource_test.go new file mode 100644 index 000000000..f2787f89c --- /dev/null +++ b/internal/types/resource/resource_test.go @@ -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) + }) + } +} diff --git a/internal/types/scope/scope.go b/internal/types/scope/scope.go new file mode 100644 index 000000000..312b7e7ba --- /dev/null +++ b/internal/types/scope/scope.go @@ -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 + } +} diff --git a/internal/types/scope/scope_test.go b/internal/types/scope/scope_test.go new file mode 100644 index 000000000..eea4cf4e8 --- /dev/null +++ b/internal/types/scope/scope_test.go @@ -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) + }) + } +}