iam and auth integration (#144)

This commit is contained in:
Jim authored and GitHub committed 2020-06-29 08:18:57 -04:00
1 parent 3d85c7ebb0
commit 3e3144117e
31 files changed
+2494 -268

No files matched your search

+1
View File
@@ -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:
+269
View File
@@ -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
}
+47 -1
View File
@@ -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{}
}
+164 -30
View File
@@ -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
View File
@@ -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
}
+45
View File
@@ -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))
})
}
}
+42 -8
View File
@@ -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;
+32 -3
View File
@@ -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
View File
@@ -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) {
+15
View File
@@ -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
View File
@@ -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 {
File diff suppressed because it is too large. Load diff
+72
View File
@@ -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
}
}
+103
View File
@@ -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.
+10
View File
@@ -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
}
}
+13
View File
@@ -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)
})
}
+1 -1
View File
@@ -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
}
+186
View File
@@ -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
}
+148
View File
@@ -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)
})
}
}
+2 -2
View File
@@ -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...
+1 -1
View File
@@ -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())
})
}
+199
View File
@@ -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
}
+71
View File
@@ -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;
}