implement schema, store and repository for IAM scopes of organization and project

This commit is contained in:
Jim authored and GitHub committed 2020-05-08 11:36:28 -04:00
1 parent 8824a6f7f9
commit 8a5197e39e
27 files changed
+2007 -38

No files matched your search

+1 -1
View File
@@ -42,9 +42,9 @@ protobuild:
@protoc --proto_path=internal/proto/local --proto_path=internal/proto/third_party --swagger_out=logtostderr=true,disable_default_errors=true,include_package_in_tags=true,fqn_for_swagger_name=true,allow_merge,merge_file_name=controller:internal/gen/. internal/proto/local/controller/api/services/v1/*.proto
@protoc-go-inject-tag -input=./internal/oplog/store/oplog.pb.go
@protoc-go-inject-tag -input=./internal/oplog/oplog_test/oplog_test.pb.go
@protoc-go-inject-tag -input=./internal/iam/store/scope.pb.go
@protoc-go-inject-tag -input=./internal/db/db_test/db_test.pb.go
cleanup:
@rm -R ${TMP_DIR}
+2 -2
View File
@@ -9,7 +9,7 @@ import (
func TestPublicIdDomain(t *testing.T) {
const (
createTable = `
create table test_table (
create table if not exists test_table (
id bigint generated always as identity primary key,
public_id wt_public_id
);
@@ -33,7 +33,7 @@ returning id;
failTests := []string{
" ",
"bar",
"0000000001000000000200000000031",
"00000009",
}
for _, tt := range failTests {
value := tt
+20
View File
@@ -0,0 +1,20 @@
package db
import (
"errors"
"fmt"
"github.com/hashicorp/vault/sdk/helper/base62"
)
// NewPublicId creates a new public id with the prefix
func NewPublicId(prefix string) (string, error) {
if prefix == "" {
return "", errors.New("error no prefix for new public id")
}
publicId, err := base62.Random(10)
if err != nil {
return "", fmt.Errorf("unable to generate public id: %w", err)
}
return fmt.Sprintf("%s_%s", prefix, publicId), nil
}
+51
View File
@@ -0,0 +1,51 @@
package db
import (
"strings"
"testing"
)
func TestNewPublicId(t *testing.T) {
type args struct {
prefix string
}
tests := []struct {
name string
args args
wantErr bool
wantLen int
}{
{
name: "valid",
args: args{
prefix: "id",
},
wantErr: false,
wantLen: 10 + len("id_"),
},
{
name: "bad-prefix",
args: args{
prefix: "",
},
wantErr: true,
wantLen: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := NewPublicId(tt.args.prefix)
if (err != nil) != tt.wantErr {
t.Errorf("NewPublicId() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && !strings.HasPrefix(got, tt.args.prefix+"_") {
t.Errorf("NewPublicId() = %v, wanted it to start with %v", got, tt.args.prefix)
}
if len(got) != tt.wantLen {
t.Errorf("NewPublicId() = %v, with len of %d and wanted len of %v", got, len(got), tt.wantLen)
}
})
}
}
@@ -2,7 +2,7 @@ begin;
create domain wt_public_id as text
check(
length(trim(value)) > 31
length(trim(value)) > 10
);
comment on domain wt_public_id is
'Random ID generated with github.com/hashicorp/vault/sdk/helper/base62';
@@ -0,0 +1,7 @@
BEGIN;
drop table if exists iam_scope CASCADE;
drop trigger if exists iam_scope_insert;
drop function if exists iam_sub_scopes_func;
COMMIT;
@@ -0,0 +1,86 @@
BEGIN;
CREATE TABLE if not exists iam_scope_type_enm (
string text NOT NULL primary key CHECK(string IN ('unknown', 'organization', 'project'))
);
INSERT INTO iam_scope_type_enm (string)
values
('unknown'),
('organization'),
('project');
CREATE TABLE if not exists iam_scope (
public_id wt_public_id primary key,
create_time timestamp with time zone default current_timestamp,
update_time timestamp with time zone default current_timestamp,
name text,
type text NOT NULL REFERENCES iam_scope_type_enm(string) CHECK(
(
type = 'organization'
and parent_id = NULL
)
or (
type = 'project'
and parent_id IS NOT NULL
)
),
description text,
parent_id text REFERENCES iam_scope(public_id) ON DELETE CASCADE ON UPDATE CASCADE
);
create table if not exists iam_scope_organization (
scope_id wt_public_id NOT NULL UNIQUE REFERENCES iam_scope(public_id) ON DELETE CASCADE ON UPDATE CASCADE,
name text UNIQUE,
primary key(scope_id)
);
create table if not exists iam_scope_project (
scope_id wt_public_id NOT NULL REFERENCES iam_scope(public_id) ON DELETE CASCADE ON UPDATE CASCADE,
parent_id wt_public_id NOT NULL REFERENCES iam_scope_organization(scope_id) ON DELETE CASCADE ON UPDATE CASCADE,
name text,
unique(parent_id, name),
primary key(scope_id, parent_id)
);
CREATE
OR REPLACE FUNCTION iam_sub_scopes_func() RETURNS TRIGGER
SET SCHEMA
'public' LANGUAGE plpgsql AS $$ DECLARE parent_type INT;
BEGIN IF new.type = 'organization' THEN
insert into iam_scope_organization (scope_id, name)
values
(new.public_id, new.name);
return NEW;
END IF;
IF new.type = 'project' THEN
insert into iam_scope_project (scope_id, parent_id, name)
values
(new.public_id, new.parent_id, new.name);
return NEW;
END IF;
RAISE EXCEPTION 'unknown scope type';
END;
$$;
CREATE TRIGGER iam_scope_insert
AFTER
insert ON iam_scope FOR EACH ROW EXECUTE PROCEDURE iam_sub_scopes_func();
CREATE
OR REPLACE FUNCTION iam_immutable_scope_type_func() RETURNS TRIGGER
SET SCHEMA
'public' LANGUAGE plpgsql AS $$ DECLARE parent_type INT;
BEGIN IF new.type != old.type THEN
RAISE EXCEPTION 'scope type cannot be updated';
END IF;
return NEW;
END;
$$;
CREATE TRIGGER iam_scope_update
BEFORE
update ON iam_scope FOR EACH ROW EXECUTE PROCEDURE iam_immutable_scope_type_func();
COMMIT;
+22 -12
View File
@@ -5,8 +5,8 @@ import (
"github.com/hashicorp/watchtower/internal/oplog"
)
// getOpts - iterate the inbound Options and return a struct
func getOpts(opt ...Option) options {
// GetOpts - iterate the inbound Options and return a struct
func GetOpts(opt ...Option) Options {
opts := getDefaultOptions()
for _, o := range opt {
o(&opts)
@@ -15,14 +15,16 @@ func getOpts(opt ...Option) options {
}
// Option - how Options are passed as arguments
type Option func(*options)
type Option func(*Options)
// options = how options are represented
type options struct {
// Options = how Options are represented
type Options struct {
withOplog bool
oplogOpts oplogOpts
withDebug bool
withLookup bool
// WithFieldMaskPaths must be accessible from other packages
WithFieldMaskPaths []string
}
type oplogOpts struct {
@@ -30,35 +32,36 @@ type oplogOpts struct {
metadata oplog.Metadata
}
func getDefaultOptions() options {
return options{
func getDefaultOptions() Options {
return Options{
withOplog: false,
oplogOpts: oplogOpts{
wrapper: nil,
metadata: oplog.Metadata{},
},
withDebug: false,
withLookup: false,
withDebug: false,
withLookup: false,
WithFieldMaskPaths: []string{},
}
}
// WithLookup enables a lookup
func WithLookup(enable bool) Option {
return func(o *options) {
return func(o *Options) {
o.withLookup = enable
}
}
// WithDebug enables debug
func WithDebug(enable bool) Option {
return func(o *options) {
return func(o *Options) {
o.withDebug = enable
}
}
// WithOplog provides an option to write an oplog entry
func WithOplog(wrapper wrapping.Wrapper, md oplog.Metadata) Option {
return func(o *options) {
return func(o *Options) {
o.withOplog = true
o.oplogOpts = oplogOpts{
wrapper: wrapper,
@@ -66,3 +69,10 @@ func WithOplog(wrapper wrapping.Wrapper, md oplog.Metadata) Option {
}
}
}
// WithOplog provides an option to provide field mask paths
func WithFieldMaskPaths(paths []string) Option {
return func(o *Options) {
o.WithFieldMaskPaths = paths
}
}
+6 -6
View File
@@ -14,7 +14,7 @@ func Test_getOpts(t *testing.T) {
t.Parallel()
t.Run("WithOplog", func(t *testing.T) {
// test default of false
opts := getOpts()
opts := GetOpts()
testOpts := getDefaultOptions()
testOpts.withOplog = false
assert.True(reflect.DeepEqual(opts, testOpts))
@@ -26,7 +26,7 @@ func Test_getOpts(t *testing.T) {
"project": []string{"central-info-systems", "local-info-systems"},
}
// try setting to true
opts = getOpts(WithOplog(wrapper, md))
opts = GetOpts(WithOplog(wrapper, md))
testOpts = getDefaultOptions()
testOpts.withOplog = true
testOpts.oplogOpts = oplogOpts{
@@ -37,13 +37,13 @@ func Test_getOpts(t *testing.T) {
})
t.Run("WithLookup", func(t *testing.T) {
// test default of true
opts := getOpts()
opts := GetOpts()
testOpts := getDefaultOptions()
testOpts.withLookup = false
assert.True(reflect.DeepEqual(opts, testOpts))
// try setting to false
opts = getOpts(WithLookup(true))
opts = GetOpts(WithLookup(true))
testOpts = getDefaultOptions()
testOpts.withLookup = true
assert.True(reflect.DeepEqual(opts, testOpts))
@@ -51,13 +51,13 @@ func Test_getOpts(t *testing.T) {
})
t.Run("WithDebug", func(t *testing.T) {
// test default of false
opts := getOpts()
opts := GetOpts()
testOpts := getDefaultOptions()
testOpts.withDebug = false
assert.True(reflect.DeepEqual(opts, testOpts))
// try setting to true
opts = getOpts(WithDebug(true))
opts = GetOpts(WithDebug(true))
testOpts = getDefaultOptions()
testOpts.withDebug = true
assert.True(reflect.DeepEqual(opts, testOpts))
+18 -16
View File
@@ -63,6 +63,10 @@ type Writer interface {
DB() (*sql.DB, error)
}
const (
StdRetryCnt = 20
)
// RetryInfo provides information on the retries of a transaction
type RetryInfo struct {
Retries int
@@ -93,7 +97,7 @@ const (
// VetForWriter provides an interface that Create and Update can use to vet the resource before sending it to the db
type VetForWriter interface {
VetForWrite(ctx context.Context, r Reader, opType OpType) error
VetForWrite(ctx context.Context, r Reader, opType OpType, opt ...Option) error
}
// GormReadWriter uses a gorm DB connection for read/write
@@ -118,15 +122,10 @@ func (rw *GormReadWriter) ScanRows(rows *sql.Rows, result interface{}) error {
return rw.Tx.ScanRows(rows, result)
}
// CreateConstraint will create a db constraint if it doesn't already exist
func (w *GormReadWriter) CreateConstraint(tableName string, constraintName string, constraint string) error {
return w.Tx.Exec("create_constraint_if_not_exists(?, ?, ?)", tableName, constraintName, constraint).Error
}
var ErrNotResourceWithId = errors.New("not a resource with an id")
func (rw *GormReadWriter) lookupAfterWrite(ctx context.Context, i interface{}, opt ...Option) error {
opts := getOpts(opt...)
opts := GetOpts(opt...)
withLookup := opts.withLookup
if !withLookup {
@@ -146,7 +145,7 @@ func (rw *GormReadWriter) Create(ctx context.Context, i interface{}, opt ...Opti
if rw.Tx == nil {
return errors.New("create tx is nil")
}
opts := getOpts(opt...)
opts := GetOpts(opt...)
withOplog := opts.withOplog
withDebug := opts.withDebug
if withDebug {
@@ -158,7 +157,7 @@ func (rw *GormReadWriter) Create(ctx context.Context, i interface{}, opt ...Opti
}
if vetter, ok := i.(VetForWriter); ok {
if err := vetter.VetForWrite(ctx, rw, CreateOp); err != nil {
return fmt.Errorf("error on Create %w", err)
return fmt.Errorf("error on create %w", err)
}
}
if err := rw.Tx.Create(i).Error; err != nil {
@@ -181,7 +180,7 @@ func (rw *GormReadWriter) Update(ctx context.Context, i interface{}, fieldMaskPa
if rw.Tx == nil {
return errors.New("update tx is nil")
}
opts := getOpts(opt...)
opts := GetOpts(opt...)
withDebug := opts.withDebug
withOplog := opts.withOplog
if withDebug {
@@ -192,8 +191,8 @@ func (rw *GormReadWriter) Update(ctx context.Context, i interface{}, fieldMaskPa
return errors.New("update interface is nil")
}
if vetter, ok := i.(VetForWriter); ok {
if err := vetter.VetForWrite(ctx, rw, UpdateOp); err != nil {
return fmt.Errorf("error on Create %w", err)
if err := vetter.VetForWrite(ctx, rw, UpdateOp, WithFieldMaskPaths(fieldMaskPaths)); err != nil {
return fmt.Errorf("error on update %w", err)
}
}
if len(fieldMaskPaths) == 0 {
@@ -246,6 +245,9 @@ func (rw *GormReadWriter) Update(ctx context.Context, i interface{}, fieldMaskPa
return err
}
}
// we need to force a lookupAfterWrite so the resource returned is correctly initialized
// from the db
opt = append(opt, WithLookup(true))
if err := rw.lookupAfterWrite(ctx, i, opt...); err != nil {
return fmt.Errorf("lookup error after update: %w", err)
}
@@ -260,7 +262,7 @@ func (rw *GormReadWriter) Delete(ctx context.Context, i interface{}, opt ...Opti
if i == nil {
return errors.New("delete interface is nil")
}
opts := getOpts(opt...)
opts := GetOpts(opt...)
withDebug := opts.withDebug
withOplog := opts.withOplog
if withDebug {
@@ -278,7 +280,7 @@ func (rw *GormReadWriter) Delete(ctx context.Context, i interface{}, opt ...Opti
return nil
}
func (rw *GormReadWriter) addOplog(ctx context.Context, opType OpType, opts options, i interface{}) error {
func (rw *GormReadWriter) addOplog(ctx context.Context, opType OpType, opts Options, i interface{}) error {
oplogArgs := opts.oplogOpts
if oplogArgs.wrapper == nil {
return errors.New("error wrapper is nil for WithWrapper")
@@ -386,7 +388,7 @@ func (rw *GormReadWriter) LookupByName(ctx context.Context, resource ResourceNam
if rw.Tx == nil {
return errors.New("error tx nil for lookup by name")
}
opts := getOpts(opt...)
opts := GetOpts(opt...)
withDebug := opts.withDebug
if withDebug {
rw.Tx.LogMode(true)
@@ -406,7 +408,7 @@ func (rw *GormReadWriter) LookupByPublicId(ctx context.Context, resource Resourc
if rw.Tx == nil {
return errors.New("error tx nil for lookup by public id")
}
opts := getOpts(opt...)
opts := GetOpts(opt...)
withDebug := opts.withDebug
if withDebug {
rw.Tx.LogMode(true)
+53
View File
@@ -0,0 +1,53 @@
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
)
func (a Action) String() string {
return [...]string{
"unknown",
"list",
"create",
"update",
"read",
"delete",
"authen",
"all",
"connect",
}[a]
}
// 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,
}
}
// 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,
}
}
+46
View File
@@ -0,0 +1,46 @@
package iam
import (
"testing"
"github.com/stretchr/testify/assert"
)
// Test_CrudActions provides unit tests for CrudActions()
func Test_CrudActions(t *testing.T) {
t.Parallel()
assert := assert.New(t)
t.Run("valid", func(t *testing.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)
})
t.Run("invalid", func(t *testing.T) {
a := CrudlActions()
action, ok := a["invalid"]
assert.Equal(ok, false)
assert.Equal(action, ActionUnknown)
})
}
// Test_CrudlActions provides unit tests for CrudlActions()
func Test_CrudlActions(t *testing.T) {
t.Parallel()
assert := assert.New(t)
t.Run("valid", func(t *testing.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)
})
t.Run("invalid", func(t *testing.T) {
a := CrudlActions()
action, ok := a["invalid"]
assert.Equal(ok, false)
assert.Equal(action, ActionUnknown)
})
}
+36
View File
@@ -0,0 +1,36 @@
package iam
import (
"reflect"
"testing"
"github.com/stretchr/testify/assert"
)
// Test_GetOpts provides unit tests for GetOpts and all the options
func Test_GetOpts(t *testing.T) {
t.Parallel()
assert := assert.New(t)
t.Run("WithName", func(t *testing.T) {
opts := getOpts(WithName("test"))
testOpts := getDefaultOptions()
testOpts.withName = "test"
assert.True(reflect.DeepEqual(opts, testOpts))
})
t.Run("withScope", func(t *testing.T) {
s, err := NewOrganization()
assert.Nil(err)
assert.True(s.Scope != nil)
opts := getOpts(withScope(s))
testOpts := getDefaultOptions()
testOpts.withScope = s
assert.True(reflect.DeepEqual(opts, testOpts))
})
t.Run("WithDescription", func(t *testing.T) {
opts := getOpts(WithDescription("test desc"))
testOpts := getDefaultOptions()
testOpts.withDescription = "test desc"
assert.True(reflect.DeepEqual(opts, testOpts))
})
}
+58
View File
@@ -0,0 +1,58 @@
package iam
// getOpts - iterate the inbound Options and return a struct
func getOpts(opt ...Option) options {
opts := getDefaultOptions()
for _, o := range opt {
o(&opts)
}
return opts
}
// Option - how Options are passed as arguments
type Option func(*options)
// options = how options are represented
type options struct {
withPublicId string
withName string
withScope *Scope
withDescription string
}
func getDefaultOptions() options {
return options{
withPublicId: "",
withScope: nil,
withDescription: "",
withName: "",
}
}
// WithPublicId provides an optional public id
func WithPublicId(id string) Option {
return func(o *options) {
o.withPublicId = id
}
}
// WithDescription provides an optional description
func WithDescription(desc string) Option {
return func(o *options) {
o.withDescription = desc
}
}
// withScope provides an optional scope and used within the package
func withScope(s *Scope) Option {
return func(o *options) {
o.withScope = s
}
}
// WithName provides an option to search by a friendly name
func WithName(name string) Option {
return func(o *options) {
o.withName = name
}
}
+128
View File
@@ -0,0 +1,128 @@
package iam
import (
"context"
"errors"
"fmt"
"strconv"
wrapping "github.com/hashicorp/go-kms-wrapping"
"github.com/hashicorp/watchtower/internal/db"
"github.com/hashicorp/watchtower/internal/oplog"
)
// Repository is the iam database repository
type Repository struct {
reader db.Reader
writer db.Writer
wrapper wrapping.Wrapper
}
// NewRepository creates a new iam Repository
func NewRepository(r db.Reader, w db.Writer, wrapper wrapping.Wrapper) (*Repository, error) {
if r == nil {
return nil, errors.New("error creating db repository with nil reader")
}
if w == nil {
return nil, errors.New("error creating db repository with nil writer")
}
if wrapper == nil {
return nil, errors.New("error creating db repository with nil wrapper")
}
return &Repository{
reader: r,
writer: w,
wrapper: wrapper,
}, nil
}
// Create will create a new iam resource in the db repository with an oplog entry
func (r *Repository) create(ctx context.Context, resource Resource, opt ...Option) (Resource, error) {
if resource == nil {
return nil, errors.New("error creating resource that is nil")
}
resourceCloner, ok := resource.(ClonableResource)
if !ok {
return nil, errors.New("error resource is not clonable for create")
}
metadata, err := r.stdMetadata(ctx, resource)
if err != nil {
return nil, fmt.Errorf("error getting metadata for create: %w", err)
}
metadata["op-type"] = []string{strconv.Itoa(int(oplog.OpType_OP_TYPE_CREATE))}
var returnedResource Resource
_, err = r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(w db.Writer) error {
returnedResource = resourceCloner.Clone()
return w.Create(
ctx,
returnedResource,
db.WithOplog(r.wrapper, metadata),
)
},
)
return returnedResource, err
}
// Update will update an iam resource in the db repository with an oplog entry
func (r *Repository) update(ctx context.Context, resource Resource, fieldMaskPaths []string, opt ...Option) (Resource, error) {
if resource == nil {
return nil, errors.New("error updating resource that is nil")
}
resourceCloner, ok := resource.(ClonableResource)
if !ok {
return nil, errors.New("error resource is not clonable for update")
}
metadata, err := r.stdMetadata(ctx, resource)
if err != nil {
return nil, fmt.Errorf("error getting metadata for update: %w", err)
}
metadata["op-type"] = []string{strconv.Itoa(int(oplog.OpType_OP_TYPE_UPDATE))}
var returnedResource Resource
_, err = r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(w db.Writer) error {
returnedResource = resourceCloner.Clone()
return w.Update(
ctx,
returnedResource,
fieldMaskPaths,
db.WithOplog(r.wrapper, metadata),
)
},
)
return returnedResource, err
}
func (r *Repository) stdMetadata(ctx context.Context, resource Resource) (oplog.Metadata, error) {
if s, ok := resource.(*Scope); ok {
if s.Type == OrganizationScope.String() {
return oplog.Metadata{
"resource-public-id": []string{resource.GetPublicId()},
"scope-id": []string{s.PublicId},
"scope-type": []string{s.Type},
"resource-type": []string{resource.ResourceType().String()},
}, nil
}
}
scope, err := resource.GetScope(ctx, r.reader)
if err != nil {
return nil, fmt.Errorf("unable to get scope for standard metadata: %w", err)
}
if scope == nil {
return nil, errors.New("scope was nil for standard metadata")
}
return oplog.Metadata{
"resource-public-id": []string{resource.GetPublicId()},
"scope-id": []string{scope.PublicId},
"scope-type": []string{scope.Type},
"resource-type": []string{resource.ResourceType().String()},
}, nil
}
+56
View File
@@ -0,0 +1,56 @@
package iam
import (
"context"
"errors"
"fmt"
)
// CreateScope will create a scope in the repository and return the written scope
func (r *Repository) CreateScope(ctx context.Context, scope *Scope, opt ...Option) (*Scope, error) {
if scope == nil {
return nil, errors.New("error scope is nil for create")
}
resource, err := r.create(ctx, scope)
if err != nil {
return nil, fmt.Errorf("failed to create scope: %w", err)
}
return resource.(*Scope), err
}
// UpdateScope will update a scope in the repository and return the written scope
func (r *Repository) UpdateScope(ctx context.Context, scope *Scope, fieldMaskPaths []string, opt ...Option) (*Scope, error) {
if scope == nil {
return nil, errors.New("error scope is nil for update")
}
resource, err := r.update(ctx, scope, fieldMaskPaths)
if err != nil {
return nil, fmt.Errorf("failed to update scope: %w", err)
}
return resource.(*Scope), err
}
// LookupScope will look up a scope in the respository
func (r *Repository) LookupScope(ctx context.Context, opt ...Option) (*Scope, error) {
opts := getOpts(opt...)
withPublicId := opts.withPublicId
withName := opts.withName
if withPublicId != "" {
scope := allocScope()
scope.PublicId = withPublicId
if err := r.reader.LookupByPublicId(ctx, &scope); err != nil {
return nil, err
}
return &scope, nil
}
if withName != "" {
scope := allocScope()
scope.Name = withName
if err := r.reader.LookupByName(ctx, &scope); err != nil {
return nil, err
}
return &scope, nil
}
return nil, errors.New("you must look up scopes by id or name")
}
+135
View File
@@ -0,0 +1,135 @@
package iam
import (
"context"
"testing"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/watchtower/internal/db"
"github.com/hashicorp/watchtower/internal/oplog"
"github.com/hashicorp/watchtower/internal/oplog/store"
"github.com/stretchr/testify/assert"
)
func Test_Repository_CreateScope(t *testing.T) {
t.Parallel()
cleanup, conn := db.TestSetup(t, "../db/migrations/postgres")
defer cleanup()
assert := assert.New(t)
defer conn.Close()
t.Run("valid-scope", func(t *testing.T) {
rw := &db.GormReadWriter{Tx: conn}
wrapper := db.TestWrapper(t)
repo, err := NewRepository(rw, rw, wrapper)
id, err := uuid.GenerateUUID()
assert.Nil(err)
s, err := NewOrganization(WithName("fname-" + id))
s, err = repo.CreateScope(context.Background(), s)
assert.Nil(err)
assert.True(s != nil)
assert.True(s.GetPublicId() != "")
assert.Equal(s.GetName(), "fname-"+id)
foundScope, err := repo.LookupScope(context.Background(), WithPublicId(s.PublicId))
assert.Nil(err)
assert.Equal(foundScope.GetPublicId(), s.GetPublicId())
foundScope, err = repo.LookupScope(context.Background(), WithName("fname-"+id))
assert.Nil(err)
assert.Equal(foundScope.GetPublicId(), s.GetPublicId())
var metadata store.Metadata
err = conn.Where("key = ? and value = ?", "resource-public-id", s.PublicId).First(&metadata).Error
assert.Nil(err)
var foundEntry oplog.Entry
err = conn.Where("id = ?", metadata.EntryId).First(&foundEntry).Error
assert.Nil(err)
})
}
func Test_Repository_UpdateScope(t *testing.T) {
t.Parallel()
cleanup, conn := db.TestSetup(t, "../db/migrations/postgres")
defer cleanup()
assert := assert.New(t)
defer conn.Close()
t.Run("valid-scope", func(t *testing.T) {
rw := &db.GormReadWriter{Tx: conn}
wrapper := db.TestWrapper(t)
repo, err := NewRepository(rw, rw, wrapper)
id, err := uuid.GenerateUUID()
assert.Nil(err)
s, err := NewOrganization(WithName("fname-" + id))
s, err = repo.CreateScope(context.Background(), s)
assert.Nil(err)
assert.True(s != nil)
assert.True(s.GetPublicId() != "")
assert.Equal(s.GetName(), "fname-"+id)
foundScope, err := repo.LookupScope(context.Background(), WithPublicId(s.PublicId))
assert.Nil(err)
assert.Equal(foundScope.GetPublicId(), s.GetPublicId())
foundScope, err = repo.LookupScope(context.Background(), WithName("fname-"+id))
assert.Nil(err)
assert.Equal(foundScope.GetPublicId(), s.GetPublicId())
var metadata store.Metadata
err = conn.Where("key = ? and value = ?", "resource-public-id", s.PublicId).First(&metadata).Error
assert.Nil(err)
var foundEntry oplog.Entry
err = conn.Where("id = ?", metadata.EntryId).First(&foundEntry).Error
assert.Nil(err)
s.Name = "fname-" + id
s.Description = "desc-id" // not in the field mask paths
s, err = repo.UpdateScope(context.Background(), s, []string{"Name"})
assert.Nil(err)
assert.True(s != nil)
assert.Equal(s.GetName(), "fname-"+id)
assert.Equal(foundScope.GetDescription(), "") // should be "" after update in db
foundScope, err = repo.LookupScope(context.Background(), WithName("fname-"+id))
assert.Nil(err)
assert.Equal(foundScope.GetPublicId(), s.GetPublicId())
assert.Equal(foundScope.GetDescription(), "")
err = conn.Where("key = ? and value = ?", "resource-public-id", s.PublicId).First(&metadata).Error
assert.Nil(err)
err = conn.Where("id = ?", metadata.EntryId).First(&foundEntry).Error
assert.Nil(err)
})
t.Run("bad-parent-scope", func(t *testing.T) {
rw := &db.GormReadWriter{Tx: conn}
wrapper := db.TestWrapper(t)
repo, err := NewRepository(rw, rw, wrapper)
id, err := uuid.GenerateUUID()
assert.Nil(err)
s, err := NewOrganization(WithName("fname-" + id))
assert.Nil(err)
s, err = repo.CreateScope(context.Background(), s)
assert.Nil(err)
assert.True(s != nil)
assert.True(s.GetPublicId() != "")
assert.Equal(s.GetName(), "fname-"+id)
project, err := NewProject(s.PublicId)
assert.Nil(err)
project, err = repo.CreateScope(context.Background(), project)
assert.Nil(err)
assert.True(project != nil)
project.ParentId = project.PublicId
project, err = repo.UpdateScope(context.Background(), project, []string{"ParentId"})
assert.True(err != nil)
assert.Equal("failed to update scope: error on update you cannot change a scope's parent", err.Error())
})
}
+198
View File
@@ -0,0 +1,198 @@
package iam
import (
"context"
"reflect"
"testing"
wrapping "github.com/hashicorp/go-kms-wrapping"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/watchtower/internal/db"
"github.com/hashicorp/watchtower/internal/oplog"
"github.com/hashicorp/watchtower/internal/oplog/store"
"github.com/stretchr/testify/assert"
)
func TestNewRepository(t *testing.T) {
t.Parallel()
cleanup, conn := db.TestSetup(t, "../db/migrations/postgres")
defer cleanup()
assert := assert.New(t)
defer conn.Close()
rw := &db.GormReadWriter{Tx: conn}
wrapper := db.TestWrapper(t)
type args struct {
r db.Reader
w db.Writer
wrapper wrapping.Wrapper
}
tests := []struct {
name string
args args
want *Repository
wantErr bool
wantErrString string
}{
{
name: "valid",
args: args{
r: rw,
w: rw,
wrapper: wrapper,
},
want: &Repository{
reader: rw,
writer: rw,
wrapper: wrapper,
},
wantErr: false,
},
{
name: "nil-wrapper",
args: args{
r: rw,
w: rw,
wrapper: nil,
},
want: nil,
wantErr: true,
wantErrString: "error creating db repository with nil wrapper",
},
{
name: "nil-writer",
args: args{
r: rw,
w: nil,
wrapper: wrapper,
},
want: nil,
wantErr: true,
wantErrString: "error creating db repository with nil writer",
},
{
name: "nil-reader",
args: args{
r: nil,
w: rw,
wrapper: wrapper,
},
want: nil,
wantErr: true,
wantErrString: "error creating db repository with nil reader",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := NewRepository(tt.args.r, tt.args.w, tt.args.wrapper)
if (err != nil) != tt.wantErr {
t.Errorf("NewRepository() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewRepository() = %v, want %v", got, tt.want)
}
if err != nil {
assert.Equal(err.Error(), tt.wantErrString)
}
})
}
}
func Test_Repository_create(t *testing.T) {
t.Parallel()
cleanup, conn := db.TestSetup(t, "../db/migrations/postgres")
defer cleanup()
assert := assert.New(t)
defer conn.Close()
t.Run("valid-scope", func(t *testing.T) {
rw := &db.GormReadWriter{Tx: conn}
wrapper := db.TestWrapper(t)
repo, err := NewRepository(rw, rw, wrapper)
id, err := uuid.GenerateUUID()
assert.Nil(err)
s, err := NewOrganization(WithName("fname-" + id))
retScope, err := repo.create(context.Background(), s)
assert.Nil(err)
assert.True(retScope != nil)
assert.True(retScope.GetPublicId() != "")
assert.Equal(retScope.GetName(), "fname-"+id)
foundScope, err := repo.LookupScope(context.Background(), WithPublicId(s.PublicId))
assert.Nil(err)
assert.Equal(foundScope.GetPublicId(), retScope.GetPublicId())
foundScope.Name = "fname-" + id
foundScope, err = repo.LookupScope(context.Background(), WithName("fname-"+id))
assert.Nil(err)
assert.Equal(foundScope.GetPublicId(), retScope.GetPublicId())
var metadata store.Metadata
err = conn.Where("key = ? and value = ?", "resource-public-id", s.PublicId).First(&metadata).Error
assert.Nil(err)
var foundEntry oplog.Entry
err = conn.Where("id = ?", metadata.EntryId).First(&foundEntry).Error
assert.Nil(err)
})
t.Run("nil-resource", func(t *testing.T) {
rw := &db.GormReadWriter{Tx: conn}
wrapper := db.TestWrapper(t)
repo, err := NewRepository(rw, rw, wrapper)
resource, err := repo.create(context.Background(), nil)
assert.True(err != nil)
assert.True(resource == nil)
assert.Equal(err.Error(), "error creating resource that is nil")
})
}
func Test_dbRepository_update(t *testing.T) {
t.Parallel()
cleanup, conn := db.TestSetup(t, "../db/migrations/postgres")
defer cleanup()
assert := assert.New(t)
defer conn.Close()
t.Run("valid-scope", func(t *testing.T) {
rw := &db.GormReadWriter{Tx: conn}
wrapper := db.TestWrapper(t)
repo, err := NewRepository(rw, rw, wrapper)
id, err := uuid.GenerateUUID()
assert.Nil(err)
s, err := NewOrganization()
retScope, err := repo.create(context.Background(), s)
assert.Nil(err)
assert.True(retScope != nil)
assert.True(retScope.GetPublicId() != "")
assert.Equal(retScope.GetName(), "")
retScope.(*Scope).Name = "fname-" + id
retScope, err = repo.update(context.Background(), retScope, []string{"Name"})
assert.Nil(err)
assert.True(retScope != nil)
assert.Equal(retScope.GetName(), "fname-"+id)
foundScope, err := repo.LookupScope(context.Background(), WithName("fname-"+id))
assert.Nil(err)
assert.Equal(foundScope.GetPublicId(), retScope.GetPublicId())
var metadata store.Metadata
err = conn.Where("key = ? and value = ?", "resource-public-id", s.PublicId).First(&metadata).Error
assert.Nil(err)
var foundEntry oplog.Entry
err = conn.Where("id = ?", metadata.EntryId).First(&foundEntry).Error
assert.Nil(err)
})
t.Run("nil-resource", func(t *testing.T) {
rw := &db.GormReadWriter{Tx: conn}
wrapper := db.TestWrapper(t)
repo, err := NewRepository(rw, rw, wrapper)
resource, err := repo.update(context.Background(), nil, nil)
assert.True(err != nil)
assert.True(resource == nil)
assert.Equal(err.Error(), "error updating resource that is nil")
})
}
+100
View File
@@ -0,0 +1,100 @@
package iam
import (
"context"
"errors"
"fmt"
"github.com/hashicorp/watchtower/internal/db"
)
// Resource declares the shared behavior of IAM Resources
type Resource interface {
// GetPublicId is the resource ID used to access the resource via an API
GetPublicId() string
// GetName is the optional friendly name used to
// access the resource via an API
GetName() string
// GetDescription is the optional description of the resource
GetDescription() string
// GetScope is the Scope that owns the Resource
GetScope(ctx context.Context, r db.Reader) (*Scope, error)
// Type of Resource (Target, Policy, User, Group, etc)
ResourceType() ResourceType
// 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]
}
type ClonableResource interface {
Clone() Resource
}
// ResourceWithScope defines an interface for Resources that have a scope
type ResourceWithScope interface {
GetPublicId() string
GetScopeId() string
}
// LookupScope looks up the resource's scope
func LookupScope(ctx context.Context, reader db.Reader, resource ResourceWithScope) (*Scope, error) {
if reader == nil {
return nil, errors.New("error reader is nil for LookupScope")
}
if resource == nil {
return nil, errors.New("error resource is nil for LookupScope")
}
if resource.GetScopeId() == "" {
return nil, errors.New("error scope is unset for LookupScope")
}
var p Scope
if err := reader.LookupWhere(ctx, &p, "public_id = ?", resource.GetScopeId()); err != nil {
return nil, fmt.Errorf("error getting scope for LookupScope: %w", err)
}
return &p, nil
}
+208
View File
@@ -0,0 +1,208 @@
package iam
import (
"context"
"errors"
"fmt"
"github.com/hashicorp/watchtower/internal/db"
"github.com/hashicorp/watchtower/internal/iam/store"
"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]
}
// 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 {
*store.Scope
// tableName which is used to support overriding the table name in the db
// and making the Scope a ReplayableMessage
tableName string `gorm:"-"`
}
// ensure that Scope implements the interfaces of: Resource, ClonableResource, and db.VetForWriter
var _ Resource = (*Scope)(nil)
var _ db.VetForWriter = (*Scope)(nil)
var _ ClonableResource = (*Scope)(nil)
func NewOrganization(opt ...Option) (*Scope, error) {
return newScope(OrganizationScope, opt...)
}
func NewProject(organizationPublicId string, opt ...Option) (*Scope, error) {
org := allocScope()
org.PublicId = organizationPublicId
opt = append(opt, withScope(&org))
p, err := newScope(ProjectScope, opt...)
if err != nil {
return nil, fmt.Errorf("error creating new project: %w", err)
}
return p, nil
}
// newScope creates a new Scope of the specified ScopeType with options:
// WithName specifies the Scope's friendly name.
// WithScope specifies the Scope's parent
func newScope(scopeType ScopeType, opt ...Option) (*Scope, error) {
opts := getOpts(opt...)
withName := opts.withName
withScope := opts.withScope
withDescription := opts.withDescription
if scopeType == UnknownScope {
return nil, errors.New("error unknown scope type for new scope")
}
if scopeType == ProjectScope {
if withScope == nil {
return nil, errors.New("error project scope must be with a scope")
}
if withScope.PublicId == "" {
return nil, errors.New("error project scope parent id is unset")
}
}
prefix := "p"
if scopeType == OrganizationScope {
prefix = "o"
}
publicId, err := db.NewPublicId(prefix)
if err != nil {
return nil, fmt.Errorf("error generating public id %w for new scope", err)
}
s := &Scope{
Scope: &store.Scope{
PublicId: publicId,
Type: scopeType.String(),
},
}
if withScope != nil {
if withScope.PublicId == "" {
return nil, errors.New("error assigning scope parent id to a scope with unset id")
}
s.ParentId = withScope.PublicId
}
if withName != "" {
s.Name = withName
}
if withDescription != "" {
s.Description = withDescription
}
return s, nil
}
func allocScope() Scope {
return Scope{
Scope: &store.Scope{},
}
}
// Clone creates a clone of the Scope
func (s *Scope) Clone() Resource {
cp := proto.Clone(s.Scope)
return &Scope{
Scope: cp.(*store.Scope),
}
}
// VetForWrite implements db.VetForWrite() interface for scopes
// 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() {
return errors.New("unknown scope type for scope write")
}
if s.PublicId == "" {
return errors.New("public id is empty string for scope write")
}
if opType == db.UpdateOp {
dbOptions := db.GetOpts(opt...)
for _, path := range dbOptions.WithFieldMaskPaths {
switch path {
case "ParentId":
return errors.New("you cannot change a scope's parent")
case "Type":
return errors.New("you cannot change a scope's type")
}
}
}
if opType == db.CreateOp {
if s.ParentId == "" && s.Type == ProjectScope.String() {
return errors.New("project has no organization")
}
if s.Type == ProjectScope.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() {
return errors.New("project parent scope is not an organization")
}
}
}
return nil
}
// ResourceType returns the type of scope
func (s *Scope) ResourceType() ResourceType {
switch s.Type {
case OrganizationScope.String():
return ResourceTypeOrganization
case ProjectScope.String():
return ResourceTypeProject
default:
return ResourceTypeScope
}
}
// Actions returns the available actions for Scopes
func (*Scope) Actions() map[string]Action {
return CrudActions()
}
// GetScope returns the scope for the "scope" if there is one defined
func (s *Scope) GetScope(ctx context.Context, r db.Reader) (*Scope, error) {
if r == nil {
return nil, errors.New("error db is nil for get scope")
}
if s.ParentId == "" {
return nil, nil
}
var p Scope
if err := r.LookupWhere(ctx, &p, "public_id = ?", s.ParentId); err != nil {
return nil, fmt.Errorf("error getting scope %w", err)
}
return &p, nil
}
// TableName returns the tablename to override the default gorm table name
func (s *Scope) TableName() string {
if s.tableName != "" {
return s.tableName
}
return "iam_scope"
}
// SetTableName sets the tablename and satisfies the ReplayableMessage interface
func (s *Scope) SetTableName(n string) {
if n != "" {
s.tableName = n
}
}
+215
View File
@@ -0,0 +1,215 @@
package iam
import (
"context"
"testing"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/watchtower/internal/db"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/proto"
)
func Test_NewScope(t *testing.T) {
t.Parallel()
cleanup, conn := db.TestSetup(t, "../db/migrations/postgres")
defer cleanup()
assert := assert.New(t)
defer conn.Close()
t.Run("valid-org-with-project", func(t *testing.T) {
w := db.GormReadWriter{Tx: conn}
s, err := NewOrganization()
assert.Nil(err)
assert.True(s.Scope != nil)
err = w.Create(context.Background(), s)
assert.Nil(err)
assert.True(s.PublicId != "")
id, err := uuid.GenerateUUID()
assert.Nil(err)
projScope, err := NewProject(s.PublicId, WithDescription(id))
assert.Nil(err)
assert.True(projScope.Scope != nil)
assert.Equal(projScope.GetParentId(), s.PublicId)
assert.Equal(projScope.GetDescription(), id)
})
t.Run("unknown-scope", func(t *testing.T) {
s, err := newScope(UnknownScope)
assert.True(err != nil)
assert.True(s == nil)
assert.Equal(err.Error(), "error unknown scope type for new scope")
})
t.Run("proj-scope-with-no-org", func(t *testing.T) {
s, err := NewProject("")
assert.True(err != nil)
assert.True(s == nil)
assert.Equal(err.Error(), "error creating new project: error project scope parent id is unset")
})
}
func Test_ScopeCreate(t *testing.T) {
t.Parallel()
cleanup, conn := db.TestSetup(t, "../db/migrations/postgres")
defer cleanup()
assert := assert.New(t)
defer conn.Close()
t.Run("valid", func(t *testing.T) {
w := db.GormReadWriter{Tx: conn}
s, err := NewOrganization()
assert.Nil(err)
assert.True(s.Scope != nil)
err = w.Create(context.Background(), s)
assert.Nil(err)
assert.True(s.PublicId != "")
})
t.Run("valid-with-parent", func(t *testing.T) {
w := db.GormReadWriter{Tx: conn}
s, err := NewOrganization()
assert.Nil(err)
assert.True(s.Scope != nil)
err = w.Create(context.Background(), s)
assert.Nil(err)
assert.True(s.PublicId != "")
id, err := uuid.GenerateUUID()
assert.Nil(err)
project, err := NewProject(s.PublicId, WithDescription(id))
assert.Nil(err)
assert.True(project.Scope != nil)
assert.Equal(project.Scope.ParentId, s.PublicId)
assert.Equal(project.GetDescription(), id)
err = w.Create(context.Background(), project)
assert.Nil(err)
assert.Equal(project.ParentId, s.PublicId)
})
}
func Test_ScopeUpdate(t *testing.T) {
t.Parallel()
cleanup, conn := db.TestSetup(t, "../db/migrations/postgres")
defer cleanup()
assert := assert.New(t)
defer conn.Close()
t.Run("valid", func(t *testing.T) {
w := db.GormReadWriter{Tx: conn}
s, err := NewOrganization()
assert.Nil(err)
assert.True(s.Scope != nil)
err = w.Create(context.Background(), s)
assert.Nil(err)
assert.True(s.PublicId != "")
id, err := uuid.GenerateUUID()
assert.Nil(err)
s.Name = id
err = w.Update(context.Background(), s, []string{"Name"})
assert.Nil(err)
})
t.Run("type-update-not-allowed", func(t *testing.T) {
w := db.GormReadWriter{Tx: conn}
s, err := NewOrganization()
assert.Nil(err)
assert.True(s.Scope != nil)
err = w.Create(context.Background(), s)
assert.Nil(err)
assert.True(s.PublicId != "")
s.Type = ProjectScope.String()
err = w.Update(context.Background(), s, []string{"Type"})
assert.True(err != nil)
})
}
func Test_ScopeGetScope(t *testing.T) {
t.Parallel()
cleanup, conn := db.TestSetup(t, "../db/migrations/postgres")
defer cleanup()
assert := assert.New(t)
defer conn.Close()
t.Run("valid-scope", func(t *testing.T) {
w := db.GormReadWriter{Tx: conn}
s, err := NewOrganization()
assert.Nil(err)
assert.True(s.Scope != nil)
err = w.Create(context.Background(), s)
assert.Nil(err)
assert.True(s.PublicId != "")
project, err := NewProject(s.PublicId)
assert.Nil(err)
assert.True(project.Scope != nil)
assert.Equal(project.ParentId, s.PublicId)
err = w.Create(context.Background(), project)
assert.Nil(err)
projectOrg, err := project.GetScope(context.Background(), &w)
assert.Nil(err)
assert.True(projectOrg != nil)
assert.Equal(projectOrg.PublicId, project.ParentId)
})
}
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)
if _, ok := a[ActionList.String()]; ok {
t.Errorf("scopes should not include %s as an action", ActionList.String())
}
}
func TestScope_ResourceType(t *testing.T) {
assert := assert.New(t)
o, err := NewOrganization()
assert.Nil(err)
ty := o.ResourceType()
assert.Equal(ty, ResourceTypeOrganization)
}
func TestScope_Clone(t *testing.T) {
t.Parallel()
cleanup, conn := db.TestSetup(t, "../db/migrations/postgres")
defer cleanup()
assert := assert.New(t)
defer conn.Close()
t.Run("valid", func(t *testing.T) {
w := db.GormReadWriter{Tx: conn}
s, err := NewOrganization()
assert.Nil(err)
assert.True(s.Scope != nil)
err = w.Create(context.Background(), s)
assert.Nil(err)
assert.True(s.PublicId != "")
cp := s.Clone()
assert.True(proto.Equal(cp.(*Scope).Scope, s.Scope))
})
t.Run("not-equal", func(t *testing.T) {
w := db.GormReadWriter{Tx: conn}
s, err := NewOrganization()
assert.Nil(err)
assert.True(s.Scope != nil)
err = w.Create(context.Background(), s)
assert.Nil(err)
assert.True(s.PublicId != "")
s2, err := NewOrganization()
assert.Nil(err)
assert.True(s2.Scope != nil)
err = w.Create(context.Background(), s2)
assert.Nil(err)
assert.True(s2.PublicId != "")
cp := s.Clone()
assert.True(!proto.Equal(cp.(*Scope).Scope, s2.Scope))
})
}
+73
View File
@@ -0,0 +1,73 @@
package store
import (
"reflect"
"testing"
"time"
"github.com/golang/protobuf/ptypes/timestamp"
"github.com/stretchr/testify/assert"
)
func Test_TimestampValue(t *testing.T) {
t.Parallel()
assert := assert.New(t)
t.Run("valid", func(t *testing.T) {
ts := Timestamp{Timestamp: &timestamp.Timestamp{Seconds: 0, Nanos: 0}}
v, err := ts.Value()
assert.Nil(err)
assert.Equal(v, utcDate(1970, 1, 1))
})
t.Run("valid nil ts", func(t *testing.T) {
var ts *Timestamp
v, err := ts.Value()
assert.Nil(err)
assert.Equal(v, nil)
})
t.Run("invalid ts", func(t *testing.T) {
ts := Timestamp{Timestamp: &timestamp.Timestamp{Seconds: maxValidSeconds, Nanos: 0}}
v, err := ts.Value()
assert.True(err != nil)
assert.Equal(err.Error(), "timestamp: seconds:253402300800 after 10000-01-01")
assert.Equal(v, utcDate(10000, 1, 1))
})
}
func Test_TimestampScan(t *testing.T) {
t.Parallel()
assert := assert.New(t)
t.Run("valid", func(t *testing.T) {
v := time.Unix(0, 0)
ts := Timestamp{}
err := ts.Scan(v)
assert.Nil(err)
assert.True(reflect.DeepEqual(ts.Timestamp, &timestamp.Timestamp{Seconds: 0, Nanos: 0}))
})
t.Run("valid default time", func(t *testing.T) {
var v time.Time
ts := Timestamp{}
err := ts.Scan(v)
assert.Nil(err)
assert.True(reflect.DeepEqual(ts.Timestamp, &timestamp.Timestamp{Seconds: -62135596800, Nanos: 0}))
})
t.Run("invalid type", func(t *testing.T) {
v := 1
ts := Timestamp{}
err := ts.Scan(v)
assert.True(err != nil)
assert.Equal(err.Error(), "Not a protobuf Timestamp")
})
t.Run("invalid time", func(t *testing.T) {
v := time.Unix(maxValidSeconds, 0)
ts := Timestamp{}
err := ts.Scan(v)
assert.True(err != nil)
assert.Equal(err.Error(), "error converting the timestamp: timestamp: seconds:253402300800 after 10000-01-01")
})
}
const maxValidSeconds = 253402300800
func utcDate(year, month, day int) time.Time {
return time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC)
}
+33
View File
@@ -0,0 +1,33 @@
package store
import (
"database/sql/driver"
"errors"
"fmt"
"time"
"github.com/golang/protobuf/ptypes"
)
// Scan supports Timestamps for oplogs
func (ts *Timestamp) Scan(value interface{}) error {
switch t := value.(type) {
case time.Time:
var err error
ts.Timestamp, err = ptypes.TimestampProto(t) // google proto version
if err != nil {
return fmt.Errorf("error converting the timestamp: %w", err)
}
default:
return errors.New("Not a protobuf Timestamp")
}
return nil
}
// Value supports Timestamps for oplogs
func (ts *Timestamp) Value() (driver.Value, error) {
if ts == nil {
return nil, nil
}
return ptypes.Timestamp(ts.Timestamp)
}
+238
View File
@@ -0,0 +1,238 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.21.0
// protoc v3.11.4
// source: controller/storage/iam/store/v1/scope.proto
package store
import (
proto "github.com/golang/protobuf/proto"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// This is a compile-time assertion that a sufficiently up-to-date version
// of the legacy proto package is being used.
const _ = proto.ProtoPackageIsVersion4
type Scope struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// public_id is the used to access the Scope via an API
// @inject_tag: gorm:"primary_key"
PublicId string `protobuf:"bytes,1,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"`
// create_time from the RDBMS
// @inject_tag: `gorm:"default:current_timestamp"`
CreateTime *Timestamp `protobuf:"bytes,2,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"`
// update_time from the RDBMS
// @inject_tag: `gorm:"default:current_timestamp"`
UpdateTime *Timestamp `protobuf:"bytes,3,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty" gorm:"default:current_timestamp"`
// name is the optional friendly name used to
// access the Scope via an API
// @inject_tag: `gorm:"default:null"`
Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty" gorm:"default:null"`
// Type of scope
Type string `protobuf:"bytes,5,opt,name=type,proto3" json:"type,omitempty"`
// parent_id is the id (primary key in the Scope aggregate) of scope's parent
// @inject_tag: `gorm:"default:0"`
ParentId string `protobuf:"bytes,6,opt,name=parent_id,json=parentId,proto3" json:"parent_id,omitempty" gorm:"default:0"`
// description of scope
// @inject_tag: `gorm:"default:null"`
Description string `protobuf:"bytes,7,opt,name=description,proto3" json:"description,omitempty" gorm:"default:null"`
}
func (x *Scope) Reset() {
*x = Scope{}
if protoimpl.UnsafeEnabled {
mi := &file_controller_storage_iam_store_v1_scope_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Scope) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Scope) ProtoMessage() {}
func (x *Scope) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_iam_store_v1_scope_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Scope.ProtoReflect.Descriptor instead.
func (*Scope) Descriptor() ([]byte, []int) {
return file_controller_storage_iam_store_v1_scope_proto_rawDescGZIP(), []int{0}
}
func (x *Scope) GetPublicId() string {
if x != nil {
return x.PublicId
}
return ""
}
func (x *Scope) GetCreateTime() *Timestamp {
if x != nil {
return x.CreateTime
}
return nil
}
func (x *Scope) GetUpdateTime() *Timestamp {
if x != nil {
return x.UpdateTime
}
return nil
}
func (x *Scope) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *Scope) GetType() string {
if x != nil {
return x.Type
}
return ""
}
func (x *Scope) GetParentId() string {
if x != nil {
return x.ParentId
}
return ""
}
func (x *Scope) GetDescription() string {
if x != nil {
return x.Description
}
return ""
}
var File_controller_storage_iam_store_v1_scope_proto protoreflect.FileDescriptor
var file_controller_storage_iam_store_v1_scope_proto_rawDesc = []byte{
0x0a, 0x2b, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f,
0x72, 0x61, 0x67, 0x65, 0x2f, 0x69, 0x61, 0x6d, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x76,
0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1f, 0x63,
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67,
0x65, 0x2e, 0x69, 0x61, 0x6d, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x1a, 0x2f,
0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61,
0x67, 0x65, 0x2f, 0x69, 0x61, 0x6d, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x31, 0x2f,
0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22,
0xa5, 0x02, 0x0a, 0x05, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62,
0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75,
0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65,
0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f,
0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65,
0x2e, 0x69, 0x61, 0x6d, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69,
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54,
0x69, 0x6d, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69,
0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72,
0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x69, 0x61,
0x6d, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73,
0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65,
0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01,
0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x72, 0x65,
0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x72,
0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70,
0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63,
0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x3a, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75,
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f,
0x77, 0x61, 0x74, 0x63, 0x68, 0x74, 0x6f, 0x77, 0x65, 0x72, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72,
0x6e, 0x61, 0x6c, 0x2f, 0x69, 0x61, 0x6d, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74,
0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_controller_storage_iam_store_v1_scope_proto_rawDescOnce sync.Once
file_controller_storage_iam_store_v1_scope_proto_rawDescData = file_controller_storage_iam_store_v1_scope_proto_rawDesc
)
func file_controller_storage_iam_store_v1_scope_proto_rawDescGZIP() []byte {
file_controller_storage_iam_store_v1_scope_proto_rawDescOnce.Do(func() {
file_controller_storage_iam_store_v1_scope_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_storage_iam_store_v1_scope_proto_rawDescData)
})
return file_controller_storage_iam_store_v1_scope_proto_rawDescData
}
var file_controller_storage_iam_store_v1_scope_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_controller_storage_iam_store_v1_scope_proto_goTypes = []interface{}{
(*Scope)(nil), // 0: controller.storage.iam.store.v1.Scope
(*Timestamp)(nil), // 1: controller.storage.iam.store.v1.Timestamp
}
var file_controller_storage_iam_store_v1_scope_proto_depIdxs = []int32{
1, // 0: controller.storage.iam.store.v1.Scope.create_time:type_name -> controller.storage.iam.store.v1.Timestamp
1, // 1: controller.storage.iam.store.v1.Scope.update_time:type_name -> controller.storage.iam.store.v1.Timestamp
2, // [2:2] is the sub-list for method output_type
2, // [2:2] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_controller_storage_iam_store_v1_scope_proto_init() }
func file_controller_storage_iam_store_v1_scope_proto_init() {
if File_controller_storage_iam_store_v1_scope_proto != nil {
return
}
file_controller_storage_iam_store_v1_timestamp_proto_init()
if !protoimpl.UnsafeEnabled {
file_controller_storage_iam_store_v1_scope_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Scope); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_controller_storage_iam_store_v1_scope_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_controller_storage_iam_store_v1_scope_proto_goTypes,
DependencyIndexes: file_controller_storage_iam_store_v1_scope_proto_depIdxs,
MessageInfos: file_controller_storage_iam_store_v1_scope_proto_msgTypes,
}.Build()
File_controller_storage_iam_store_v1_scope_proto = out.File
file_controller_storage_iam_store_v1_scope_proto_rawDesc = nil
file_controller_storage_iam_store_v1_scope_proto_goTypes = nil
file_controller_storage_iam_store_v1_scope_proto_depIdxs = nil
}
+165
View File
@@ -0,0 +1,165 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.21.0
// protoc v3.11.4
// source: controller/storage/iam/store/v1/timestamp.proto
package store
import (
proto "github.com/golang/protobuf/proto"
timestamp "github.com/golang/protobuf/ptypes/timestamp"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// This is a compile-time assertion that a sufficiently up-to-date version
// of the legacy proto package is being used.
const _ = proto.ProtoPackageIsVersion4
// Timestamp for iam messages. We've defined a new local type wrapper
// of google.protobuf.Timestamp so we can implement sql.Scanner and sql.Valuer
// interfaces. See:
// https://golang.org/pkg/database/sql/#Scanner
// https://golang.org/pkg/database/sql/driver/#Valuer
type Timestamp struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Timestamp *timestamp.Timestamp `protobuf:"bytes,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
}
func (x *Timestamp) Reset() {
*x = Timestamp{}
if protoimpl.UnsafeEnabled {
mi := &file_controller_storage_iam_store_v1_timestamp_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Timestamp) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Timestamp) ProtoMessage() {}
func (x *Timestamp) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_iam_store_v1_timestamp_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Timestamp.ProtoReflect.Descriptor instead.
func (*Timestamp) Descriptor() ([]byte, []int) {
return file_controller_storage_iam_store_v1_timestamp_proto_rawDescGZIP(), []int{0}
}
func (x *Timestamp) GetTimestamp() *timestamp.Timestamp {
if x != nil {
return x.Timestamp
}
return nil
}
var File_controller_storage_iam_store_v1_timestamp_proto protoreflect.FileDescriptor
var file_controller_storage_iam_store_v1_timestamp_proto_rawDesc = []byte{
0x0a, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f,
0x72, 0x61, 0x67, 0x65, 0x2f, 0x69, 0x61, 0x6d, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x76,
0x31, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x12, 0x1f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74,
0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x69, 0x61, 0x6d, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e,
0x76, 0x31, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x22, 0x45, 0x0a, 0x09, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52,
0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x3a, 0x5a, 0x38, 0x67, 0x69,
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f,
0x72, 0x70, 0x2f, 0x77, 0x61, 0x74, 0x63, 0x68, 0x74, 0x6f, 0x77, 0x65, 0x72, 0x2f, 0x69, 0x6e,
0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x69, 0x61, 0x6d, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65,
0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_controller_storage_iam_store_v1_timestamp_proto_rawDescOnce sync.Once
file_controller_storage_iam_store_v1_timestamp_proto_rawDescData = file_controller_storage_iam_store_v1_timestamp_proto_rawDesc
)
func file_controller_storage_iam_store_v1_timestamp_proto_rawDescGZIP() []byte {
file_controller_storage_iam_store_v1_timestamp_proto_rawDescOnce.Do(func() {
file_controller_storage_iam_store_v1_timestamp_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_storage_iam_store_v1_timestamp_proto_rawDescData)
})
return file_controller_storage_iam_store_v1_timestamp_proto_rawDescData
}
var file_controller_storage_iam_store_v1_timestamp_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_controller_storage_iam_store_v1_timestamp_proto_goTypes = []interface{}{
(*Timestamp)(nil), // 0: controller.storage.iam.store.v1.Timestamp
(*timestamp.Timestamp)(nil), // 1: google.protobuf.Timestamp
}
var file_controller_storage_iam_store_v1_timestamp_proto_depIdxs = []int32{
1, // 0: controller.storage.iam.store.v1.Timestamp.timestamp:type_name -> google.protobuf.Timestamp
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_controller_storage_iam_store_v1_timestamp_proto_init() }
func file_controller_storage_iam_store_v1_timestamp_proto_init() {
if File_controller_storage_iam_store_v1_timestamp_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_controller_storage_iam_store_v1_timestamp_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Timestamp); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_controller_storage_iam_store_v1_timestamp_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_controller_storage_iam_store_v1_timestamp_proto_goTypes,
DependencyIndexes: file_controller_storage_iam_store_v1_timestamp_proto_depIdxs,
MessageInfos: file_controller_storage_iam_store_v1_timestamp_proto_msgTypes,
}.Build()
File_controller_storage_iam_store_v1_timestamp_proto = out.File
file_controller_storage_iam_store_v1_timestamp_proto_rawDesc = nil
file_controller_storage_iam_store_v1_timestamp_proto_goTypes = nil
file_controller_storage_iam_store_v1_timestamp_proto_depIdxs = nil
}
@@ -0,0 +1,36 @@
syntax = "proto3";
package controller.storage.iam.store.v1;
option go_package = "github.com/hashicorp/watchtower/internal/iam/store;store";
import "controller/storage/iam/store/v1/timestamp.proto";
message Scope {
// public_id is the used to access the Scope via an API
// @inject_tag: gorm:"primary_key"
string public_id = 1;
// create_time from the RDBMS
// @inject_tag: `gorm:"default:current_timestamp"`
Timestamp create_time = 2;
// update_time from the RDBMS
// @inject_tag: `gorm:"default:current_timestamp"`
Timestamp update_time = 3;
// name is the optional friendly name used to
// access the Scope via an API
// @inject_tag: `gorm:"default:null"`
string name = 4;
// Type of scope
string type = 5;
// parent_id is the id (primary key in the Scope aggregate) of scope's parent
// @inject_tag: `gorm:"default:0"`
string parent_id = 6;
// description of scope
// @inject_tag: `gorm:"default:null"`
string description = 7;
}
@@ -0,0 +1,15 @@
syntax = "proto3";
package controller.storage.iam.store.v1;
option go_package = "github.com/hashicorp/watchtower/internal/iam/store;store";
import "google/protobuf/timestamp.proto";
// Timestamp for iam messages. We've defined a new local type wrapper
// of google.protobuf.Timestamp so we can implement sql.Scanner and sql.Valuer
// interfaces. See:
// https://golang.org/pkg/database/sql/#Scanner
// https://golang.org/pkg/database/sql/driver/#Valuer
message Timestamp {
google.protobuf.Timestamp timestamp = 1;
}