Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cli/auth/createPermissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ type CreatePermissions struct {
Limits Limits `json:"limits"`
Users []UserId `json:"users"`
Roles []Role `json:"roles"`
FromId []string `json:"from"`
FromType []string `json:"from-type"`
TimeToLive uint64 `json:"ttl"`
}

Expand Down
53 changes: 41 additions & 12 deletions cli/auth/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,16 @@ import (
// - WantTo: checks if the current user can perform the given operation on the resource.
// - Grant: grants a share to the resource.
type Resource struct {
Owners map[UserId]struct{} `json:"owner"`
Shares []Share `json:"Shares"`
Mode Mode `json:"mode"`
mu sync.RWMutex
kv jetstream.KeyValue
id *ids.StateId
Expires time.Time
revision uint64
Owners map[UserId]struct{} `json:"owner"`
Shares []Share `json:"Shares"`
Mode Mode `json:"mode"`
mu sync.RWMutex
kv jetstream.KeyValue
id *ids.StateId
AllowedFromTypes []string `json:"allowed_from_types"`
AllowedFromIds []*ids.StateId `json:"allowed_from_ids"`
Expires time.Time
revision uint64
}

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

r.mu.Lock()
defer r.mu.Unlock()
if currentUser != nil && !keepPermissions {
if !keepPermissions {
delete(r.Owners, currentUser.UserId)
}
r.Owners[newUser] = struct{}{}
Expand All @@ -123,13 +125,13 @@ func (r *Resource) ApplyPerms(id *ids.StateId, ctx context.Context, logger *zap.
}

// CanCreate Load permissions from cache if available, otherwise fetch from external source
func (r *Resource) CanCreate(id *ids.StateId, ctx context.Context, logger *zap.Logger) bool {
func (r *Resource) CanCreate(id *ids.StateId, from *ids.StateId, ctx context.Context, logger *zap.Logger) bool {
perms, err := r.getOrCreatePermissions(id, ctx, logger)
if err != nil {
logger.Error("failed to create permissions", zap.Error(err))
return false
}
return r.isUserPermitted(perms, ctx)
return r.isUserPermitted(perms, ctx) && r.AllowedFrom(from)
}

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

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

func (r *Resource) isUserPermitted(perms CreatePermissions, ctx context.Context) bool {
r.Mode = perms.Mode
for _, p := range perms.FromId {
r.AllowedFromIds = append(r.AllowedFromIds, ids.ParseStateId(p))
}
r.AllowedFromTypes = perms.FromType
r.Expires = time.Now().Add(time.Duration(perms.TimeToLive) * time.Nanosecond)
switch perms.Mode {
case AnonymousMode:
Expand Down Expand Up @@ -219,6 +225,29 @@ func (r *Resource) IsOwner(ctx context.Context) bool {
return false
}

func (r *Resource) AllowedFrom(from *ids.StateId) bool {
if len(r.AllowedFromIds) == 0 && len(r.AllowedFromTypes) == 0 {
return true
}

for _, id := range r.AllowedFromIds {
if id.Kind == from.Kind && id.Id == from.Id {
return true
}
}

for _, t := range r.AllowedFromTypes {
if ent, found := from.ToEntityId(); found && ent.Name == t {
return true
}
if orch, found := from.ToOrchestrationId(); found && orch.InstanceId == t {
return true
}
}

return false
}

// WantTo determines if the user is able to perform the specified operation on the resource.
// It accepts the operation to be performed and the context containing the user information.
// If the resource mode is set to AnonymousMode, it allows any operation.
Expand Down
15 changes: 11 additions & 4 deletions cli/auth/resourceManager.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func GetResourceManager(ctx context.Context, stream jetstream.JetStream) *Resour

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

data, err := r.kv.Get(ctx, id.ToSubject().String())
Expand All @@ -58,7 +58,7 @@ func (r *ResourceManager) DiscoverResource(ctx context.Context, id *ids.StateId,
resource.kv = r.kv
resource.id = id
resource.revision = 0
if resource.CanCreate(id, ctx, logger) {
if resource.CanCreate(id, from, ctx, logger) {
err = resource.Update(ctx, logger)
if err != nil {
return nil, err
Expand Down Expand Up @@ -120,12 +120,19 @@ func (r *ResourceManager) ToAuthContext(ctx context.Context, resource *Resource)
}
}

fromIds := []string{}
for _, f := range resource.AllowedFromIds {
fromIds = append(fromIds, f.String())
}

c := map[string]interface{}{
"contextId": map[string]string{
"id": resource.id.String(),
},
"owners": owners,
"shares": shares,
"owners": owners,
"shares": shares,
"fromTypes": resource.AllowedFromTypes,
"fromIds": fromIds,
}

return json.Marshal(c)
Expand Down
7 changes: 4 additions & 3 deletions cli/glue/glue.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func NewGlue(bootstrap string, function Method, input []any, payload string) *Gl
}
}

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) {
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) {
temp, err := os.CreateTemp("", "reqbody")
if err != nil {
return nil, "", err, nil, false
Expand Down Expand Up @@ -103,7 +103,7 @@ func FromApiRequest(ctx context.Context, r *http.Request, function Method, logge
remoteAddr := strings.Split(r.RemoteAddr, ":")[0]
env["REMOTE_ADDR"] = remoteAddr

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

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

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) {
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) {
var dir string
var ok bool
if dir, ok = GetLibraryDir("glue.php"); !ok {
Expand All @@ -126,6 +126,7 @@ func (g *Glue) Execute(ctx context.Context, headers http.Header, logger *zap.Log
headers.Add("DPHP_BOOTSTRAP", g.bootstrap)
headers.Add("DPHP_FUNCTION", string(g.function))
headers.Add("DPHP_PAYLOAD", g.payload)
headers.Add("DPHP_SOURCE", from.String())

provenance := ctx.Value(appcontext.CurrentUserKey)
if provenance != nil {
Expand Down
22 changes: 17 additions & 5 deletions cli/ids/id.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,20 @@ const (
Activity IdKind = "activity"
Entity IdKind = "entity"
Orchestration IdKind = "orchestration"
API IdKind = "--api--"
SYSTEM IdKind = "--system--"
)

var ApiSource *StateId = &StateId{
Id: "--api--",
Kind: "--api--",
}

var SystemSource *StateId = &StateId{
Id: "--system--",
Kind: "--system--",
}

// subjects

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

func (id StateId) Name() string {
func (id StateId) Name() IdKind {
if before, _, found := strings.Cut(id.Id, ":"); found {
return before
return IdKind(before)
}

return string(Activity)
return Activity
}

func (id StateId) ToEntityId() (*EntityId, bool) {
Expand Down Expand Up @@ -174,6 +186,6 @@ func (id *OrchestrationId) ToStateId() *StateId {
}

type StateId struct {
Id string
Kind IdKind
Id string `json:"id,omitempty"`
Kind IdKind `json:"kind,omitempty"`
}
24 changes: 12 additions & 12 deletions cli/lib/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ func Startup(ctx context.Context, js jetstream.JetStream, logger *zap.Logger, po
defer cancel()

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

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

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

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

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

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

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

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

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

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

rs, err := rm.DiscoverResource(ctx, id.ToStateId(), logger, true)
rs, err := rm.DiscoverResource(ctx, id.ToStateId(), ids.ApiSource, logger, true)
if err != nil {
logger.Error("Failed to discover a resource for deletion", zap.Error(err))
http.Error(writer, "Not Found", http.StatusNotFound)
Expand Down Expand Up @@ -1032,7 +1032,7 @@ func authorize(
logger.Info("Authenticating with user", zap.Any("user", user))
ctx = auth.DecorateContextWithUser(ctx, user)
}
resource, err := rm.DiscoverResource(ctx, id, logger, preventCreation)
resource, err := rm.DiscoverResource(ctx, id, ids.ApiSource, logger, preventCreation)
if err != nil {
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))
http.Error(writer, "Not Authorized", http.StatusForbidden)
Expand Down
Loading