Skip to content

Commit d3d56d4

Browse files
Add from capability (#154)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added a new event wrapper to track the source of each event for improved traceability. * Introduced an attribute to specify allowed creation sources for entities. * Enhanced permission system with origin-based filtering for resource creation. * **Improvements** * Enhanced event metadata to consistently include origin information across various actions. * Improved type safety and clarity in event representations and string outputs. * Updated event property accessibility for better integration and flexibility. * Extended resource discovery and authorization to consider source identifiers. * Added explicit permission checks for share-related events to enforce access control. * Implemented attribute-based access control checks on entity and activity operations, enforcing security restrictions. * Enriched event dispatching and orchestration with source context for better provenance tracking. * Refined resource permission logic to include origin-based validation. * Added new global constants representing API and system sources for authorization context. * Included source context in HTTP API requests and authorization headers. * **Bug Fixes** * Minor corrections to type declarations in event methods for stricter typing compliance. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Robert Landers <landers.robert@gmail.com>
1 parent e49d080 commit d3d56d4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1013
-176
lines changed

cli/auth/createPermissions.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ type CreatePermissions struct {
2525
Limits Limits `json:"limits"`
2626
Users []UserId `json:"users"`
2727
Roles []Role `json:"roles"`
28+
FromId []string `json:"from"`
29+
FromType []string `json:"from-type"`
2830
TimeToLive uint64 `json:"ttl"`
2931
}
3032

cli/auth/resource.go

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,16 @@ import (
4646
// - WantTo: checks if the current user can perform the given operation on the resource.
4747
// - Grant: grants a share to the resource.
4848
type Resource struct {
49-
Owners map[UserId]struct{} `json:"owner"`
50-
Shares []Share `json:"Shares"`
51-
Mode Mode `json:"mode"`
52-
mu sync.RWMutex
53-
kv jetstream.KeyValue
54-
id *ids.StateId
55-
Expires time.Time
56-
revision uint64
49+
Owners map[UserId]struct{} `json:"owner"`
50+
Shares []Share `json:"Shares"`
51+
Mode Mode `json:"mode"`
52+
mu sync.RWMutex
53+
kv jetstream.KeyValue
54+
id *ids.StateId
55+
AllowedFromTypes []string `json:"allowed_from_types"`
56+
AllowedFromIds []*ids.StateId `json:"allowed_from_ids"`
57+
Expires time.Time
58+
revision uint64
5759
}
5860

5961
// NewResourcePermissions creates a new Resource with the specified owner and mode. If the owner is nil, the resource
@@ -101,7 +103,7 @@ func (r *Resource) ShareOwnership(newUser UserId, currentUser *User, keepPermiss
101103

102104
r.mu.Lock()
103105
defer r.mu.Unlock()
104-
if currentUser != nil && !keepPermissions {
106+
if !keepPermissions {
105107
delete(r.Owners, currentUser.UserId)
106108
}
107109
r.Owners[newUser] = struct{}{}
@@ -123,13 +125,13 @@ func (r *Resource) ApplyPerms(id *ids.StateId, ctx context.Context, logger *zap.
123125
}
124126

125127
// CanCreate Load permissions from cache if available, otherwise fetch from external source
126-
func (r *Resource) CanCreate(id *ids.StateId, ctx context.Context, logger *zap.Logger) bool {
128+
func (r *Resource) CanCreate(id *ids.StateId, from *ids.StateId, ctx context.Context, logger *zap.Logger) bool {
127129
perms, err := r.getOrCreatePermissions(id, ctx, logger)
128130
if err != nil {
129131
logger.Error("failed to create permissions", zap.Error(err))
130132
return false
131133
}
132-
return r.isUserPermitted(perms, ctx)
134+
return r.isUserPermitted(perms, ctx) && r.AllowedFrom(from)
133135
}
134136

135137
func (r *Resource) getOrCreatePermissions(id *ids.StateId, ctx context.Context, logger *zap.Logger) (CreatePermissions, error) {
@@ -146,7 +148,7 @@ func (r *Resource) getOrCreatePermissions(id *ids.StateId, ctx context.Context,
146148

147149
glu := glue.NewGlue(ctx.Value("bootstrap").(string), glue.GetPermissions, make([]any, 0), result.Name())
148150
env := map[string]string{"STATE_ID": id.String()}
149-
_, headers, _, _ := glu.Execute(ctx, make(http.Header), logger, env, nil, id)
151+
_, headers, _, _ := glu.Execute(ctx, make(http.Header), logger, env, nil, id, ids.SystemSource)
150152
data := headers.Get("Permissions")
151153
if err = json.Unmarshal([]byte(data), &perms); err != nil {
152154
return perms, err
@@ -158,6 +160,10 @@ func (r *Resource) getOrCreatePermissions(id *ids.StateId, ctx context.Context,
158160

159161
func (r *Resource) isUserPermitted(perms CreatePermissions, ctx context.Context) bool {
160162
r.Mode = perms.Mode
163+
for _, p := range perms.FromId {
164+
r.AllowedFromIds = append(r.AllowedFromIds, ids.ParseStateId(p))
165+
}
166+
r.AllowedFromTypes = perms.FromType
161167
r.Expires = time.Now().Add(time.Duration(perms.TimeToLive) * time.Nanosecond)
162168
switch perms.Mode {
163169
case AnonymousMode:
@@ -219,6 +225,29 @@ func (r *Resource) IsOwner(ctx context.Context) bool {
219225
return false
220226
}
221227

228+
func (r *Resource) AllowedFrom(from *ids.StateId) bool {
229+
if len(r.AllowedFromIds) == 0 && len(r.AllowedFromTypes) == 0 {
230+
return true
231+
}
232+
233+
for _, id := range r.AllowedFromIds {
234+
if id.Kind == from.Kind && id.Id == from.Id {
235+
return true
236+
}
237+
}
238+
239+
for _, t := range r.AllowedFromTypes {
240+
if ent, found := from.ToEntityId(); found && ent.Name == t {
241+
return true
242+
}
243+
if orch, found := from.ToOrchestrationId(); found && orch.InstanceId == t {
244+
return true
245+
}
246+
}
247+
248+
return false
249+
}
250+
222251
// WantTo determines if the user is able to perform the specified operation on the resource.
223252
// It accepts the operation to be performed and the context containing the user information.
224253
// If the resource mode is set to AnonymousMode, it allows any operation.

cli/auth/resourceManager.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ func GetResourceManager(ctx context.Context, stream jetstream.JetStream) *Resour
4747

4848
// DiscoverResource is a method of the ResourceManager struct that is responsible for discovering a resource based on
4949
// the provided context, state ID, logger, and preventCreation flag
50-
func (r *ResourceManager) DiscoverResource(ctx context.Context, id *ids.StateId, logger *zap.Logger, preventCreation bool) (*Resource, error) {
50+
func (r *ResourceManager) DiscoverResource(ctx context.Context, id *ids.StateId, from *ids.StateId, logger *zap.Logger, preventCreation bool) (*Resource, error) {
5151
currentUser, _ := ctx.Value(appcontext.CurrentUserKey).(*User)
5252

5353
data, err := r.kv.Get(ctx, id.ToSubject().String())
@@ -58,7 +58,7 @@ func (r *ResourceManager) DiscoverResource(ctx context.Context, id *ids.StateId,
5858
resource.kv = r.kv
5959
resource.id = id
6060
resource.revision = 0
61-
if resource.CanCreate(id, ctx, logger) {
61+
if resource.CanCreate(id, from, ctx, logger) {
6262
err = resource.Update(ctx, logger)
6363
if err != nil {
6464
return nil, err
@@ -120,12 +120,19 @@ func (r *ResourceManager) ToAuthContext(ctx context.Context, resource *Resource)
120120
}
121121
}
122122

123+
fromIds := []string{}
124+
for _, f := range resource.AllowedFromIds {
125+
fromIds = append(fromIds, f.String())
126+
}
127+
123128
c := map[string]interface{}{
124129
"contextId": map[string]string{
125130
"id": resource.id.String(),
126131
},
127-
"owners": owners,
128-
"shares": shares,
132+
"owners": owners,
133+
"shares": shares,
134+
"fromTypes": resource.AllowedFromTypes,
135+
"fromIds": fromIds,
129136
}
130137

131138
return json.Marshal(c)

cli/glue/glue.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ func NewGlue(bootstrap string, function Method, input []any, payload string) *Gl
7373
}
7474
}
7575

76-
func FromApiRequest(ctx context.Context, r *http.Request, function Method, logger *zap.Logger, stream jetstream.JetStream, id *ids.StateId, headers http.Header) ([]*nats.Msg, string, error, *http.Header, bool) {
76+
func FromApiRequest(ctx context.Context, r *http.Request, function Method, logger *zap.Logger, stream jetstream.JetStream, id *ids.StateId, from *ids.StateId, headers http.Header) ([]*nats.Msg, string, error, *http.Header, bool) {
7777
temp, err := os.CreateTemp("", "reqbody")
7878
if err != nil {
7979
return nil, "", err, nil, false
@@ -103,7 +103,7 @@ func FromApiRequest(ctx context.Context, r *http.Request, function Method, logge
103103
remoteAddr := strings.Split(r.RemoteAddr, ":")[0]
104104
env["REMOTE_ADDR"] = remoteAddr
105105

106-
msgs, responseHeaders, _, deleteAfter := glu.Execute(ctx, headers, logger, env, stream, id)
106+
msgs, responseHeaders, _, deleteAfter := glu.Execute(ctx, headers, logger, env, stream, id, from)
107107

108108
for _, msg := range msgs {
109109
msg.Header.Add("Remote-Addr", remoteAddr)
@@ -112,7 +112,7 @@ func FromApiRequest(ctx context.Context, r *http.Request, function Method, logge
112112
return msgs, temp.Name(), nil, &responseHeaders, deleteAfter
113113
}
114114

115-
func (g *Glue) Execute(ctx context.Context, headers http.Header, logger *zap.Logger, env map[string]string, stream jetstream.JetStream, id *ids.StateId) ([]*nats.Msg, http.Header, int, bool) {
115+
func (g *Glue) Execute(ctx context.Context, headers http.Header, logger *zap.Logger, env map[string]string, stream jetstream.JetStream, id *ids.StateId, from *ids.StateId) ([]*nats.Msg, http.Header, int, bool) {
116116
var dir string
117117
var ok bool
118118
if dir, ok = GetLibraryDir("glue.php"); !ok {
@@ -126,6 +126,7 @@ func (g *Glue) Execute(ctx context.Context, headers http.Header, logger *zap.Log
126126
headers.Add("DPHP_BOOTSTRAP", g.bootstrap)
127127
headers.Add("DPHP_FUNCTION", string(g.function))
128128
headers.Add("DPHP_PAYLOAD", g.payload)
129+
headers.Add("DPHP_SOURCE", from.String())
129130

130131
provenance := ctx.Value(appcontext.CurrentUserKey)
131132
if provenance != nil {

cli/ids/id.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,20 @@ const (
1212
Activity IdKind = "activity"
1313
Entity IdKind = "entity"
1414
Orchestration IdKind = "orchestration"
15+
API IdKind = "--api--"
16+
SYSTEM IdKind = "--system--"
1517
)
1618

19+
var ApiSource *StateId = &StateId{
20+
Id: "--api--",
21+
Kind: "--api--",
22+
}
23+
24+
var SystemSource *StateId = &StateId{
25+
Id: "--system--",
26+
Kind: "--system--",
27+
}
28+
1729
// subjects
1830

1931
type Subject struct {
@@ -73,12 +85,12 @@ func (id StateId) String() string {
7385
return fmt.Sprintf("%s:%s", id.Kind, id.Id)
7486
}
7587

76-
func (id StateId) Name() string {
88+
func (id StateId) Name() IdKind {
7789
if before, _, found := strings.Cut(id.Id, ":"); found {
78-
return before
90+
return IdKind(before)
7991
}
8092

81-
return string(Activity)
93+
return Activity
8294
}
8395

8496
func (id StateId) ToEntityId() (*EntityId, bool) {
@@ -174,6 +186,6 @@ func (id *OrchestrationId) ToStateId() *StateId {
174186
}
175187

176188
type StateId struct {
177-
Id string
178-
Kind IdKind
189+
Id string `json:"id,omitempty"`
190+
Kind IdKind `json:"kind,omitempty"`
179191
}

cli/lib/api.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ func Startup(ctx context.Context, js jetstream.JetStream, logger *zap.Logger, po
262262
defer cancel()
263263

264264
rm := auth.GetResourceManager(ctx, js)
265-
res, err := rm.DiscoverResource(ctx, id, logger, true)
265+
res, err := rm.DiscoverResource(ctx, id, ids.ApiSource, logger, true)
266266
if err != nil {
267267
logger.Error("DiscoverResource", zap.Error(err))
268268
panic(err)
@@ -272,7 +272,7 @@ func Startup(ctx context.Context, js jetstream.JetStream, logger *zap.Logger, po
272272
headers.Add("DPHP_AUTH_CONTEXT", string(ac))
273273
}
274274

275-
msgs, stateFile, err, responseHeaders, deleteAfter := glue.FromApiRequest(ctx, request, function, logger, js, id, headers)
275+
msgs, stateFile, err, responseHeaders, deleteAfter := glue.FromApiRequest(ctx, request, function, logger, js, id, ids.ApiSource, headers)
276276
if err != nil {
277277
http.Error(writer, "Internal Server Error", http.StatusInternalServerError)
278278
logger.Error("Failed to glue", zap.Error(err))
@@ -312,7 +312,7 @@ func Startup(ctx context.Context, js jetstream.JetStream, logger *zap.Logger, po
312312
}
313313

314314
if deleteAfter {
315-
resource, err := rm.DiscoverResource(ctx, id, logger, false)
315+
resource, err := rm.DiscoverResource(ctx, id, ids.ApiSource, logger, false)
316316
if err != nil {
317317
logger.Error("Unable to delete resource", zap.Error(err))
318318
http.Error(writer, "Internal Server Error", http.StatusInternalServerError)
@@ -354,7 +354,7 @@ func Startup(ctx context.Context, js jetstream.JetStream, logger *zap.Logger, po
354354
return
355355
}
356356

357-
r, err := rm.DiscoverResource(ctx, stateId, logger, true)
357+
r, err := rm.DiscoverResource(ctx, stateId, ids.ApiSource, logger, true)
358358
if err != nil {
359359
logger.Error("Failed to discover resource", zap.Error(err))
360360
http.Error(writer, "Not Found", http.StatusNotFound)
@@ -430,7 +430,7 @@ func Startup(ctx context.Context, js jetstream.JetStream, logger *zap.Logger, po
430430
return
431431
}
432432

433-
r, err := rm.DiscoverResource(ctx, stateId, logger, true)
433+
r, err := rm.DiscoverResource(ctx, stateId, ids.ApiSource, logger, true)
434434
if err != nil {
435435
logger.Error("Failed to discover resource", zap.Error(err))
436436
http.Error(writer, "", http.StatusNotFound)
@@ -485,7 +485,7 @@ func Startup(ctx context.Context, js jetstream.JetStream, logger *zap.Logger, po
485485
return
486486
}
487487

488-
r, err := rm.DiscoverResource(ctx, stateId, logger, true)
488+
r, err := rm.DiscoverResource(ctx, stateId, ids.ApiSource, logger, true)
489489
if err != nil {
490490
logger.Error("Failed to discover resource", zap.Error(err))
491491
http.Error(writer, "", http.StatusNotFound)
@@ -571,7 +571,7 @@ func Startup(ctx context.Context, js jetstream.JetStream, logger *zap.Logger, po
571571
}
572572

573573
logger.Debug("Delete entity", zap.String("id", id.String()))
574-
rs, err := rm.DiscoverResource(ctx, id.ToStateId(), logger, true)
574+
rs, err := rm.DiscoverResource(ctx, id.ToStateId(), ids.ApiSource, logger, true)
575575
if err != nil {
576576
logger.Error("Failed to discover resource", zap.Error(err))
577577
http.Error(writer, "Not Found", http.StatusNotFound)
@@ -682,7 +682,7 @@ func Startup(ctx context.Context, js jetstream.JetStream, logger *zap.Logger, po
682682
return
683683
}
684684

685-
r, err := rm.DiscoverResource(ctx, stateId, logger, true)
685+
r, err := rm.DiscoverResource(ctx, stateId, ids.ApiSource, logger, true)
686686
if err != nil {
687687
logger.Error("Failed to discover resource", zap.Error(err))
688688
http.Error(writer, "Not Found", http.StatusNotFound)
@@ -758,7 +758,7 @@ func Startup(ctx context.Context, js jetstream.JetStream, logger *zap.Logger, po
758758
return
759759
}
760760

761-
r, err := rm.DiscoverResource(ctx, stateId, logger, true)
761+
r, err := rm.DiscoverResource(ctx, stateId, ids.ApiSource, logger, true)
762762
if err != nil {
763763
logger.Error("Failed to discover resource", zap.Error(err))
764764
http.Error(writer, "", http.StatusNotFound)
@@ -813,7 +813,7 @@ func Startup(ctx context.Context, js jetstream.JetStream, logger *zap.Logger, po
813813
return
814814
}
815815

816-
r, err := rm.DiscoverResource(ctx, stateId, logger, true)
816+
r, err := rm.DiscoverResource(ctx, stateId, ids.ApiSource, logger, true)
817817
if err != nil {
818818
logger.Error("Failed to discover resource", zap.Error(err))
819819
http.Error(writer, "", http.StatusNotFound)
@@ -878,7 +878,7 @@ func Startup(ctx context.Context, js jetstream.JetStream, logger *zap.Logger, po
878878
return
879879
}
880880

881-
rs, err := rm.DiscoverResource(ctx, id.ToStateId(), logger, true)
881+
rs, err := rm.DiscoverResource(ctx, id.ToStateId(), ids.ApiSource, logger, true)
882882
if err != nil {
883883
logger.Error("Failed to discover a resource for deletion", zap.Error(err))
884884
http.Error(writer, "Not Found", http.StatusNotFound)
@@ -1032,7 +1032,7 @@ func authorize(
10321032
logger.Info("Authenticating with user", zap.Any("user", user))
10331033
ctx = auth.DecorateContextWithUser(ctx, user)
10341034
}
1035-
resource, err := rm.DiscoverResource(ctx, id, logger, preventCreation)
1035+
resource, err := rm.DiscoverResource(ctx, id, ids.ApiSource, logger, preventCreation)
10361036
if err != nil {
10371037
logger.Warn("User attempted to create new resource not authorized to create", zap.Any("id", id.String()), zap.Any("user", auth.GetUserFromContext(ctx)), zap.Error(err))
10381038
http.Error(writer, "Not Authorized", http.StatusForbidden)

0 commit comments

Comments
 (0)