-
Notifications
You must be signed in to change notification settings - Fork 290
/
Copy pathscope.go
213 lines (194 loc) · 6.66 KB
/
scope.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
package iam
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam/store"
"github.com/hashicorp/boundary/internal/types/action"
"github.com/hashicorp/boundary/internal/types/resource"
"github.com/hashicorp/boundary/internal/types/scope"
"google.golang.org/protobuf/proto"
)
const (
defaultScopeTableName = "iam_scope"
)
// Scope is used to create a hierarchy of "containers" that encompass the scope of
// an IAM resource. Scopes are Global, Orgs and Projects.
type Scope struct {
*store.Scope
// tableName which is used to support overriding the table name in the db
// and making the Scope a ReplayableMessage
tableName string `gorm:"-"`
}
// ensure that Scope implements the interfaces of: Resource, Cloneable, and db.VetForWriter
var (
_ Resource = (*Scope)(nil)
_ db.VetForWriter = (*Scope)(nil)
_ Cloneable = (*Scope)(nil)
)
func NewOrg(opt ...Option) (*Scope, error) {
global := AllocScope()
global.PublicId = scope.Global.String()
return newScope(&global, opt...)
}
func NewProject(orgPublicId string, opt ...Option) (*Scope, error) {
const op = "iam.NewProject"
org := AllocScope()
org.PublicId = orgPublicId
p, err := newScope(&org, opt...)
if err != nil {
return nil, errors.WrapDeprecated(err, op)
}
return p, nil
}
// newScope creates a new Scope with options: WithName specifies the Scope's
// friendly name. WithDescription specifies the scope's description. WithScope
// specifies the Scope's parent and must be filled in. The type of the parent is
// used to determine the type of the child. WithPrimaryAuthMethodId specifies
// the primary auth method for the scope
func newScope(parent *Scope, opt ...Option) (*Scope, error) {
const op = "iam.newScope"
if parent == nil || parent.PublicId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "child scope is missing its parent")
}
var typ scope.Type
switch {
case parent.PublicId == scope.Global.String():
typ = scope.Org
case strings.HasPrefix(parent.PublicId, scope.Org.Prefix()):
typ = scope.Project
}
if typ == scope.Unknown {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "unknown scope type")
}
opts := getOpts(opt...)
s := &Scope{
Scope: &store.Scope{
Type: typ.String(),
Name: opts.withName,
Description: opts.withDescription,
ParentId: parent.PublicId,
PrimaryAuthMethodId: opts.withPrimaryAuthMethodId,
},
}
return s, nil
}
func AllocScope() Scope {
return Scope{
Scope: &store.Scope{},
}
}
// Clone creates a clone of the Scope
func (s *Scope) Clone() any {
cp := proto.Clone(s.Scope)
return &Scope{
Scope: cp.(*store.Scope),
}
}
// VetForWrite implements db.VetForWrite() interface for scopes
// this function is intended to be callled by a db.Writer (Create and Update) to validate
// the scope before writing it to the db.
func (s *Scope) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
const op = "iam.(Scope).VetForWrite"
if s.Type == scope.Unknown.String() {
return errors.New(ctx, errors.InvalidParameter, op, "unknown scope type")
}
if s.PublicId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing public id")
}
if opType == db.UpdateOp {
dbOptions := db.GetOpts(opt...)
for _, path := range dbOptions.WithFieldMaskPaths {
switch path {
case "ParentId":
return errors.New(ctx, errors.InvalidParameter, op, "you cannot change a scope's parent")
case "Type":
return errors.New(ctx, errors.InvalidParameter, op, "you cannot change a scope's type")
}
}
}
if opType == db.CreateOp {
switch {
case s.Type == scope.Global.String():
return errors.New(ctx, errors.InvalidParameter, op, "you cannot create a global scope")
case s.ParentId == "":
return errors.New(ctx, errors.InvalidParameter, op, "scope must have a parent")
case s.Type == scope.Org.String():
if s.ParentId != scope.Global.String() {
return errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf(`org's parent must be "%s"`, scope.Global.String()))
}
case s.Type == scope.Project.String():
parentScope := AllocScope()
parentScope.PublicId = s.ParentId
if err := r.LookupByPublicId(ctx, &parentScope, opt...); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to verify project's org scope"))
}
if parentScope.Type != scope.Org.String() {
return errors.New(ctx, errors.InvalidParameter, op, "project parent scope is not an org")
}
}
}
return nil
}
// ResourceType returns the type of scope
func (s *Scope) ResourceType() resource.Type {
return resource.Scope
}
// Actions returns the available actions for Scopes
func (*Scope) Actions() map[string]action.Type {
return CrudlActions()
}
// GetScope returns the scope for the "scope" if there is one defined
func (s *Scope) GetScope(ctx context.Context, r db.Reader) (*Scope, error) {
const op = "iam.(Scope).GetScope"
if r == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "nil reader")
}
if s.PublicId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing public id")
}
if s.Type == "" && s.ParentId == "" {
if err := r.LookupByPublicId(ctx, s); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
}
// HANDLE_GLOBAL
switch s.Type {
case scope.Global.String():
return nil, nil
default:
var p Scope
switch s.ParentId {
case "":
// no parent id, so use the public_id to find the parent scope. This
// won't work for if the scope hasn't been written to the db yet,
// like during create but in that case the parent id should be set
// for all scopes which are not global, and the global case was
// handled at HANDLE_GLOBAL
where := "public_id in (select parent_id from iam_scope where public_id = ?)"
if err := r.LookupWhere(ctx, &p, where, []any{s.PublicId}); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to lookup parent public id from public id"))
}
default:
if err := r.LookupWhere(ctx, &p, "public_id = ?", []any{s.ParentId}); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to lookup parent from public id"))
}
}
return &p, nil
}
}
// TableName returns the tablename to override the default gorm table name
func (s *Scope) TableName() string {
if s.tableName != "" {
return s.tableName
}
return defaultScopeTableName
}
// SetTableName sets the tablename and satisfies the ReplayableMessage
// interface. If the caller attempts to set the name to "" the name will be
// reset to the default name.
func (s *Scope) SetTableName(n string) {
s.tableName = n
}