iam and auth integration (#144)
This commit is contained in:
31 files changed
+2494
-268
No files matched your search
@@ -60,6 +60,7 @@ protobuild:
|
||||
@protoc-go-inject-tag -input=./internal/iam/store/group.pb.go
|
||||
@protoc-go-inject-tag -input=./internal/db/db_test/db_test.pb.go
|
||||
@protoc-go-inject-tag -input=./internal/host/static/store/static.pb.go
|
||||
@protoc-go-inject-tag -input=./internal/iam/store/auth_account.pb.go
|
||||
@rm -R ${TMP_DIR}
|
||||
|
||||
protolint:
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.24.0
|
||||
// protoc v3.12.3
|
||||
// source: controller/storage/auth/store/v1/auth_method.proto
|
||||
|
||||
package store
|
||||
|
||||
import (
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
_ "github.com/hashicorp/watchtower/internal/db/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
|
||||
|
||||
type AuthMethod struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// @inject_tag: gorm:"primary_key"
|
||||
AuthAccountId string `protobuf:"bytes,1,opt,name=auth_account_id,json=authAccountId,proto3" json:"auth_account_id,omitempty"`
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
IamScopeId string `protobuf:"bytes,2,opt,name=iam_scope_id,json=iamScopeId,proto3" json:"iam_scope_id,omitempty"`
|
||||
}
|
||||
|
||||
func (x *AuthMethod) Reset() {
|
||||
*x = AuthMethod{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_controller_storage_auth_store_v1_auth_method_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *AuthMethod) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*AuthMethod) ProtoMessage() {}
|
||||
|
||||
func (x *AuthMethod) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_controller_storage_auth_store_v1_auth_method_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 AuthMethod.ProtoReflect.Descriptor instead.
|
||||
func (*AuthMethod) Descriptor() ([]byte, []int) {
|
||||
return file_controller_storage_auth_store_v1_auth_method_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *AuthMethod) GetAuthAccountId() string {
|
||||
if x != nil {
|
||||
return x.AuthAccountId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *AuthMethod) GetIamScopeId() string {
|
||||
if x != nil {
|
||||
return x.IamScopeId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type AuthAccount struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// @inject_tag: gorm:"primary_key"
|
||||
AuthAccountId string `protobuf:"bytes,1,opt,name=auth_account_id,json=authAccountId,proto3" json:"auth_account_id,omitempty"`
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
AuthMethodId string `protobuf:"bytes,2,opt,name=auth_method_id,json=authMethodId,proto3" json:"auth_method_id,omitempty"`
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
IamScopeId string `protobuf:"bytes,3,opt,name=iam_scope_id,json=iamScopeId,proto3" json:"iam_scope_id,omitempty"`
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
IamUserId string `protobuf:"bytes,4,opt,name=iam_user_id,json=iamUserId,proto3" json:"iam_user_id,omitempty"`
|
||||
}
|
||||
|
||||
func (x *AuthAccount) Reset() {
|
||||
*x = AuthAccount{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_controller_storage_auth_store_v1_auth_method_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *AuthAccount) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*AuthAccount) ProtoMessage() {}
|
||||
|
||||
func (x *AuthAccount) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_controller_storage_auth_store_v1_auth_method_proto_msgTypes[1]
|
||||
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 AuthAccount.ProtoReflect.Descriptor instead.
|
||||
func (*AuthAccount) Descriptor() ([]byte, []int) {
|
||||
return file_controller_storage_auth_store_v1_auth_method_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *AuthAccount) GetAuthAccountId() string {
|
||||
if x != nil {
|
||||
return x.AuthAccountId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *AuthAccount) GetAuthMethodId() string {
|
||||
if x != nil {
|
||||
return x.AuthMethodId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *AuthAccount) GetIamScopeId() string {
|
||||
if x != nil {
|
||||
return x.IamScopeId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *AuthAccount) GetIamUserId() string {
|
||||
if x != nil {
|
||||
return x.IamUserId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_controller_storage_auth_store_v1_auth_method_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_controller_storage_auth_store_v1_auth_method_proto_rawDesc = []byte{
|
||||
0x0a, 0x32, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f,
|
||||
0x72, 0x61, 0x67, 0x65, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f,
|
||||
0x76, 0x31, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x12, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72,
|
||||
0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x61, 0x75, 0x74, 0x68, 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, 0x74, 0x69, 0x6d, 0x65, 0x73,
|
||||
0x74, 0x61, 0x6d, 0x70, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
|
||||
0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x56, 0x0a, 0x0a, 0x41, 0x75, 0x74, 0x68, 0x4d,
|
||||
0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x61, 0x63,
|
||||
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d,
|
||||
0x61, 0x75, 0x74, 0x68, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x20, 0x0a,
|
||||
0x0c, 0x69, 0x61, 0x6d, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x61, 0x6d, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x64, 0x22,
|
||||
0x9d, 0x01, 0x0a, 0x0b, 0x41, 0x75, 0x74, 0x68, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12,
|
||||
0x26, 0x0a, 0x0f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f,
|
||||
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x75, 0x74, 0x68, 0x41, 0x63,
|
||||
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x5f,
|
||||
0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x0c, 0x61, 0x75, 0x74, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x20, 0x0a,
|
||||
0x0c, 0x69, 0x61, 0x6d, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x61, 0x6d, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x64, 0x12,
|
||||
0x1e, 0x0a, 0x0b, 0x69, 0x61, 0x6d, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x69, 0x61, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x42,
|
||||
0x3b, 0x5a, 0x39, 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, 0x61, 0x75, 0x74, 0x68,
|
||||
0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_controller_storage_auth_store_v1_auth_method_proto_rawDescOnce sync.Once
|
||||
file_controller_storage_auth_store_v1_auth_method_proto_rawDescData = file_controller_storage_auth_store_v1_auth_method_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_controller_storage_auth_store_v1_auth_method_proto_rawDescGZIP() []byte {
|
||||
file_controller_storage_auth_store_v1_auth_method_proto_rawDescOnce.Do(func() {
|
||||
file_controller_storage_auth_store_v1_auth_method_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_storage_auth_store_v1_auth_method_proto_rawDescData)
|
||||
})
|
||||
return file_controller_storage_auth_store_v1_auth_method_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_controller_storage_auth_store_v1_auth_method_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_controller_storage_auth_store_v1_auth_method_proto_goTypes = []interface{}{
|
||||
(*AuthMethod)(nil), // 0: controller.storage.auth.store.v1.AuthMethod
|
||||
(*AuthAccount)(nil), // 1: controller.storage.auth.store.v1.AuthAccount
|
||||
}
|
||||
var file_controller_storage_auth_store_v1_auth_method_proto_depIdxs = []int32{
|
||||
0, // [0:0] is the sub-list for method output_type
|
||||
0, // [0:0] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_controller_storage_auth_store_v1_auth_method_proto_init() }
|
||||
func file_controller_storage_auth_store_v1_auth_method_proto_init() {
|
||||
if File_controller_storage_auth_store_v1_auth_method_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_controller_storage_auth_store_v1_auth_method_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*AuthMethod); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_controller_storage_auth_store_v1_auth_method_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*AuthAccount); 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_auth_store_v1_auth_method_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_controller_storage_auth_store_v1_auth_method_proto_goTypes,
|
||||
DependencyIndexes: file_controller_storage_auth_store_v1_auth_method_proto_depIdxs,
|
||||
MessageInfos: file_controller_storage_auth_store_v1_auth_method_proto_msgTypes,
|
||||
}.Build()
|
||||
File_controller_storage_auth_store_v1_auth_method_proto = out.File
|
||||
file_controller_storage_auth_store_v1_auth_method_proto_rawDesc = nil
|
||||
file_controller_storage_auth_store_v1_auth_method_proto_goTypes = nil
|
||||
file_controller_storage_auth_store_v1_auth_method_proto_depIdxs = nil
|
||||
}
|
||||
@@ -30,7 +30,7 @@ func AllocTestUser() TestUser {
|
||||
}
|
||||
|
||||
// Clone is useful when you're retrying transactions and you need to send the user several times
|
||||
func (u *TestUser) Clone() *TestUser {
|
||||
func (u *TestUser) Clone() interface{} {
|
||||
s := proto.Clone(u.StoreTestUser)
|
||||
return &TestUser{
|
||||
StoreTestUser: s.(*StoreTestUser),
|
||||
@@ -107,3 +107,49 @@ func (r *TestRental) SetTableName(name string) {
|
||||
r.table = name
|
||||
}
|
||||
}
|
||||
|
||||
type TestScooter struct {
|
||||
*StoreTestScooter
|
||||
table string `gorm:"-"`
|
||||
}
|
||||
|
||||
func NewTestScooter() (*TestScooter, error) {
|
||||
privateId, err := base62.Random(20)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &TestScooter{
|
||||
StoreTestScooter: &StoreTestScooter{
|
||||
PrivateId: privateId,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *TestScooter) Clone() interface{} {
|
||||
s := proto.Clone(t.StoreTestScooter)
|
||||
return &TestScooter{
|
||||
StoreTestScooter: s.(*StoreTestScooter),
|
||||
}
|
||||
}
|
||||
func (t *TestScooter) TableName() string {
|
||||
if t.table != "" {
|
||||
return t.table
|
||||
}
|
||||
return "db_test_scooter"
|
||||
}
|
||||
|
||||
func (t *TestScooter) SetTableName(name string) {
|
||||
if name != "" {
|
||||
t.table = name
|
||||
}
|
||||
}
|
||||
|
||||
type Cloner interface {
|
||||
Clone() interface{}
|
||||
}
|
||||
|
||||
type NotIder struct{}
|
||||
|
||||
func (i *NotIder) Clone() interface{} {
|
||||
return &NotIder{}
|
||||
}
|
||||
@@ -44,10 +44,10 @@ type StoreTestUser struct {
|
||||
// update_time from the RDBMS
|
||||
// @inject_tag: `gorm:"default:CURRENT_TIMESTAMP"`
|
||||
UpdateTime *timestamp.Timestamp `protobuf:"bytes,3,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty" gorm:"default:CURRENT_TIMESTAMP"`
|
||||
// public_id is the used to access the AssignablScope via an API
|
||||
// public_id is the used to access the user via an API
|
||||
PublicId string `protobuf:"bytes,4,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty"`
|
||||
// name is the optional friendly name used to
|
||||
// access the Scope via an API
|
||||
// access the user via an API
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
Name string `protobuf:"bytes,5,opt,name=name,proto3" json:"name,omitempty" gorm:"default:null"`
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
@@ -160,14 +160,16 @@ type StoreTestCar struct {
|
||||
// update_time from the RDBMS
|
||||
// @inject_tag: `gorm:"default:CURRENT_TIMESTAMP"`
|
||||
UpdateTime *timestamp.Timestamp `protobuf:"bytes,3,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty" gorm:"default:CURRENT_TIMESTAMP"`
|
||||
// public_id is the used to access the AssignablScope via an API
|
||||
// public_id is the used to access the car via an API
|
||||
PublicId string `protobuf:"bytes,4,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty"`
|
||||
// name is the optional friendly name used to
|
||||
// access the Scope via an API
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
Name string `protobuf:"bytes,5,opt,name=name,proto3" json:"name,omitempty" gorm:"default:null"`
|
||||
Model string `protobuf:"bytes,6,opt,name=model,proto3" json:"model,omitempty"`
|
||||
Mpg int32 `protobuf:"varint,7,opt,name=mpg,proto3" json:"mpg,omitempty"`
|
||||
Name string `protobuf:"bytes,5,opt,name=name,proto3" json:"name,omitempty" gorm:"default:null"`
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
Model string `protobuf:"bytes,6,opt,name=model,proto3" json:"model,omitempty" gorm:"default:null"`
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
Mpg int32 `protobuf:"varint,7,opt,name=mpg,proto3" json:"mpg,omitempty" gorm:"default:null"`
|
||||
}
|
||||
|
||||
func (x *StoreTestCar) Reset() {
|
||||
@@ -265,14 +267,16 @@ type StoreTestRental struct {
|
||||
// update_time from the RDBMS
|
||||
// @inject_tag: `gorm:"default:CURRENT_TIMESTAMP"`
|
||||
UpdateTime *timestamp.Timestamp `protobuf:"bytes,3,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty" gorm:"default:CURRENT_TIMESTAMP"`
|
||||
// public_id is the used to access the AssignablScope via an API
|
||||
// public_id is the used to access the rental via an API
|
||||
PublicId string `protobuf:"bytes,4,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty"`
|
||||
// name is the optional friendly name used to
|
||||
// access the Scope via an API
|
||||
// access the rental via an API
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
Name string `protobuf:"bytes,5,opt,name=name,proto3" json:"name,omitempty" gorm:"default:null"`
|
||||
UserId uint32 `protobuf:"varint,6,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
|
||||
CarId uint32 `protobuf:"varint,7,opt,name=car_id,json=carId,proto3" json:"car_id,omitempty"`
|
||||
Name string `protobuf:"bytes,5,opt,name=name,proto3" json:"name,omitempty" gorm:"default:null"`
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
UserId uint32 `protobuf:"varint,6,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty" gorm:"default:null"`
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
CarId uint32 `protobuf:"varint,7,opt,name=car_id,json=carId,proto3" json:"car_id,omitempty" gorm:"default:null"`
|
||||
}
|
||||
|
||||
func (x *StoreTestRental) Reset() {
|
||||
@@ -356,6 +360,104 @@ func (x *StoreTestRental) GetCarId() uint32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// StoreTestScooter used in the db tests only and provides a gorm resource with
|
||||
// a private id.
|
||||
type StoreTestScooter struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// @inject_tag: gorm:"primary_key"
|
||||
Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty" gorm:"primary_key"`
|
||||
// create_time from the RDBMS
|
||||
// @inject_tag: `gorm:"default:CURRENT_TIMESTAMP"`
|
||||
CreateTime *timestamp.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.Timestamp `protobuf:"bytes,3,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty" gorm:"default:CURRENT_TIMESTAMP"`
|
||||
// private_id is the used to access scooter, but not intended to be available
|
||||
// via the API
|
||||
PrivateId string `protobuf:"bytes,4,opt,name=private_id,json=privateId,proto3" json:"private_id,omitempty"`
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
Model string `protobuf:"bytes,6,opt,name=model,proto3" json:"model,omitempty" gorm:"default:null"`
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
Mpg int32 `protobuf:"varint,7,opt,name=mpg,proto3" json:"mpg,omitempty" gorm:"default:null"`
|
||||
}
|
||||
|
||||
func (x *StoreTestScooter) Reset() {
|
||||
*x = StoreTestScooter{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_controller_storage_db_db_test_v1_db_test_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *StoreTestScooter) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*StoreTestScooter) ProtoMessage() {}
|
||||
|
||||
func (x *StoreTestScooter) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_controller_storage_db_db_test_v1_db_test_proto_msgTypes[3]
|
||||
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 StoreTestScooter.ProtoReflect.Descriptor instead.
|
||||
func (*StoreTestScooter) Descriptor() ([]byte, []int) {
|
||||
return file_controller_storage_db_db_test_v1_db_test_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *StoreTestScooter) GetId() uint32 {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *StoreTestScooter) GetCreateTime() *timestamp.Timestamp {
|
||||
if x != nil {
|
||||
return x.CreateTime
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *StoreTestScooter) GetUpdateTime() *timestamp.Timestamp {
|
||||
if x != nil {
|
||||
return x.UpdateTime
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *StoreTestScooter) GetPrivateId() string {
|
||||
if x != nil {
|
||||
return x.PrivateId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *StoreTestScooter) GetModel() string {
|
||||
if x != nil {
|
||||
return x.Model
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *StoreTestScooter) GetMpg() int32 {
|
||||
if x != nil {
|
||||
return x.Mpg
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
var File_controller_storage_db_db_test_v1_db_test_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_controller_storage_db_db_test_v1_db_test_proto_rawDesc = []byte{
|
||||
@@ -422,11 +524,28 @@ var file_controller_storage_db_db_test_v1_db_test_proto_rawDesc = []byte{
|
||||
0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69,
|
||||
0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12,
|
||||
0x15, 0x0a, 0x06, 0x63, 0x61, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52,
|
||||
0x05, 0x63, 0x61, 0x72, 0x49, 0x64, 0x42, 0x3d, 0x5a, 0x3b, 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, 0x64, 0x62, 0x2f, 0x64, 0x62, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x3b, 0x64, 0x62,
|
||||
0x5f, 0x74, 0x65, 0x73, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x05, 0x63, 0x61, 0x72, 0x49, 0x64, 0x22, 0x83, 0x02, 0x0a, 0x10, 0x53, 0x74, 0x6f, 0x72, 0x65,
|
||||
0x54, 0x65, 0x73, 0x74, 0x53, 0x63, 0x6f, 0x6f, 0x74, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69,
|
||||
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 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, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 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, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 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, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65,
|
||||
0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x69, 0x76, 0x61,
|
||||
0x74, 0x65, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x18, 0x06, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x70,
|
||||
0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6d, 0x70, 0x67, 0x42, 0x3d, 0x5a, 0x3b,
|
||||
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, 0x64, 0x62, 0x2f, 0x64, 0x62, 0x5f, 0x74,
|
||||
0x65, 0x73, 0x74, 0x3b, 0x64, 0x62, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -441,25 +560,28 @@ func file_controller_storage_db_db_test_v1_db_test_proto_rawDescGZIP() []byte {
|
||||
return file_controller_storage_db_db_test_v1_db_test_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_controller_storage_db_db_test_v1_db_test_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
||||
var file_controller_storage_db_db_test_v1_db_test_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
|
||||
var file_controller_storage_db_db_test_v1_db_test_proto_goTypes = []interface{}{
|
||||
(*StoreTestUser)(nil), // 0: controller.storage.db.db_test.v1.StoreTestUser
|
||||
(*StoreTestCar)(nil), // 1: controller.storage.db.db_test.v1.StoreTestCar
|
||||
(*StoreTestRental)(nil), // 2: controller.storage.db.db_test.v1.StoreTestRental
|
||||
(*timestamp.Timestamp)(nil), // 3: controller.storage.timestamp.v1.Timestamp
|
||||
(*StoreTestScooter)(nil), // 3: controller.storage.db.db_test.v1.StoreTestScooter
|
||||
(*timestamp.Timestamp)(nil), // 4: controller.storage.timestamp.v1.Timestamp
|
||||
}
|
||||
var file_controller_storage_db_db_test_v1_db_test_proto_depIdxs = []int32{
|
||||
3, // 0: controller.storage.db.db_test.v1.StoreTestUser.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
3, // 1: controller.storage.db.db_test.v1.StoreTestUser.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
3, // 2: controller.storage.db.db_test.v1.StoreTestCar.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
3, // 3: controller.storage.db.db_test.v1.StoreTestCar.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
3, // 4: controller.storage.db.db_test.v1.StoreTestRental.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
3, // 5: controller.storage.db.db_test.v1.StoreTestRental.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
6, // [6:6] is the sub-list for method output_type
|
||||
6, // [6:6] is the sub-list for method input_type
|
||||
6, // [6:6] is the sub-list for extension type_name
|
||||
6, // [6:6] is the sub-list for extension extendee
|
||||
0, // [0:6] is the sub-list for field type_name
|
||||
4, // 0: controller.storage.db.db_test.v1.StoreTestUser.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
4, // 1: controller.storage.db.db_test.v1.StoreTestUser.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
4, // 2: controller.storage.db.db_test.v1.StoreTestCar.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
4, // 3: controller.storage.db.db_test.v1.StoreTestCar.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
4, // 4: controller.storage.db.db_test.v1.StoreTestRental.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
4, // 5: controller.storage.db.db_test.v1.StoreTestRental.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
4, // 6: controller.storage.db.db_test.v1.StoreTestScooter.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
4, // 7: controller.storage.db.db_test.v1.StoreTestScooter.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
8, // [8:8] is the sub-list for method output_type
|
||||
8, // [8:8] is the sub-list for method input_type
|
||||
8, // [8:8] is the sub-list for extension type_name
|
||||
8, // [8:8] is the sub-list for extension extendee
|
||||
0, // [0:8] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_controller_storage_db_db_test_v1_db_test_proto_init() }
|
||||
@@ -504,6 +626,18 @@ func file_controller_storage_db_db_test_v1_db_test_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_controller_storage_db_db_test_v1_db_test_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*StoreTestScooter); 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{
|
||||
@@ -511,7 +645,7 @@ func file_controller_storage_db_db_test_v1_db_test_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_controller_storage_db_db_test_v1_db_test_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 3,
|
||||
NumMessages: 4,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
+10
-3
@@ -1,20 +1,27 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/vault/sdk/helper/base62"
|
||||
)
|
||||
|
||||
func NewPrivateId(prefix string) (string, error) {
|
||||
return newId(prefix)
|
||||
}
|
||||
|
||||
// NewPublicId creates a new public id with the prefix
|
||||
func NewPublicId(prefix string) (string, error) {
|
||||
return newId(prefix)
|
||||
}
|
||||
|
||||
func newId(prefix string) (string, error) {
|
||||
if prefix == "" {
|
||||
return "", errors.New("error no prefix for new public id")
|
||||
return "", fmt.Errorf("missing prefix %w", ErrInvalidParameter)
|
||||
}
|
||||
publicId, err := base62.Random(10)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to generate public id: %w", err)
|
||||
return "", fmt.Errorf("unable to generate id: %w", err)
|
||||
}
|
||||
return fmt.Sprintf("%s_%s", prefix, publicId), nil
|
||||
}
|
||||
@@ -3,6 +3,9 @@ package db
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewPublicId(t *testing.T) {
|
||||
@@ -49,3 +52,45 @@ func TestNewPublicId(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewPrivateId(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) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
got, err := NewPrivateId(tt.args.prefix)
|
||||
if tt.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.True(strings.HasPrefix(got, tt.args.prefix+"_"))
|
||||
assert.Equal(tt.wantLen, len(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -240,7 +240,6 @@ values
|
||||
('default', 1),
|
||||
('iam_scope', 1),
|
||||
('iam_user', 1),
|
||||
('iam_auth_method', 1),
|
||||
('iam_group', 1),
|
||||
('iam_group_member_user', 1),
|
||||
('iam_role', 1),
|
||||
@@ -249,7 +248,11 @@ values
|
||||
('iam_role_user', 1),
|
||||
('db_test_user', 1),
|
||||
('db_test_car', 1),
|
||||
('db_test_rental', 1);
|
||||
('db_test_rental', 1),
|
||||
('db_test_scooter', 1),
|
||||
('auth_account', 1);
|
||||
;
|
||||
|
||||
|
||||
commit;
|
||||
|
||||
@@ -261,9 +264,10 @@ commit;
|
||||
bytes: []byte(`
|
||||
begin;
|
||||
|
||||
drop table db_test_rental;
|
||||
drop table db_test_car;
|
||||
drop table db_test_user;
|
||||
drop table db_test_rental cascade;
|
||||
drop table db_test_car cascade;
|
||||
drop table db_test_user cascade;
|
||||
drop table db_test_scooter cascade;
|
||||
|
||||
commit;
|
||||
|
||||
@@ -274,9 +278,10 @@ commit;
|
||||
bytes: []byte(`
|
||||
begin;
|
||||
|
||||
-- create test tables used in the unit tests for the internal/db package
|
||||
-- these tables (db_test_user, db_test_car, db_test_rental) are not part
|
||||
-- of the watchtower domain model... they are simply used for testing the internal/db package
|
||||
-- create test tables used in the unit tests for the internal/db package
|
||||
-- these tables (db_test_user, db_test_car, db_test_rental, db_test_scooter) are
|
||||
-- not part of the watchtower domain model... they are simply used for testing
|
||||
-- the internal/db package
|
||||
create table if not exists db_test_user (
|
||||
id bigint generated always as identity primary key,
|
||||
create_time wt_timestamp,
|
||||
@@ -368,6 +373,34 @@ insert on db_test_rental
|
||||
for each row execute procedure default_create_time();
|
||||
|
||||
|
||||
create table if not exists db_test_scooter (
|
||||
id bigint generated always as identity primary key,
|
||||
create_time wt_timestamp,
|
||||
update_time wt_timestamp,
|
||||
private_id text not null unique,
|
||||
name text unique,
|
||||
model text,
|
||||
mpg smallint
|
||||
);
|
||||
|
||||
create trigger
|
||||
update_time_column
|
||||
before
|
||||
update on db_test_scooter
|
||||
for each row execute procedure update_time_column();
|
||||
|
||||
create trigger
|
||||
create_time_column
|
||||
before
|
||||
update on db_test_scooter
|
||||
for each row execute procedure immutable_create_time_func();
|
||||
|
||||
create trigger
|
||||
default_create_time_column
|
||||
before
|
||||
insert on db_test_scooter
|
||||
for each row execute procedure default_create_time();
|
||||
|
||||
commit;
|
||||
|
||||
`),
|
||||
@@ -719,6 +752,7 @@ begin;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
|
||||
commit;
|
||||
|
||||
`),
|
||||
|
||||
@@ -89,7 +89,6 @@ values
|
||||
('default', 1),
|
||||
('iam_scope', 1),
|
||||
('iam_user', 1),
|
||||
('iam_auth_method', 1),
|
||||
('iam_group', 1),
|
||||
('iam_group_member_user', 1),
|
||||
('iam_role', 1),
|
||||
@@ -98,7 +97,11 @@ values
|
||||
('iam_role_user', 1),
|
||||
('db_test_user', 1),
|
||||
('db_test_car', 1),
|
||||
('db_test_rental', 1);
|
||||
('db_test_rental', 1),
|
||||
('db_test_scooter', 1),
|
||||
('auth_account', 1);
|
||||
;
|
||||
|
||||
|
||||
commit;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
begin;
|
||||
|
||||
drop table db_test_rental;
|
||||
drop table db_test_car;
|
||||
drop table db_test_user;
|
||||
drop table db_test_rental cascade;
|
||||
drop table db_test_car cascade;
|
||||
drop table db_test_user cascade;
|
||||
drop table db_test_scooter cascade;
|
||||
|
||||
commit;
|
||||
@@ -1,8 +1,9 @@
|
||||
begin;
|
||||
|
||||
-- create test tables used in the unit tests for the internal/db package
|
||||
-- these tables (db_test_user, db_test_car, db_test_rental) are not part
|
||||
-- of the watchtower domain model... they are simply used for testing the internal/db package
|
||||
-- create test tables used in the unit tests for the internal/db package
|
||||
-- these tables (db_test_user, db_test_car, db_test_rental, db_test_scooter) are
|
||||
-- not part of the watchtower domain model... they are simply used for testing
|
||||
-- the internal/db package
|
||||
create table if not exists db_test_user (
|
||||
id bigint generated always as identity primary key,
|
||||
create_time wt_timestamp,
|
||||
@@ -94,4 +95,32 @@ insert on db_test_rental
|
||||
for each row execute procedure default_create_time();
|
||||
|
||||
|
||||
create table if not exists db_test_scooter (
|
||||
id bigint generated always as identity primary key,
|
||||
create_time wt_timestamp,
|
||||
update_time wt_timestamp,
|
||||
private_id text not null unique,
|
||||
name text unique,
|
||||
model text,
|
||||
mpg smallint
|
||||
);
|
||||
|
||||
create trigger
|
||||
update_time_column
|
||||
before
|
||||
update on db_test_scooter
|
||||
for each row execute procedure update_time_column();
|
||||
|
||||
create trigger
|
||||
create_time_column
|
||||
before
|
||||
update on db_test_scooter
|
||||
for each row execute procedure immutable_create_time_func();
|
||||
|
||||
create trigger
|
||||
default_create_time_column
|
||||
before
|
||||
insert on db_test_scooter
|
||||
for each row execute procedure default_create_time();
|
||||
|
||||
commit;
|
||||
@@ -66,4 +66,5 @@ begin;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
|
||||
commit;
|
||||
+13
-1
@@ -28,6 +28,8 @@ type Options struct {
|
||||
WithFieldMaskPaths []string
|
||||
// WithNullPaths must be accessible from other packages.
|
||||
WithNullPaths []string
|
||||
|
||||
newOplogMsg *oplog.Message
|
||||
// WithVersion must be accessible from other packages
|
||||
WithVersion int
|
||||
}
|
||||
@@ -59,7 +61,8 @@ func WithLookup(enable bool) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithOplog provides an option to write an oplog entry.
|
||||
// WithOplog provides an option to write an oplog entry. WithOplog and
|
||||
// NewOplogMsg cannot be used together.
|
||||
func WithOplog(wrapper wrapping.Wrapper, md oplog.Metadata) Option {
|
||||
return func(o *Options) {
|
||||
o.withOplog = true
|
||||
@@ -70,6 +73,15 @@ func WithOplog(wrapper wrapping.Wrapper, md oplog.Metadata) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// NewOplogMsg provides an option to ask for a new in-memory oplog message. The
|
||||
// new msg will be returned in the provided *oplog.Message parameter. WithOplog
|
||||
// and NewOplogMsg cannot be used together.
|
||||
func NewOplogMsg(msg *oplog.Message) Option {
|
||||
return func(o *Options) {
|
||||
o.newOplogMsg = msg
|
||||
}
|
||||
}
|
||||
|
||||
// WithFieldMaskPaths provides an option to provide field mask paths.
|
||||
func WithFieldMaskPaths(paths []string) Option {
|
||||
return func(o *Options) {
|
||||
|
||||
@@ -94,6 +94,21 @@ func Test_getOpts(t *testing.T) {
|
||||
testOpts.WithLimit = 1
|
||||
assert.Equal(opts, testOpts)
|
||||
})
|
||||
t.Run("NewOplogMsg", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
// test default of false
|
||||
opts := GetOpts()
|
||||
testOpts := getDefaultOptions()
|
||||
testOpts.newOplogMsg = nil
|
||||
assert.Equal(opts, testOpts)
|
||||
|
||||
msg := oplog.Message{}
|
||||
// try setting to true
|
||||
opts = GetOpts(NewOplogMsg(&msg))
|
||||
testOpts = getDefaultOptions()
|
||||
testOpts.newOplogMsg = &msg
|
||||
assert.Equal(opts, testOpts)
|
||||
})
|
||||
t.Run("WithVersion", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
// test default of 0
|
||||
|
||||
+210
-36
@@ -9,6 +9,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping"
|
||||
"github.com/hashicorp/watchtower/internal/db/common"
|
||||
"github.com/hashicorp/watchtower/internal/oplog"
|
||||
"github.com/hashicorp/watchtower/internal/oplog/store"
|
||||
@@ -28,7 +29,12 @@ type Reader interface {
|
||||
// LookupByName will lookup resource by its friendly name which must be unique
|
||||
LookupByName(ctx context.Context, resource ResourceNamer, opt ...Option) error
|
||||
|
||||
// LookupByPublicId will lookup resource by its public_id which must be unique
|
||||
// LookupById will lookup a resource by its primary key id, which must be
|
||||
// unique. The resourceWithIder must implement either ResourcePublicIder or
|
||||
// ResourcePrivateIder interface.
|
||||
LookupById(ctx context.Context, resourceWithIder interface{}, opt ...Option) error
|
||||
|
||||
// LookupByPublicId will lookup resource by its public_id which must be unique.
|
||||
LookupByPublicId(ctx context.Context, resource ResourcePublicIder, opt ...Option) error
|
||||
|
||||
// LookupWhere will lookup and return the first resource using a where clause with parameters
|
||||
@@ -79,6 +85,21 @@ type Writer interface {
|
||||
|
||||
// DB returns the sql.DB
|
||||
DB() (*sql.DB, error)
|
||||
|
||||
// GetTicket returns an oplog ticket for the aggregate root of "i" which can
|
||||
// be used to WriteOplogEntryWith for that aggregate root.
|
||||
GetTicket(i interface{}) (*store.Ticket, error)
|
||||
|
||||
// WriteOplogEntryWith will write an oplog entry with the msgs provided for
|
||||
// the ticket's aggregateName. No options are currently supported.
|
||||
WriteOplogEntryWith(
|
||||
ctx context.Context,
|
||||
wrapper wrapping.Wrapper,
|
||||
ticket *store.Ticket,
|
||||
metadata oplog.Metadata,
|
||||
msgs []*oplog.Message,
|
||||
opt ...Option,
|
||||
) error
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -94,11 +115,18 @@ type RetryInfo struct {
|
||||
// TxHandler defines a handler for a func that writes a transaction for use with DoTx
|
||||
type TxHandler func(Reader, Writer) error
|
||||
|
||||
// ResourcePublicIder defines an interface that LookupByPublicId() can use to get the resource's public id
|
||||
// ResourcePublicIder defines an interface that LookupByPublicId() can use to
|
||||
// get the resource's public id.
|
||||
type ResourcePublicIder interface {
|
||||
GetPublicId() string
|
||||
}
|
||||
|
||||
// ResourcePrivateIder defines an interface that LookupById() can use to get the
|
||||
// resource's private id.
|
||||
type ResourcePrivateIder interface {
|
||||
GetPrivateId() string
|
||||
}
|
||||
|
||||
// ResourceNamer defines an interface that LookupByName() can use to get the resource's friendly name
|
||||
type ResourceNamer interface {
|
||||
GetName() string
|
||||
@@ -169,8 +197,10 @@ func (rw *Db) lookupAfterWrite(ctx context.Context, i interface{}, opt ...Option
|
||||
return errors.New("not a resource with an id")
|
||||
}
|
||||
|
||||
// Create an object in the db with options: WithOplog and WithLookup (to force a
|
||||
// lookup after create).
|
||||
// Create an object in the db with options: WithOplog, NewOplogMsg and
|
||||
// WithLookup. WithOplog will write an oplog entry for the create.
|
||||
// NewOplogMsg will return in-memory oplog message. WithOplog and NewOplogMsg
|
||||
// cannot be used together. WithLookup with to force a lookup after create.
|
||||
func (rw *Db) Create(ctx context.Context, i interface{}, opt ...Option) error {
|
||||
if rw.underlying == nil {
|
||||
return fmt.Errorf("create: missing underlying db %w", ErrNilParameter)
|
||||
@@ -180,6 +210,9 @@ func (rw *Db) Create(ctx context.Context, i interface{}, opt ...Option) error {
|
||||
}
|
||||
opts := GetOpts(opt...)
|
||||
withOplog := opts.withOplog
|
||||
if withOplog && opts.newOplogMsg != nil {
|
||||
return fmt.Errorf("create: both WithOplog and NewOplogMsg options have been specified: %w", ErrInvalidParameter)
|
||||
}
|
||||
if withOplog {
|
||||
// let's validate oplog options before we start writing to the database
|
||||
_, err := validateOplogArgs(i, opts)
|
||||
@@ -199,7 +232,7 @@ func (rw *Db) Create(ctx context.Context, i interface{}, opt ...Option) error {
|
||||
var ticket *store.Ticket
|
||||
if withOplog {
|
||||
var err error
|
||||
ticket, err = rw.getTicket(i)
|
||||
ticket, err = rw.GetTicket(i)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create: unable to get ticket: %w", err)
|
||||
}
|
||||
@@ -212,6 +245,13 @@ func (rw *Db) Create(ctx context.Context, i interface{}, opt ...Option) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if opts.newOplogMsg != nil {
|
||||
msg, err := rw.newOplogMessage(ctx, CreateOp, i)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create: returning oplog failed %w", err)
|
||||
}
|
||||
*opts.newOplogMsg = *msg
|
||||
}
|
||||
if err := rw.lookupAfterWrite(ctx, i, opt...); err != nil {
|
||||
return fmt.Errorf("create: lookup error %w", err)
|
||||
}
|
||||
@@ -248,7 +288,7 @@ func (rw *Db) CreateItems(ctx context.Context, createItems []interface{}, opt ..
|
||||
if err != nil {
|
||||
return fmt.Errorf("create items: oplog validation failed: %w", err)
|
||||
}
|
||||
ticket, err = rw.getTicket(createItems[0])
|
||||
ticket, err = rw.GetTicket(createItems[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("create items: unable to get ticket: %w", err)
|
||||
}
|
||||
@@ -273,11 +313,15 @@ func (rw *Db) CreateItems(ctx context.Context, createItems []interface{}, opt ..
|
||||
// is responsible for the transaction life cycle of the writer and if an
|
||||
// error is returned the caller must decide what to do with the transaction,
|
||||
// which almost always should be to rollback. Update returns the number of
|
||||
// rows updated. Supported options: WithOplog and WithVersion. If WithVersion
|
||||
// is used, then the update will include the version number in the update where
|
||||
// clause, which basically makes the update use optimistic locking and the
|
||||
// update will only succeed if the existing rows version matches the WithVersion
|
||||
// option.
|
||||
// rows updated.
|
||||
//
|
||||
// Supported options: WithOplog, NewOplogMsg and WithVersion.
|
||||
// WithOplog will write an oplog entry for the update. NewOplogMsg
|
||||
// will return in-memory oplog message. WithOplog and NewOplogMsg cannot be
|
||||
// used together. If WithVersion is used, then the update will include the
|
||||
// version number in the update where clause, which basically makes the update
|
||||
// use optimistic locking and the update will only succeed if the existing rows
|
||||
// version matches the WithVersion option.
|
||||
func (rw *Db) Update(ctx context.Context, i interface{}, fieldMaskPaths []string, setToNullPaths []string, opt ...Option) (int, error) {
|
||||
if rw.underlying == nil {
|
||||
return NoRowsAffected, fmt.Errorf("update: missing underlying db %w", ErrNilParameter)
|
||||
@@ -288,6 +332,11 @@ func (rw *Db) Update(ctx context.Context, i interface{}, fieldMaskPaths []string
|
||||
if len(fieldMaskPaths) == 0 && len(setToNullPaths) == 0 {
|
||||
return NoRowsAffected, errors.New("update: both fieldMaskPaths and setToNullPaths are missing")
|
||||
}
|
||||
opts := GetOpts(opt...)
|
||||
withOplog := opts.withOplog
|
||||
if withOplog && opts.newOplogMsg != nil {
|
||||
return NoRowsAffected, fmt.Errorf("update: both WithOplog and NewOplogMsg options have been specified: %w", ErrInvalidParameter)
|
||||
}
|
||||
|
||||
// we need to filter out some non-updatable fields (like: CreateTime, etc)
|
||||
fieldMaskPaths = filterPaths(fieldMaskPaths)
|
||||
@@ -317,8 +366,6 @@ func (rw *Db) Update(ctx context.Context, i interface{}, fieldMaskPaths []string
|
||||
}
|
||||
}
|
||||
|
||||
opts := GetOpts(opt...)
|
||||
withOplog := opts.withOplog
|
||||
if withOplog {
|
||||
// let's validate oplog options before we start writing to the database
|
||||
_, err := validateOplogArgs(i, opts)
|
||||
@@ -334,7 +381,7 @@ func (rw *Db) Update(ctx context.Context, i interface{}, fieldMaskPaths []string
|
||||
var ticket *store.Ticket
|
||||
if withOplog {
|
||||
var err error
|
||||
ticket, err = rw.getTicket(i)
|
||||
ticket, err = rw.GetTicket(i)
|
||||
if err != nil {
|
||||
return NoRowsAffected, fmt.Errorf("update: unable to get ticket: %w", err)
|
||||
}
|
||||
@@ -356,7 +403,7 @@ func (rw *Db) Update(ctx context.Context, i interface{}, fieldMaskPaths []string
|
||||
return NoRowsAffected, fmt.Errorf("update: failed %w", underlying.Error)
|
||||
}
|
||||
rowsUpdated := int(underlying.RowsAffected)
|
||||
if withOplog && rowsUpdated > 0 {
|
||||
if rowsUpdated > 0 && (withOplog || opts.newOplogMsg != nil) {
|
||||
// we don't want to change the inbound slices in opts, so we'll make our
|
||||
// own copy to pass to addOplog()
|
||||
oplogFieldMasks := make([]string, len(fieldMaskPaths))
|
||||
@@ -369,8 +416,17 @@ func (rw *Db) Update(ctx context.Context, i interface{}, fieldMaskPaths []string
|
||||
WithFieldMaskPaths: oplogFieldMasks,
|
||||
WithNullPaths: oplogNullPaths,
|
||||
}
|
||||
if err := rw.addOplog(ctx, UpdateOp, oplogOpts, ticket, i); err != nil {
|
||||
return rowsUpdated, fmt.Errorf("update: add oplog failed %w", err)
|
||||
if withOplog {
|
||||
if err := rw.addOplog(ctx, UpdateOp, oplogOpts, ticket, i); err != nil {
|
||||
return rowsUpdated, fmt.Errorf("update: add oplog failed %w", err)
|
||||
}
|
||||
}
|
||||
if opts.newOplogMsg != nil {
|
||||
msg, err := rw.newOplogMessage(ctx, UpdateOp, i, WithFieldMaskPaths(oplogFieldMasks), WithNullPaths(oplogNullPaths))
|
||||
if err != nil {
|
||||
return rowsUpdated, fmt.Errorf("update: returning oplog failed %w", err)
|
||||
}
|
||||
*opts.newOplogMsg = *msg
|
||||
}
|
||||
}
|
||||
// we need to force a lookupAfterWrite so the resource returned is correctly initialized
|
||||
@@ -382,9 +438,10 @@ func (rw *Db) Update(ctx context.Context, i interface{}, fieldMaskPaths []string
|
||||
return rowsUpdated, nil
|
||||
}
|
||||
|
||||
// Delete an object in the db with options: WithOplog (which requires
|
||||
// WithMetadata, WithWrapper). Delete returns the number of rows deleted and
|
||||
// any errors.
|
||||
// Delete an object in the db with options: WithOplog and NewOplogMsg. WithOplog
|
||||
// will write an oplog entry for the delete. NewOplogMsg will return in-memory
|
||||
// oplog message. WithOplog and NewOplogMsg cannot be used together. Delete
|
||||
// returns the number of rows deleted and any errors.
|
||||
func (rw *Db) Delete(ctx context.Context, i interface{}, opt ...Option) (int, error) {
|
||||
if rw.underlying == nil {
|
||||
return NoRowsAffected, fmt.Errorf("delete: missing underlying db %w", ErrNilParameter)
|
||||
@@ -392,14 +449,17 @@ func (rw *Db) Delete(ctx context.Context, i interface{}, opt ...Option) (int, er
|
||||
if isNil(i) {
|
||||
return NoRowsAffected, fmt.Errorf("delete: interface is missing %w", ErrNilParameter)
|
||||
}
|
||||
opts := GetOpts(opt...)
|
||||
withOplog := opts.withOplog
|
||||
if withOplog && opts.newOplogMsg != nil {
|
||||
return NoRowsAffected, fmt.Errorf("delete: both WithOplog and NewOplogMsg options have been specified: %w", ErrInvalidParameter)
|
||||
}
|
||||
// This is not a watchtower scope, but rather a gorm Scope:
|
||||
// https://godoc.org/github.com/jinzhu/gorm#DB.NewScope
|
||||
scope := rw.underlying.NewScope(i)
|
||||
if scope.PrimaryKeyZero() {
|
||||
return NoRowsAffected, fmt.Errorf("delete: primary key is not set")
|
||||
}
|
||||
opts := GetOpts(opt...)
|
||||
withOplog := opts.withOplog
|
||||
if withOplog {
|
||||
_, err := validateOplogArgs(i, opts)
|
||||
if err != nil {
|
||||
@@ -409,7 +469,7 @@ func (rw *Db) Delete(ctx context.Context, i interface{}, opt ...Option) (int, er
|
||||
var ticket *store.Ticket
|
||||
if withOplog {
|
||||
var err error
|
||||
ticket, err = rw.getTicket(i)
|
||||
ticket, err = rw.GetTicket(i)
|
||||
if err != nil {
|
||||
return NoRowsAffected, fmt.Errorf("delete: unable to get ticket: %w", err)
|
||||
}
|
||||
@@ -419,9 +479,18 @@ func (rw *Db) Delete(ctx context.Context, i interface{}, opt ...Option) (int, er
|
||||
return NoRowsAffected, fmt.Errorf("delete: failed %w", underlying.Error)
|
||||
}
|
||||
rowsDeleted := int(underlying.RowsAffected)
|
||||
if withOplog && rowsDeleted > 0 {
|
||||
if err := rw.addOplog(ctx, DeleteOp, opts, ticket, i); err != nil {
|
||||
return rowsDeleted, fmt.Errorf("delete: add oplog failed %w", err)
|
||||
if rowsDeleted > 0 && (withOplog || opts.newOplogMsg != nil) {
|
||||
if withOplog {
|
||||
if err := rw.addOplog(ctx, DeleteOp, opts, ticket, i); err != nil {
|
||||
return rowsDeleted, fmt.Errorf("delete: add oplog failed %w", err)
|
||||
}
|
||||
}
|
||||
if opts.newOplogMsg != nil {
|
||||
msg, err := rw.newOplogMessage(ctx, DeleteOp, i)
|
||||
if err != nil {
|
||||
return rowsDeleted, fmt.Errorf("delete: returning oplog failed %w", err)
|
||||
}
|
||||
*opts.newOplogMsg = *msg
|
||||
}
|
||||
}
|
||||
return rowsDeleted, nil
|
||||
@@ -454,7 +523,7 @@ func (rw *Db) DeleteItems(ctx context.Context, deleteItems []interface{}, opt ..
|
||||
if err != nil {
|
||||
return NoRowsAffected, fmt.Errorf("delete items: oplog validation failed: %w", err)
|
||||
}
|
||||
ticket, err = rw.getTicket(deleteItems[0])
|
||||
ticket, err = rw.GetTicket(deleteItems[0])
|
||||
if err != nil {
|
||||
return NoRowsAffected, fmt.Errorf("delete items: unable to get ticket: %w", err)
|
||||
}
|
||||
@@ -507,10 +576,16 @@ func (rw *Db) getTicketFor(aggregateName string) (*store.Ticket, error) {
|
||||
}
|
||||
return ticket, nil
|
||||
}
|
||||
func (rw *Db) getTicket(i interface{}) (*store.Ticket, error) {
|
||||
|
||||
// GetTicket returns an oplog ticket for the aggregate root of "i" which can
|
||||
// be used to WriteOplogEntryWith for that aggregate root.
|
||||
func (rw *Db) GetTicket(i interface{}) (*store.Ticket, error) {
|
||||
if rw.underlying == nil {
|
||||
return nil, fmt.Errorf("get ticket: underlying db missing: %w", ErrNilParameter)
|
||||
}
|
||||
if isNil(i) {
|
||||
return nil, fmt.Errorf("get ticket: interface is missing %w", ErrNilParameter)
|
||||
}
|
||||
replayable, ok := i.(oplog.ReplayableMessage)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("get ticket: not a replayable message %w", ErrInvalidParameter)
|
||||
@@ -643,6 +718,79 @@ func (rw *Db) addOplog(ctx context.Context, opType OpType, opts Options, ticket
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteOplogEntryWith will write an oplog entry with the msgs provided for
|
||||
// the ticket's aggregateName. No options are currently supported.
|
||||
func (rw *Db) WriteOplogEntryWith(ctx context.Context, wrapper wrapping.Wrapper, ticket *store.Ticket, metadata oplog.Metadata, msgs []*oplog.Message, opt ...Option) error {
|
||||
if wrapper == nil {
|
||||
return fmt.Errorf("write oplog: wrapper is unset %w", ErrNilParameter)
|
||||
}
|
||||
if ticket == nil {
|
||||
return fmt.Errorf("write oplog: ticket is unset %w", ErrNilParameter)
|
||||
}
|
||||
if len(msgs) == 0 {
|
||||
return fmt.Errorf("write oplog: msgs are empty %w", ErrInvalidParameter)
|
||||
}
|
||||
if rw.underlying == nil {
|
||||
return fmt.Errorf("write oplog: underlying is unset %w", ErrNilParameter)
|
||||
}
|
||||
if metadata == nil {
|
||||
return fmt.Errorf("write oplog: metadata is unset %w", ErrNilParameter)
|
||||
}
|
||||
if len(metadata) == 0 {
|
||||
return fmt.Errorf("write oplog: metadata is empty %w", ErrInvalidParameter)
|
||||
}
|
||||
|
||||
ticketer, err := oplog.NewGormTicketer(rw.underlying, oplog.WithAggregateNames(true))
|
||||
if err != nil {
|
||||
return fmt.Errorf("write oplog: unable to get Ticketer %w", err)
|
||||
}
|
||||
|
||||
entry, err := oplog.NewEntry(
|
||||
ticket.Name,
|
||||
metadata,
|
||||
wrapper,
|
||||
ticketer,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write oplog: unable to create oplog entry: %w", err)
|
||||
}
|
||||
err = entry.WriteEntryWith(
|
||||
ctx,
|
||||
&oplog.GormWriter{Tx: rw.underlying},
|
||||
ticket,
|
||||
msgs...,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write oplog: unable to write oplog entry: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rw *Db) newOplogMessage(ctx context.Context, opType OpType, i interface{}, opt ...Option) (*oplog.Message, error) {
|
||||
opts := GetOpts(opt...)
|
||||
replayable, ok := i.(oplog.ReplayableMessage)
|
||||
if !ok {
|
||||
return nil, errors.New("error not a replayable interface")
|
||||
}
|
||||
msg := oplog.Message{
|
||||
Message: i.(proto.Message),
|
||||
TypeName: replayable.TableName(),
|
||||
}
|
||||
switch opType {
|
||||
case CreateOp:
|
||||
msg.OpType = oplog.OpType_OP_TYPE_CREATE
|
||||
case UpdateOp:
|
||||
msg.OpType = oplog.OpType_OP_TYPE_UPDATE
|
||||
msg.FieldMaskPaths = opts.WithFieldMaskPaths
|
||||
msg.SetToNullPaths = opts.WithNullPaths
|
||||
case DeleteOp:
|
||||
msg.OpType = oplog.OpType_OP_TYPE_DELETE
|
||||
default:
|
||||
return nil, fmt.Errorf("add oplog: operation type %v is not supported", opType)
|
||||
}
|
||||
return &msg, nil
|
||||
}
|
||||
|
||||
// DoTx will wrap the Handler func passed within a transaction with retries
|
||||
// you should ensure that any objects written to the db in your TxHandler are retryable, which
|
||||
// means that the object may be sent to the db several times (retried), so things like the primary key must
|
||||
@@ -705,18 +853,20 @@ func (rw *Db) LookupByName(ctx context.Context, resource ResourceNamer, opt ...O
|
||||
return nil
|
||||
}
|
||||
|
||||
// LookupByPublicId will lookup resource my its public_id which must be unique
|
||||
func (rw *Db) LookupByPublicId(ctx context.Context, resource ResourcePublicIder, opt ...Option) error {
|
||||
// LookupByPublicId will lookup resource by its public_id or private_id, which
|
||||
// must be unique. Options are ignored.
|
||||
func (rw *Db) LookupById(ctx context.Context, resourceWithIder interface{}, opt ...Option) error {
|
||||
if rw.underlying == nil {
|
||||
return errors.New("error underlying db nil for lookup by public id")
|
||||
return fmt.Errorf("lookup by id: underlying db nil %w", ErrNilParameter)
|
||||
}
|
||||
if reflect.ValueOf(resource).Kind() != reflect.Ptr {
|
||||
return errors.New("error interface parameter must to be a pointer for lookup by public id")
|
||||
if reflect.ValueOf(resourceWithIder).Kind() != reflect.Ptr {
|
||||
return fmt.Errorf("lookup by id: interface parameter must to be a pointer %w", ErrInvalidParameter)
|
||||
}
|
||||
if resource.GetPublicId() == "" {
|
||||
return errors.New("error public id empty string for lookup by public id")
|
||||
primaryKey, where, err := primaryKeyWhere(resourceWithIder)
|
||||
if err != nil {
|
||||
return fmt.Errorf("lookup by id: %w", err)
|
||||
}
|
||||
if err := rw.underlying.Where("public_id = ?", resource.GetPublicId()).First(resource).Error; err != nil {
|
||||
if err := rw.underlying.Where(where, primaryKey).First(resourceWithIder).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return ErrRecordNotFound
|
||||
}
|
||||
@@ -725,6 +875,30 @@ func (rw *Db) LookupByPublicId(ctx context.Context, resource ResourcePublicIder,
|
||||
return nil
|
||||
}
|
||||
|
||||
func primaryKeyWhere(resourceWithIder interface{}) (pkey string, w string, e error) {
|
||||
var primaryKey, where string
|
||||
switch resourceType := resourceWithIder.(type) {
|
||||
case ResourcePublicIder:
|
||||
primaryKey = resourceType.GetPublicId()
|
||||
where = "public_id = ?"
|
||||
case ResourcePrivateIder:
|
||||
primaryKey = resourceType.GetPrivateId()
|
||||
where = "private_id = ?"
|
||||
default:
|
||||
return "", "", fmt.Errorf("unsupported interface type %w", ErrInvalidParameter)
|
||||
}
|
||||
if primaryKey == "" {
|
||||
return "", "", fmt.Errorf("primary key unset %w", ErrInvalidParameter)
|
||||
}
|
||||
return primaryKey, where, nil
|
||||
}
|
||||
|
||||
// LookupByPublicId will lookup resource by its public_id, which must be unique.
|
||||
// Options are ignored.
|
||||
func (rw *Db) LookupByPublicId(ctx context.Context, resource ResourcePublicIder, opt ...Option) error {
|
||||
return rw.LookupById(ctx, resource, opt...)
|
||||
}
|
||||
|
||||
// LookupWhere will lookup the first resource using a where clause with parameters (it only returns the first one)
|
||||
func (rw *Db) LookupWhere(ctx context.Context, resource interface{}, where string, args ...interface{}) error {
|
||||
if rw.underlying == nil {
|
||||
|
||||
+733
-172
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,72 @@
|
||||
package iam
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/watchtower/internal/db"
|
||||
"github.com/hashicorp/watchtower/internal/iam/store"
|
||||
"github.com/hashicorp/watchtower/internal/oplog"
|
||||
"github.com/hashicorp/watchtower/internal/types/scope"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// AuthAccount is from the auth subsystem and iam is only allowed to: lookup and
|
||||
// update auth accounts. That's why there is no "new" factory for AuthAccounts.
|
||||
type AuthAccount struct {
|
||||
*store.AuthAccount
|
||||
tableName string `gorm:"-"`
|
||||
}
|
||||
|
||||
var _ Clonable = (*AuthAccount)(nil)
|
||||
var _ db.VetForWriter = (*AuthAccount)(nil)
|
||||
var _ oplog.ReplayableMessage = (*AuthAccount)(nil)
|
||||
|
||||
func allocAuthAccount() AuthAccount {
|
||||
return AuthAccount{
|
||||
AuthAccount: &store.AuthAccount{},
|
||||
}
|
||||
}
|
||||
|
||||
// Clone creates a clone of the auth account.
|
||||
func (a *AuthAccount) Clone() interface{} {
|
||||
cp := proto.Clone(a.AuthAccount)
|
||||
return &AuthAccount{
|
||||
AuthAccount: cp.(*store.AuthAccount),
|
||||
}
|
||||
}
|
||||
|
||||
// VetForWrite implements db.VetForWrite() interface.
|
||||
func (a *AuthAccount) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
|
||||
if a.PublicId == "" {
|
||||
return fmt.Errorf("error public id is empty string for auth account write: %w", db.ErrInvalidParameter)
|
||||
}
|
||||
if err := validateScopeForWrite(ctx, r, a, opType, opt...); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AuthAccount) validScopeTypes() []scope.Type {
|
||||
return []scope.Type{scope.Organization}
|
||||
}
|
||||
|
||||
// GetScope returns the scope for the auth account.
|
||||
func (a *AuthAccount) GetScope(ctx context.Context, r db.Reader) (*Scope, error) {
|
||||
return LookupScope(ctx, r, a)
|
||||
}
|
||||
|
||||
// TableName returns the tablename to override the default gorm table name.
|
||||
func (a *AuthAccount) TableName() string {
|
||||
if a.tableName != "" {
|
||||
return a.tableName
|
||||
}
|
||||
return "auth_account"
|
||||
}
|
||||
|
||||
// SetTableName sets the tablename and satisfies the ReplayableMessage interface.
|
||||
func (a *AuthAccount) SetTableName(n string) {
|
||||
if n != "" {
|
||||
a.tableName = n
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package iam
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/watchtower/internal/db"
|
||||
dbassert "github.com/hashicorp/watchtower/internal/db/assert"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// this is a very limited test. we are not testing the auth account triggers
|
||||
// and integrity checks (we will leave that to the auth subsystem, since that's
|
||||
// where they are implemented). we just want to make sure that the iam subsystem
|
||||
// can update the IamUserId field successfully since that's what the iam system
|
||||
// relies on.
|
||||
func Test_AuthAccountUpdate(t *testing.T) {
|
||||
t.Parallel()
|
||||
cleanup, conn, _ := db.TestSetup(t, "postgres")
|
||||
defer func() {
|
||||
err := cleanup()
|
||||
assert.NoError(t, err)
|
||||
err = conn.Close()
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
org, _ := TestScopes(t, conn)
|
||||
rw := db.New(conn)
|
||||
t.Run("simple-update", func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
u := TestUser(t, conn, org.PublicId)
|
||||
authMethodPublicId := testAuthMethod(t, conn, org.PublicId)
|
||||
acct := testAuthAccount(t, conn, org.PublicId, authMethodPublicId, "")
|
||||
|
||||
updateAcct := acct.Clone().(*AuthAccount)
|
||||
updateAcct.IamUserId = u.PublicId
|
||||
updatedRows, err := rw.Update(context.Background(), updateAcct, []string{"IamUserId"}, nil)
|
||||
require.NoError(err)
|
||||
assert.Equal(1, updatedRows)
|
||||
|
||||
foundAcct := allocAuthAccount()
|
||||
foundAcct.PublicId = acct.PublicId
|
||||
err = rw.LookupByPublicId(context.Background(), &foundAcct)
|
||||
require.NoError(err)
|
||||
assert.Equal(u.PublicId, foundAcct.IamUserId)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthAccount_GetScope(t *testing.T) {
|
||||
t.Parallel()
|
||||
cleanup, conn, _ := db.TestSetup(t, "postgres")
|
||||
defer func() {
|
||||
err := cleanup()
|
||||
assert.NoError(t, err)
|
||||
err = conn.Close()
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
org, _ := TestScopes(t, conn)
|
||||
|
||||
t.Run("valid-org", func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
w := db.New(conn)
|
||||
u := TestUser(t, conn, org.PublicId)
|
||||
authMethodPublicId := testAuthMethod(t, conn, org.PublicId)
|
||||
acct := testAuthAccount(t, conn, org.PublicId, authMethodPublicId, u.PublicId)
|
||||
scope, err := acct.GetScope(context.Background(), w)
|
||||
require.NoError(err)
|
||||
assert.True(proto.Equal(org, scope))
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthAccount_Clone(t *testing.T) {
|
||||
t.Parallel()
|
||||
cleanup, conn, _ := db.TestSetup(t, "postgres")
|
||||
defer func() {
|
||||
err := cleanup()
|
||||
assert.NoError(t, err)
|
||||
err = conn.Close()
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
org, _ := TestScopes(t, conn)
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
u := TestUser(t, conn, org.PublicId)
|
||||
authMethodPublicId := testAuthMethod(t, conn, org.PublicId)
|
||||
acct := testAuthAccount(t, conn, org.PublicId, authMethodPublicId, u.PublicId)
|
||||
cp := acct.Clone()
|
||||
assert.True(proto.Equal(cp.(*AuthAccount).AuthAccount, acct.AuthAccount))
|
||||
})
|
||||
t.Run("not-equal", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
rw := db.New(conn)
|
||||
u := TestUser(t, conn, org.PublicId)
|
||||
authMethodPublicId := testAuthMethod(t, conn, org.PublicId)
|
||||
acct := testAuthAccount(t, conn, org.PublicId, authMethodPublicId, u.PublicId)
|
||||
acct2 := testAuthAccount(t, conn, org.PublicId, authMethodPublicId, "")
|
||||
dbassert := dbassert.New(t, rw)
|
||||
dbassert.IsNull(acct2, "IamUserId")
|
||||
cp := acct.Clone()
|
||||
assert.True(!proto.Equal(cp.(*AuthAccount).AuthAccount, acct2.AuthAccount))
|
||||
})
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
@@ -20,6 +20,7 @@ type options struct {
|
||||
withDescription string
|
||||
withParentId *string
|
||||
withLimit int
|
||||
withAutoVivify bool
|
||||
}
|
||||
|
||||
func getDefaultOptions() options {
|
||||
@@ -30,6 +31,7 @@ func getDefaultOptions() options {
|
||||
withName: "",
|
||||
withParentId: nil,
|
||||
withLimit: 0,
|
||||
withAutoVivify: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,3 +71,11 @@ func WithLimit(limit int) Option {
|
||||
o.withLimit = limit
|
||||
}
|
||||
}
|
||||
|
||||
// WithAutoVivify provides an option to enable user auto vivification when
|
||||
// calling repo.LookupUserWithLogin().
|
||||
func WithAutoVivify(enable bool) Option {
|
||||
return func(o *options) {
|
||||
o.withAutoVivify = enable
|
||||
}
|
||||
}
|
||||
@@ -52,4 +52,17 @@ func Test_GetOpts(t *testing.T) {
|
||||
testOpts.withLimit = 1
|
||||
assert.Equal(opts, testOpts)
|
||||
})
|
||||
t.Run("WithAutoVivify", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
// test default of false
|
||||
opts := getOpts()
|
||||
testOpts := getDefaultOptions()
|
||||
testOpts.withAutoVivify = false
|
||||
assert.Equal(opts, testOpts)
|
||||
|
||||
opts = getOpts(WithAutoVivify(true))
|
||||
testOpts = getDefaultOptions()
|
||||
testOpts.withAutoVivify = true
|
||||
assert.Equal(opts, testOpts)
|
||||
})
|
||||
}
|
||||
@@ -100,7 +100,7 @@ func (r *Repository) LookupScope(ctx context.Context, withPublicId string, opt .
|
||||
if err == db.ErrRecordNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("lookup group: failed %w fo %s", err, withPublicId)
|
||||
return nil, fmt.Errorf("lookup scope: failed %w fo %s", err, withPublicId)
|
||||
}
|
||||
return &scope, nil
|
||||
}
|
||||
|
||||
@@ -2,10 +2,13 @@ package iam
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/watchtower/internal/db"
|
||||
"github.com/hashicorp/watchtower/internal/oplog"
|
||||
"github.com/hashicorp/watchtower/internal/types/scope"
|
||||
)
|
||||
|
||||
// CreateUser will create a user in the repository and return the written user
|
||||
@@ -118,3 +121,186 @@ func (r *Repository) ListUsers(ctx context.Context, withOrganizationId string, o
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// LookupUserWithLogin will attempt to lookup the user with a matching auth
|
||||
// account id and return the user if found. If a user is not found and the
|
||||
// WithAutoVivify() option is true, then a new iam User will be
|
||||
// created in the scope of the auth account, and associated with the auth
|
||||
// account. If a new user is auto vivified, then the WithName and
|
||||
// WithDescription options are supported as well.
|
||||
func (r *Repository) LookupUserWithLogin(ctx context.Context, withAuthAccountId string, opt ...Option) (*User, error) {
|
||||
opts := getOpts(opt...)
|
||||
if withAuthAccountId == "" {
|
||||
return nil, fmt.Errorf("lookup user with login: missing auth account id %w", db.ErrInvalidParameter)
|
||||
}
|
||||
u, err := r.getUserWithAuthAccount(ctx, withAuthAccountId)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("lookup user with login: %w", err)
|
||||
}
|
||||
if u != nil {
|
||||
return u, nil
|
||||
}
|
||||
if !opts.withAutoVivify {
|
||||
return nil, fmt.Errorf("lookup user with login: user not found for auth account %s: %w", withAuthAccountId, db.ErrRecordNotFound)
|
||||
}
|
||||
|
||||
acct := allocAuthAccount()
|
||||
acct.PublicId = withAuthAccountId
|
||||
err = r.reader.LookupByPublicId(context.Background(), &acct)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("lookup user with login: unable to lookup auth account %s: %w", withAuthAccountId, err)
|
||||
}
|
||||
|
||||
metadata := oplog.Metadata{
|
||||
"resource-public-id": []string{withAuthAccountId},
|
||||
"scope-id": []string{acct.ScopeId},
|
||||
"scope-type": []string{scope.Organization.String()},
|
||||
"resource-type": []string{"auth-account"},
|
||||
}
|
||||
|
||||
// We will create a new user and associate the user with the auth account
|
||||
// within one retryable transaction using writer.DoTx
|
||||
var obtainedUser *User
|
||||
_, err = r.writer.DoTx(
|
||||
ctx,
|
||||
db.StdRetryCnt,
|
||||
db.ExpBackoff{},
|
||||
func(_ db.Reader, w db.Writer) error {
|
||||
msgs := make([]*oplog.Message, 0, 2)
|
||||
ticket, err := w.GetTicket(&acct)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
obtainedUser, err = NewUser(acct.ScopeId, opt...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
id, err := newUserId()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var createMsg oplog.Message
|
||||
obtainedUser.PublicId = id
|
||||
err = w.Create(ctx, obtainedUser, db.NewOplogMsg(&createMsg))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msgs = append(msgs, &createMsg)
|
||||
|
||||
var updateMsg oplog.Message
|
||||
updateAcct := acct.Clone().(*AuthAccount)
|
||||
updateAcct.IamUserId = id
|
||||
updatedRows, err := w.Update(ctx, updateAcct, []string{"IamUserId"}, nil, db.NewOplogMsg(&updateMsg))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if updatedRows != 1 {
|
||||
return fmt.Errorf("auth account update affected %d rows", updatedRows)
|
||||
}
|
||||
msgs = append(msgs, &updateMsg)
|
||||
if err := w.WriteOplogEntryWith(ctx, r.wrapper, ticket, metadata, msgs); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("lookup user with login: unable to associate user and account: %w", err)
|
||||
}
|
||||
return obtainedUser, nil
|
||||
}
|
||||
|
||||
func (r *Repository) getUserWithAuthAccount(ctx context.Context, withAuthAccountId string, opt ...Option) (*User, error) {
|
||||
if withAuthAccountId == "" {
|
||||
return nil, fmt.Errorf("missing auth account id %w", db.ErrInvalidParameter)
|
||||
}
|
||||
underlying, err := r.reader.DB()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get underlying db for auth account search: %w", err)
|
||||
}
|
||||
userTable := func() *User { u := allocUser(); return &u }().TableName()
|
||||
acctTable := func() *AuthAccount { a := allocAuthAccount(); return &a }().TableName()
|
||||
|
||||
where := fmt.Sprintf(`
|
||||
select %[1]s.*
|
||||
from %[1]s
|
||||
inner join %[2]s
|
||||
on %[1]s.public_id = %[2]s.iam_user_id
|
||||
where
|
||||
%[1]s.scope_id = %[2]s.scope_id and
|
||||
%[2]s.public_id = $1`, userTable, acctTable)
|
||||
rows, err := underlying.Query(where, withAuthAccountId)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to query auth account %s", withAuthAccountId)
|
||||
}
|
||||
defer rows.Close()
|
||||
u := allocUser()
|
||||
if rows.Next() {
|
||||
err = r.reader.ScanRows(rows, &u)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to scan rows for auth account %s: %w", withAuthAccountId, err)
|
||||
}
|
||||
} else {
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("unable to get next auth account: %w", err)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get next row for auth accounts %s: %w", withAuthAccountId, err)
|
||||
}
|
||||
return &u, nil
|
||||
}
|
||||
|
||||
func (r *Repository) validateLoginParameters(ctx context.Context, withScope, withAuthMethodId, withAuthAccountId string) (*AuthAccount, error) {
|
||||
if withScope == "" {
|
||||
return nil, fmt.Errorf("missing scope id %w", db.ErrInvalidParameter)
|
||||
}
|
||||
if withAuthMethodId == "" {
|
||||
return nil, fmt.Errorf("missing auth method id %w", db.ErrInvalidParameter)
|
||||
}
|
||||
if withAuthAccountId == "" {
|
||||
return nil, fmt.Errorf("missing auth account id %w", db.ErrInvalidParameter)
|
||||
|
||||
}
|
||||
ok, err := r.validateAuthMethodId(ctx, withScope, withAuthMethodId)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to validate auth method %s in scope %s: %w", withAuthMethodId, withScope, err)
|
||||
}
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("auth method id %s in scope %s is not valid: %w", withAuthMethodId, withScope, db.ErrInvalidParameter)
|
||||
}
|
||||
acct := allocAuthAccount()
|
||||
err = r.reader.LookupWhere(context.Background(), &acct, "public_id = ? and auth_method_id = ?", withAuthAccountId, withAuthMethodId)
|
||||
if err != nil {
|
||||
if errors.Is(err, db.ErrRecordNotFound) {
|
||||
return nil, fmt.Errorf("unable to search for auth account %s in auth method %s: %w", withAuthAccountId, withAuthMethodId, db.ErrInvalidParameter)
|
||||
}
|
||||
return nil, fmt.Errorf("unable to search for auth account %s in auth method %s: %w", withAuthAccountId, withAuthMethodId, err)
|
||||
}
|
||||
return &acct, nil
|
||||
}
|
||||
|
||||
// TODO (jimlambrt 6/2020) replace the raw query with a Lookup using the auth
|
||||
// method repo.
|
||||
func (r *Repository) validateAuthMethodId(ctx context.Context, scopeId, authMethodId string) (bool, error) {
|
||||
if scopeId == "" {
|
||||
return false, fmt.Errorf("scope id is unset: %w", db.ErrInvalidParameter)
|
||||
}
|
||||
if authMethodId == "" {
|
||||
return false, fmt.Errorf("auth method id is unset: %w", db.ErrInvalidParameter)
|
||||
}
|
||||
const where = `select count(*) from auth_method where public_id = $1 and scope_id = $2`
|
||||
db, err := r.reader.DB()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
var cnt int
|
||||
if err := db.QueryRow(where, authMethodId, scopeId).Scan(&cnt); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if cnt == 0 {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
@@ -531,3 +531,151 @@ func TestRepository_ListUsers(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepository_LookupUserWithLogin(t *testing.T) {
|
||||
t.Parallel()
|
||||
cleanup, conn, _ := db.TestSetup(t, "postgres")
|
||||
defer func() {
|
||||
err := cleanup()
|
||||
assert.NoError(t, err)
|
||||
err = conn.Close()
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
repo, err := NewRepository(rw, rw, wrapper)
|
||||
require.NoError(t, err)
|
||||
|
||||
id := testId(t)
|
||||
org, _ := TestScopes(t, conn)
|
||||
authMethodId := testAuthMethod(t, conn, org.PublicId)
|
||||
newAuthAcct := testAuthAccount(t, conn, org.PublicId, authMethodId, "")
|
||||
newAuthAcctWithoutVivify := testAuthAccount(t, conn, org.PublicId, authMethodId, "")
|
||||
// negativeTestAuthAcct := testAuthAccount(t, conn, org.PublicId, authMethodId, "")
|
||||
|
||||
user := TestUser(t, conn, org.PublicId, WithName("existing-"+id))
|
||||
existingAuthAcct := testAuthAccount(t, conn, org.PublicId, authMethodId, user.PublicId)
|
||||
require.Equal(t, user.PublicId, existingAuthAcct.IamUserId)
|
||||
|
||||
type args struct {
|
||||
withAuthAccountId string
|
||||
opt []Option
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantName string
|
||||
wantDescription string
|
||||
wantErr bool
|
||||
wantErrIs error
|
||||
wantUser *User
|
||||
}{
|
||||
{
|
||||
name: "valid",
|
||||
args: args{
|
||||
withAuthAccountId: newAuthAcct.PublicId,
|
||||
opt: []Option{
|
||||
WithAutoVivify(true),
|
||||
WithName("valid-" + id),
|
||||
WithDescription("valid-" + id),
|
||||
},
|
||||
},
|
||||
wantName: "valid-" + id,
|
||||
wantDescription: "valid-" + id,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "new-acct-without-vivify",
|
||||
args: args{
|
||||
withAuthAccountId: newAuthAcctWithoutVivify.PublicId,
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrIs: db.ErrRecordNotFound,
|
||||
},
|
||||
{
|
||||
name: "missing auth acct id",
|
||||
args: args{
|
||||
withAuthAccountId: "",
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrIs: db.ErrInvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "existing-auth-account",
|
||||
args: args{
|
||||
withAuthAccountId: existingAuthAcct.PublicId,
|
||||
},
|
||||
wantErr: false,
|
||||
wantName: "existing-" + id,
|
||||
wantUser: user,
|
||||
},
|
||||
{
|
||||
name: "existing-auth-account-with-vivify",
|
||||
args: args{
|
||||
withAuthAccountId: existingAuthAcct.PublicId,
|
||||
opt: []Option{
|
||||
WithAutoVivify(true),
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
wantName: "existing-" + id,
|
||||
wantUser: user,
|
||||
},
|
||||
{
|
||||
name: "bad-auth-account-id",
|
||||
args: args{
|
||||
withAuthAccountId: id,
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrIs: db.ErrRecordNotFound,
|
||||
},
|
||||
{
|
||||
name: "bad-auth-account-id-with-vivify",
|
||||
args: args{
|
||||
withAuthAccountId: id,
|
||||
opt: []Option{
|
||||
WithAutoVivify(true),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrIs: db.ErrRecordNotFound,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
dbassert := dbassert.New(t, rw)
|
||||
got, err := repo.LookupUserWithLogin(context.Background(), tt.args.withAuthAccountId, tt.args.opt...)
|
||||
if tt.wantErr {
|
||||
require.Error(err)
|
||||
assert.Nil(got)
|
||||
if tt.wantErrIs != nil {
|
||||
assert.Truef(errors.Is(err, tt.wantErrIs), "unexpected error %s", err.Error())
|
||||
}
|
||||
if tt.args.withAuthAccountId != "" && tt.args.withAuthAccountId != id {
|
||||
// need to assert that userid in auth_account is still null
|
||||
acct := allocAuthAccount()
|
||||
acct.PublicId = tt.args.withAuthAccountId
|
||||
dbassert.IsNull(&acct, "IamUserId")
|
||||
}
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
if tt.wantName != "" {
|
||||
assert.Equal(tt.wantName, got.Name)
|
||||
}
|
||||
if tt.wantDescription != "" {
|
||||
assert.Equal(tt.wantDescription, got.Description)
|
||||
}
|
||||
require.NotEmpty(got.PublicId)
|
||||
if tt.wantUser != nil {
|
||||
assert.True(proto.Equal(tt.wantUser.User, got.User))
|
||||
}
|
||||
acct := allocAuthAccount()
|
||||
acct.PublicId = tt.args.withAuthAccountId
|
||||
err = rw.LookupByPublicId(context.Background(), &acct)
|
||||
require.NoError(err)
|
||||
assert.Equal(got.PublicId, acct.IamUserId)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -55,11 +55,11 @@ func LookupScope(ctx context.Context, reader db.Reader, resource ResourceWithSco
|
||||
return nil, errors.New("error resource is nil for LookupScope")
|
||||
}
|
||||
if resource.GetPublicId() == "" {
|
||||
return nil, errors.New("error resource has an unset public id")
|
||||
return nil, fmt.Errorf("LookupScope: scope id is unset %w", db.ErrInvalidParameter)
|
||||
}
|
||||
if resource.GetScopeId() == "" {
|
||||
// try to retrieve it from db with it's scope id
|
||||
if err := reader.LookupByPublicId(ctx, resource); err != nil {
|
||||
if err := reader.LookupById(ctx, resource); err != nil {
|
||||
return nil, fmt.Errorf("unable to get resource by public id: %w", err)
|
||||
}
|
||||
// if it's still not set after getting it from the db...
|
||||
|
||||
@@ -53,6 +53,6 @@ func Test_LookupScope(t *testing.T) {
|
||||
user2 := allocUser()
|
||||
s, err = LookupScope(context.Background(), w, &user2)
|
||||
assert.Nil(s)
|
||||
assert.Equal("error resource has an unset public id", err.Error())
|
||||
assert.Equal("LookupScope: scope id is unset invalid parameter", err.Error())
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.24.0
|
||||
// protoc v3.12.3
|
||||
// source: controller/storage/iam/store/v1/auth_account.proto
|
||||
|
||||
package store
|
||||
|
||||
import (
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
_ "github.com/hashicorp/watchtower/internal/db/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
|
||||
|
||||
// AuthAccount is a "shared table" between the iam and auth subsystems. The
|
||||
// auth system is responsible for creating entries. The iam system is only
|
||||
// responsible/allowed to update the iam_user_id.
|
||||
type AuthAccount struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// auth_account_id is primary key for the user account
|
||||
// @inject_tag: gorm:"primary_key"
|
||||
PublicId string `protobuf:"bytes,1,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"`
|
||||
// auth_method_id is the auth method associated with this user account.
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
AuthMethodId string `protobuf:"bytes,2,opt,name=auth_method_id,json=authMethodId,proto3" json:"auth_method_id,omitempty" gorm:"default:null"`
|
||||
// scope_id for the user account
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
ScopeId string `protobuf:"bytes,4,opt,name=scope_id,json=scopeId,proto3" json:"scope_id,omitempty" gorm:"default:null"`
|
||||
// user_id is the iam.user associated with this account
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
IamUserId string `protobuf:"bytes,5,opt,name=iam_user_id,json=iamUserId,proto3" json:"iam_user_id,omitempty" gorm:"default:null"`
|
||||
}
|
||||
|
||||
func (x *AuthAccount) Reset() {
|
||||
*x = AuthAccount{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_controller_storage_iam_store_v1_auth_account_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *AuthAccount) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*AuthAccount) ProtoMessage() {}
|
||||
|
||||
func (x *AuthAccount) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_controller_storage_iam_store_v1_auth_account_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 AuthAccount.ProtoReflect.Descriptor instead.
|
||||
func (*AuthAccount) Descriptor() ([]byte, []int) {
|
||||
return file_controller_storage_iam_store_v1_auth_account_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *AuthAccount) GetPublicId() string {
|
||||
if x != nil {
|
||||
return x.PublicId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *AuthAccount) GetAuthMethodId() string {
|
||||
if x != nil {
|
||||
return x.AuthMethodId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *AuthAccount) GetScopeId() string {
|
||||
if x != nil {
|
||||
return x.ScopeId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *AuthAccount) GetIamUserId() string {
|
||||
if x != nil {
|
||||
return x.IamUserId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_controller_storage_iam_store_v1_auth_account_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_controller_storage_iam_store_v1_auth_account_proto_rawDesc = []byte{
|
||||
0x0a, 0x32, 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, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 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, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
|
||||
0x61, 0x6d, 0x70, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x8b, 0x01, 0x0a, 0x0b, 0x41, 0x75, 0x74, 0x68, 0x41,
|
||||
0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 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, 0x24, 0x0a, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6d, 0x65, 0x74, 0x68,
|
||||
0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x75, 0x74,
|
||||
0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x63, 0x6f,
|
||||
0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x63, 0x6f,
|
||||
0x70, 0x65, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0b, 0x69, 0x61, 0x6d, 0x5f, 0x75, 0x73, 0x65, 0x72,
|
||||
0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x69, 0x61, 0x6d, 0x55, 0x73,
|
||||
0x65, 0x72, 0x49, 0x64, 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_auth_account_proto_rawDescOnce sync.Once
|
||||
file_controller_storage_iam_store_v1_auth_account_proto_rawDescData = file_controller_storage_iam_store_v1_auth_account_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_controller_storage_iam_store_v1_auth_account_proto_rawDescGZIP() []byte {
|
||||
file_controller_storage_iam_store_v1_auth_account_proto_rawDescOnce.Do(func() {
|
||||
file_controller_storage_iam_store_v1_auth_account_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_storage_iam_store_v1_auth_account_proto_rawDescData)
|
||||
})
|
||||
return file_controller_storage_iam_store_v1_auth_account_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_controller_storage_iam_store_v1_auth_account_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_controller_storage_iam_store_v1_auth_account_proto_goTypes = []interface{}{
|
||||
(*AuthAccount)(nil), // 0: controller.storage.iam.store.v1.AuthAccount
|
||||
}
|
||||
var file_controller_storage_iam_store_v1_auth_account_proto_depIdxs = []int32{
|
||||
0, // [0:0] is the sub-list for method output_type
|
||||
0, // [0:0] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_controller_storage_iam_store_v1_auth_account_proto_init() }
|
||||
func file_controller_storage_iam_store_v1_auth_account_proto_init() {
|
||||
if File_controller_storage_iam_store_v1_auth_account_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_controller_storage_iam_store_v1_auth_account_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*AuthAccount); 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_auth_account_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 1,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_controller_storage_iam_store_v1_auth_account_proto_goTypes,
|
||||
DependencyIndexes: file_controller_storage_iam_store_v1_auth_account_proto_depIdxs,
|
||||
MessageInfos: file_controller_storage_iam_store_v1_auth_account_proto_msgTypes,
|
||||
}.Build()
|
||||
File_controller_storage_iam_store_v1_auth_account_proto = out.File
|
||||
file_controller_storage_iam_store_v1_auth_account_proto_rawDesc = nil
|
||||
file_controller_storage_iam_store_v1_auth_account_proto_goTypes = nil
|
||||
file_controller_storage_iam_store_v1_auth_account_proto_depIdxs = nil
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/watchtower/internal/db"
|
||||
dbassert "github.com/hashicorp/watchtower/internal/db/assert"
|
||||
"github.com/hashicorp/watchtower/internal/iam/store"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -139,3 +141,72 @@ func TestGroup(t *testing.T, conn *gorm.DB, scopeId string, opt ...Option) *Grou
|
||||
require.NotEmpty(grp.PublicId)
|
||||
return grp
|
||||
}
|
||||
|
||||
// testAuthAccount is a temporary test function. TODO - replace with an auth
|
||||
// subsystem testAuthAccount function. If userId is zero value, then an auth
|
||||
// account will be created with a null IamUserId
|
||||
func testAuthAccount(t *testing.T, conn *gorm.DB, scopeId, authMethodId, userId string) *AuthAccount {
|
||||
const (
|
||||
authAccountPrefix = "aa_"
|
||||
where = `select count(*) from auth_method where public_id = $1 and scope_id = $2`
|
||||
)
|
||||
t.Helper()
|
||||
rw := db.New(conn)
|
||||
require := require.New(t)
|
||||
require.NotNil(conn)
|
||||
require.NotEmpty(scopeId)
|
||||
require.NotEmpty(authMethodId)
|
||||
|
||||
if userId != "" {
|
||||
foundUser := allocUser()
|
||||
foundUser.PublicId = userId
|
||||
err := rw.LookupByPublicId(context.Background(), &foundUser)
|
||||
require.NoError(err)
|
||||
require.Equal(scopeId, foundUser.ScopeId)
|
||||
}
|
||||
|
||||
var count int
|
||||
err := conn.DB().QueryRow(where, authMethodId, scopeId).Scan(&count)
|
||||
require.NoError(err)
|
||||
require.Equal(1, count)
|
||||
|
||||
id, err := db.NewPublicId(authAccountPrefix)
|
||||
require.NoError(err)
|
||||
|
||||
acct := &AuthAccount{
|
||||
AuthAccount: &store.AuthAccount{
|
||||
PublicId: id,
|
||||
ScopeId: scopeId,
|
||||
AuthMethodId: authMethodId,
|
||||
IamUserId: userId,
|
||||
},
|
||||
}
|
||||
err = rw.Create(context.Background(), acct)
|
||||
require.NoError(err)
|
||||
require.NotEmpty(acct.PublicId)
|
||||
|
||||
if userId == "" {
|
||||
dbassert := dbassert.New(t, rw)
|
||||
dbassert.IsNull(acct, "IamUserId")
|
||||
}
|
||||
return acct
|
||||
}
|
||||
|
||||
// testAuthMethod is a temporary test function. TODO - replace with an auth
|
||||
// subsystem testAuthMethod function.
|
||||
func testAuthMethod(t *testing.T, conn *gorm.DB, scopeId string) string {
|
||||
const (
|
||||
authMethodPrefix = "am_"
|
||||
insert = `insert into auth_method (public_id, scope_id) values ($1, $2)`
|
||||
)
|
||||
t.Helper()
|
||||
require := require.New(t)
|
||||
require.NotNil(conn)
|
||||
require.NotEmpty(scopeId)
|
||||
id, err := db.NewPublicId(authMethodPrefix)
|
||||
require.NoError(err)
|
||||
|
||||
_, err = conn.DB().Exec(insert, id, scopeId)
|
||||
require.NoError(err)
|
||||
return id
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package controller.storage.auth.store.v1;
|
||||
option go_package = "github.com/hashicorp/watchtower/internal/auth/store;store";
|
||||
|
||||
import "controller/storage/timestamp/v1/timestamp.proto";
|
||||
|
||||
// TODO - fill out the comments for these protobufs... at this point, I'm just
|
||||
// trying to get aligned on the basic data structures so we can keep moving
|
||||
// forward to the prototype at the end of next week.
|
||||
|
||||
message AuthMethod {
|
||||
// @inject_tag: gorm:"primary_key"
|
||||
string auth_account_id = 1;
|
||||
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
string iam_scope_id = 2;
|
||||
}
|
||||
|
||||
message AuthAccount {
|
||||
// @inject_tag: gorm:"primary_key"
|
||||
string auth_account_id = 1;
|
||||
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
string auth_method_id = 2;
|
||||
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
string iam_scope_id = 3;
|
||||
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
string iam_user_id = 4;
|
||||
}
|
||||
@@ -21,11 +21,11 @@ message StoreTestUser {
|
||||
// @inject_tag: `gorm:"default:CURRENT_TIMESTAMP"`
|
||||
timestamp.v1.Timestamp update_time = 3;
|
||||
|
||||
// public_id is the used to access the AssignablScope via an API
|
||||
// public_id is the used to access the user via an API
|
||||
string public_id = 4;
|
||||
|
||||
// name is the optional friendly name used to
|
||||
// access the Scope via an API
|
||||
// access the user via an API
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
string name = 5;
|
||||
|
||||
@@ -38,6 +38,7 @@ message StoreTestUser {
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
uint32 version = 8;
|
||||
}
|
||||
|
||||
// TestCar for gorm test car model
|
||||
message StoreTestCar {
|
||||
// @inject_tag: gorm:"primary_key"
|
||||
@@ -51,7 +52,7 @@ message StoreTestCar {
|
||||
// @inject_tag: `gorm:"default:CURRENT_TIMESTAMP"`
|
||||
timestamp.v1.Timestamp update_time = 3;
|
||||
|
||||
// public_id is the used to access the AssignablScope via an API
|
||||
// public_id is the used to access the car via an API
|
||||
string public_id = 4;
|
||||
|
||||
// name is the optional friendly name used to
|
||||
@@ -59,9 +60,13 @@ message StoreTestCar {
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
string name = 5;
|
||||
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
string model = 6;
|
||||
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
int32 mpg = 7;
|
||||
}
|
||||
|
||||
// TestRental for gorm test rental model
|
||||
message StoreTestRental {
|
||||
// @inject_tag: gorm:"primary_key"
|
||||
@@ -75,14 +80,42 @@ message StoreTestRental {
|
||||
// @inject_tag: `gorm:"default:CURRENT_TIMESTAMP"`
|
||||
timestamp.v1.Timestamp update_time = 3;
|
||||
|
||||
// public_id is the used to access the AssignablScope via an API
|
||||
// public_id is the used to access the rental via an API
|
||||
string public_id = 4;
|
||||
|
||||
// name is the optional friendly name used to
|
||||
// access the Scope via an API
|
||||
// access the rental via an API
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
string name = 5;
|
||||
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
uint32 user_id = 6;
|
||||
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
uint32 car_id = 7;
|
||||
}
|
||||
|
||||
// StoreTestScooter used in the db tests only and provides a gorm resource with
|
||||
// a private id.
|
||||
message StoreTestScooter {
|
||||
// @inject_tag: gorm:"primary_key"
|
||||
uint32 id = 1;
|
||||
|
||||
// create_time from the RDBMS
|
||||
// @inject_tag: `gorm:"default:CURRENT_TIMESTAMP"`
|
||||
timestamp.v1.Timestamp create_time = 2;
|
||||
|
||||
// update_time from the RDBMS
|
||||
// @inject_tag: `gorm:"default:CURRENT_TIMESTAMP"`
|
||||
timestamp.v1.Timestamp update_time = 3;
|
||||
|
||||
// private_id is the used to access scooter, but not intended to be available
|
||||
// via the API
|
||||
string private_id = 4;
|
||||
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
string model = 6;
|
||||
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
int32 mpg = 7;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package controller.storage.iam.store.v1;
|
||||
option go_package = "github.com/hashicorp/watchtower/internal/iam/store;store";
|
||||
|
||||
import "controller/storage/timestamp/v1/timestamp.proto";
|
||||
|
||||
// AuthAccount is a "shared table" between the iam and auth subsystems. The
|
||||
// auth system is responsible for creating entries. The iam system is only
|
||||
// responsible/allowed to update the iam_user_id.
|
||||
message AuthAccount {
|
||||
// auth_account_id is primary key for the user account
|
||||
// @inject_tag: gorm:"primary_key"
|
||||
string public_id = 1;
|
||||
|
||||
// auth_method_id is the auth method associated with this user account.
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
string auth_method_id = 2;
|
||||
|
||||
// scope_id for the user account
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
string scope_id = 4;
|
||||
|
||||
// user_id is the iam.user associated with this account
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
string iam_user_id = 5;
|
||||
}
|
||||
Reference in new issue
Block a user