From d915d0c541c0af92b68cb6a30c7312f31c7b22c7 Mon Sep 17 00:00:00 2001 From: totegamma Date: Mon, 1 Jul 2024 00:51:36 +0900 Subject: [PATCH 01/22] update policy model --- core/model.go | 8 +++++--- x/policy/service.go | 9 ++------- x/policy/service_test.go | 4 ---- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/core/model.go b/core/model.go index 9619f68b..75d8fe06 100644 --- a/core/model.go +++ b/core/model.go @@ -30,9 +30,11 @@ type Policy struct { } type Statement struct { - Actions []string `json:"actions"` - Effect string `json:"effect"` - Condition Expr `json:"condition"` + Actions []string `json:"actions"` + Dominant bool `json:"dominant"` + DefaultOnTrue bool `json:"defaultOnTrue"` + DefaultOnFalse bool `json:"defaultOnFalse"` + Condition Expr `json:"condition"` } type Expr struct { diff --git a/x/policy/service.go b/x/policy/service.go index 0b91b5cd..37abd570 100644 --- a/x/policy/service.go +++ b/x/policy/service.go @@ -71,13 +71,8 @@ func (s service) Test(ctx context.Context, policy core.Policy, context core.Requ return false, err } - if statement.Effect == "allow" { - span.End() - return result_bool, nil - } else { - span.End() - return !result_bool, nil - } + span.End() + return result_bool, nil } } span.SetAttributes(attribute.Bool("examined", false)) diff --git a/x/policy/service_test.go b/x/policy/service_test.go index d7be453c..9354a25a 100644 --- a/x/policy/service_test.go +++ b/x/policy/service_test.go @@ -29,7 +29,6 @@ func TestPolicyTimelineCreatorIsLocalUserOrHasTag(t *testing.T) { Statements: []core.Statement{ { Actions: []string{"timeline"}, - Effect: "allow", // allow: これにマッチしなければ拒否 Condition: core.Expr{ Operator: "Or", Args: []core.Expr{ @@ -67,7 +66,6 @@ func TestPolicyTimelineLimitAccess(t *testing.T) { Statements: []core.Statement{ { Actions: []string{"distribute", "GET:/message/*"}, - Effect: "allow", Condition: core.Expr{ Operator: "Contains", Args: []core.Expr{ @@ -111,7 +109,6 @@ func TestPolicyTimelineLimitMessageSchema(t *testing.T) { Statements: []core.Statement{ { Actions: []string{"distribute"}, - Effect: "allow", Condition: core.Expr{ Operator: "Contains", Args: []core.Expr{ @@ -156,7 +153,6 @@ func TestPolicyMessageLimitAction(t *testing.T) { Statements: []core.Statement{ { Actions: []string{"association"}, - Effect: "allow", Condition: core.Expr{ Operator: "Contains", Args: []core.Expr{ From 81c80382cdd9186c06de1007bf15470e422624a7 Mon Sep 17 00:00:00 2001 From: totegamma Date: Mon, 1 Jul 2024 00:59:48 +0900 Subject: [PATCH 02/22] change request context --- core/model.go | 3 ++- x/message/service.go | 8 ++++---- x/timeline/service.go | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/core/model.go b/core/model.go index 75d8fe06..c023f109 100644 --- a/core/model.go +++ b/core/model.go @@ -19,7 +19,8 @@ type Chunk struct { type RequestContext struct { Requester Entity Document any - Self any + Parent any + Resource any Params map[string]any } diff --git a/x/message/service.go b/x/message/service.go index 703391e3..6aa3b1bc 100644 --- a/x/message/service.go +++ b/x/message/service.go @@ -106,7 +106,7 @@ func (s *service) Get(ctx context.Context, id string, requester string) (core.Me ctx, timeline.Policy, core.RequestContext{ - Self: timeline, + Parent: timeline, Params: params, }, action, @@ -175,7 +175,7 @@ func (s *service) GetWithOwnAssociations(ctx context.Context, id string, request } requestContext := core.RequestContext{ - Self: timeline, + Parent: timeline, Params: params, Requester: requesterEntity, } @@ -286,7 +286,7 @@ func (s *service) Create(ctx context.Context, mode core.CommitMode, document str ctx, timeline.Policy, core.RequestContext{ - Self: timeline, + Parent: timeline, Params: params, }, "GET:/message/", @@ -457,7 +457,7 @@ func (s *service) Delete(ctx context.Context, mode core.CommitMode, document, si ctx, timeline.Policy, core.RequestContext{ - Self: timeline, + Parent: timeline, Params: params, }, "GET:/message/", diff --git a/x/timeline/service.go b/x/timeline/service.go index 033e5000..171c48f2 100644 --- a/x/timeline/service.go +++ b/x/timeline/service.go @@ -396,7 +396,7 @@ func (s *service) PostItem(ctx context.Context, timeline string, item core.Timel } requestContext := core.RequestContext{ - Self: tl, + Parent: tl, Params: params, Requester: requesterEntity, } @@ -431,7 +431,7 @@ func (s *service) PostItem(ctx context.Context, timeline string, item core.Timel } requestContext := core.RequestContext{ - Self: tl, + Parent: tl, Params: params, Requester: requesterEntity, } From 776bd611994615a1baac609c62eaceaaf780b18e Mon Sep 17 00:00:00 2001 From: totegamma Date: Sat, 13 Jul 2024 14:31:19 +0900 Subject: [PATCH 03/22] add policy magnitude --- core/const.go | 10 +++++ core/interfaces.go | 5 ++- x/policy/functions.go | 23 ++++++++++ x/policy/service.go | 61 ++++++++++++++++++++++----- x/policy/service_test.go | 33 +++++++++++++++ x/timeline/service.go | 90 +++++++++++++--------------------------- 6 files changed, 149 insertions(+), 73 deletions(-) diff --git a/core/const.go b/core/const.go index dc28a1cd..557398ba 100644 --- a/core/const.go +++ b/core/const.go @@ -37,6 +37,16 @@ const ( CommitModeLocalOnlyExec ) +type PolicyEvalResult int + +const ( + PolicyEvalResultDefault PolicyEvalResult = iota + PolicyEvalResultNever + PolicyEvalResultDeny + PolicyEvalResultAllow + PolicyEvalResultAlways +) + const ( Unknown = iota LocalUser diff --git a/core/interfaces.go b/core/interfaces.go index f6c8403f..d60eaeec 100644 --- a/core/interfaces.go +++ b/core/interfaces.go @@ -88,8 +88,9 @@ type MessageService interface { } type PolicyService interface { - Test(ctx context.Context, policy Policy, context RequestContext, action string) (bool, error) - TestWithPolicyURL(ctx context.Context, url string, context RequestContext, action string) (bool, error) + Test(ctx context.Context, policy Policy, context RequestContext, action string) (PolicyEvalResult, error) + TestWithPolicyURL(ctx context.Context, url string, context RequestContext, action string) (PolicyEvalResult, error) + TestWithGlobalPolicy(ctx context.Context, context RequestContext, action string) (PolicyEvalResult, error) } type ProfileService interface { diff --git a/x/policy/functions.go b/x/policy/functions.go index d74a50fd..f60f8b29 100644 --- a/x/policy/functions.go +++ b/x/policy/functions.go @@ -6,8 +6,31 @@ import ( "reflect" "regexp" "strings" + + "github.com/totegamma/concurrent/core" ) +func Summerize(results []core.PolicyEvalResult) bool { + result := false + + for _, r := range results { + switch r { + case core.PolicyEvalResultAlways: + return true + case core.PolicyEvalResultNever: + return false + case core.PolicyEvalResultAllow: + result = true + case core.PolicyEvalResultDeny: + result = false + case core.PolicyEvalResultDefault: + continue + } + } + + return result +} + func debugPrint(comment string, v interface{}) { b, _ := json.MarshalIndent(v, "", " ") fmt.Println(comment, string(b)) diff --git a/x/policy/service.go b/x/policy/service.go index 37abd570..e89bf198 100644 --- a/x/policy/service.go +++ b/x/policy/service.go @@ -17,36 +17,47 @@ import ( var tracer = otel.Tracer("policy") type service struct { - repository Repository - config core.Config + repository Repository + policyConfig core.PolicyConfig + config core.Config } -func NewService(repository Repository, config core.Config) core.PolicyService { +func NewService(repository Repository, globalPolicy core.Policy, config core.Config) core.PolicyService { return &service{ repository, + globalPolicy, config, } } -func (s service) TestWithPolicyURL(ctx context.Context, url string, context core.RequestContext, action string) (bool, error) { +func (s service) TestWithGlobalPolicy(ctx context.Context, context core.RequestContext, action string) (core.PolicyEvalResult, error) { + ctx, span := tracer.Start(ctx, "Policy.Service.TestWithGlobalPolicy") + defer span.End() + + return s.Test(ctx, s.globalPolicy, context, action) +} + +func (s service) TestWithPolicyURL(ctx context.Context, url string, context core.RequestContext, action string) (core.PolicyEvalResult, error) { ctx, span := tracer.Start(ctx, "Policy.Service.TestWithPolicyURL") defer span.End() policy, err := s.repository.Get(ctx, url) if err != nil { span.SetStatus(codes.Error, err.Error()) - return false, err + return core.PolicyEvalResultDefault, err } return s.Test(ctx, policy, context, action) } -func (s service) Test(ctx context.Context, policy core.Policy, context core.RequestContext, action string) (bool, error) { +func (s service) Test(ctx context.Context, policy core.Policy, context core.RequestContext, action string) (core.PolicyEvalResult, error) { ctx, span := tracer.Start(ctx, "Policy.Service.Test") defer span.End() span.SetAttributes(attribute.String("action", action)) + results := make([]core.PolicyEvalResult, 0) + for _, statement := range policy.Statements { _, span := tracer.Start(ctx, "Policy.Service.Test.Statement") for _, a := range statement.Actions { @@ -60,7 +71,7 @@ func (s service) Test(ctx context.Context, policy core.Policy, context core.Requ if err != nil { span.SetStatus(codes.Error, err.Error()) span.End() - return false, err + return core.PolicyEvalResultDefault, err } result_bool, ok := result.Result.(bool) @@ -68,17 +79,47 @@ func (s service) Test(ctx context.Context, policy core.Policy, context core.Requ err := fmt.Errorf("bad argument type for Policy. Expected bool but got %s\n", reflect.TypeOf(result).String()) span.SetStatus(codes.Error, err.Error()) span.End() - return false, err + return core.PolicyEvalResultDefault, err } span.End() - return result_bool, nil + if statement.DefaultOnTrue && result_bool { + results = append(results, core.PolicyEvalResultDefault) + } else if statement.DefaultOnFalse && !result_bool { + results = append(results, core.PolicyEvalResultDefault) + } else if statement.Dominant && result_bool { + results = append(results, core.PolicyEvalResultAlways) + } else if statement.Dominant && !result_bool { + results = append(results, core.PolicyEvalResultNever) + } else if result_bool { + results = append(results, core.PolicyEvalResultAllow) + } else { + results = append(results, core.PolicyEvalResultDeny) + } } } span.SetAttributes(attribute.Bool("examined", false)) span.End() } - return false, nil + + result := core.PolicyEvalResultDefault + + for _, r := range results { + switch r { + case core.PolicyEvalResultAlways: + return core.PolicyEvalResultAlways, nil + case core.PolicyEvalResultNever: + return core.PolicyEvalResultNever, nil + case core.PolicyEvalResultAllow: + result = core.PolicyEvalResultAllow + case core.PolicyEvalResultDeny: + result = core.PolicyEvalResultDeny + case core.PolicyEvalResultDefault: + continue + } + } + + return result, nil } func (s service) eval(expr core.Expr, requestCtx core.RequestContext) (core.EvalResult, error) { diff --git a/x/policy/service_test.go b/x/policy/service_test.go index 9354a25a..7e77e239 100644 --- a/x/policy/service_test.go +++ b/x/policy/service_test.go @@ -2,6 +2,7 @@ package policy import ( "context" + "encoding/json" "github.com/stretchr/testify/assert" "testing" @@ -11,8 +12,40 @@ import ( var s service var ctx = context.Background() +var globalPolicyJson = ` +{ + "global": { + "dominant": true, + "defaultOnTrue": true, + "condition": { + "op": "Not", + "args": [ + { + "op": "Or", + "args": [ + { + "op": "RequesterDomainHasTag", + "const": "_block" + }, + { + "op": "RequesterHasTag", + "const": "_block" + } + ] + } + ] + }, + }, + "bases": [ + ] +} +` + func TestMain(m *testing.M) { + var globalPolicy core.Policy + json.Unmarshal([]byte(globalPolicyJson), &globalPolicy) + s = service{ config: core.Config{}, } diff --git a/x/timeline/service.go b/x/timeline/service.go index 171c48f2..b5be9a38 100644 --- a/x/timeline/service.go +++ b/x/timeline/service.go @@ -15,6 +15,7 @@ import ( "github.com/totegamma/concurrent/cdid" "github.com/totegamma/concurrent/core" + "github.com/totegamma/concurrent/x/policy" ) type service struct { @@ -370,83 +371,50 @@ func (s *service) PostItem(ctx context.Context, timeline string, item core.Timel } var writable bool + results := make([]core.PolicyEvalResult, 0) if tl.Author == author { writable = true - goto skipAuth } - if tl.DomainOwned { - writable = true - if tl.Policy != "" { - var params map[string]any = make(map[string]any) - if tl.PolicyParams != nil { - err := json.Unmarshal([]byte(*tl.PolicyParams), ¶ms) - if err != nil { - span.SetStatus(codes.Error, err.Error()) - span.RecordError(err) - goto skipAuth - } - } + requesterEntity, err := s.entity.Get(ctx, author) + if err != nil { + span.RecordError(err) + } - requesterEntity, err := s.entity.Get(ctx, author) - if err != nil { - span.RecordError(err) - goto skipAuth - } + requestContext := core.RequestContext{ + Parent: tl, + Requester: requesterEntity, + } - requestContext := core.RequestContext{ - Parent: tl, - Params: params, - Requester: requesterEntity, - } + result, err := s.policy.TestWithGlobalPolicy(ctx, requestContext, "distribute") + if err != nil { + span.RecordError(err) + goto skipAuth + } + results = append(results, result) - ok, err := s.policy.TestWithPolicyURL(ctx, tl.Policy, requestContext, "distribute") + if tl.Policy != "" { + var params map[string]any = make(map[string]any) + if tl.PolicyParams != nil { + err := json.Unmarshal([]byte(*tl.PolicyParams), ¶ms) if err != nil { + span.SetStatus(codes.Error, err.Error()) span.RecordError(err) goto skipAuth } - - if !ok { - writable = false - } } - } else { - writable = false - if tl.Policy != "" { - var params map[string]any = make(map[string]any) - if tl.PolicyParams != nil { - err := json.Unmarshal([]byte(*tl.PolicyParams), ¶ms) - if err != nil { - span.SetStatus(codes.Error, err.Error()) - span.RecordError(err) - goto skipAuth - } - } - - requesterEntity, err := s.entity.Get(ctx, author) - if err != nil { - span.RecordError(err) - goto skipAuth - } - - requestContext := core.RequestContext{ - Parent: tl, - Params: params, - Requester: requesterEntity, - } - ok, err := s.policy.TestWithPolicyURL(ctx, tl.Policy, requestContext, "distribute") - if err != nil { - span.RecordError(err) - goto skipAuth - } - - if ok { - writable = true - } + result, err = s.policy.TestWithPolicyURL(ctx, tl.Policy, requestContext, "distribute") + if err != nil { + span.RecordError(err) + goto skipAuth } + results = append(results, result) } + + writable = policy.Summerize(results) + skipAuth: if !writable { From 8a43fa8cb6399024d7a4f69fa26cf376e8af6e5d Mon Sep 17 00:00:00 2001 From: totegamma Date: Mon, 15 Jul 2024 00:12:31 +0900 Subject: [PATCH 04/22] update policy test --- core/model.go | 30 +-- internal/testutil/dockertest.go | 12 +- x/policy/service.go | 207 +++++++++++++------ x/policy/service_test.go | 338 ++++++++++++++++++++++++++------ 4 files changed, 452 insertions(+), 135 deletions(-) diff --git a/core/model.go b/core/model.go index c023f109..fda330c2 100644 --- a/core/model.go +++ b/core/model.go @@ -17,25 +17,29 @@ type Chunk struct { } type RequestContext struct { - Requester Entity - Document any - Parent any - Resource any - Params map[string]any + Requester Entity + RequesterDomain Domain + Document any + Self any + Resource any + Params map[string]any +} + +type PolicyDocument struct { + Name string `json:"name"` + Description string `json:"description"` + Versions map[string][]Policy `json:"versions"` } type Policy struct { - Name string `json:"name"` - Version string `json:"version"` - Statements []Statement `json:"statements"` + Statements map[string]Statement `json:"statements"` } type Statement struct { - Actions []string `json:"actions"` - Dominant bool `json:"dominant"` - DefaultOnTrue bool `json:"defaultOnTrue"` - DefaultOnFalse bool `json:"defaultOnFalse"` - Condition Expr `json:"condition"` + Dominant bool `json:"dominant"` + DefaultOnTrue bool `json:"defaultOnTrue"` + DefaultOnFalse bool `json:"defaultOnFalse"` + Condition Expr `json:"condition"` } type Expr struct { diff --git a/internal/testutil/dockertest.go b/internal/testutil/dockertest.go index 0df20078..0c4cf173 100644 --- a/internal/testutil/dockertest.go +++ b/internal/testutil/dockertest.go @@ -1,6 +1,7 @@ package testutil import ( + "context" "encoding/json" "fmt" "log" @@ -45,11 +46,20 @@ func SetupMockTraceProvider() *tracetest.InMemoryExporter { return spanChecker } -func printJson(v interface{}) { +func PrintJson(v interface{}) { b, _ := json.MarshalIndent(v, "", " ") log.Println(string(b)) } +func SetupTraceCtx() (context.Context, string) { + ctx, span := tracer.Start(context.Background(), "testRoot") + defer span.End() + + traceID := span.SpanContext().TraceID().String() + + return ctx, traceID +} + func CreateHttpRequest() (echo.Context, *http.Request, *httptest.ResponseRecorder, string) { e := echo.New() req := httptest.NewRequest(http.MethodGet, "/", nil) diff --git a/x/policy/service.go b/x/policy/service.go index e89bf198..cd2406ae 100644 --- a/x/policy/service.go +++ b/x/policy/service.go @@ -12,14 +12,15 @@ import ( "go.opentelemetry.io/otel/codes" "github.com/totegamma/concurrent/core" + "github.com/totegamma/concurrent/internal/testutil" ) var tracer = otel.Tracer("policy") type service struct { - repository Repository - policyConfig core.PolicyConfig - config core.Config + repository Repository + global core.Policy + config core.Config } func NewService(repository Repository, globalPolicy core.Policy, config core.Config) core.PolicyService { @@ -34,7 +35,7 @@ func (s service) TestWithGlobalPolicy(ctx context.Context, context core.RequestC ctx, span := tracer.Start(ctx, "Policy.Service.TestWithGlobalPolicy") defer span.End() - return s.Test(ctx, s.globalPolicy, context, action) + return s.Test(ctx, s.global, context, action) } func (s service) TestWithPolicyURL(ctx context.Context, url string, context core.RequestContext, action string) (core.PolicyEvalResult, error) { @@ -56,70 +57,41 @@ func (s service) Test(ctx context.Context, policy core.Policy, context core.Requ span.SetAttributes(attribute.String("action", action)) - results := make([]core.PolicyEvalResult, 0) - - for _, statement := range policy.Statements { - _, span := tracer.Start(ctx, "Policy.Service.Test.Statement") - for _, a := range statement.Actions { - span.SetAttributes(attribute.StringSlice("actions", statement.Actions)) - if isActionMatch(action, a) { - span.SetAttributes(attribute.Bool("examined", true)) - - result, err := s.eval(statement.Condition, context) - resultJson, _ := json.MarshalIndent(result, "", " ") - span.SetAttributes(attribute.String("result", string(resultJson))) - if err != nil { - span.SetStatus(codes.Error, err.Error()) - span.End() - return core.PolicyEvalResultDefault, err - } - - result_bool, ok := result.Result.(bool) - if !ok { - err := fmt.Errorf("bad argument type for Policy. Expected bool but got %s\n", reflect.TypeOf(result).String()) - span.SetStatus(codes.Error, err.Error()) - span.End() - return core.PolicyEvalResultDefault, err - } - - span.End() - if statement.DefaultOnTrue && result_bool { - results = append(results, core.PolicyEvalResultDefault) - } else if statement.DefaultOnFalse && !result_bool { - results = append(results, core.PolicyEvalResultDefault) - } else if statement.Dominant && result_bool { - results = append(results, core.PolicyEvalResultAlways) - } else if statement.Dominant && !result_bool { - results = append(results, core.PolicyEvalResultNever) - } else if result_bool { - results = append(results, core.PolicyEvalResultAllow) - } else { - results = append(results, core.PolicyEvalResultDeny) - } - } - } - span.SetAttributes(attribute.Bool("examined", false)) - span.End() + statement, ok := policy.Statements[action] + if !ok { + testutil.PrintJson(policy) + span.SetAttributes(attribute.String("debug", "no rule")) + return core.PolicyEvalResultDefault, nil } - result := core.PolicyEvalResultDefault - - for _, r := range results { - switch r { - case core.PolicyEvalResultAlways: - return core.PolicyEvalResultAlways, nil - case core.PolicyEvalResultNever: - return core.PolicyEvalResultNever, nil - case core.PolicyEvalResultAllow: - result = core.PolicyEvalResultAllow - case core.PolicyEvalResultDeny: - result = core.PolicyEvalResultDeny - case core.PolicyEvalResultDefault: - continue - } + result, err := s.eval(statement.Condition, context) + resultJson, _ := json.MarshalIndent(result, "", " ") + span.SetAttributes(attribute.String("result", string(resultJson))) + if err != nil { + span.SetStatus(codes.Error, err.Error()) + return core.PolicyEvalResultDefault, err } - return result, nil + result_bool, ok := result.Result.(bool) + if !ok { + err := fmt.Errorf("bad argument type for Policy. Expected bool but got %s\n", reflect.TypeOf(result).String()) + span.SetStatus(codes.Error, err.Error()) + return core.PolicyEvalResultDefault, err + } + + if statement.DefaultOnTrue && result_bool { + return core.PolicyEvalResultDefault, nil + } else if statement.DefaultOnFalse && !result_bool { + return core.PolicyEvalResultDefault, nil + } else if statement.Dominant && result_bool { + return core.PolicyEvalResultAlways, nil + } else if statement.Dominant && !result_bool { + return core.PolicyEvalResultNever, nil + } else if result_bool { + return core.PolicyEvalResultAllow, nil + } else { + return core.PolicyEvalResultDeny, nil + } } func (s service) eval(expr core.Expr, requestCtx core.RequestContext) (core.EvalResult, error) { @@ -205,6 +177,72 @@ func (s service) eval(expr core.Expr, requestCtx core.RequestContext) (core.Eval Args: args, Result: false, }, nil + case "Not": + if len(expr.Args) != 1 { + err := fmt.Errorf("bad argument length for NOT. Expected 1 but got %d\n", len(expr.Args)) + return core.EvalResult{ + Operator: "Not", + Error: err.Error(), + }, err + } + + arg0_raw, err := s.eval(expr.Args[0], requestCtx) + if err != nil { + return core.EvalResult{ + Operator: "Not", + Args: []core.EvalResult{arg0_raw}, + Error: err.Error(), + }, err + } + + arg0, ok := arg0_raw.Result.(bool) + if !ok { + err := fmt.Errorf("bad argument type for NOT. Expected bool but got %s\n", reflect.TypeOf(arg0_raw.Result)) + return core.EvalResult{ + Operator: "Not", + Args: []core.EvalResult{arg0_raw}, + Error: err.Error(), + }, err + } + + return core.EvalResult{ + Operator: "Not", + Args: []core.EvalResult{arg0_raw}, + Result: !arg0, + }, nil + + case "Eq": + if len(expr.Args) != 2 { + err := fmt.Errorf("bad argument length for EQ. Expected 2 but got %d\n", len(expr.Args)) + return core.EvalResult{ + Operator: "Eq", + Error: err.Error(), + }, err + } + + arg0_raw, err := s.eval(expr.Args[0], requestCtx) + if err != nil { + return core.EvalResult{ + Operator: "Eq", + Args: []core.EvalResult{arg0_raw}, + Error: err.Error(), + }, err + } + + arg1_raw, err := s.eval(expr.Args[1], requestCtx) + if err != nil { + return core.EvalResult{ + Operator: "Eq", + Args: []core.EvalResult{arg0_raw, arg1_raw}, + Error: err.Error(), + }, err + } + + return core.EvalResult{ + Operator: "Eq", + Args: []core.EvalResult{arg0_raw, arg1_raw}, + Result: arg0_raw.Result == arg1_raw.Result, + }, nil case "Const": return core.EvalResult{ @@ -314,6 +352,31 @@ func (s service) eval(expr core.Expr, requestCtx core.RequestContext) (core.Eval Result: value, }, nil + case "LoadSelf": + key, ok := expr.Constant.(string) + if !ok { + err := fmt.Errorf("bad argument type for LoadSelf. Expected string but got %s\n", reflect.TypeOf(expr.Constant)) + return core.EvalResult{ + Operator: "LoadSelf", + Error: err.Error(), + }, err + } + + mappedSelf := structToMap(requestCtx.Self) + value, ok := resolveDotNotation(mappedSelf, key) + if !ok { + err := fmt.Errorf("key not found: %s\n", key) + return core.EvalResult{ + Operator: "LoadSelf", + Error: err.Error(), + }, err + } + + return core.EvalResult{ + Operator: "LoadSelf", + Result: value, + }, nil + case "IsRequesterLocalUser": domain := requestCtx.Requester.Domain return core.EvalResult{ @@ -356,6 +419,22 @@ func (s service) eval(expr core.Expr, requestCtx core.RequestContext) (core.Eval Result: requestCtx.Requester.ID, }, nil + case "RequesterDomainHasTag": + target, ok := expr.Constant.(string) + if !ok { + err := fmt.Errorf("bad argument type for RequesterDomainHasTag. Expected string but got %s\n", reflect.TypeOf(expr.Constant)) + return core.EvalResult{ + Operator: "RequesterDomainHasTag", + Error: err.Error(), + }, err + } + + tags := core.ParseTags(requestCtx.RequesterDomain.Tag) + return core.EvalResult{ + Operator: "RequesterDomainHasTag", + Result: tags.Has(target), + }, nil + default: err := fmt.Errorf("unknown operator: %s\n", expr.Operator) return core.EvalResult{ diff --git a/x/policy/service_test.go b/x/policy/service_test.go index 7e77e239..7f781b5e 100644 --- a/x/policy/service_test.go +++ b/x/policy/service_test.go @@ -1,95 +1,318 @@ package policy import ( - "context" "encoding/json" - "github.com/stretchr/testify/assert" "testing" + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/otel/sdk/trace/tracetest" + "github.com/totegamma/concurrent/core" + "github.com/totegamma/concurrent/internal/testutil" ) -var s service -var ctx = context.Background() +var s core.PolicyService var globalPolicyJson = ` { - "global": { - "dominant": true, - "defaultOnTrue": true, - "condition": { - "op": "Not", - "args": [ - { + "statements": { + "global": { + "dominant": true, + "defaultOnTrue": true, + "condition": { + "op": "Not", + "args": [ + { + "op": "Or", + "args": [ + { + "op": "RequesterDomainHasTag", + "const": "_block" + }, + { + "op": "RequesterHasTag", + "const": "_block" + } + ] + } + ] + } + }, + "timeline.message.read": { + "condition": { + "op": "Or", + "args": [ + { + "op": "LoadSelf", + "const": "domainOwned" + }, + { + "op": "Eq", + "args": [ + { + "op": "LoadSelf", + "const": "author" + }, + { + "op": "RequesterID" + } + ] + } + ] + } + } + } +} +` + +var checker *tracetest.InMemoryExporter + +func TestMain(m *testing.M) { + + checker = testutil.SetupMockTraceProvider() + + var globalPolicy core.Policy + err := json.Unmarshal([]byte(globalPolicyJson), &globalPolicy) + if err != nil { + panic(err) + } + + repository := NewRepository(nil) + + s = NewService( + repository, + globalPolicy, + core.Config{ + FQDN: "local.example.com", + }, + ) + + m.Run() +} + +// 0. block判定 +func TestGlobalBlock(t *testing.T) { + + rctx0 := core.RequestContext{ + Requester: core.Entity{ + Domain: "local.example.com", + }, + } + + ctx, id := testutil.SetupTraceCtx() + result, err := s.TestWithGlobalPolicy(ctx, rctx0, "global") + test0OK := assert.NoError(t, err) + test0OK = test0OK && assert.Equal(t, core.PolicyEvalResultDefault, result) + + if !test0OK { + testutil.PrintSpans(checker.GetSpans(), id) + } + + rctx1 := core.RequestContext{ + Requester: core.Entity{ + Domain: "local.example.com", + Tag: "_block", + }, + } + + ctx, id = testutil.SetupTraceCtx() + result, err = s.TestWithGlobalPolicy(ctx, rctx1, "global") + test1OK := assert.NoError(t, err) + test1OK = test1OK && assert.Equal(t, core.PolicyEvalResultNever, result) + + if !test1OK { + testutil.PrintSpans(checker.GetSpans(), id) + } + + rctx2 := core.RequestContext{ + Requester: core.Entity{ + Domain: "other.example.net", + }, + RequesterDomain: core.Domain{ + Tag: "_block", + }, + } + + ctx, id = testutil.SetupTraceCtx() + result, err = s.TestWithGlobalPolicy(ctx, rctx2, "global") + test2OK := assert.NoError(t, err) + test2OK = test2OK && assert.Equal(t, core.PolicyEvalResultNever, result) + + if !test2OK { + testutil.PrintSpans(checker.GetSpans(), id) + } +} + +// 1. timelineを作れるのはローカルユーザーか、特定のタグを持つユーザー +func TestPolicyTimelineCreatorIsLocalUserOrHasTag(t *testing.T) { + + const policyJson = ` + { + "statements": { + "timeline": { + "condition": { "op": "Or", "args": [ { - "op": "RequesterDomainHasTag", - "const": "_block" + "op": "IsRequesterLocalUser" }, { "op": "RequesterHasTag", - "const": "_block" + "const": "timeline_creator" } ] } - ] - }, - }, - "bases": [ - ] -} -` + } + } + }` -func TestMain(m *testing.M) { + var policy core.Policy + json.Unmarshal([]byte(policyJson), &policy) - var globalPolicy core.Policy - json.Unmarshal([]byte(globalPolicyJson), &globalPolicy) + // ローカルユーザー かつ タグ無し (成功) + rctx0 := core.RequestContext{ + Requester: core.Entity{ + Domain: "local.example.com", + }, + } + + ctx, id := testutil.SetupTraceCtx() + result, err := s.Test(ctx, policy, rctx0, "timeline") + + test0OK := assert.NoError(t, err) + test0OK = test0OK && assert.Equal(t, core.PolicyEvalResultAllow, result) - s = service{ - config: core.Config{}, + if !test0OK { + testutil.PrintSpans(checker.GetSpans(), id) } - m.Run() + // ローカルユーザー かつ タグあり (成功) + rctx1 := core.RequestContext{ + Requester: core.Entity{ + Domain: "local.example.com", + Tag: "timeline_creator", + }, + } + + ctx, id = testutil.SetupTraceCtx() + result, err = s.Test(ctx, policy, rctx1, "timeline") + + test1OK := assert.NoError(t, err) + test1OK = test1OK && assert.Equal(t, core.PolicyEvalResultAllow, result) + + if !test1OK { + testutil.PrintSpans(checker.GetSpans(), id) + } + + // ローカルユーザーでない かつ タグ無し (失敗) + rctx2 := core.RequestContext{ + Requester: core.Entity{ + Domain: "other.example.net", + }, + } + + ctx, id = testutil.SetupTraceCtx() + result, err = s.Test(ctx, policy, rctx2, "timeline") + + test2OK := assert.NoError(t, err) + test2OK = test2OK && assert.Equal(t, core.PolicyEvalResultDeny, result) + + if !test2OK { + testutil.PrintSpans(checker.GetSpans(), id) + } + + // ローカルユーザーでない かつ タグあり (成功) + rctx3 := core.RequestContext{ + Requester: core.Entity{ + Domain: "other.example.net", + Tag: "timeline_creator", + }, + } + + ctx, id = testutil.SetupTraceCtx() + result, err = s.Test(ctx, policy, rctx3, "timeline") + + test3OK := assert.NoError(t, err) + test3OK = test3OK && assert.Equal(t, core.PolicyEvalResultAllow, result) + + if !test3OK { + testutil.PrintSpans(checker.GetSpans(), id) + } } -// 1. timelineを作れるのはローカルユーザーか、特定のタグを持つユーザー (Globalレベル想定) -func TestPolicyTimelineCreatorIsLocalUserOrHasTag(t *testing.T) { +// 2. messageのread +func TestPolicyMessageRead(t *testing.T) { + // globalでの処理 + // domainOwnedのとき: 誰でも読める + // domainOwnedでないとき: timelineのcreatorに限る - policy := core.Policy{ - Name: "StreamCreatorIsLocalUserOrHasTag", - Version: "2024-05-01", - Statements: []core.Statement{ - { - Actions: []string{"timeline"}, - Condition: core.Expr{ - Operator: "Or", - Args: []core.Expr{ - { - Operator: "IsRequesterLocalUser", - }, - { - Operator: "RequesterHasTag", - Args: []core.Expr{ - { - Operator: "Const", - Constant: "timeline_creator", - }, - }, - }, - }, - }, - }, + rctx0 := core.RequestContext{ + Requester: core.Entity{ + Domain: "local.example.com", + }, + Self: core.Timeline{ + DomainOwned: true, + Author: "user1", }, } - context := core.RequestContext{} + ctx, id := testutil.SetupTraceCtx() + result, err := s.TestWithGlobalPolicy(ctx, rctx0, "timeline.message.read") + test0OK := assert.NoError(t, err) + test0OK = test0OK && assert.Equal(t, core.PolicyEvalResultAllow, result) - canPerform, err := s.Test(ctx, policy, context, "timeline") - assert.NoError(t, err) - assert.True(t, canPerform) + if !test0OK { + testutil.PrintSpans(checker.GetSpans(), id) + } + + rctx1 := core.RequestContext{ + Requester: core.Entity{ + ID: "user1", + Domain: "local.example.com", + }, + Self: core.Timeline{ + DomainOwned: false, + Author: "user1", + }, + } + + ctx, id = testutil.SetupTraceCtx() + result, err = s.TestWithGlobalPolicy(ctx, rctx1, "timeline.message.read") + test1OK := assert.NoError(t, err) + test1OK = test1OK && assert.Equal(t, core.PolicyEvalResultAllow, result) + + if !test1OK { + testutil.PrintSpans(checker.GetSpans(), id) + } + + rctx2 := core.RequestContext{ + Requester: core.Entity{ + ID: "user2", + Domain: "local.example.com", + }, + Self: core.Timeline{ + DomainOwned: false, + Author: "user1", + }, + } + + ctx, id = testutil.SetupTraceCtx() + result, err = s.TestWithGlobalPolicy(ctx, rctx2, "timeline.message.read") + test2OK := assert.NoError(t, err) + test2OK = test2OK && assert.Equal(t, core.PolicyEvalResultDeny, result) + + if !test2OK { + testutil.PrintSpans(checker.GetSpans(), id) + } + + // timelineでのチェック + // リストにあれば許可 + + // messageでのチェック + // リストにあれば許可 } +/* // 2. timelineに投稿・閲覧できるのは特定のユーザー (timelineレベル想定) func TestPolicyTimelineLimitAccess(t *testing.T) { @@ -220,3 +443,4 @@ func TestPolicyMessageLimitAction(t *testing.T) { assert.NoError(t, err) assert.True(t, canPerform) } +*/ From 26cdd73f22a598127401576869832f5f61428d2b Mon Sep 17 00:00:00 2001 From: totegamma Date: Mon, 15 Jul 2024 15:31:09 +0900 Subject: [PATCH 05/22] update policy test --- x/policy/service_test.go | 236 +++++++++++++++++++++------------------ 1 file changed, 129 insertions(+), 107 deletions(-) diff --git a/x/policy/service_test.go b/x/policy/service_test.go index 7f781b5e..8483c1c4 100644 --- a/x/policy/service_test.go +++ b/x/policy/service_test.go @@ -308,139 +308,161 @@ func TestPolicyMessageRead(t *testing.T) { // timelineでのチェック // リストにあれば許可 - // messageでのチェック - // リストにあれば許可 -} + timelinePolicyJson := ` + { + "statements": { + "timeline.message.read": { + "condition": { + "op": "Or", + "args": [ + { + "op": "LoadParam", + "const": "isReadPublic" + }, + { + "op": "Contains", + "args": [ + { + "op": "LoadParam", + "const": "reader" + }, + { + "op": "RequesterID" + } + ] + } + ] + } + } + } + }` -/* -// 2. timelineに投稿・閲覧できるのは特定のユーザー (timelineレベル想定) -func TestPolicyTimelineLimitAccess(t *testing.T) { - - policy := core.Policy{ - Name: "StreamLimitAccess", - Version: "2024-05-01", - Statements: []core.Statement{ - { - Actions: []string{"distribute", "GET:/message/*"}, - Condition: core.Expr{ - Operator: "Contains", - Args: []core.Expr{ - { - Operator: "LoadParam", - Constant: "allowlist", - }, - { - Operator: "RequesterID", - }, - }, - }, - }, - }, + var timelinePolicy core.Policy + err = json.Unmarshal([]byte(timelinePolicyJson), &timelinePolicy) + if err != nil { + panic(err) } - context := core.RequestContext{ + rctx3 := core.RequestContext{ Requester: core.Entity{ - ID: "user1", + ID: "user1", + Domain: "local.example.com", + }, + Self: core.Timeline{ + DomainOwned: false, + Author: "user3", }, Params: map[string]any{ - "allowlist": []any{"user1", "user2"}, + "isWritePublic": false, + "isReadPublic": false, + "reader": []any{"user1"}, }, } - canDistribute, err := s.Test(ctx, policy, context, "distribute") - assert.NoError(t, err) - assert.True(t, canDistribute) + ctx, id = testutil.SetupTraceCtx() + result, err = s.Test(ctx, timelinePolicy, rctx3, "timeline.message.read") + test3OK := assert.NoError(t, err) + test3OK = test3OK && assert.Equal(t, core.PolicyEvalResultAllow, result) - canRead, err := s.Test(ctx, policy, context, "GET:/message/msneb1k006zqtpqsg067jyebxtm") - assert.NoError(t, err) - assert.True(t, canRead) -} + if !test3OK { + testutil.PrintSpans(checker.GetSpans(), id) + } -// 3. timelineに投稿できるのは特定のschema (timelineレベル想定) -func TestPolicyTimelineLimitMessageSchema(t *testing.T) { - - policy := core.Policy{ - Name: "StreamLimitMessageSchema", - Version: "2024-05-01", - Statements: []core.Statement{ - { - Actions: []string{"distribute"}, - Condition: core.Expr{ - Operator: "Contains", - Args: []core.Expr{ - { - Operator: "LoadParam", - Constant: "allowlist", - }, - { - Operator: "LoadDocument", - Constant: "schema", - }, - }, - }, - }, + rctx4 := core.RequestContext{ + Requester: core.Entity{ + ID: "user2", + Domain: "local.example.com", + }, + Self: core.Timeline{ + DomainOwned: false, + Author: "user3", + }, + Params: map[string]any{ + "isWritePublic": false, + "isReadPublic": false, + "reader": []any{"user1"}, }, } - document := core.MessageDocument[any]{ - DocumentBase: core.DocumentBase[any]{ - Schema: "schema1", - }, + ctx, id = testutil.SetupTraceCtx() + result, err = s.Test(ctx, timelinePolicy, rctx4, "timeline.message.read") + test4OK := assert.NoError(t, err) + test4OK = test4OK && assert.Equal(t, core.PolicyEvalResultDeny, result) + + if !test4OK { + testutil.PrintSpans(checker.GetSpans(), id) + } + + // messageでのチェック + // リストにあれば許可 + + messagePolicyJson := ` + { + "statements": { + "message.read": { + "condition": { + "op": "Contains", + "args": [ + { + "op": "LoadParam", + "const": "reader" + }, + { + "op": "RequesterID" + } + ] + } + } + } + }` + + var messagePolicy core.Policy + err = json.Unmarshal([]byte(messagePolicyJson), &messagePolicy) + if err != nil { + panic(err) } - context := core.RequestContext{ + rctx5 := core.RequestContext{ + Requester: core.Entity{ + ID: "user1", + Domain: "local.example.com", + }, + Self: core.Message{ + Author: "user3", + }, Params: map[string]any{ - "allowlist": []any{"schema1", "schema2"}, + "reader": []any{"user1"}, }, - Document: document, } - canPerform, err := s.Test(ctx, policy, context, "distribute") - assert.NoError(t, err) - assert.True(t, canPerform) -} + ctx, id = testutil.SetupTraceCtx() + result, err = s.Test(ctx, messagePolicy, rctx5, "message.read") + test5OK := assert.NoError(t, err) + test5OK = test5OK && assert.Equal(t, core.PolicyEvalResultAllow, result) -// 4. このメッセージに対してのアクションは特定のスキーマのみ (メッセージレベル想定) -func TestPolicyMessageLimitAction(t *testing.T) { - - policy := core.Policy{ - Name: "MessageLimitAction", - Version: "2024-05-01", - Statements: []core.Statement{ - { - Actions: []string{"association"}, - Condition: core.Expr{ - Operator: "Contains", - Args: []core.Expr{ - { - Operator: "LoadParam", - Constant: "allowlist", - }, - { - Operator: "LoadDocument", - Constant: "schema", - }, - }, - }, - }, - }, + if !test5OK { + testutil.PrintSpans(checker.GetSpans(), id) } - document := core.AssociationDocument[any]{ - DocumentBase: core.DocumentBase[any]{ - Schema: "schema1", + rctx6 := core.RequestContext{ + Requester: core.Entity{ + ID: "user2", + Domain: "local.example.com", + }, + Self: core.Message{ + Author: "user3", }, - } - - context := core.RequestContext{ Params: map[string]any{ - "allowlist": []any{"schema1", "schema2"}, + "reader": []any{"user1"}, }, - Document: document, } - canPerform, err := s.Test(ctx, policy, context, "association") - assert.NoError(t, err) - assert.True(t, canPerform) + ctx, id = testutil.SetupTraceCtx() + result, err = s.Test(ctx, messagePolicy, rctx6, "message.read") + test6OK := assert.NoError(t, err) + test6OK = test6OK && assert.Equal(t, core.PolicyEvalResultDeny, result) + + if !test6OK { + testutil.PrintSpans(checker.GetSpans(), id) + } } -*/ From 9694fa581f9faa1a87f5418729cef02397809a7e Mon Sep 17 00:00:00 2001 From: totegamma Date: Mon, 15 Jul 2024 17:07:17 +0900 Subject: [PATCH 06/22] update policy interface --- cmd/api/main.go | 69 ++++++++++- wire.go | 16 +-- wire_gen.go | 40 +++---- x/message/service.go | 263 ++++++++++++++++++++---------------------- x/policy/functions.go | 47 ++++++++ x/policy/service.go | 29 ++++- x/timeline/service.go | 2 +- 7 files changed, 292 insertions(+), 174 deletions(-) diff --git a/cmd/api/main.go b/cmd/api/main.go index 580aed16..9f610268 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -2,6 +2,7 @@ package main import ( "context" + "encoding/json" "fmt" "log" "log/slog" @@ -206,7 +207,65 @@ func main() { client := client.NewClient() - agent := concurrent.SetupAgent(db, rdb, mc, client, conconf, config.Server.RepositoryPath) + var globalPolicyJson = ` + { + "statements": { + "global": { + "dominant": true, + "defaultOnTrue": true, + "condition": { + "op": "Not", + "args": [ + { + "op": "Or", + "args": [ + { + "op": "RequesterDomainHasTag", + "const": "_block" + }, + { + "op": "RequesterHasTag", + "const": "_block" + } + ] + } + ] + } + }, + "timeline.message.read": { + "condition": { + "op": "Or", + "args": [ + { + "op": "LoadSelf", + "const": "domainOwned" + }, + { + "op": "Eq", + "args": [ + { + "op": "LoadSelf", + "const": "author" + }, + { + "op": "RequesterID" + } + ] + } + ] + } + } + } + }` + + globalPolicy := core.Policy{} + err = json.Unmarshal([]byte(globalPolicyJson), &globalPolicy) + if err != nil { + panic("failed to parse global policy") + } + + policy := concurrent.SetupPolicyService(rdb, globalPolicy, conconf) + agent := concurrent.SetupAgent(db, rdb, mc, client, policy, conconf, config.Server.RepositoryPath) domainService := concurrent.SetupDomainService(db, client, conconf) domainHandler := domain.NewHandler(domainService) @@ -214,16 +273,16 @@ func main() { userKvService := concurrent.SetupUserkvService(db) userkvHandler := userkv.NewHandler(userKvService) - messageService := concurrent.SetupMessageService(db, rdb, mc, client, conconf) + messageService := concurrent.SetupMessageService(db, rdb, mc, client, policy, conconf) messageHandler := message.NewHandler(messageService) - associationService := concurrent.SetupAssociationService(db, rdb, mc, client, conconf) + associationService := concurrent.SetupAssociationService(db, rdb, mc, client, policy, conconf) associationHandler := association.NewHandler(associationService) profileService := concurrent.SetupProfileService(db, rdb, mc, client, conconf) profileHandler := profile.NewHandler(profileService) - timelineService := concurrent.SetupTimelineService(db, rdb, mc, client, conconf) + timelineService := concurrent.SetupTimelineService(db, rdb, mc, client, policy, conconf) timelineHandler := timeline.NewHandler(timelineService) entityService := concurrent.SetupEntityService(db, rdb, mc, client, conconf) @@ -238,7 +297,7 @@ func main() { ackService := concurrent.SetupAckService(db, rdb, mc, client, conconf) ackHandler := ack.NewHandler(ackService) - storeService := concurrent.SetupStoreService(db, rdb, mc, client, conconf, config.Server.RepositoryPath) + storeService := concurrent.SetupStoreService(db, rdb, mc, client, policy, conconf, config.Server.RepositoryPath) storeHandler := store.NewHandler(storeService) subscriptionService := concurrent.SetupSubscriptionService(db) diff --git a/wire.go b/wire.go index 3d5a5352..708ccf12 100644 --- a/wire.go +++ b/wire.go @@ -46,7 +46,7 @@ var entityServiceProvider = wire.NewSet(entity.NewService, entity.NewRepository, var subscriptionServiceProvider = wire.NewSet(subscription.NewService, subscription.NewRepository, SetupSchemaService) // Lv2 -var timelineServiceProvider = wire.NewSet(timeline.NewService, timeline.NewRepository, SetupEntityService, SetupDomainService, SetupSchemaService, SetupSemanticidService, SetupSubscriptionService, SetupPolicyService) +var timelineServiceProvider = wire.NewSet(timeline.NewService, timeline.NewRepository, SetupEntityService, SetupDomainService, SetupSchemaService, SetupSemanticidService, SetupSubscriptionService) // Lv3 var profileServiceProvider = wire.NewSet(profile.NewService, profile.NewRepository, SetupEntityService, SetupKeyService, SetupSchemaService, SetupSemanticidService) @@ -54,7 +54,7 @@ var authServiceProvider = wire.NewSet(auth.NewService, SetupEntityService, Setup var ackServiceProvider = wire.NewSet(ack.NewService, ack.NewRepository, SetupEntityService, SetupKeyService) // Lv4 -var messageServiceProvider = wire.NewSet(message.NewService, message.NewRepository, SetupEntityService, SetupDomainService, SetupTimelineService, SetupKeyService, SetupPolicyService, SetupSchemaService) +var messageServiceProvider = wire.NewSet(message.NewService, message.NewRepository, SetupEntityService, SetupDomainService, SetupTimelineService, SetupKeyService, SetupSchemaService) // Lv5 var associationServiceProvider = wire.NewSet(association.NewService, association.NewRepository, SetupEntityService, SetupDomainService, SetupTimelineService, SetupMessageService, SetupKeyService, SetupSchemaService) @@ -79,7 +79,7 @@ var agentServiceProvider = wire.NewSet(agent.NewAgent, SetupStoreService, SetupJ // ----------- -func SetupPolicyService(rdb *redis.Client, config core.Config) core.PolicyService { +func SetupPolicyService(rdb *redis.Client, globalPolicy core.Policy, config core.Config) core.PolicyService { wire.Build(policyServiceProvider) return nil } @@ -104,7 +104,7 @@ func SetupKeyService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client return nil } -func SetupMessageService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client client.Client, config core.Config) core.MessageService { +func SetupMessageService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client client.Client, policy core.PolicyService, config core.Config) core.MessageService { wire.Build(messageServiceProvider) return nil } @@ -114,12 +114,12 @@ func SetupProfileService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, cl return nil } -func SetupAssociationService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client client.Client, config core.Config) core.AssociationService { +func SetupAssociationService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client client.Client, policy core.PolicyService, config core.Config) core.AssociationService { wire.Build(associationServiceProvider) return nil } -func SetupTimelineService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client client.Client, config core.Config) core.TimelineService { +func SetupTimelineService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client client.Client, policy core.PolicyService, config core.Config) core.TimelineService { wire.Build(timelineServiceProvider) return nil } @@ -134,7 +134,7 @@ func SetupEntityService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, cli return nil } -func SetupAgent(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client client.Client, config core.Config, repositoryPath string) core.AgentService { +func SetupAgent(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client client.Client, policy core.PolicyService, config core.Config, repositoryPath string) core.AgentService { wire.Build(agentServiceProvider) return nil } @@ -154,7 +154,7 @@ func SetupSchemaService(db *gorm.DB) core.SchemaService { return nil } -func SetupStoreService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client client.Client, config core.Config, repositoryPath string) core.StoreService { +func SetupStoreService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client client.Client, policy core.PolicyService, config core.Config, repositoryPath string) core.StoreService { wire.Build(storeServiceProvider) return nil } diff --git a/wire_gen.go b/wire_gen.go index 036b90f8..012b838f 100644 --- a/wire_gen.go +++ b/wire_gen.go @@ -1,6 +1,6 @@ // Code generated by Wire. DO NOT EDIT. -//go:generate go run github.com/google/wire/cmd/wire +//go:generate go run -mod=mod github.com/google/wire/cmd/wire //go:build !wireinject // +build !wireinject @@ -35,9 +35,9 @@ import ( // Injectors from wire.go: -func SetupPolicyService(rdb *redis.Client, config core.Config) core.PolicyService { +func SetupPolicyService(rdb *redis.Client, globalPolicy core.Policy, config core.Config) core.PolicyService { repository := policy.NewRepository(rdb) - policyService := policy.NewService(repository, config) + policyService := policy.NewService(repository, globalPolicy, config) return policyService } @@ -67,15 +67,14 @@ func SetupKeyService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client return keyService } -func SetupMessageService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client2 client.Client, config core.Config) core.MessageService { +func SetupMessageService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client2 client.Client, policy2 core.PolicyService, config core.Config) core.MessageService { schemaService := SetupSchemaService(db) repository := message.NewRepository(db, mc, schemaService) entityService := SetupEntityService(db, rdb, mc, client2, config) domainService := SetupDomainService(db, client2, config) - timelineService := SetupTimelineService(db, rdb, mc, client2, config) + timelineService := SetupTimelineService(db, rdb, mc, client2, policy2, config) keyService := SetupKeyService(db, rdb, mc, client2, config) - policyService := SetupPolicyService(rdb, config) - messageService := message.NewService(repository, client2, entityService, domainService, timelineService, keyService, policyService, config) + messageService := message.NewService(repository, client2, entityService, domainService, timelineService, keyService, policy2, config) return messageService } @@ -88,27 +87,26 @@ func SetupProfileService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, cl return profileService } -func SetupAssociationService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client2 client.Client, config core.Config) core.AssociationService { +func SetupAssociationService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client2 client.Client, policy2 core.PolicyService, config core.Config) core.AssociationService { schemaService := SetupSchemaService(db) repository := association.NewRepository(db, mc, schemaService) entityService := SetupEntityService(db, rdb, mc, client2, config) domainService := SetupDomainService(db, client2, config) - timelineService := SetupTimelineService(db, rdb, mc, client2, config) - messageService := SetupMessageService(db, rdb, mc, client2, config) + timelineService := SetupTimelineService(db, rdb, mc, client2, policy2, config) + messageService := SetupMessageService(db, rdb, mc, client2, policy2, config) keyService := SetupKeyService(db, rdb, mc, client2, config) associationService := association.NewService(repository, client2, entityService, domainService, timelineService, messageService, keyService, config) return associationService } -func SetupTimelineService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client2 client.Client, config core.Config) core.TimelineService { +func SetupTimelineService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client2 client.Client, policy2 core.PolicyService, config core.Config) core.TimelineService { schemaService := SetupSchemaService(db) repository := timeline.NewRepository(db, rdb, mc, client2, schemaService, config) entityService := SetupEntityService(db, rdb, mc, client2, config) domainService := SetupDomainService(db, client2, config) semanticIDService := SetupSemanticidService(db) subscriptionService := SetupSubscriptionService(db) - policyService := SetupPolicyService(rdb, config) - timelineService := timeline.NewService(repository, entityService, domainService, semanticIDService, subscriptionService, policyService, config) + timelineService := timeline.NewService(repository, entityService, domainService, semanticIDService, subscriptionService, policy2, config) return timelineService } @@ -127,8 +125,8 @@ func SetupEntityService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, cli return entityService } -func SetupAgent(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client2 client.Client, config core.Config, repositoryPath string) core.AgentService { - storeService := SetupStoreService(db, rdb, mc, client2, config, repositoryPath) +func SetupAgent(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client2 client.Client, policy2 core.PolicyService, config core.Config, repositoryPath string) core.AgentService { + storeService := SetupStoreService(db, rdb, mc, client2, policy2, config, repositoryPath) jobService := SetupJobService(db) agentService := agent.NewAgent(mc, rdb, storeService, jobService, config, repositoryPath) return agentService @@ -154,14 +152,14 @@ func SetupSchemaService(db *gorm.DB) core.SchemaService { return schemaService } -func SetupStoreService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client2 client.Client, config core.Config, repositoryPath string) core.StoreService { +func SetupStoreService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client2 client.Client, policy2 core.PolicyService, config core.Config, repositoryPath string) core.StoreService { repository := store.NewRepository(rdb) keyService := SetupKeyService(db, rdb, mc, client2, config) entityService := SetupEntityService(db, rdb, mc, client2, config) - messageService := SetupMessageService(db, rdb, mc, client2, config) - associationService := SetupAssociationService(db, rdb, mc, client2, config) + messageService := SetupMessageService(db, rdb, mc, client2, policy2, config) + associationService := SetupAssociationService(db, rdb, mc, client2, policy2, config) profileService := SetupProfileService(db, rdb, mc, client2, config) - timelineService := SetupTimelineService(db, rdb, mc, client2, config) + timelineService := SetupTimelineService(db, rdb, mc, client2, policy2, config) ackService := SetupAckService(db, rdb, mc, client2, config) subscriptionService := SetupSubscriptionService(db) semanticIDService := SetupSemanticidService(db) @@ -207,7 +205,7 @@ var entityServiceProvider = wire.NewSet(entity.NewService, entity.NewRepository, var subscriptionServiceProvider = wire.NewSet(subscription.NewService, subscription.NewRepository, SetupSchemaService) // Lv2 -var timelineServiceProvider = wire.NewSet(timeline.NewService, timeline.NewRepository, SetupEntityService, SetupDomainService, SetupSchemaService, SetupSemanticidService, SetupSubscriptionService, SetupPolicyService) +var timelineServiceProvider = wire.NewSet(timeline.NewService, timeline.NewRepository, SetupEntityService, SetupDomainService, SetupSchemaService, SetupSemanticidService, SetupSubscriptionService) // Lv3 var profileServiceProvider = wire.NewSet(profile.NewService, profile.NewRepository, SetupEntityService, SetupKeyService, SetupSchemaService, SetupSemanticidService) @@ -217,7 +215,7 @@ var authServiceProvider = wire.NewSet(auth.NewService, SetupEntityService, Setup var ackServiceProvider = wire.NewSet(ack.NewService, ack.NewRepository, SetupEntityService, SetupKeyService) // Lv4 -var messageServiceProvider = wire.NewSet(message.NewService, message.NewRepository, SetupEntityService, SetupDomainService, SetupTimelineService, SetupKeyService, SetupPolicyService, SetupSchemaService) +var messageServiceProvider = wire.NewSet(message.NewService, message.NewRepository, SetupEntityService, SetupDomainService, SetupTimelineService, SetupKeyService, SetupSchemaService) // Lv5 var associationServiceProvider = wire.NewSet(association.NewService, association.NewRepository, SetupEntityService, SetupDomainService, SetupTimelineService, SetupMessageService, SetupKeyService, SetupSchemaService) diff --git a/x/message/service.go b/x/message/service.go index 6aa3b1bc..33be1411 100644 --- a/x/message/service.go +++ b/x/message/service.go @@ -13,6 +13,7 @@ import ( "github.com/totegamma/concurrent/cdid" "github.com/totegamma/concurrent/client" "github.com/totegamma/concurrent/core" + "github.com/totegamma/concurrent/x/policy" ) type service struct { @@ -57,39 +58,22 @@ func (s *service) Count(ctx context.Context) (int64, error) { return s.repo.Count(ctx) } -// Get returns a message by ID -func (s *service) Get(ctx context.Context, id string, requester string) (core.Message, error) { - ctx, span := tracer.Start(ctx, "Message.Service.Get") +func (s *service) isMessagePublic(ctx context.Context, message core.Message) (bool, error) { + ctx, span := tracer.Start(ctx, "Message.Service.isMessagePublic") defer span.End() - message, err := s.repo.Get(ctx, id) - if err != nil { - span.RecordError(err) - return core.Message{}, err - } - - canRead := false - for _, timelineID := range message.Timelines { + // timeline policy check + timelinePolicyResults := make([]core.PolicyEvalResult, len(message.Timelines)) + for i, timelineID := range message.Timelines { timeline, err := s.timeline.GetTimelineAutoDomain(ctx, timelineID) if err != nil { span.SetStatus(codes.Error, err.Error()) continue } - if timeline.Author == requester { // 自分のタイムラインなら読める - canRead = true - break - } - if timeline.Policy == "" { - canRead = true - break - } - - action, ok := ctx.Value(core.RequestPathCtxKey).(string) - if !ok { - span.RecordError(fmt.Errorf("action not found")) - return core.Message{}, fmt.Errorf("invalid action") + timelinePolicyResults[i] = core.PolicyEvalResultDefault + continue } var params map[string]any = make(map[string]any) @@ -102,27 +86,89 @@ func (s *service) Get(ctx context.Context, id string, requester string) (core.Me } } - ok, err = s.policy.TestWithPolicyURL( + result, err := s.policy.TestWithPolicyURL( ctx, timeline.Policy, core.RequestContext{ - Parent: timeline, + Self: timeline, Params: params, }, - action, + "timeline.message.read", ) if err != nil { span.SetStatus(codes.Error, err.Error()) + timelinePolicyResults[i] = core.PolicyEvalResultDefault continue } - if ok { - canRead = true - break + timelinePolicyResults[i] = result + } + + timelinePolicyResult := policy.AccumulateOr(timelinePolicyResults) + timelinePolicyIsDominant, timelinePolicyAllowed := policy.IsDominant(timelinePolicyResult) + if timelinePolicyIsDominant && !timelinePolicyAllowed { + return false, nil + } + + // message policy check + messagePolicyResult := core.PolicyEvalResultDefault + if message.Policy != "" { + + var params map[string]any = make(map[string]any) + if message.PolicyParams != nil { + err := json.Unmarshal([]byte(*message.PolicyParams), ¶ms) + if err != nil { + span.SetStatus(codes.Error, err.Error()) + span.RecordError(err) + goto SKIP_EVAL_MESSAGE_POLICY + } } + var err error + messagePolicyResult, err = s.policy.TestWithPolicyURL( + ctx, + message.Policy, + core.RequestContext{ + Self: message, + Params: params, + }, + "message.read", + ) + if err != nil { + span.SetStatus(codes.Error, err.Error()) + goto SKIP_EVAL_MESSAGE_POLICY + + } + } +SKIP_EVAL_MESSAGE_POLICY: + + // accumulate polies + result := policy.Summerize([]core.PolicyEvalResult{messagePolicyResult, timelinePolicyResult}) + if !result { + return false, nil } - if !canRead { + + return true, nil +} + +// Get returns a message by ID +func (s *service) Get(ctx context.Context, id string, requester string) (core.Message, error) { + ctx, span := tracer.Start(ctx, "Message.Service.Get") + defer span.End() + + message, err := s.repo.Get(ctx, id) + if err != nil { + span.RecordError(err) + return core.Message{}, err + } + + isPublic, err := s.isMessagePublic(ctx, message) + if err != nil { + span.RecordError(err) + return core.Message{}, err + } + + if !isPublic { return core.Message{}, fmt.Errorf("no read access") } @@ -146,22 +192,17 @@ func (s *service) GetWithOwnAssociations(ctx context.Context, id string, request return core.Message{}, err } - canRead := false - for _, timelineID := range message.Timelines { + timelinePolicyResults := make([]core.PolicyEvalResult, len(message.Timelines)) + for i, timelineID := range message.Timelines { timeline, err := s.timeline.GetTimelineAutoDomain(ctx, timelineID) if err != nil { span.SetStatus(codes.Error, err.Error()) continue } - if timeline.Author == requester { // 自分のタイムラインなら読める - canRead = true - break - } - if timeline.Policy == "" { - canRead = true - break + timelinePolicyResults[i] = core.PolicyEvalResultDefault + continue } var params map[string]any = make(map[string]any) @@ -175,30 +216,55 @@ func (s *service) GetWithOwnAssociations(ctx context.Context, id string, request } requestContext := core.RequestContext{ - Parent: timeline, + Self: timeline, Params: params, Requester: requesterEntity, } - action, ok := ctx.Value(core.RequestPathCtxKey).(string) - if !ok { - span.RecordError(fmt.Errorf("action not found")) - return core.Message{}, fmt.Errorf("invalid action") - } - - ok, err = s.policy.TestWithPolicyURL(ctx, timeline.Policy, requestContext, action) + result, err := s.policy.TestWithPolicyURL(ctx, timeline.Policy, requestContext, "timeline.message.read") if err != nil { span.SetStatus(codes.Error, err.Error()) + timelinePolicyResults[i] = core.PolicyEvalResultDefault continue } + timelinePolicyResults[i] = result + } + + timelinePolicyResult := policy.AccumulateOr(timelinePolicyResults) + timelinePolicyIsDominant, timelinePolicyAllowed := policy.IsDominant(timelinePolicyResult) + if timelinePolicyIsDominant && !timelinePolicyAllowed { + return core.Message{}, fmt.Errorf("no read access") + } + + messagePolicyResult := core.PolicyEvalResultDefault + if message.Policy != "" { + + var params map[string]any = make(map[string]any) + if message.PolicyParams != nil { + err := json.Unmarshal([]byte(*message.PolicyParams), ¶ms) + if err != nil { + span.SetStatus(codes.Error, err.Error()) + span.RecordError(err) + goto SKIP_EVAL_MESSAGE_POLICY + } + } - if ok { - canRead = true - break + requestContext := core.RequestContext{ + Self: message, + Params: params, + Requester: requesterEntity, } + messagePolicyResult, err = s.policy.TestWithPolicyURL(ctx, message.Policy, requestContext, "message.read") + if err != nil { + span.SetStatus(codes.Error, err.Error()) + goto SKIP_EVAL_MESSAGE_POLICY + } } - if !canRead { +SKIP_EVAL_MESSAGE_POLICY: + + result := policy.Summerize([]core.PolicyEvalResult{messagePolicyResult, timelinePolicyResult}) + if !result { return core.Message{}, fmt.Errorf("no read access") } @@ -258,50 +324,8 @@ func (s *service) Create(ctx context.Context, mode core.CommitMode, document str } - ispublic := false destinations := make(map[string][]string) for _, timelineID := range doc.Timelines { - - timeline, err := s.timeline.GetTimelineAutoDomain(ctx, timelineID) - if err != nil { - span.SetStatus(codes.Error, err.Error()) - continue - } - - if timeline.Policy == "" { - ispublic = true - } else if ispublic == false { - - var params map[string]any = make(map[string]any) - if timeline.PolicyParams != nil { - err := json.Unmarshal([]byte(*timeline.PolicyParams), ¶ms) - if err != nil { - span.SetStatus(codes.Error, err.Error()) - span.RecordError(err) - continue - } - } - - ok, err := s.policy.TestWithPolicyURL( - ctx, - timeline.Policy, - core.RequestContext{ - Parent: timeline, - Params: params, - }, - "GET:/message/", - ) - if err != nil { - span.SetStatus(codes.Error, err.Error()) - span.RecordError(err) - continue - } - - if ok { - ispublic = true - } - } - normalized, err := s.timeline.NormalizeTimelineID(ctx, timelineID) if err != nil { span.RecordError(errors.Wrap(err, "failed to normalize timeline id")) @@ -320,6 +344,12 @@ func (s *service) Create(ctx context.Context, mode core.CommitMode, document str destinations[domain] = append(destinations[domain], timelineID) } + ispublic, err := s.isMessagePublic(ctx, created) + if err != nil { + span.RecordError(err) + return core.Message{}, err + } + sendDocument := "" sendSignature := "" if ispublic { @@ -430,50 +460,7 @@ func (s *service) Delete(ctx context.Context, mode core.CommitMode, document, si span.RecordError(err) } - ispublic := false - for _, timelineID := range deleteTarget.Timelines { - timeline, err := s.timeline.GetTimelineAutoDomain(ctx, timelineID) - if err != nil { - span.SetStatus(codes.Error, err.Error()) - continue - } - - if timeline.Policy == "" { - ispublic = true - break - } - - var params map[string]any = make(map[string]any) - if timeline.PolicyParams != nil { - err := json.Unmarshal([]byte(*timeline.PolicyParams), ¶ms) - if err != nil { - span.SetStatus(codes.Error, err.Error()) - span.RecordError(err) - continue - } - } - - ok, err := s.policy.TestWithPolicyURL( - ctx, - timeline.Policy, - core.RequestContext{ - Parent: timeline, - Params: params, - }, - "GET:/message/", - ) - if err != nil { - span.SetStatus(codes.Error, err.Error()) - span.RecordError(err) - continue - } - - if ok { - ispublic = true - break - } - } - + ispublic, err := s.isMessagePublic(ctx, deleteTarget) if err != nil { span.RecordError(err) return core.Message{}, err diff --git a/x/policy/functions.go b/x/policy/functions.go index f60f8b29..280c4ea7 100644 --- a/x/policy/functions.go +++ b/x/policy/functions.go @@ -10,6 +10,53 @@ import ( "github.com/totegamma/concurrent/core" ) +func IsDominant(result core.PolicyEvalResult) (bool, bool) { + if result == core.PolicyEvalResultAlways { + return true, true + } else if result == core.PolicyEvalResultNever { + return true, false + } else { + return false, false + } +} + +func AccumulateOr(results []core.PolicyEvalResult) core.PolicyEvalResult { + var hasAlways bool + var hasNever bool + var hasAllow bool + var hasDeny bool + + for _, r := range results { + if r == core.PolicyEvalResultAlways { + hasAlways = true + } else if r == core.PolicyEvalResultNever { + hasNever = true + } else if r == core.PolicyEvalResultAllow { + hasAllow = true + } else if r == core.PolicyEvalResultDeny { + hasDeny = true + } + } + + if hasAlways && hasNever { + return core.PolicyEvalResultDefault + } else if hasAlways { + return core.PolicyEvalResultAlways + } else if hasNever { + return core.PolicyEvalResultNever + } + + if hasAllow && hasDeny { + return core.PolicyEvalResultDefault + } else if hasAllow { + return core.PolicyEvalResultAllow + } else if hasDeny { + return core.PolicyEvalResultDeny + } + + return core.PolicyEvalResultDefault +} + func Summerize(results []core.PolicyEvalResult) bool { result := false diff --git a/x/policy/service.go b/x/policy/service.go index cd2406ae..1e3586a4 100644 --- a/x/policy/service.go +++ b/x/policy/service.go @@ -35,7 +35,7 @@ func (s service) TestWithGlobalPolicy(ctx context.Context, context core.RequestC ctx, span := tracer.Start(ctx, "Policy.Service.TestWithGlobalPolicy") defer span.End() - return s.Test(ctx, s.global, context, action) + return s.test(ctx, s.global, context, action) } func (s service) TestWithPolicyURL(ctx context.Context, url string, context core.RequestContext, action string) (core.PolicyEvalResult, error) { @@ -55,6 +55,33 @@ func (s service) Test(ctx context.Context, policy core.Policy, context core.Requ ctx, span := tracer.Start(ctx, "Policy.Service.Test") defer span.End() + globalResult, err := s.test(ctx, s.global, context, action) + if err != nil { + span.SetStatus(codes.Error, err.Error()) + return core.PolicyEvalResultDefault, err + } + + if globalResult == core.PolicyEvalResultAlways || globalResult == core.PolicyEvalResultNever { + return globalResult, nil + } + + localResult, err := s.test(ctx, policy, context, action) + if err != nil { + span.SetStatus(codes.Error, err.Error()) + return core.PolicyEvalResultDefault, err + } + + if localResult == core.PolicyEvalResultDefault { + return globalResult, nil + } + + return localResult, nil +} + +func (s service) test(ctx context.Context, policy core.Policy, context core.RequestContext, action string) (core.PolicyEvalResult, error) { + ctx, span := tracer.Start(ctx, "Policy.Service.test") + defer span.End() + span.SetAttributes(attribute.String("action", action)) statement, ok := policy.Statements[action] diff --git a/x/timeline/service.go b/x/timeline/service.go index b5be9a38..b86d0f9f 100644 --- a/x/timeline/service.go +++ b/x/timeline/service.go @@ -383,7 +383,7 @@ func (s *service) PostItem(ctx context.Context, timeline string, item core.Timel } requestContext := core.RequestContext{ - Parent: tl, + Self: tl, Requester: requesterEntity, } From ff5538596cefdb1b7e3c8f97a51d77b6012d37d0 Mon Sep 17 00:00:00 2001 From: totegamma Date: Mon, 15 Jul 2024 17:10:45 +0900 Subject: [PATCH 07/22] remove requestpath middleware --- cmd/api/main.go | 2 +- core/const.go | 4 ---- x/auth/middleware.go | 17 ----------------- 3 files changed, 1 insertion(+), 22 deletions(-) diff --git a/cmd/api/main.go b/cmd/api/main.go index 9f610268..3890ef75 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -306,7 +306,7 @@ func main() { jobService := concurrent.SetupJobService(db) jobHandler := job.NewHandler(jobService) - apiV1 := e.Group("", auth.SetRequestPath, auth.ReceiveGatewayAuthPropagation) + apiV1 := e.Group("", auth.ReceiveGatewayAuthPropagation) // store apiV1.POST("/commit", storeHandler.Commit) diff --git a/core/const.go b/core/const.go index 557398ba..54b7df67 100644 --- a/core/const.go +++ b/core/const.go @@ -24,10 +24,6 @@ const ( CaptchaVerifiedHeader = "cc-captcha-verified" ) -const ( - RequestPathCtxKey = "cc-request-path" -) - type CommitMode int const ( diff --git a/x/auth/middleware.go b/x/auth/middleware.go index 07ca0412..98b283cc 100644 --- a/x/auth/middleware.go +++ b/x/auth/middleware.go @@ -339,23 +339,6 @@ func ReceiveGatewayAuthPropagation(next echo.HandlerFunc) echo.HandlerFunc { } } -func SetRequestPath(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - ctx, span := tracer.Start(c.Request().Context(), "Auth.Service.SetRequestPath") - defer span.End() - - url := c.Request().URL - method := c.Request().Method - path := method + ":" + url.Path - - ctx = context.WithValue(ctx, core.RequestPathCtxKey, path) - span.SetAttributes(attribute.String("RequestPath", path)) - - c.SetRequest(c.Request().WithContext(ctx)) - return next(c) - } -} - func Restrict(principal Principal) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { From 0262feb96911a10ce8cd3adb100589d264c6106f Mon Sep 17 00:00:00 2001 From: totegamma Date: Mon, 15 Jul 2024 17:35:26 +0900 Subject: [PATCH 08/22] update policy defaults --- core/interfaces.go | 1 + core/model.go | 1 + x/message/service.go | 4 ++-- x/policy/functions.go | 21 ------------------ x/policy/service.go | 24 +++++++++++++++++++++ x/timeline/service.go | 50 +++++++++++-------------------------------- 6 files changed, 41 insertions(+), 60 deletions(-) diff --git a/core/interfaces.go b/core/interfaces.go index d60eaeec..75d0db7c 100644 --- a/core/interfaces.go +++ b/core/interfaces.go @@ -91,6 +91,7 @@ type PolicyService interface { Test(ctx context.Context, policy Policy, context RequestContext, action string) (PolicyEvalResult, error) TestWithPolicyURL(ctx context.Context, url string, context RequestContext, action string) (PolicyEvalResult, error) TestWithGlobalPolicy(ctx context.Context, context RequestContext, action string) (PolicyEvalResult, error) + Summerize(results []PolicyEvalResult, action string) bool } type ProfileService interface { diff --git a/core/model.go b/core/model.go index fda330c2..3b307c87 100644 --- a/core/model.go +++ b/core/model.go @@ -33,6 +33,7 @@ type PolicyDocument struct { type Policy struct { Statements map[string]Statement `json:"statements"` + Defaults map[string]bool `json:"defaults"` } type Statement struct { diff --git a/x/message/service.go b/x/message/service.go index 33be1411..e9f57b16 100644 --- a/x/message/service.go +++ b/x/message/service.go @@ -143,7 +143,7 @@ func (s *service) isMessagePublic(ctx context.Context, message core.Message) (bo SKIP_EVAL_MESSAGE_POLICY: // accumulate polies - result := policy.Summerize([]core.PolicyEvalResult{messagePolicyResult, timelinePolicyResult}) + result := s.policy.Summerize([]core.PolicyEvalResult{messagePolicyResult, timelinePolicyResult}, "message.read") if !result { return false, nil } @@ -263,7 +263,7 @@ func (s *service) GetWithOwnAssociations(ctx context.Context, id string, request } SKIP_EVAL_MESSAGE_POLICY: - result := policy.Summerize([]core.PolicyEvalResult{messagePolicyResult, timelinePolicyResult}) + result := s.policy.Summerize([]core.PolicyEvalResult{messagePolicyResult, timelinePolicyResult}, "message.read") if !result { return core.Message{}, fmt.Errorf("no read access") } diff --git a/x/policy/functions.go b/x/policy/functions.go index 280c4ea7..198fdedc 100644 --- a/x/policy/functions.go +++ b/x/policy/functions.go @@ -57,27 +57,6 @@ func AccumulateOr(results []core.PolicyEvalResult) core.PolicyEvalResult { return core.PolicyEvalResultDefault } -func Summerize(results []core.PolicyEvalResult) bool { - result := false - - for _, r := range results { - switch r { - case core.PolicyEvalResultAlways: - return true - case core.PolicyEvalResultNever: - return false - case core.PolicyEvalResultAllow: - result = true - case core.PolicyEvalResultDeny: - result = false - case core.PolicyEvalResultDefault: - continue - } - } - - return result -} - func debugPrint(comment string, v interface{}) { b, _ := json.MarshalIndent(v, "", " ") fmt.Println(comment, string(b)) diff --git a/x/policy/service.go b/x/policy/service.go index 1e3586a4..217ce983 100644 --- a/x/policy/service.go +++ b/x/policy/service.go @@ -31,6 +31,30 @@ func NewService(repository Repository, globalPolicy core.Policy, config core.Con } } +func (s service) Summerize(results []core.PolicyEvalResult, action string) bool { + result, ok := s.global.Defaults[action] + if !ok { + return false + } + + for _, r := range results { + switch r { + case core.PolicyEvalResultAlways: + return true + case core.PolicyEvalResultNever: + return false + case core.PolicyEvalResultAllow: + result = true + case core.PolicyEvalResultDeny: + result = false + case core.PolicyEvalResultDefault: + continue + } + } + + return result +} + func (s service) TestWithGlobalPolicy(ctx context.Context, context core.RequestContext, action string) (core.PolicyEvalResult, error) { ctx, span := tracer.Start(ctx, "Policy.Service.TestWithGlobalPolicy") defer span.End() diff --git a/x/timeline/service.go b/x/timeline/service.go index b86d0f9f..7ba4ec21 100644 --- a/x/timeline/service.go +++ b/x/timeline/service.go @@ -11,11 +11,9 @@ import ( "github.com/pkg/errors" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" "github.com/totegamma/concurrent/cdid" "github.com/totegamma/concurrent/core" - "github.com/totegamma/concurrent/x/policy" ) type service struct { @@ -370,52 +368,30 @@ func (s *service) PostItem(ctx context.Context, timeline string, item core.Timel return core.TimelineItem{}, err } - var writable bool - results := make([]core.PolicyEvalResult, 0) - - if tl.Author == author { - writable = true - } - requesterEntity, err := s.entity.Get(ctx, author) if err != nil { span.RecordError(err) } - requestContext := core.RequestContext{ - Self: tl, - Requester: requesterEntity, + var params map[string]any = make(map[string]any) + if tl.PolicyParams != nil { + json.Unmarshal([]byte(*tl.PolicyParams), ¶ms) } - result, err := s.policy.TestWithGlobalPolicy(ctx, requestContext, "distribute") + result, err := s.policy.TestWithPolicyURL( + ctx, + tl.Policy, + core.RequestContext{ + Self: tl, + Requester: requesterEntity, + }, + "distribute", + ) if err != nil { span.RecordError(err) - goto skipAuth } - results = append(results, result) - - if tl.Policy != "" { - var params map[string]any = make(map[string]any) - if tl.PolicyParams != nil { - err := json.Unmarshal([]byte(*tl.PolicyParams), ¶ms) - if err != nil { - span.SetStatus(codes.Error, err.Error()) - span.RecordError(err) - goto skipAuth - } - } - - result, err = s.policy.TestWithPolicyURL(ctx, tl.Policy, requestContext, "distribute") - if err != nil { - span.RecordError(err) - goto skipAuth - } - results = append(results, result) - } - - writable = policy.Summerize(results) -skipAuth: + writable := s.policy.Summerize([]core.PolicyEvalResult{result}, "distribute") if !writable { slog.InfoContext( From 99478e23fd491f004afe744b309c0c9ac8fe3056 Mon Sep 17 00:00:00 2001 From: totegamma Date: Mon, 15 Jul 2024 19:58:01 +0900 Subject: [PATCH 09/22] fix policy bug --- cmd/api/main.go | 23 +++++++++++++++++++++++ core/model.go | 6 +++--- x/message/service.go | 22 ++++++++++------------ x/policy/repository.go | 22 +++++++++++++++++++++- x/policy/service.go | 18 +++++++++++++----- x/timeline/service.go | 6 ++++-- 6 files changed, 74 insertions(+), 23 deletions(-) diff --git a/cmd/api/main.go b/cmd/api/main.go index 3890ef75..daaf12a0 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -232,6 +232,29 @@ func main() { ] } }, + "timeline.distribute": { + "condition": { + "op": "Or", + "args": [ + { + "op": "LoadSelf", + "const": "domainOwned" + }, + { + "op": "Eq", + "args": [ + { + "op": "LoadSelf", + "const": "author" + }, + { + "op": "RequesterID" + } + ] + } + ] + } + }, "timeline.message.read": { "condition": { "op": "Or", diff --git a/core/model.go b/core/model.go index 3b307c87..5dabcbbc 100644 --- a/core/model.go +++ b/core/model.go @@ -26,9 +26,9 @@ type RequestContext struct { } type PolicyDocument struct { - Name string `json:"name"` - Description string `json:"description"` - Versions map[string][]Policy `json:"versions"` + Name string `json:"name"` + Description string `json:"description"` + Versions map[string]Policy `json:"versions"` } type Policy struct { diff --git a/x/message/service.go b/x/message/service.go index e9f57b16..1ad693a7 100644 --- a/x/message/service.go +++ b/x/message/service.go @@ -200,11 +200,6 @@ func (s *service) GetWithOwnAssociations(ctx context.Context, id string, request continue } - if timeline.Policy == "" { - timelinePolicyResults[i] = core.PolicyEvalResultDefault - continue - } - var params map[string]any = make(map[string]any) if timeline.PolicyParams != nil { err := json.Unmarshal([]byte(*timeline.PolicyParams), ¶ms) @@ -215,13 +210,16 @@ func (s *service) GetWithOwnAssociations(ctx context.Context, id string, request } } - requestContext := core.RequestContext{ - Self: timeline, - Params: params, - Requester: requesterEntity, - } - - result, err := s.policy.TestWithPolicyURL(ctx, timeline.Policy, requestContext, "timeline.message.read") + result, err := s.policy.TestWithPolicyURL( + ctx, + timeline.Policy, + core.RequestContext{ + Self: timeline, + Params: params, + Requester: requesterEntity, + }, + "timeline.message.read", + ) if err != nil { span.SetStatus(codes.Error, err.Error()) timelinePolicyResults[i] = core.PolicyEvalResultDefault diff --git a/x/policy/repository.go b/x/policy/repository.go index e9ba80f0..66b3ff2e 100644 --- a/x/policy/repository.go +++ b/x/policy/repository.go @@ -72,8 +72,28 @@ func (r *repository) Get(ctx context.Context, url string) (core.Policy, error) { } // cache policy + var policyDoc core.PolicyDocument + err = json.Unmarshal(jsonStr, &policyDoc) + if err != nil { + span.SetStatus(codes.Error, err.Error()) + return core.Policy{}, err + } + var policy core.Policy - err = json.Unmarshal(jsonStr, &policy) + policy20240701, ok := policyDoc.Versions["2024-07-01"] + if ok { + span.AddEvent("use version 2024-07-01") + policy = policy20240701 + } else { + span.AddEvent("fallback to latest version") + err = json.Unmarshal(jsonStr, &policy) + if err != nil { + span.SetStatus(codes.Error, err.Error()) + return core.Policy{}, err + } + } + + jsonStr, err = json.Marshal(policy) if err != nil { span.SetStatus(codes.Error, err.Error()) return core.Policy{}, err diff --git a/x/policy/service.go b/x/policy/service.go index 217ce983..6be556d8 100644 --- a/x/policy/service.go +++ b/x/policy/service.go @@ -34,7 +34,7 @@ func NewService(repository Repository, globalPolicy core.Policy, config core.Con func (s service) Summerize(results []core.PolicyEvalResult, action string) bool { result, ok := s.global.Defaults[action] if !ok { - return false + result = false } for _, r := range results { @@ -66,10 +66,14 @@ func (s service) TestWithPolicyURL(ctx context.Context, url string, context core ctx, span := tracer.Start(ctx, "Policy.Service.TestWithPolicyURL") defer span.End() - policy, err := s.repository.Get(ctx, url) - if err != nil { - span.SetStatus(codes.Error, err.Error()) - return core.PolicyEvalResultDefault, err + var policy core.Policy + if url != "" { + var err error + policy, err = s.repository.Get(ctx, url) + if err != nil { + span.SetStatus(codes.Error, err.Error()) + return core.PolicyEvalResultDefault, err + } } return s.Test(ctx, policy, context, action) @@ -89,6 +93,10 @@ func (s service) Test(ctx context.Context, policy core.Policy, context core.Requ return globalResult, nil } + if len(policy.Statements) == 0 { + return globalResult, nil + } + localResult, err := s.test(ctx, policy, context, action) if err != nil { span.SetStatus(codes.Error, err.Error()) diff --git a/x/timeline/service.go b/x/timeline/service.go index 7ba4ec21..d5f14f19 100644 --- a/x/timeline/service.go +++ b/x/timeline/service.go @@ -385,15 +385,17 @@ func (s *service) PostItem(ctx context.Context, timeline string, item core.Timel Self: tl, Requester: requesterEntity, }, - "distribute", + "timeline.distribute", ) if err != nil { span.RecordError(err) } - writable := s.policy.Summerize([]core.PolicyEvalResult{result}, "distribute") + writable := s.policy.Summerize([]core.PolicyEvalResult{result}, "timeline.distribute") if !writable { + span.RecordError(fmt.Errorf("You don't have timeline.distribute access to %v", timelineID)) + span.SetAttributes(attribute.Int("result", int(result))) slog.InfoContext( ctx, "failed to post to timeline", slog.String("type", "audit"), From ef6b286b3c07c2ca7e0f62c82455deccd3ab2b1a Mon Sep 17 00:00:00 2001 From: totegamma Date: Mon, 15 Jul 2024 20:29:41 +0900 Subject: [PATCH 10/22] update block definition --- cmd/api/main.go | 2 +- wire.go | 2 +- wire_gen.go | 4 ++-- x/auth/middleware.go | 22 +++++++++++++++++++++- x/auth/service.go | 11 +++++++++-- 5 files changed, 34 insertions(+), 7 deletions(-) diff --git a/cmd/api/main.go b/cmd/api/main.go index daaf12a0..9f99d761 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -311,7 +311,7 @@ func main() { entityService := concurrent.SetupEntityService(db, rdb, mc, client, conconf) entityHandler := entity.NewHandler(entityService) - authService := concurrent.SetupAuthService(db, rdb, mc, client, conconf) + authService := concurrent.SetupAuthService(db, rdb, mc, client, policy, conconf) authHandler := auth.NewHandler(authService) keyService := concurrent.SetupKeyService(db, rdb, mc, client, conconf) diff --git a/wire.go b/wire.go index 708ccf12..41730c81 100644 --- a/wire.go +++ b/wire.go @@ -139,7 +139,7 @@ func SetupAgent(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client clie return nil } -func SetupAuthService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client client.Client, config core.Config) core.AuthService { +func SetupAuthService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client client.Client, policy core.PolicyService, config core.Config) core.AuthService { wire.Build(authServiceProvider) return nil } diff --git a/wire_gen.go b/wire_gen.go index 012b838f..c004af44 100644 --- a/wire_gen.go +++ b/wire_gen.go @@ -132,11 +132,11 @@ func SetupAgent(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client2 cli return agentService } -func SetupAuthService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client2 client.Client, config core.Config) core.AuthService { +func SetupAuthService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client2 client.Client, policy2 core.PolicyService, config core.Config) core.AuthService { entityService := SetupEntityService(db, rdb, mc, client2, config) domainService := SetupDomainService(db, client2, config) keyService := SetupKeyService(db, rdb, mc, client2, config) - authService := auth.NewService(config, entityService, domainService, keyService) + authService := auth.NewService(config, entityService, domainService, keyService, policy2) return authService } diff --git a/x/auth/middleware.go b/x/auth/middleware.go index 98b283cc..67d5604d 100644 --- a/x/auth/middleware.go +++ b/x/auth/middleware.go @@ -203,6 +203,7 @@ func (s *service) IdentifyIdentity(next echo.HandlerFunc) echo.HandlerFunc { }) } + var domain core.Domain if entity.Domain == s.config.FQDN { // local user @@ -226,7 +227,7 @@ func (s *service) IdentifyIdentity(next echo.HandlerFunc) echo.HandlerFunc { span.SetAttributes(attribute.String("RequesterType", core.RequesterTypeString(core.LocalUser))) } else { - domain, err := s.domain.GetByFQDN(ctx, entity.Domain) + domain, err = s.domain.GetByFQDN(ctx, entity.Domain) if err != nil { span.RecordError(errors.Wrap(err, "failed to get domain by fqdn")) return c.JSON(http.StatusForbidden, echo.Map{}) @@ -257,6 +258,25 @@ func (s *service) IdentifyIdentity(next echo.HandlerFunc) echo.HandlerFunc { } else { ctx = context.WithValue(ctx, core.RequesterIsRegisteredKey, false) } + + rctx := core.RequestContext{ + Requester: entity, + RequesterDomain: domain, + } + + accessOK, err := s.policy.TestWithGlobalPolicy(ctx, rctx, "global") + if err != nil { + span.RecordError(errors.Wrap(err, "failed to test with global policy")) + return c.JSON(http.StatusForbidden, echo.Map{}) + } + + if accessOK == core.PolicyEvalResultNever || accessOK == core.PolicyEvalResultDeny { + return c.JSON(http.StatusForbidden, echo.Map{ + "error": "you are not authorized to perform this action", + "detail": "you are not allowed by global policy", + }) + } + } skipCheckAuthorization: c.SetRequest(c.Request().WithContext(ctx)) diff --git a/x/auth/service.go b/x/auth/service.go index 878ad4e5..1095bf5e 100644 --- a/x/auth/service.go +++ b/x/auth/service.go @@ -16,11 +16,18 @@ type service struct { entity core.EntityService domain core.DomainService key core.KeyService + policy core.PolicyService } // NewService creates a new auth service -func NewService(config core.Config, entity core.EntityService, domain core.DomainService, key core.KeyService) core.AuthService { - return &service{config, entity, domain, key} +func NewService( + config core.Config, + entity core.EntityService, + domain core.DomainService, + key core.KeyService, + policy core.PolicyService, +) core.AuthService { + return &service{config, entity, domain, key, policy} } // GetPassport takes client signed JWT and returns server signed JWT From f4181b21343dbe1952e16eda5eaa66236767b685 Mon Sep 17 00:00:00 2001 From: totegamma Date: Mon, 15 Jul 2024 20:38:32 +0900 Subject: [PATCH 11/22] update invite definition --- cmd/api/main.go | 12 +++++++++--- wire.go | 6 +++--- wire_gen.go | 26 +++++++++++++------------- x/entity/service.go | 25 +++++++++++++++++++++---- 4 files changed, 46 insertions(+), 23 deletions(-) diff --git a/cmd/api/main.go b/cmd/api/main.go index 9f99d761..8be5e328 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -232,6 +232,12 @@ func main() { ] } }, + "invite": { + "condition": { + "op": "RequesterHasTag", + "const": "_invite" + } + }, "timeline.distribute": { "condition": { "op": "Or", @@ -302,13 +308,13 @@ func main() { associationService := concurrent.SetupAssociationService(db, rdb, mc, client, policy, conconf) associationHandler := association.NewHandler(associationService) - profileService := concurrent.SetupProfileService(db, rdb, mc, client, conconf) + profileService := concurrent.SetupProfileService(db, rdb, mc, client, policy, conconf) profileHandler := profile.NewHandler(profileService) timelineService := concurrent.SetupTimelineService(db, rdb, mc, client, policy, conconf) timelineHandler := timeline.NewHandler(timelineService) - entityService := concurrent.SetupEntityService(db, rdb, mc, client, conconf) + entityService := concurrent.SetupEntityService(db, rdb, mc, client, policy, conconf) entityHandler := entity.NewHandler(entityService) authService := concurrent.SetupAuthService(db, rdb, mc, client, policy, conconf) @@ -317,7 +323,7 @@ func main() { keyService := concurrent.SetupKeyService(db, rdb, mc, client, conconf) keyHandler := key.NewHandler(keyService) - ackService := concurrent.SetupAckService(db, rdb, mc, client, conconf) + ackService := concurrent.SetupAckService(db, rdb, mc, client, policy, conconf) ackHandler := ack.NewHandler(ackService) storeService := concurrent.SetupStoreService(db, rdb, mc, client, policy, conconf, config.Server.RepositoryPath) diff --git a/wire.go b/wire.go index 41730c81..f8eaeb0d 100644 --- a/wire.go +++ b/wire.go @@ -94,7 +94,7 @@ func SetupJobService(db *gorm.DB) core.JobService { return nil } -func SetupAckService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client client.Client, config core.Config) core.AckService { +func SetupAckService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client client.Client, policy core.PolicyService, config core.Config) core.AckService { wire.Build(ackServiceProvider) return nil } @@ -109,7 +109,7 @@ func SetupMessageService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, cl return nil } -func SetupProfileService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client client.Client, config core.Config) core.ProfileService { +func SetupProfileService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client client.Client, policy core.PolicyService, config core.Config) core.ProfileService { wire.Build(profileServiceProvider) return nil } @@ -129,7 +129,7 @@ func SetupDomainService(db *gorm.DB, client client.Client, config core.Config) c return nil } -func SetupEntityService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client client.Client, config core.Config) core.EntityService { +func SetupEntityService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client client.Client, policy core.PolicyService, config core.Config) core.EntityService { wire.Build(entityServiceProvider) return nil } diff --git a/wire_gen.go b/wire_gen.go index c004af44..8b7f9ae4 100644 --- a/wire_gen.go +++ b/wire_gen.go @@ -53,9 +53,9 @@ func SetupJobService(db *gorm.DB) core.JobService { return jobService } -func SetupAckService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client2 client.Client, config core.Config) core.AckService { +func SetupAckService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client2 client.Client, policy2 core.PolicyService, config core.Config) core.AckService { repository := ack.NewRepository(db) - entityService := SetupEntityService(db, rdb, mc, client2, config) + entityService := SetupEntityService(db, rdb, mc, client2, policy2, config) keyService := SetupKeyService(db, rdb, mc, client2, config) ackService := ack.NewService(repository, client2, entityService, keyService, config) return ackService @@ -70,7 +70,7 @@ func SetupKeyService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client func SetupMessageService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client2 client.Client, policy2 core.PolicyService, config core.Config) core.MessageService { schemaService := SetupSchemaService(db) repository := message.NewRepository(db, mc, schemaService) - entityService := SetupEntityService(db, rdb, mc, client2, config) + entityService := SetupEntityService(db, rdb, mc, client2, policy2, config) domainService := SetupDomainService(db, client2, config) timelineService := SetupTimelineService(db, rdb, mc, client2, policy2, config) keyService := SetupKeyService(db, rdb, mc, client2, config) @@ -78,10 +78,10 @@ func SetupMessageService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, cl return messageService } -func SetupProfileService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client2 client.Client, config core.Config) core.ProfileService { +func SetupProfileService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client2 client.Client, policy2 core.PolicyService, config core.Config) core.ProfileService { schemaService := SetupSchemaService(db) repository := profile.NewRepository(db, mc, schemaService) - entityService := SetupEntityService(db, rdb, mc, client2, config) + entityService := SetupEntityService(db, rdb, mc, client2, policy2, config) semanticIDService := SetupSemanticidService(db) profileService := profile.NewService(repository, entityService, semanticIDService) return profileService @@ -90,7 +90,7 @@ func SetupProfileService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, cl func SetupAssociationService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client2 client.Client, policy2 core.PolicyService, config core.Config) core.AssociationService { schemaService := SetupSchemaService(db) repository := association.NewRepository(db, mc, schemaService) - entityService := SetupEntityService(db, rdb, mc, client2, config) + entityService := SetupEntityService(db, rdb, mc, client2, policy2, config) domainService := SetupDomainService(db, client2, config) timelineService := SetupTimelineService(db, rdb, mc, client2, policy2, config) messageService := SetupMessageService(db, rdb, mc, client2, policy2, config) @@ -102,7 +102,7 @@ func SetupAssociationService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client func SetupTimelineService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client2 client.Client, policy2 core.PolicyService, config core.Config) core.TimelineService { schemaService := SetupSchemaService(db) repository := timeline.NewRepository(db, rdb, mc, client2, schemaService, config) - entityService := SetupEntityService(db, rdb, mc, client2, config) + entityService := SetupEntityService(db, rdb, mc, client2, policy2, config) domainService := SetupDomainService(db, client2, config) semanticIDService := SetupSemanticidService(db) subscriptionService := SetupSubscriptionService(db) @@ -116,12 +116,12 @@ func SetupDomainService(db *gorm.DB, client2 client.Client, config core.Config) return domainService } -func SetupEntityService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client2 client.Client, config core.Config) core.EntityService { +func SetupEntityService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client2 client.Client, policy2 core.PolicyService, config core.Config) core.EntityService { schemaService := SetupSchemaService(db) repository := entity.NewRepository(db, mc, schemaService) keyService := SetupKeyService(db, rdb, mc, client2, config) service := SetupJwtService(rdb) - entityService := entity.NewService(repository, client2, config, keyService, service) + entityService := entity.NewService(repository, client2, config, keyService, policy2, service) return entityService } @@ -133,7 +133,7 @@ func SetupAgent(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client2 cli } func SetupAuthService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client2 client.Client, policy2 core.PolicyService, config core.Config) core.AuthService { - entityService := SetupEntityService(db, rdb, mc, client2, config) + entityService := SetupEntityService(db, rdb, mc, client2, policy2, config) domainService := SetupDomainService(db, client2, config) keyService := SetupKeyService(db, rdb, mc, client2, config) authService := auth.NewService(config, entityService, domainService, keyService, policy2) @@ -155,12 +155,12 @@ func SetupSchemaService(db *gorm.DB) core.SchemaService { func SetupStoreService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client2 client.Client, policy2 core.PolicyService, config core.Config, repositoryPath string) core.StoreService { repository := store.NewRepository(rdb) keyService := SetupKeyService(db, rdb, mc, client2, config) - entityService := SetupEntityService(db, rdb, mc, client2, config) + entityService := SetupEntityService(db, rdb, mc, client2, policy2, config) messageService := SetupMessageService(db, rdb, mc, client2, policy2, config) associationService := SetupAssociationService(db, rdb, mc, client2, policy2, config) - profileService := SetupProfileService(db, rdb, mc, client2, config) + profileService := SetupProfileService(db, rdb, mc, client2, policy2, config) timelineService := SetupTimelineService(db, rdb, mc, client2, policy2, config) - ackService := SetupAckService(db, rdb, mc, client2, config) + ackService := SetupAckService(db, rdb, mc, client2, policy2, config) subscriptionService := SetupSubscriptionService(db) semanticIDService := SetupSemanticidService(db) storeService := store.NewService(repository, keyService, entityService, messageService, associationService, profileService, timelineService, ackService, subscriptionService, semanticIDService, config, repositoryPath) diff --git a/x/entity/service.go b/x/entity/service.go index cbb0b971..e1a42b22 100644 --- a/x/entity/service.go +++ b/x/entity/service.go @@ -14,7 +14,6 @@ import ( "github.com/totegamma/concurrent/client" "github.com/totegamma/concurrent/core" "github.com/totegamma/concurrent/x/jwt" - "golang.org/x/exp/slices" ) type service struct { @@ -22,16 +21,25 @@ type service struct { client client.Client config core.Config key core.KeyService + policy core.PolicyService jwtService jwt.Service } // NewService creates a new entity service -func NewService(repository Repository, client client.Client, config core.Config, key core.KeyService, jwtService jwt.Service) core.EntityService { +func NewService( + repository Repository, + client client.Client, + config core.Config, + key core.KeyService, + policy core.PolicyService, + jwtService jwt.Service, +) core.EntityService { return &service{ repository, client, config, key, + policy, jwtService, } } @@ -188,8 +196,17 @@ func (s *service) Affiliation(ctx context.Context, mode core.CommitMode, documen return core.Entity{}, err } - inviterTags := strings.Split(inviter.Tag, ",") - if !slices.Contains(inviterTags, "_invite") { + rctx := core.RequestContext{ + Requester: inviter, + } + + policyResult, err := s.policy.TestWithGlobalPolicy(ctx, rctx, "invite") + if err != nil { + span.RecordError(err) + return core.Entity{}, err + } + + if policyResult == core.PolicyEvalResultNever || policyResult == core.PolicyEvalResultDeny { return core.Entity{}, fmt.Errorf("inviter is not allowed to invite") } From 6eccb3dcbbe61b360391b6a50bb91c16fe363dc3 Mon Sep 17 00:00:00 2001 From: totegamma Date: Mon, 15 Jul 2024 20:46:46 +0900 Subject: [PATCH 12/22] refactor: create api/policy.go --- cmd/api/main.go | 87 +----------------------------------------- cmd/api/policy.go | 97 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 86 deletions(-) create mode 100644 cmd/api/policy.go diff --git a/cmd/api/main.go b/cmd/api/main.go index 8be5e328..19539f7e 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -2,7 +2,6 @@ package main import ( "context" - "encoding/json" "fmt" "log" "log/slog" @@ -207,91 +206,7 @@ func main() { client := client.NewClient() - var globalPolicyJson = ` - { - "statements": { - "global": { - "dominant": true, - "defaultOnTrue": true, - "condition": { - "op": "Not", - "args": [ - { - "op": "Or", - "args": [ - { - "op": "RequesterDomainHasTag", - "const": "_block" - }, - { - "op": "RequesterHasTag", - "const": "_block" - } - ] - } - ] - } - }, - "invite": { - "condition": { - "op": "RequesterHasTag", - "const": "_invite" - } - }, - "timeline.distribute": { - "condition": { - "op": "Or", - "args": [ - { - "op": "LoadSelf", - "const": "domainOwned" - }, - { - "op": "Eq", - "args": [ - { - "op": "LoadSelf", - "const": "author" - }, - { - "op": "RequesterID" - } - ] - } - ] - } - }, - "timeline.message.read": { - "condition": { - "op": "Or", - "args": [ - { - "op": "LoadSelf", - "const": "domainOwned" - }, - { - "op": "Eq", - "args": [ - { - "op": "LoadSelf", - "const": "author" - }, - { - "op": "RequesterID" - } - ] - } - ] - } - } - } - }` - - globalPolicy := core.Policy{} - err = json.Unmarshal([]byte(globalPolicyJson), &globalPolicy) - if err != nil { - panic("failed to parse global policy") - } + globalPolicy := getDefaultGlobalPolicy() policy := concurrent.SetupPolicyService(rdb, globalPolicy, conconf) agent := concurrent.SetupAgent(db, rdb, mc, client, policy, conconf, config.Server.RepositoryPath) diff --git a/cmd/api/policy.go b/cmd/api/policy.go new file mode 100644 index 00000000..6ac8efe0 --- /dev/null +++ b/cmd/api/policy.go @@ -0,0 +1,97 @@ +package main + +import ( + "encoding/json" + + "github.com/totegamma/concurrent/core" +) + +var globalPolicyJson = ` +{ + "statements": { + "global": { + "dominant": true, + "defaultOnTrue": true, + "condition": { + "op": "Not", + "args": [ + { + "op": "Or", + "args": [ + { + "op": "RequesterDomainHasTag", + "const": "_block" + }, + { + "op": "RequesterHasTag", + "const": "_block" + } + ] + } + ] + } + }, + "invite": { + "condition": { + "op": "RequesterHasTag", + "const": "_invite" + } + }, + "timeline.distribute": { + "condition": { + "op": "Or", + "args": [ + { + "op": "LoadSelf", + "const": "domainOwned" + }, + { + "op": "Eq", + "args": [ + { + "op": "LoadSelf", + "const": "author" + }, + { + "op": "RequesterID" + } + ] + } + ] + } + }, + "timeline.message.read": { + "condition": { + "op": "Or", + "args": [ + { + "op": "LoadSelf", + "const": "domainOwned" + }, + { + "op": "Eq", + "args": [ + { + "op": "LoadSelf", + "const": "author" + }, + { + "op": "RequesterID" + } + ] + } + ] + } + } + } +}` + +func getDefaultGlobalPolicy() core.Policy { + globalPolicy := core.Policy{} + err := json.Unmarshal([]byte(globalPolicyJson), &globalPolicy) + if err != nil { + panic("failed to parse global policy") + } + + return globalPolicy +} From 8ef0f5fffe65ce5bcad2524888ee1b7796e9b7bd Mon Sep 17 00:00:00 2001 From: totegamma Date: Mon, 15 Jul 2024 21:12:58 +0900 Subject: [PATCH 13/22] fix test --- client/mock/client.go | 23 ++-- core/mock/services.go | 264 ++++++++++++++++++++---------------- x/auth/middleware_test.go | 8 +- x/userkv/mock/repository.go | 11 +- 4 files changed, 177 insertions(+), 129 deletions(-) diff --git a/client/mock/client.go b/client/mock/client.go index 3cffc72c..b8a6f9f8 100644 --- a/client/mock/client.go +++ b/client/mock/client.go @@ -1,5 +1,10 @@ // Code generated by MockGen. DO NOT EDIT. // Source: client.go +// +// Generated by this command: +// +// mockgen -source=client.go -destination=mock/client.go +// // Package mock_client is a generated GoMock package. package mock_client @@ -48,7 +53,7 @@ func (m *MockClient) Commit(ctx context.Context, domain, body string, response a } // Commit indicates an expected call of Commit. -func (mr *MockClientMockRecorder) Commit(ctx, domain, body, response, opts interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) Commit(ctx, domain, body, response, opts any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Commit", reflect.TypeOf((*MockClient)(nil).Commit), ctx, domain, body, response, opts) } @@ -63,7 +68,7 @@ func (m *MockClient) GetAssociation(ctx context.Context, domain, id string, opts } // GetAssociation indicates an expected call of GetAssociation. -func (mr *MockClientMockRecorder) GetAssociation(ctx, domain, id, opts interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) GetAssociation(ctx, domain, id, opts any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAssociation", reflect.TypeOf((*MockClient)(nil).GetAssociation), ctx, domain, id, opts) } @@ -78,7 +83,7 @@ func (m *MockClient) GetChunks(ctx context.Context, domain string, timelines []s } // GetChunks indicates an expected call of GetChunks. -func (mr *MockClientMockRecorder) GetChunks(ctx, domain, timelines, queryTime, opts interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) GetChunks(ctx, domain, timelines, queryTime, opts any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChunks", reflect.TypeOf((*MockClient)(nil).GetChunks), ctx, domain, timelines, queryTime, opts) } @@ -93,7 +98,7 @@ func (m *MockClient) GetDomain(ctx context.Context, domain string, opts *client. } // GetDomain indicates an expected call of GetDomain. -func (mr *MockClientMockRecorder) GetDomain(ctx, domain, opts interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) GetDomain(ctx, domain, opts any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDomain", reflect.TypeOf((*MockClient)(nil).GetDomain), ctx, domain, opts) } @@ -108,7 +113,7 @@ func (m *MockClient) GetEntity(ctx context.Context, domain, address string, opts } // GetEntity indicates an expected call of GetEntity. -func (mr *MockClientMockRecorder) GetEntity(ctx, domain, address, opts interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) GetEntity(ctx, domain, address, opts any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEntity", reflect.TypeOf((*MockClient)(nil).GetEntity), ctx, domain, address, opts) } @@ -123,7 +128,7 @@ func (m *MockClient) GetKey(ctx context.Context, domain, id string, opts *client } // GetKey indicates an expected call of GetKey. -func (mr *MockClientMockRecorder) GetKey(ctx, domain, id, opts interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) GetKey(ctx, domain, id, opts any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetKey", reflect.TypeOf((*MockClient)(nil).GetKey), ctx, domain, id, opts) } @@ -138,7 +143,7 @@ func (m *MockClient) GetMessage(ctx context.Context, domain, id string, opts *cl } // GetMessage indicates an expected call of GetMessage. -func (mr *MockClientMockRecorder) GetMessage(ctx, domain, id, opts interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) GetMessage(ctx, domain, id, opts any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMessage", reflect.TypeOf((*MockClient)(nil).GetMessage), ctx, domain, id, opts) } @@ -153,7 +158,7 @@ func (m *MockClient) GetProfile(ctx context.Context, domain, address string, opt } // GetProfile indicates an expected call of GetProfile. -func (mr *MockClientMockRecorder) GetProfile(ctx, domain, address, opts interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) GetProfile(ctx, domain, address, opts any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProfile", reflect.TypeOf((*MockClient)(nil).GetProfile), ctx, domain, address, opts) } @@ -168,7 +173,7 @@ func (m *MockClient) GetTimeline(ctx context.Context, domain, id string, opts *c } // GetTimeline indicates an expected call of GetTimeline. -func (mr *MockClientMockRecorder) GetTimeline(ctx, domain, id, opts interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) GetTimeline(ctx, domain, id, opts any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTimeline", reflect.TypeOf((*MockClient)(nil).GetTimeline), ctx, domain, id, opts) } diff --git a/core/mock/services.go b/core/mock/services.go index 31421dee..122dcf1a 100644 --- a/core/mock/services.go +++ b/core/mock/services.go @@ -1,5 +1,10 @@ // Code generated by MockGen. DO NOT EDIT. // Source: interfaces.go +// +// Generated by this command: +// +// mockgen -source=interfaces.go -destination=mock/services.go +// // Package mock_core is a generated GoMock package. package mock_core @@ -49,7 +54,7 @@ func (m *MockAckService) Ack(ctx context.Context, mode core.CommitMode, document } // Ack indicates an expected call of Ack. -func (mr *MockAckServiceMockRecorder) Ack(ctx, mode, document, signature interface{}) *gomock.Call { +func (mr *MockAckServiceMockRecorder) Ack(ctx, mode, document, signature any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ack", reflect.TypeOf((*MockAckService)(nil).Ack), ctx, mode, document, signature) } @@ -64,7 +69,7 @@ func (m *MockAckService) GetAcker(ctx context.Context, key string) ([]core.Ack, } // GetAcker indicates an expected call of GetAcker. -func (mr *MockAckServiceMockRecorder) GetAcker(ctx, key interface{}) *gomock.Call { +func (mr *MockAckServiceMockRecorder) GetAcker(ctx, key any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAcker", reflect.TypeOf((*MockAckService)(nil).GetAcker), ctx, key) } @@ -79,7 +84,7 @@ func (m *MockAckService) GetAcking(ctx context.Context, key string) ([]core.Ack, } // GetAcking indicates an expected call of GetAcking. -func (mr *MockAckServiceMockRecorder) GetAcking(ctx, key interface{}) *gomock.Call { +func (mr *MockAckServiceMockRecorder) GetAcking(ctx, key any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAcking", reflect.TypeOf((*MockAckService)(nil).GetAcking), ctx, key) } @@ -151,7 +156,7 @@ func (m *MockAssociationService) Clean(ctx context.Context, ccid string) error { } // Clean indicates an expected call of Clean. -func (mr *MockAssociationServiceMockRecorder) Clean(ctx, ccid interface{}) *gomock.Call { +func (mr *MockAssociationServiceMockRecorder) Clean(ctx, ccid any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clean", reflect.TypeOf((*MockAssociationService)(nil).Clean), ctx, ccid) } @@ -166,7 +171,7 @@ func (m *MockAssociationService) Count(ctx context.Context) (int64, error) { } // Count indicates an expected call of Count. -func (mr *MockAssociationServiceMockRecorder) Count(ctx interface{}) *gomock.Call { +func (mr *MockAssociationServiceMockRecorder) Count(ctx any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Count", reflect.TypeOf((*MockAssociationService)(nil).Count), ctx) } @@ -181,7 +186,7 @@ func (m *MockAssociationService) Create(ctx context.Context, mode core.CommitMod } // Create indicates an expected call of Create. -func (mr *MockAssociationServiceMockRecorder) Create(ctx, mode, document, signature interface{}) *gomock.Call { +func (mr *MockAssociationServiceMockRecorder) Create(ctx, mode, document, signature any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockAssociationService)(nil).Create), ctx, mode, document, signature) } @@ -196,7 +201,7 @@ func (m *MockAssociationService) Delete(ctx context.Context, mode core.CommitMod } // Delete indicates an expected call of Delete. -func (mr *MockAssociationServiceMockRecorder) Delete(ctx, mode, document, signature interface{}) *gomock.Call { +func (mr *MockAssociationServiceMockRecorder) Delete(ctx, mode, document, signature any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockAssociationService)(nil).Delete), ctx, mode, document, signature) } @@ -211,7 +216,7 @@ func (m *MockAssociationService) Get(ctx context.Context, id string) (core.Assoc } // Get indicates an expected call of Get. -func (mr *MockAssociationServiceMockRecorder) Get(ctx, id interface{}) *gomock.Call { +func (mr *MockAssociationServiceMockRecorder) Get(ctx, id any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockAssociationService)(nil).Get), ctx, id) } @@ -226,7 +231,7 @@ func (m *MockAssociationService) GetBySchema(ctx context.Context, messageID, sch } // GetBySchema indicates an expected call of GetBySchema. -func (mr *MockAssociationServiceMockRecorder) GetBySchema(ctx, messageID, schema interface{}) *gomock.Call { +func (mr *MockAssociationServiceMockRecorder) GetBySchema(ctx, messageID, schema any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBySchema", reflect.TypeOf((*MockAssociationService)(nil).GetBySchema), ctx, messageID, schema) } @@ -241,7 +246,7 @@ func (m *MockAssociationService) GetBySchemaAndVariant(ctx context.Context, mess } // GetBySchemaAndVariant indicates an expected call of GetBySchemaAndVariant. -func (mr *MockAssociationServiceMockRecorder) GetBySchemaAndVariant(ctx, messageID, schema, variant interface{}) *gomock.Call { +func (mr *MockAssociationServiceMockRecorder) GetBySchemaAndVariant(ctx, messageID, schema, variant any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBySchemaAndVariant", reflect.TypeOf((*MockAssociationService)(nil).GetBySchemaAndVariant), ctx, messageID, schema, variant) } @@ -256,7 +261,7 @@ func (m *MockAssociationService) GetByTarget(ctx context.Context, targetID strin } // GetByTarget indicates an expected call of GetByTarget. -func (mr *MockAssociationServiceMockRecorder) GetByTarget(ctx, targetID interface{}) *gomock.Call { +func (mr *MockAssociationServiceMockRecorder) GetByTarget(ctx, targetID any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetByTarget", reflect.TypeOf((*MockAssociationService)(nil).GetByTarget), ctx, targetID) } @@ -271,7 +276,7 @@ func (m *MockAssociationService) GetCountsBySchema(ctx context.Context, messageI } // GetCountsBySchema indicates an expected call of GetCountsBySchema. -func (mr *MockAssociationServiceMockRecorder) GetCountsBySchema(ctx, messageID interface{}) *gomock.Call { +func (mr *MockAssociationServiceMockRecorder) GetCountsBySchema(ctx, messageID any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCountsBySchema", reflect.TypeOf((*MockAssociationService)(nil).GetCountsBySchema), ctx, messageID) } @@ -286,7 +291,7 @@ func (m *MockAssociationService) GetCountsBySchemaAndVariant(ctx context.Context } // GetCountsBySchemaAndVariant indicates an expected call of GetCountsBySchemaAndVariant. -func (mr *MockAssociationServiceMockRecorder) GetCountsBySchemaAndVariant(ctx, messageID, schema interface{}) *gomock.Call { +func (mr *MockAssociationServiceMockRecorder) GetCountsBySchemaAndVariant(ctx, messageID, schema any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCountsBySchemaAndVariant", reflect.TypeOf((*MockAssociationService)(nil).GetCountsBySchemaAndVariant), ctx, messageID, schema) } @@ -301,7 +306,7 @@ func (m *MockAssociationService) GetOwn(ctx context.Context, author string) ([]c } // GetOwn indicates an expected call of GetOwn. -func (mr *MockAssociationServiceMockRecorder) GetOwn(ctx, author interface{}) *gomock.Call { +func (mr *MockAssociationServiceMockRecorder) GetOwn(ctx, author any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOwn", reflect.TypeOf((*MockAssociationService)(nil).GetOwn), ctx, author) } @@ -316,7 +321,7 @@ func (m *MockAssociationService) GetOwnByTarget(ctx context.Context, targetID, a } // GetOwnByTarget indicates an expected call of GetOwnByTarget. -func (mr *MockAssociationServiceMockRecorder) GetOwnByTarget(ctx, targetID, author interface{}) *gomock.Call { +func (mr *MockAssociationServiceMockRecorder) GetOwnByTarget(ctx, targetID, author any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOwnByTarget", reflect.TypeOf((*MockAssociationService)(nil).GetOwnByTarget), ctx, targetID, author) } @@ -353,7 +358,7 @@ func (m *MockAuthService) IdentifyIdentity(next echo.HandlerFunc) echo.HandlerFu } // IdentifyIdentity indicates an expected call of IdentifyIdentity. -func (mr *MockAuthServiceMockRecorder) IdentifyIdentity(next interface{}) *gomock.Call { +func (mr *MockAuthServiceMockRecorder) IdentifyIdentity(next any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IdentifyIdentity", reflect.TypeOf((*MockAuthService)(nil).IdentifyIdentity), next) } @@ -368,7 +373,7 @@ func (m *MockAuthService) IssuePassport(ctx context.Context, requester string, k } // IssuePassport indicates an expected call of IssuePassport. -func (mr *MockAuthServiceMockRecorder) IssuePassport(ctx, requester, key interface{}) *gomock.Call { +func (mr *MockAuthServiceMockRecorder) IssuePassport(ctx, requester, key any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IssuePassport", reflect.TypeOf((*MockAuthService)(nil).IssuePassport), ctx, requester, key) } @@ -405,7 +410,7 @@ func (m *MockDomainService) Delete(ctx context.Context, id string) error { } // Delete indicates an expected call of Delete. -func (mr *MockDomainServiceMockRecorder) Delete(ctx, id interface{}) *gomock.Call { +func (mr *MockDomainServiceMockRecorder) Delete(ctx, id any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockDomainService)(nil).Delete), ctx, id) } @@ -420,7 +425,7 @@ func (m *MockDomainService) GetByCCID(ctx context.Context, key string) (core.Dom } // GetByCCID indicates an expected call of GetByCCID. -func (mr *MockDomainServiceMockRecorder) GetByCCID(ctx, key interface{}) *gomock.Call { +func (mr *MockDomainServiceMockRecorder) GetByCCID(ctx, key any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetByCCID", reflect.TypeOf((*MockDomainService)(nil).GetByCCID), ctx, key) } @@ -435,7 +440,7 @@ func (m *MockDomainService) GetByFQDN(ctx context.Context, key string) (core.Dom } // GetByFQDN indicates an expected call of GetByFQDN. -func (mr *MockDomainServiceMockRecorder) GetByFQDN(ctx, key interface{}) *gomock.Call { +func (mr *MockDomainServiceMockRecorder) GetByFQDN(ctx, key any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetByFQDN", reflect.TypeOf((*MockDomainService)(nil).GetByFQDN), ctx, key) } @@ -450,7 +455,7 @@ func (m *MockDomainService) List(ctx context.Context) ([]core.Domain, error) { } // List indicates an expected call of List. -func (mr *MockDomainServiceMockRecorder) List(ctx interface{}) *gomock.Call { +func (mr *MockDomainServiceMockRecorder) List(ctx any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockDomainService)(nil).List), ctx) } @@ -464,7 +469,7 @@ func (m *MockDomainService) Update(ctx context.Context, host core.Domain) error } // Update indicates an expected call of Update. -func (mr *MockDomainServiceMockRecorder) Update(ctx, host interface{}) *gomock.Call { +func (mr *MockDomainServiceMockRecorder) Update(ctx, host any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockDomainService)(nil).Update), ctx, host) } @@ -478,7 +483,7 @@ func (m *MockDomainService) UpdateScrapeTime(ctx context.Context, id string, scr } // UpdateScrapeTime indicates an expected call of UpdateScrapeTime. -func (mr *MockDomainServiceMockRecorder) UpdateScrapeTime(ctx, id, scrapeTime interface{}) *gomock.Call { +func (mr *MockDomainServiceMockRecorder) UpdateScrapeTime(ctx, id, scrapeTime any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateScrapeTime", reflect.TypeOf((*MockDomainService)(nil).UpdateScrapeTime), ctx, id, scrapeTime) } @@ -493,7 +498,7 @@ func (m *MockDomainService) Upsert(ctx context.Context, host core.Domain) (core. } // Upsert indicates an expected call of Upsert. -func (mr *MockDomainServiceMockRecorder) Upsert(ctx, host interface{}) *gomock.Call { +func (mr *MockDomainServiceMockRecorder) Upsert(ctx, host any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Upsert", reflect.TypeOf((*MockDomainService)(nil).Upsert), ctx, host) } @@ -531,7 +536,7 @@ func (m *MockEntityService) Affiliation(ctx context.Context, mode core.CommitMod } // Affiliation indicates an expected call of Affiliation. -func (mr *MockEntityServiceMockRecorder) Affiliation(ctx, mode, document, signature, meta interface{}) *gomock.Call { +func (mr *MockEntityServiceMockRecorder) Affiliation(ctx, mode, document, signature, meta any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Affiliation", reflect.TypeOf((*MockEntityService)(nil).Affiliation), ctx, mode, document, signature, meta) } @@ -545,7 +550,7 @@ func (m *MockEntityService) Clean(ctx context.Context, ccid string) error { } // Clean indicates an expected call of Clean. -func (mr *MockEntityServiceMockRecorder) Clean(ctx, ccid interface{}) *gomock.Call { +func (mr *MockEntityServiceMockRecorder) Clean(ctx, ccid any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clean", reflect.TypeOf((*MockEntityService)(nil).Clean), ctx, ccid) } @@ -560,7 +565,7 @@ func (m *MockEntityService) Count(ctx context.Context) (int64, error) { } // Count indicates an expected call of Count. -func (mr *MockEntityServiceMockRecorder) Count(ctx interface{}) *gomock.Call { +func (mr *MockEntityServiceMockRecorder) Count(ctx any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Count", reflect.TypeOf((*MockEntityService)(nil).Count), ctx) } @@ -574,7 +579,7 @@ func (m *MockEntityService) Delete(ctx context.Context, id string) error { } // Delete indicates an expected call of Delete. -func (mr *MockEntityServiceMockRecorder) Delete(ctx, id interface{}) *gomock.Call { +func (mr *MockEntityServiceMockRecorder) Delete(ctx, id any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockEntityService)(nil).Delete), ctx, id) } @@ -589,7 +594,7 @@ func (m *MockEntityService) Get(ctx context.Context, ccid string) (core.Entity, } // Get indicates an expected call of Get. -func (mr *MockEntityServiceMockRecorder) Get(ctx, ccid interface{}) *gomock.Call { +func (mr *MockEntityServiceMockRecorder) Get(ctx, ccid any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockEntityService)(nil).Get), ctx, ccid) } @@ -604,7 +609,7 @@ func (m *MockEntityService) GetByAlias(ctx context.Context, alias string) (core. } // GetByAlias indicates an expected call of GetByAlias. -func (mr *MockEntityServiceMockRecorder) GetByAlias(ctx, alias interface{}) *gomock.Call { +func (mr *MockEntityServiceMockRecorder) GetByAlias(ctx, alias any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetByAlias", reflect.TypeOf((*MockEntityService)(nil).GetByAlias), ctx, alias) } @@ -619,7 +624,7 @@ func (m *MockEntityService) GetMeta(ctx context.Context, ccid string) (core.Enti } // GetMeta indicates an expected call of GetMeta. -func (mr *MockEntityServiceMockRecorder) GetMeta(ctx, ccid interface{}) *gomock.Call { +func (mr *MockEntityServiceMockRecorder) GetMeta(ctx, ccid any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMeta", reflect.TypeOf((*MockEntityService)(nil).GetMeta), ctx, ccid) } @@ -634,7 +639,7 @@ func (m *MockEntityService) GetWithHint(ctx context.Context, ccid, hint string) } // GetWithHint indicates an expected call of GetWithHint. -func (mr *MockEntityServiceMockRecorder) GetWithHint(ctx, ccid, hint interface{}) *gomock.Call { +func (mr *MockEntityServiceMockRecorder) GetWithHint(ctx, ccid, hint any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWithHint", reflect.TypeOf((*MockEntityService)(nil).GetWithHint), ctx, ccid, hint) } @@ -648,7 +653,7 @@ func (m *MockEntityService) IsUserExists(ctx context.Context, user string) bool } // IsUserExists indicates an expected call of IsUserExists. -func (mr *MockEntityServiceMockRecorder) IsUserExists(ctx, user interface{}) *gomock.Call { +func (mr *MockEntityServiceMockRecorder) IsUserExists(ctx, user any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsUserExists", reflect.TypeOf((*MockEntityService)(nil).IsUserExists), ctx, user) } @@ -663,7 +668,7 @@ func (m *MockEntityService) List(ctx context.Context) ([]core.Entity, error) { } // List indicates an expected call of List. -func (mr *MockEntityServiceMockRecorder) List(ctx interface{}) *gomock.Call { +func (mr *MockEntityServiceMockRecorder) List(ctx any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockEntityService)(nil).List), ctx) } @@ -678,7 +683,7 @@ func (m *MockEntityService) PullEntityFromRemote(ctx context.Context, id, domain } // PullEntityFromRemote indicates an expected call of PullEntityFromRemote. -func (mr *MockEntityServiceMockRecorder) PullEntityFromRemote(ctx, id, domain interface{}) *gomock.Call { +func (mr *MockEntityServiceMockRecorder) PullEntityFromRemote(ctx, id, domain any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PullEntityFromRemote", reflect.TypeOf((*MockEntityService)(nil).PullEntityFromRemote), ctx, id, domain) } @@ -693,7 +698,7 @@ func (m *MockEntityService) Tombstone(ctx context.Context, mode core.CommitMode, } // Tombstone indicates an expected call of Tombstone. -func (mr *MockEntityServiceMockRecorder) Tombstone(ctx, mode, document, signature interface{}) *gomock.Call { +func (mr *MockEntityServiceMockRecorder) Tombstone(ctx, mode, document, signature any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Tombstone", reflect.TypeOf((*MockEntityService)(nil).Tombstone), ctx, mode, document, signature) } @@ -707,7 +712,7 @@ func (m *MockEntityService) UpdateScore(ctx context.Context, id string, score in } // UpdateScore indicates an expected call of UpdateScore. -func (mr *MockEntityServiceMockRecorder) UpdateScore(ctx, id, score interface{}) *gomock.Call { +func (mr *MockEntityServiceMockRecorder) UpdateScore(ctx, id, score any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateScore", reflect.TypeOf((*MockEntityService)(nil).UpdateScore), ctx, id, score) } @@ -721,7 +726,7 @@ func (m *MockEntityService) UpdateTag(ctx context.Context, id, tag string) error } // UpdateTag indicates an expected call of UpdateTag. -func (mr *MockEntityServiceMockRecorder) UpdateTag(ctx, id, tag interface{}) *gomock.Call { +func (mr *MockEntityServiceMockRecorder) UpdateTag(ctx, id, tag any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateTag", reflect.TypeOf((*MockEntityService)(nil).UpdateTag), ctx, id, tag) } @@ -758,7 +763,7 @@ func (m *MockKeyService) Clean(ctx context.Context, ccid string) error { } // Clean indicates an expected call of Clean. -func (mr *MockKeyServiceMockRecorder) Clean(ctx, ccid interface{}) *gomock.Call { +func (mr *MockKeyServiceMockRecorder) Clean(ctx, ccid any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clean", reflect.TypeOf((*MockKeyService)(nil).Clean), ctx, ccid) } @@ -773,7 +778,7 @@ func (m *MockKeyService) Enact(ctx context.Context, mode core.CommitMode, payloa } // Enact indicates an expected call of Enact. -func (mr *MockKeyServiceMockRecorder) Enact(ctx, mode, payload, signature interface{}) *gomock.Call { +func (mr *MockKeyServiceMockRecorder) Enact(ctx, mode, payload, signature any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Enact", reflect.TypeOf((*MockKeyService)(nil).Enact), ctx, mode, payload, signature) } @@ -788,7 +793,7 @@ func (m *MockKeyService) GetAllKeys(ctx context.Context, owner string) ([]core.K } // GetAllKeys indicates an expected call of GetAllKeys. -func (mr *MockKeyServiceMockRecorder) GetAllKeys(ctx, owner interface{}) *gomock.Call { +func (mr *MockKeyServiceMockRecorder) GetAllKeys(ctx, owner any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllKeys", reflect.TypeOf((*MockKeyService)(nil).GetAllKeys), ctx, owner) } @@ -803,7 +808,7 @@ func (m *MockKeyService) GetKeyResolution(ctx context.Context, keyID string) ([] } // GetKeyResolution indicates an expected call of GetKeyResolution. -func (mr *MockKeyServiceMockRecorder) GetKeyResolution(ctx, keyID interface{}) *gomock.Call { +func (mr *MockKeyServiceMockRecorder) GetKeyResolution(ctx, keyID any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetKeyResolution", reflect.TypeOf((*MockKeyService)(nil).GetKeyResolution), ctx, keyID) } @@ -818,7 +823,7 @@ func (m *MockKeyService) GetRemoteKeyResolution(ctx context.Context, remote, key } // GetRemoteKeyResolution indicates an expected call of GetRemoteKeyResolution. -func (mr *MockKeyServiceMockRecorder) GetRemoteKeyResolution(ctx, remote, keyID interface{}) *gomock.Call { +func (mr *MockKeyServiceMockRecorder) GetRemoteKeyResolution(ctx, remote, keyID any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRemoteKeyResolution", reflect.TypeOf((*MockKeyService)(nil).GetRemoteKeyResolution), ctx, remote, keyID) } @@ -833,7 +838,7 @@ func (m *MockKeyService) ResolveSubkey(ctx context.Context, keyID string) (strin } // ResolveSubkey indicates an expected call of ResolveSubkey. -func (mr *MockKeyServiceMockRecorder) ResolveSubkey(ctx, keyID interface{}) *gomock.Call { +func (mr *MockKeyServiceMockRecorder) ResolveSubkey(ctx, keyID any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResolveSubkey", reflect.TypeOf((*MockKeyService)(nil).ResolveSubkey), ctx, keyID) } @@ -848,7 +853,7 @@ func (m *MockKeyService) Revoke(ctx context.Context, mode core.CommitMode, paylo } // Revoke indicates an expected call of Revoke. -func (mr *MockKeyServiceMockRecorder) Revoke(ctx, mode, payload, signature interface{}) *gomock.Call { +func (mr *MockKeyServiceMockRecorder) Revoke(ctx, mode, payload, signature any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Revoke", reflect.TypeOf((*MockKeyService)(nil).Revoke), ctx, mode, payload, signature) } @@ -885,7 +890,7 @@ func (m *MockMessageService) Clean(ctx context.Context, ccid string) error { } // Clean indicates an expected call of Clean. -func (mr *MockMessageServiceMockRecorder) Clean(ctx, ccid interface{}) *gomock.Call { +func (mr *MockMessageServiceMockRecorder) Clean(ctx, ccid any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clean", reflect.TypeOf((*MockMessageService)(nil).Clean), ctx, ccid) } @@ -900,7 +905,7 @@ func (m *MockMessageService) Count(ctx context.Context) (int64, error) { } // Count indicates an expected call of Count. -func (mr *MockMessageServiceMockRecorder) Count(ctx interface{}) *gomock.Call { +func (mr *MockMessageServiceMockRecorder) Count(ctx any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Count", reflect.TypeOf((*MockMessageService)(nil).Count), ctx) } @@ -915,7 +920,7 @@ func (m *MockMessageService) Create(ctx context.Context, mode core.CommitMode, d } // Create indicates an expected call of Create. -func (mr *MockMessageServiceMockRecorder) Create(ctx, mode, document, signature interface{}) *gomock.Call { +func (mr *MockMessageServiceMockRecorder) Create(ctx, mode, document, signature any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockMessageService)(nil).Create), ctx, mode, document, signature) } @@ -930,7 +935,7 @@ func (m *MockMessageService) Delete(ctx context.Context, mode core.CommitMode, d } // Delete indicates an expected call of Delete. -func (mr *MockMessageServiceMockRecorder) Delete(ctx, mode, document, signature interface{}) *gomock.Call { +func (mr *MockMessageServiceMockRecorder) Delete(ctx, mode, document, signature any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockMessageService)(nil).Delete), ctx, mode, document, signature) } @@ -945,7 +950,7 @@ func (m *MockMessageService) Get(ctx context.Context, id, requester string) (cor } // Get indicates an expected call of Get. -func (mr *MockMessageServiceMockRecorder) Get(ctx, id, requester interface{}) *gomock.Call { +func (mr *MockMessageServiceMockRecorder) Get(ctx, id, requester any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockMessageService)(nil).Get), ctx, id, requester) } @@ -960,7 +965,7 @@ func (m *MockMessageService) GetWithOwnAssociations(ctx context.Context, id, req } // GetWithOwnAssociations indicates an expected call of GetWithOwnAssociations. -func (mr *MockMessageServiceMockRecorder) GetWithOwnAssociations(ctx, id, requester interface{}) *gomock.Call { +func (mr *MockMessageServiceMockRecorder) GetWithOwnAssociations(ctx, id, requester any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWithOwnAssociations", reflect.TypeOf((*MockMessageService)(nil).GetWithOwnAssociations), ctx, id, requester) } @@ -988,32 +993,61 @@ func (m *MockPolicyService) EXPECT() *MockPolicyServiceMockRecorder { return m.recorder } +// Summerize mocks base method. +func (m *MockPolicyService) Summerize(results []core.PolicyEvalResult, action string) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Summerize", results, action) + ret0, _ := ret[0].(bool) + return ret0 +} + +// Summerize indicates an expected call of Summerize. +func (mr *MockPolicyServiceMockRecorder) Summerize(results, action any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Summerize", reflect.TypeOf((*MockPolicyService)(nil).Summerize), results, action) +} + // Test mocks base method. -func (m *MockPolicyService) Test(ctx context.Context, policy core.Policy, context core.RequestContext, action string) (bool, error) { +func (m *MockPolicyService) Test(ctx context.Context, policy core.Policy, context core.RequestContext, action string) (core.PolicyEvalResult, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Test", ctx, policy, context, action) - ret0, _ := ret[0].(bool) + ret0, _ := ret[0].(core.PolicyEvalResult) ret1, _ := ret[1].(error) return ret0, ret1 } // Test indicates an expected call of Test. -func (mr *MockPolicyServiceMockRecorder) Test(ctx, policy, context, action interface{}) *gomock.Call { +func (mr *MockPolicyServiceMockRecorder) Test(ctx, policy, context, action any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Test", reflect.TypeOf((*MockPolicyService)(nil).Test), ctx, policy, context, action) } +// TestWithGlobalPolicy mocks base method. +func (m *MockPolicyService) TestWithGlobalPolicy(ctx context.Context, context core.RequestContext, action string) (core.PolicyEvalResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TestWithGlobalPolicy", ctx, context, action) + ret0, _ := ret[0].(core.PolicyEvalResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// TestWithGlobalPolicy indicates an expected call of TestWithGlobalPolicy. +func (mr *MockPolicyServiceMockRecorder) TestWithGlobalPolicy(ctx, context, action any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TestWithGlobalPolicy", reflect.TypeOf((*MockPolicyService)(nil).TestWithGlobalPolicy), ctx, context, action) +} + // TestWithPolicyURL mocks base method. -func (m *MockPolicyService) TestWithPolicyURL(ctx context.Context, url string, context core.RequestContext, action string) (bool, error) { +func (m *MockPolicyService) TestWithPolicyURL(ctx context.Context, url string, context core.RequestContext, action string) (core.PolicyEvalResult, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "TestWithPolicyURL", ctx, url, context, action) - ret0, _ := ret[0].(bool) + ret0, _ := ret[0].(core.PolicyEvalResult) ret1, _ := ret[1].(error) return ret0, ret1 } // TestWithPolicyURL indicates an expected call of TestWithPolicyURL. -func (mr *MockPolicyServiceMockRecorder) TestWithPolicyURL(ctx, url, context, action interface{}) *gomock.Call { +func (mr *MockPolicyServiceMockRecorder) TestWithPolicyURL(ctx, url, context, action any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TestWithPolicyURL", reflect.TypeOf((*MockPolicyService)(nil).TestWithPolicyURL), ctx, url, context, action) } @@ -1050,7 +1084,7 @@ func (m *MockProfileService) Clean(ctx context.Context, ccid string) error { } // Clean indicates an expected call of Clean. -func (mr *MockProfileServiceMockRecorder) Clean(ctx, ccid interface{}) *gomock.Call { +func (mr *MockProfileServiceMockRecorder) Clean(ctx, ccid any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clean", reflect.TypeOf((*MockProfileService)(nil).Clean), ctx, ccid) } @@ -1065,7 +1099,7 @@ func (m *MockProfileService) Count(ctx context.Context) (int64, error) { } // Count indicates an expected call of Count. -func (mr *MockProfileServiceMockRecorder) Count(ctx interface{}) *gomock.Call { +func (mr *MockProfileServiceMockRecorder) Count(ctx any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Count", reflect.TypeOf((*MockProfileService)(nil).Count), ctx) } @@ -1080,7 +1114,7 @@ func (m *MockProfileService) Delete(ctx context.Context, mode core.CommitMode, d } // Delete indicates an expected call of Delete. -func (mr *MockProfileServiceMockRecorder) Delete(ctx, mode, document interface{}) *gomock.Call { +func (mr *MockProfileServiceMockRecorder) Delete(ctx, mode, document any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockProfileService)(nil).Delete), ctx, mode, document) } @@ -1095,7 +1129,7 @@ func (m *MockProfileService) Get(ctx context.Context, id string) (core.Profile, } // Get indicates an expected call of Get. -func (mr *MockProfileServiceMockRecorder) Get(ctx, id interface{}) *gomock.Call { +func (mr *MockProfileServiceMockRecorder) Get(ctx, id any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockProfileService)(nil).Get), ctx, id) } @@ -1110,7 +1144,7 @@ func (m *MockProfileService) GetByAuthor(ctx context.Context, owner string) ([]c } // GetByAuthor indicates an expected call of GetByAuthor. -func (mr *MockProfileServiceMockRecorder) GetByAuthor(ctx, owner interface{}) *gomock.Call { +func (mr *MockProfileServiceMockRecorder) GetByAuthor(ctx, owner any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetByAuthor", reflect.TypeOf((*MockProfileService)(nil).GetByAuthor), ctx, owner) } @@ -1125,7 +1159,7 @@ func (m *MockProfileService) GetByAuthorAndSchema(ctx context.Context, owner, sc } // GetByAuthorAndSchema indicates an expected call of GetByAuthorAndSchema. -func (mr *MockProfileServiceMockRecorder) GetByAuthorAndSchema(ctx, owner, schema interface{}) *gomock.Call { +func (mr *MockProfileServiceMockRecorder) GetByAuthorAndSchema(ctx, owner, schema any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetByAuthorAndSchema", reflect.TypeOf((*MockProfileService)(nil).GetByAuthorAndSchema), ctx, owner, schema) } @@ -1140,7 +1174,7 @@ func (m *MockProfileService) GetBySchema(ctx context.Context, schema string) ([] } // GetBySchema indicates an expected call of GetBySchema. -func (mr *MockProfileServiceMockRecorder) GetBySchema(ctx, schema interface{}) *gomock.Call { +func (mr *MockProfileServiceMockRecorder) GetBySchema(ctx, schema any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBySchema", reflect.TypeOf((*MockProfileService)(nil).GetBySchema), ctx, schema) } @@ -1155,7 +1189,7 @@ func (m *MockProfileService) GetBySemanticID(ctx context.Context, semanticID, ow } // GetBySemanticID indicates an expected call of GetBySemanticID. -func (mr *MockProfileServiceMockRecorder) GetBySemanticID(ctx, semanticID, owner interface{}) *gomock.Call { +func (mr *MockProfileServiceMockRecorder) GetBySemanticID(ctx, semanticID, owner any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBySemanticID", reflect.TypeOf((*MockProfileService)(nil).GetBySemanticID), ctx, semanticID, owner) } @@ -1170,7 +1204,7 @@ func (m *MockProfileService) Upsert(ctx context.Context, mode core.CommitMode, d } // Upsert indicates an expected call of Upsert. -func (mr *MockProfileServiceMockRecorder) Upsert(ctx, mode, document, signature interface{}) *gomock.Call { +func (mr *MockProfileServiceMockRecorder) Upsert(ctx, mode, document, signature any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Upsert", reflect.TypeOf((*MockProfileService)(nil).Upsert), ctx, mode, document, signature) } @@ -1208,7 +1242,7 @@ func (m *MockSchemaService) IDToUrl(ctx context.Context, id uint) (string, error } // IDToUrl indicates an expected call of IDToUrl. -func (mr *MockSchemaServiceMockRecorder) IDToUrl(ctx, id interface{}) *gomock.Call { +func (mr *MockSchemaServiceMockRecorder) IDToUrl(ctx, id any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IDToUrl", reflect.TypeOf((*MockSchemaService)(nil).IDToUrl), ctx, id) } @@ -1223,7 +1257,7 @@ func (m *MockSchemaService) UrlToID(ctx context.Context, url string) (uint, erro } // UrlToID indicates an expected call of UrlToID. -func (mr *MockSchemaServiceMockRecorder) UrlToID(ctx, url interface{}) *gomock.Call { +func (mr *MockSchemaServiceMockRecorder) UrlToID(ctx, url any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UrlToID", reflect.TypeOf((*MockSchemaService)(nil).UrlToID), ctx, url) } @@ -1260,7 +1294,7 @@ func (m *MockSemanticIDService) Clean(ctx context.Context, ccid string) error { } // Clean indicates an expected call of Clean. -func (mr *MockSemanticIDServiceMockRecorder) Clean(ctx, ccid interface{}) *gomock.Call { +func (mr *MockSemanticIDServiceMockRecorder) Clean(ctx, ccid any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clean", reflect.TypeOf((*MockSemanticIDService)(nil).Clean), ctx, ccid) } @@ -1274,7 +1308,7 @@ func (m *MockSemanticIDService) Delete(ctx context.Context, id, owner string) er } // Delete indicates an expected call of Delete. -func (mr *MockSemanticIDServiceMockRecorder) Delete(ctx, id, owner interface{}) *gomock.Call { +func (mr *MockSemanticIDServiceMockRecorder) Delete(ctx, id, owner any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockSemanticIDService)(nil).Delete), ctx, id, owner) } @@ -1289,7 +1323,7 @@ func (m *MockSemanticIDService) Lookup(ctx context.Context, id, owner string) (s } // Lookup indicates an expected call of Lookup. -func (mr *MockSemanticIDServiceMockRecorder) Lookup(ctx, id, owner interface{}) *gomock.Call { +func (mr *MockSemanticIDServiceMockRecorder) Lookup(ctx, id, owner any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Lookup", reflect.TypeOf((*MockSemanticIDService)(nil).Lookup), ctx, id, owner) } @@ -1304,7 +1338,7 @@ func (m *MockSemanticIDService) Name(ctx context.Context, id, owner, target, doc } // Name indicates an expected call of Name. -func (mr *MockSemanticIDServiceMockRecorder) Name(ctx, id, owner, target, document, signature interface{}) *gomock.Call { +func (mr *MockSemanticIDServiceMockRecorder) Name(ctx, id, owner, target, document, signature any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockSemanticIDService)(nil).Name), ctx, id, owner, target, document, signature) } @@ -1353,7 +1387,7 @@ func (m *MockSocketManager) Subscribe(conn *websocket.Conn, timelines []string) } // Subscribe indicates an expected call of Subscribe. -func (mr *MockSocketManagerMockRecorder) Subscribe(conn, timelines interface{}) *gomock.Call { +func (mr *MockSocketManagerMockRecorder) Subscribe(conn, timelines any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Subscribe", reflect.TypeOf((*MockSocketManager)(nil).Subscribe), conn, timelines) } @@ -1365,7 +1399,7 @@ func (m *MockSocketManager) Unsubscribe(conn *websocket.Conn) { } // Unsubscribe indicates an expected call of Unsubscribe. -func (mr *MockSocketManagerMockRecorder) Unsubscribe(conn interface{}) *gomock.Call { +func (mr *MockSocketManagerMockRecorder) Unsubscribe(conn any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unsubscribe", reflect.TypeOf((*MockSocketManager)(nil).Unsubscribe), conn) } @@ -1402,7 +1436,7 @@ func (m *MockStoreService) CleanUserAllData(ctx context.Context, target string) } // CleanUserAllData indicates an expected call of CleanUserAllData. -func (mr *MockStoreServiceMockRecorder) CleanUserAllData(ctx, target interface{}) *gomock.Call { +func (mr *MockStoreServiceMockRecorder) CleanUserAllData(ctx, target any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CleanUserAllData", reflect.TypeOf((*MockStoreService)(nil).CleanUserAllData), ctx, target) } @@ -1417,7 +1451,7 @@ func (m *MockStoreService) Commit(ctx context.Context, mode core.CommitMode, doc } // Commit indicates an expected call of Commit. -func (mr *MockStoreServiceMockRecorder) Commit(ctx, mode, document, signature, option, keys interface{}) *gomock.Call { +func (mr *MockStoreServiceMockRecorder) Commit(ctx, mode, document, signature, option, keys any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Commit", reflect.TypeOf((*MockStoreService)(nil).Commit), ctx, mode, document, signature, option, keys) } @@ -1431,7 +1465,7 @@ func (m *MockStoreService) GetPath(ctx context.Context, id string) string { } // GetPath indicates an expected call of GetPath. -func (mr *MockStoreServiceMockRecorder) GetPath(ctx, id interface{}) *gomock.Call { +func (mr *MockStoreServiceMockRecorder) GetPath(ctx, id any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPath", reflect.TypeOf((*MockStoreService)(nil).GetPath), ctx, id) } @@ -1446,7 +1480,7 @@ func (m *MockStoreService) Restore(ctx context.Context, archive io.Reader, from } // Restore indicates an expected call of Restore. -func (mr *MockStoreServiceMockRecorder) Restore(ctx, archive, from interface{}) *gomock.Call { +func (mr *MockStoreServiceMockRecorder) Restore(ctx, archive, from any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Restore", reflect.TypeOf((*MockStoreService)(nil).Restore), ctx, archive, from) } @@ -1461,7 +1495,7 @@ func (m *MockStoreService) Since(ctx context.Context, since string) ([]core.Comm } // Since indicates an expected call of Since. -func (mr *MockStoreServiceMockRecorder) Since(ctx, since interface{}) *gomock.Call { +func (mr *MockStoreServiceMockRecorder) Since(ctx, since any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Since", reflect.TypeOf((*MockStoreService)(nil).Since), ctx, since) } @@ -1475,7 +1509,7 @@ func (m *MockStoreService) ValidateDocument(ctx context.Context, document, signa } // ValidateDocument indicates an expected call of ValidateDocument. -func (mr *MockStoreServiceMockRecorder) ValidateDocument(ctx, document, signature, keys interface{}) *gomock.Call { +func (mr *MockStoreServiceMockRecorder) ValidateDocument(ctx, document, signature, keys any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateDocument", reflect.TypeOf((*MockStoreService)(nil).ValidateDocument), ctx, document, signature, keys) } @@ -1512,7 +1546,7 @@ func (m *MockSubscriptionService) Clean(ctx context.Context, ccid string) error } // Clean indicates an expected call of Clean. -func (mr *MockSubscriptionServiceMockRecorder) Clean(ctx, ccid interface{}) *gomock.Call { +func (mr *MockSubscriptionServiceMockRecorder) Clean(ctx, ccid any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clean", reflect.TypeOf((*MockSubscriptionService)(nil).Clean), ctx, ccid) } @@ -1527,7 +1561,7 @@ func (m *MockSubscriptionService) CreateSubscription(ctx context.Context, mode c } // CreateSubscription indicates an expected call of CreateSubscription. -func (mr *MockSubscriptionServiceMockRecorder) CreateSubscription(ctx, mode, document, signature interface{}) *gomock.Call { +func (mr *MockSubscriptionServiceMockRecorder) CreateSubscription(ctx, mode, document, signature any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSubscription", reflect.TypeOf((*MockSubscriptionService)(nil).CreateSubscription), ctx, mode, document, signature) } @@ -1542,7 +1576,7 @@ func (m *MockSubscriptionService) DeleteSubscription(ctx context.Context, mode c } // DeleteSubscription indicates an expected call of DeleteSubscription. -func (mr *MockSubscriptionServiceMockRecorder) DeleteSubscription(ctx, mode, document interface{}) *gomock.Call { +func (mr *MockSubscriptionServiceMockRecorder) DeleteSubscription(ctx, mode, document any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSubscription", reflect.TypeOf((*MockSubscriptionService)(nil).DeleteSubscription), ctx, mode, document) } @@ -1557,7 +1591,7 @@ func (m *MockSubscriptionService) GetOwnSubscriptions(ctx context.Context, owner } // GetOwnSubscriptions indicates an expected call of GetOwnSubscriptions. -func (mr *MockSubscriptionServiceMockRecorder) GetOwnSubscriptions(ctx, owner interface{}) *gomock.Call { +func (mr *MockSubscriptionServiceMockRecorder) GetOwnSubscriptions(ctx, owner any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOwnSubscriptions", reflect.TypeOf((*MockSubscriptionService)(nil).GetOwnSubscriptions), ctx, owner) } @@ -1572,7 +1606,7 @@ func (m *MockSubscriptionService) GetSubscription(ctx context.Context, id string } // GetSubscription indicates an expected call of GetSubscription. -func (mr *MockSubscriptionServiceMockRecorder) GetSubscription(ctx, id interface{}) *gomock.Call { +func (mr *MockSubscriptionServiceMockRecorder) GetSubscription(ctx, id any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubscription", reflect.TypeOf((*MockSubscriptionService)(nil).GetSubscription), ctx, id) } @@ -1587,7 +1621,7 @@ func (m *MockSubscriptionService) Subscribe(ctx context.Context, mode core.Commi } // Subscribe indicates an expected call of Subscribe. -func (mr *MockSubscriptionServiceMockRecorder) Subscribe(ctx, mode, document, signature interface{}) *gomock.Call { +func (mr *MockSubscriptionServiceMockRecorder) Subscribe(ctx, mode, document, signature any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Subscribe", reflect.TypeOf((*MockSubscriptionService)(nil).Subscribe), ctx, mode, document, signature) } @@ -1602,7 +1636,7 @@ func (m *MockSubscriptionService) Unsubscribe(ctx context.Context, mode core.Com } // Unsubscribe indicates an expected call of Unsubscribe. -func (mr *MockSubscriptionServiceMockRecorder) Unsubscribe(ctx, mode, document interface{}) *gomock.Call { +func (mr *MockSubscriptionServiceMockRecorder) Unsubscribe(ctx, mode, document any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unsubscribe", reflect.TypeOf((*MockSubscriptionService)(nil).Unsubscribe), ctx, mode, document) } @@ -1639,7 +1673,7 @@ func (m *MockTimelineService) Clean(ctx context.Context, ccid string) error { } // Clean indicates an expected call of Clean. -func (mr *MockTimelineServiceMockRecorder) Clean(ctx, ccid interface{}) *gomock.Call { +func (mr *MockTimelineServiceMockRecorder) Clean(ctx, ccid any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clean", reflect.TypeOf((*MockTimelineService)(nil).Clean), ctx, ccid) } @@ -1654,7 +1688,7 @@ func (m *MockTimelineService) Count(ctx context.Context) (int64, error) { } // Count indicates an expected call of Count. -func (mr *MockTimelineServiceMockRecorder) Count(ctx interface{}) *gomock.Call { +func (mr *MockTimelineServiceMockRecorder) Count(ctx any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Count", reflect.TypeOf((*MockTimelineService)(nil).Count), ctx) } @@ -1669,7 +1703,7 @@ func (m *MockTimelineService) DeleteTimeline(ctx context.Context, mode core.Comm } // DeleteTimeline indicates an expected call of DeleteTimeline. -func (mr *MockTimelineServiceMockRecorder) DeleteTimeline(ctx, mode, document interface{}) *gomock.Call { +func (mr *MockTimelineServiceMockRecorder) DeleteTimeline(ctx, mode, document any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteTimeline", reflect.TypeOf((*MockTimelineService)(nil).DeleteTimeline), ctx, mode, document) } @@ -1684,7 +1718,7 @@ func (m *MockTimelineService) Event(ctx context.Context, mode core.CommitMode, d } // Event indicates an expected call of Event. -func (mr *MockTimelineServiceMockRecorder) Event(ctx, mode, document, signature interface{}) *gomock.Call { +func (mr *MockTimelineServiceMockRecorder) Event(ctx, mode, document, signature any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Event", reflect.TypeOf((*MockTimelineService)(nil).Event), ctx, mode, document, signature) } @@ -1699,7 +1733,7 @@ func (m *MockTimelineService) GetChunks(ctx context.Context, timelines []string, } // GetChunks indicates an expected call of GetChunks. -func (mr *MockTimelineServiceMockRecorder) GetChunks(ctx, timelines, pivot interface{}) *gomock.Call { +func (mr *MockTimelineServiceMockRecorder) GetChunks(ctx, timelines, pivot any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChunks", reflect.TypeOf((*MockTimelineService)(nil).GetChunks), ctx, timelines, pivot) } @@ -1714,7 +1748,7 @@ func (m *MockTimelineService) GetChunksFromRemote(ctx context.Context, host stri } // GetChunksFromRemote indicates an expected call of GetChunksFromRemote. -func (mr *MockTimelineServiceMockRecorder) GetChunksFromRemote(ctx, host, timelines, pivot interface{}) *gomock.Call { +func (mr *MockTimelineServiceMockRecorder) GetChunksFromRemote(ctx, host, timelines, pivot any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChunksFromRemote", reflect.TypeOf((*MockTimelineService)(nil).GetChunksFromRemote), ctx, host, timelines, pivot) } @@ -1729,7 +1763,7 @@ func (m *MockTimelineService) GetImmediateItems(ctx context.Context, timelines [ } // GetImmediateItems indicates an expected call of GetImmediateItems. -func (mr *MockTimelineServiceMockRecorder) GetImmediateItems(ctx, timelines, since, limit interface{}) *gomock.Call { +func (mr *MockTimelineServiceMockRecorder) GetImmediateItems(ctx, timelines, since, limit any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetImmediateItems", reflect.TypeOf((*MockTimelineService)(nil).GetImmediateItems), ctx, timelines, since, limit) } @@ -1744,7 +1778,7 @@ func (m *MockTimelineService) GetImmediateItemsFromSubscription(ctx context.Cont } // GetImmediateItemsFromSubscription indicates an expected call of GetImmediateItemsFromSubscription. -func (mr *MockTimelineServiceMockRecorder) GetImmediateItemsFromSubscription(ctx, subscription, since, limit interface{}) *gomock.Call { +func (mr *MockTimelineServiceMockRecorder) GetImmediateItemsFromSubscription(ctx, subscription, since, limit any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetImmediateItemsFromSubscription", reflect.TypeOf((*MockTimelineService)(nil).GetImmediateItemsFromSubscription), ctx, subscription, since, limit) } @@ -1759,7 +1793,7 @@ func (m *MockTimelineService) GetItem(ctx context.Context, timeline, id string) } // GetItem indicates an expected call of GetItem. -func (mr *MockTimelineServiceMockRecorder) GetItem(ctx, timeline, id interface{}) *gomock.Call { +func (mr *MockTimelineServiceMockRecorder) GetItem(ctx, timeline, id any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetItem", reflect.TypeOf((*MockTimelineService)(nil).GetItem), ctx, timeline, id) } @@ -1774,7 +1808,7 @@ func (m *MockTimelineService) GetRecentItems(ctx context.Context, timelines []st } // GetRecentItems indicates an expected call of GetRecentItems. -func (mr *MockTimelineServiceMockRecorder) GetRecentItems(ctx, timelines, until, limit interface{}) *gomock.Call { +func (mr *MockTimelineServiceMockRecorder) GetRecentItems(ctx, timelines, until, limit any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRecentItems", reflect.TypeOf((*MockTimelineService)(nil).GetRecentItems), ctx, timelines, until, limit) } @@ -1789,7 +1823,7 @@ func (m *MockTimelineService) GetRecentItemsFromSubscription(ctx context.Context } // GetRecentItemsFromSubscription indicates an expected call of GetRecentItemsFromSubscription. -func (mr *MockTimelineServiceMockRecorder) GetRecentItemsFromSubscription(ctx, subscription, until, limit interface{}) *gomock.Call { +func (mr *MockTimelineServiceMockRecorder) GetRecentItemsFromSubscription(ctx, subscription, until, limit any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRecentItemsFromSubscription", reflect.TypeOf((*MockTimelineService)(nil).GetRecentItemsFromSubscription), ctx, subscription, until, limit) } @@ -1804,7 +1838,7 @@ func (m *MockTimelineService) GetTimeline(ctx context.Context, key string) (core } // GetTimeline indicates an expected call of GetTimeline. -func (mr *MockTimelineServiceMockRecorder) GetTimeline(ctx, key interface{}) *gomock.Call { +func (mr *MockTimelineServiceMockRecorder) GetTimeline(ctx, key any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTimeline", reflect.TypeOf((*MockTimelineService)(nil).GetTimeline), ctx, key) } @@ -1819,7 +1853,7 @@ func (m *MockTimelineService) GetTimelineAutoDomain(ctx context.Context, timelin } // GetTimelineAutoDomain indicates an expected call of GetTimelineAutoDomain. -func (mr *MockTimelineServiceMockRecorder) GetTimelineAutoDomain(ctx, timelineID interface{}) *gomock.Call { +func (mr *MockTimelineServiceMockRecorder) GetTimelineAutoDomain(ctx, timelineID any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTimelineAutoDomain", reflect.TypeOf((*MockTimelineService)(nil).GetTimelineAutoDomain), ctx, timelineID) } @@ -1834,7 +1868,7 @@ func (m *MockTimelineService) ListTimelineByAuthor(ctx context.Context, author s } // ListTimelineByAuthor indicates an expected call of ListTimelineByAuthor. -func (mr *MockTimelineServiceMockRecorder) ListTimelineByAuthor(ctx, author interface{}) *gomock.Call { +func (mr *MockTimelineServiceMockRecorder) ListTimelineByAuthor(ctx, author any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListTimelineByAuthor", reflect.TypeOf((*MockTimelineService)(nil).ListTimelineByAuthor), ctx, author) } @@ -1849,7 +1883,7 @@ func (m *MockTimelineService) ListTimelineBySchema(ctx context.Context, schema s } // ListTimelineBySchema indicates an expected call of ListTimelineBySchema. -func (mr *MockTimelineServiceMockRecorder) ListTimelineBySchema(ctx, schema interface{}) *gomock.Call { +func (mr *MockTimelineServiceMockRecorder) ListTimelineBySchema(ctx, schema any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListTimelineBySchema", reflect.TypeOf((*MockTimelineService)(nil).ListTimelineBySchema), ctx, schema) } @@ -1864,7 +1898,7 @@ func (m *MockTimelineService) ListTimelineSubscriptions(ctx context.Context) (ma } // ListTimelineSubscriptions indicates an expected call of ListTimelineSubscriptions. -func (mr *MockTimelineServiceMockRecorder) ListTimelineSubscriptions(ctx interface{}) *gomock.Call { +func (mr *MockTimelineServiceMockRecorder) ListTimelineSubscriptions(ctx any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListTimelineSubscriptions", reflect.TypeOf((*MockTimelineService)(nil).ListTimelineSubscriptions), ctx) } @@ -1879,7 +1913,7 @@ func (m *MockTimelineService) NormalizeTimelineID(ctx context.Context, timeline } // NormalizeTimelineID indicates an expected call of NormalizeTimelineID. -func (mr *MockTimelineServiceMockRecorder) NormalizeTimelineID(ctx, timeline interface{}) *gomock.Call { +func (mr *MockTimelineServiceMockRecorder) NormalizeTimelineID(ctx, timeline any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NormalizeTimelineID", reflect.TypeOf((*MockTimelineService)(nil).NormalizeTimelineID), ctx, timeline) } @@ -1894,7 +1928,7 @@ func (m *MockTimelineService) PostItem(ctx context.Context, timeline string, ite } // PostItem indicates an expected call of PostItem. -func (mr *MockTimelineServiceMockRecorder) PostItem(ctx, timeline, item, document, signature interface{}) *gomock.Call { +func (mr *MockTimelineServiceMockRecorder) PostItem(ctx, timeline, item, document, signature any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PostItem", reflect.TypeOf((*MockTimelineService)(nil).PostItem), ctx, timeline, item, document, signature) } @@ -1908,7 +1942,7 @@ func (m *MockTimelineService) PublishEvent(ctx context.Context, event core.Event } // PublishEvent indicates an expected call of PublishEvent. -func (mr *MockTimelineServiceMockRecorder) PublishEvent(ctx, event interface{}) *gomock.Call { +func (mr *MockTimelineServiceMockRecorder) PublishEvent(ctx, event any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PublishEvent", reflect.TypeOf((*MockTimelineService)(nil).PublishEvent), ctx, event) } @@ -1920,7 +1954,7 @@ func (m *MockTimelineService) Realtime(ctx context.Context, request <-chan []str } // Realtime indicates an expected call of Realtime. -func (mr *MockTimelineServiceMockRecorder) Realtime(ctx, request, response interface{}) *gomock.Call { +func (mr *MockTimelineServiceMockRecorder) Realtime(ctx, request, response any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Realtime", reflect.TypeOf((*MockTimelineService)(nil).Realtime), ctx, request, response) } @@ -1932,7 +1966,7 @@ func (m *MockTimelineService) RemoveItem(ctx context.Context, timeline, id strin } // RemoveItem indicates an expected call of RemoveItem. -func (mr *MockTimelineServiceMockRecorder) RemoveItem(ctx, timeline, id interface{}) *gomock.Call { +func (mr *MockTimelineServiceMockRecorder) RemoveItem(ctx, timeline, id any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveItem", reflect.TypeOf((*MockTimelineService)(nil).RemoveItem), ctx, timeline, id) } @@ -1946,7 +1980,7 @@ func (m *MockTimelineService) RemoveItemsByResourceID(ctx context.Context, resou } // RemoveItemsByResourceID indicates an expected call of RemoveItemsByResourceID. -func (mr *MockTimelineServiceMockRecorder) RemoveItemsByResourceID(ctx, resourceID interface{}) *gomock.Call { +func (mr *MockTimelineServiceMockRecorder) RemoveItemsByResourceID(ctx, resourceID any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveItemsByResourceID", reflect.TypeOf((*MockTimelineService)(nil).RemoveItemsByResourceID), ctx, resourceID) } @@ -1961,7 +1995,7 @@ func (m *MockTimelineService) UpsertTimeline(ctx context.Context, mode core.Comm } // UpsertTimeline indicates an expected call of UpsertTimeline. -func (mr *MockTimelineServiceMockRecorder) UpsertTimeline(ctx, mode, document, signature interface{}) *gomock.Call { +func (mr *MockTimelineServiceMockRecorder) UpsertTimeline(ctx, mode, document, signature any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertTimeline", reflect.TypeOf((*MockTimelineService)(nil).UpsertTimeline), ctx, mode, document, signature) } @@ -1999,7 +2033,7 @@ func (m *MockJobService) Cancel(ctx context.Context, id string) (core.Job, error } // Cancel indicates an expected call of Cancel. -func (mr *MockJobServiceMockRecorder) Cancel(ctx, id interface{}) *gomock.Call { +func (mr *MockJobServiceMockRecorder) Cancel(ctx, id any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Cancel", reflect.TypeOf((*MockJobService)(nil).Cancel), ctx, id) } @@ -2014,7 +2048,7 @@ func (m *MockJobService) Complete(ctx context.Context, id, status, result string } // Complete indicates an expected call of Complete. -func (mr *MockJobServiceMockRecorder) Complete(ctx, id, status, result interface{}) *gomock.Call { +func (mr *MockJobServiceMockRecorder) Complete(ctx, id, status, result any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Complete", reflect.TypeOf((*MockJobService)(nil).Complete), ctx, id, status, result) } @@ -2029,7 +2063,7 @@ func (m *MockJobService) Create(ctx context.Context, requester, typ, payload str } // Create indicates an expected call of Create. -func (mr *MockJobServiceMockRecorder) Create(ctx, requester, typ, payload, scheduled interface{}) *gomock.Call { +func (mr *MockJobServiceMockRecorder) Create(ctx, requester, typ, payload, scheduled any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockJobService)(nil).Create), ctx, requester, typ, payload, scheduled) } @@ -2044,7 +2078,7 @@ func (m *MockJobService) Dequeue(ctx context.Context) (*core.Job, error) { } // Dequeue indicates an expected call of Dequeue. -func (mr *MockJobServiceMockRecorder) Dequeue(ctx interface{}) *gomock.Call { +func (mr *MockJobServiceMockRecorder) Dequeue(ctx any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Dequeue", reflect.TypeOf((*MockJobService)(nil).Dequeue), ctx) } @@ -2059,7 +2093,7 @@ func (m *MockJobService) List(ctx context.Context, requester string) ([]core.Job } // List indicates an expected call of List. -func (mr *MockJobServiceMockRecorder) List(ctx, requester interface{}) *gomock.Call { +func (mr *MockJobServiceMockRecorder) List(ctx, requester any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockJobService)(nil).List), ctx, requester) } diff --git a/x/auth/middleware_test.go b/x/auth/middleware_test.go index c4184f85..e7af6c5d 100644 --- a/x/auth/middleware_test.go +++ b/x/auth/middleware_test.go @@ -61,12 +61,14 @@ func TestLocalRootSuccess(t *testing.T) { mockEntity.EXPECT().GetMeta(gomock.Any(), gomock.Any()).Return(core.EntityMeta{}, nil).AnyTimes() mockDomain := mock_core.NewMockDomainService(ctrl) mockKey := mock_core.NewMockKeyService(ctrl) + mockPolicy := mock_core.NewMockPolicyService(ctrl) + mockPolicy.EXPECT().TestWithGlobalPolicy(gomock.Any(), gomock.Any(), gomock.Any()).Return(core.PolicyEvalResultAllow, nil) config := core.Config{ FQDN: "local.example.com", } - service := NewService(config, mockEntity, mockDomain, mockKey) + service := NewService(config, mockEntity, mockDomain, mockKey, mockPolicy) c, req, rec, traceID := testutil.CreateHttpRequest() @@ -125,12 +127,14 @@ func TestRemoteRootSuccess(t *testing.T) { }, nil).Times(2) mockKey := mock_core.NewMockKeyService(ctrl) + mockPolicy := mock_core.NewMockPolicyService(ctrl) + mockPolicy.EXPECT().TestWithGlobalPolicy(gomock.Any(), gomock.Any(), gomock.Any()).Return(core.PolicyEvalResultAllow, nil) config := core.Config{ FQDN: "local.example.com", } - service := NewService(config, mockEntity, mockDomain, mockKey) + service := NewService(config, mockEntity, mockDomain, mockKey, mockPolicy) c, req, rec, traceID := testutil.CreateHttpRequest() fmt.Print("traceID: ", traceID, "\n") diff --git a/x/userkv/mock/repository.go b/x/userkv/mock/repository.go index 95c791fc..bc6b6914 100644 --- a/x/userkv/mock/repository.go +++ b/x/userkv/mock/repository.go @@ -1,5 +1,10 @@ // Code generated by MockGen. DO NOT EDIT. // Source: repository.go +// +// Generated by this command: +// +// mockgen -source=repository.go -destination=mock/repository.go +// // Package mock_userkv is a generated GoMock package. package mock_userkv @@ -43,7 +48,7 @@ func (m *MockRepository) Clean(ctx context.Context, ccid string) error { } // Clean indicates an expected call of Clean. -func (mr *MockRepositoryMockRecorder) Clean(ctx, ccid interface{}) *gomock.Call { +func (mr *MockRepositoryMockRecorder) Clean(ctx, ccid any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clean", reflect.TypeOf((*MockRepository)(nil).Clean), ctx, ccid) } @@ -58,7 +63,7 @@ func (m *MockRepository) Get(ctx context.Context, owner, key string) (string, er } // Get indicates an expected call of Get. -func (mr *MockRepositoryMockRecorder) Get(ctx, owner, key interface{}) *gomock.Call { +func (mr *MockRepositoryMockRecorder) Get(ctx, owner, key any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockRepository)(nil).Get), ctx, owner, key) } @@ -72,7 +77,7 @@ func (m *MockRepository) Upsert(ctx context.Context, owner, key, value string) e } // Upsert indicates an expected call of Upsert. -func (mr *MockRepositoryMockRecorder) Upsert(ctx, owner, key, value interface{}) *gomock.Call { +func (mr *MockRepositoryMockRecorder) Upsert(ctx, owner, key, value any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Upsert", reflect.TypeOf((*MockRepository)(nil).Upsert), ctx, owner, key, value) } From 74556615bb1f0dcdc572b62fddda3ca9e953d43c Mon Sep 17 00:00:00 2001 From: totegamma Date: Tue, 16 Jul 2024 23:03:47 +0900 Subject: [PATCH 14/22] restrict association by message timeline policy --- cmd/api/policy.go | 3 ++ cmd/gateway/main.go | 4 +- cmd/gateway/policy.go | 97 +++++++++++++++++++++++++++++++++++++ wire_gen.go | 2 +- x/association/service.go | 102 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 cmd/gateway/policy.go diff --git a/cmd/api/policy.go b/cmd/api/policy.go index 6ac8efe0..74249d07 100644 --- a/cmd/api/policy.go +++ b/cmd/api/policy.go @@ -83,6 +83,9 @@ var globalPolicyJson = ` ] } } + }, + "defaults": { + "association.attach": true } }` diff --git a/cmd/gateway/main.go b/cmd/gateway/main.go index d5b9b2db..7f7a3de8 100644 --- a/cmd/gateway/main.go +++ b/cmd/gateway/main.go @@ -208,7 +208,9 @@ func main() { defer mc.Close() client := client.NewClient() - authService := concurrent.SetupAuthService(db, rdb, mc, client, conconf) + globalPolicy := getDefaultGlobalPolicy() + policy := concurrent.SetupPolicyService(rdb, globalPolicy, conconf) + authService := concurrent.SetupAuthService(db, rdb, mc, client, policy, conconf) e.Use(authService.IdentifyIdentity) diff --git a/cmd/gateway/policy.go b/cmd/gateway/policy.go new file mode 100644 index 00000000..6ac8efe0 --- /dev/null +++ b/cmd/gateway/policy.go @@ -0,0 +1,97 @@ +package main + +import ( + "encoding/json" + + "github.com/totegamma/concurrent/core" +) + +var globalPolicyJson = ` +{ + "statements": { + "global": { + "dominant": true, + "defaultOnTrue": true, + "condition": { + "op": "Not", + "args": [ + { + "op": "Or", + "args": [ + { + "op": "RequesterDomainHasTag", + "const": "_block" + }, + { + "op": "RequesterHasTag", + "const": "_block" + } + ] + } + ] + } + }, + "invite": { + "condition": { + "op": "RequesterHasTag", + "const": "_invite" + } + }, + "timeline.distribute": { + "condition": { + "op": "Or", + "args": [ + { + "op": "LoadSelf", + "const": "domainOwned" + }, + { + "op": "Eq", + "args": [ + { + "op": "LoadSelf", + "const": "author" + }, + { + "op": "RequesterID" + } + ] + } + ] + } + }, + "timeline.message.read": { + "condition": { + "op": "Or", + "args": [ + { + "op": "LoadSelf", + "const": "domainOwned" + }, + { + "op": "Eq", + "args": [ + { + "op": "LoadSelf", + "const": "author" + }, + { + "op": "RequesterID" + } + ] + } + ] + } + } + } +}` + +func getDefaultGlobalPolicy() core.Policy { + globalPolicy := core.Policy{} + err := json.Unmarshal([]byte(globalPolicyJson), &globalPolicy) + if err != nil { + panic("failed to parse global policy") + } + + return globalPolicy +} diff --git a/wire_gen.go b/wire_gen.go index 8b7f9ae4..76bd9a54 100644 --- a/wire_gen.go +++ b/wire_gen.go @@ -95,7 +95,7 @@ func SetupAssociationService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client timelineService := SetupTimelineService(db, rdb, mc, client2, policy2, config) messageService := SetupMessageService(db, rdb, mc, client2, policy2, config) keyService := SetupKeyService(db, rdb, mc, client2, config) - associationService := association.NewService(repository, client2, entityService, domainService, timelineService, messageService, keyService, config) + associationService := association.NewService(repository, client2, entityService, domainService, timelineService, messageService, keyService, policy2, config) return associationService } diff --git a/x/association/service.go b/x/association/service.go index d30e76c1..4e9d10c0 100644 --- a/x/association/service.go +++ b/x/association/service.go @@ -10,10 +10,12 @@ import ( "time" "github.com/pkg/errors" + "go.opentelemetry.io/otel/codes" "github.com/totegamma/concurrent/cdid" "github.com/totegamma/concurrent/client" "github.com/totegamma/concurrent/core" + "github.com/totegamma/concurrent/x/policy" ) type service struct { @@ -24,6 +26,7 @@ type service struct { timeline core.TimelineService message core.MessageService key core.KeyService + policy core.PolicyService config core.Config } @@ -36,6 +39,7 @@ func NewService( timeline core.TimelineService, message core.MessageService, key core.KeyService, + policy core.PolicyService, config core.Config, ) core.AssociationService { return &service{ @@ -46,6 +50,7 @@ func NewService( timeline, message, key, + policy, config, } } @@ -121,6 +126,103 @@ func (s *service) Create(ctx context.Context, mode core.CommitMode, document str } if owner.Domain == s.config.FQDN { // signerが自ドメイン管轄の場合、リソースを作成 + + switch doc.Target[0] { + case 'm': // message + target, err := s.message.Get(ctx, association.Target, doc.Signer) + if err != nil { + span.RecordError(err) + return association, err + } + + timelinePolicyResults := make([]core.PolicyEvalResult, len(target.Timelines)) + for i, timelineID := range target.Timelines { + timeline, err := s.timeline.GetTimelineAutoDomain(ctx, timelineID) + if err != nil { + span.SetStatus(codes.Error, err.Error()) + continue + } + + if timeline.Policy == "" { + timelinePolicyResults[i] = core.PolicyEvalResultDefault + continue + } + + var params map[string]any = make(map[string]any) + if timeline.PolicyParams != nil { + err := json.Unmarshal([]byte(*timeline.PolicyParams), ¶ms) + if err != nil { + span.SetStatus(codes.Error, err.Error()) + span.RecordError(err) + continue + } + } + + result, err := s.policy.TestWithPolicyURL( + ctx, + timeline.Policy, + core.RequestContext{ + Self: timeline, + Params: params, + Document: doc, + }, + "timeline.message.association.attach", + ) + if err != nil { + span.SetStatus(codes.Error, err.Error()) + timelinePolicyResults[i] = core.PolicyEvalResultDefault + continue + } + + timelinePolicyResults[i] = result + } + + timelinePolicyResult := policy.AccumulateOr(timelinePolicyResults) + timelinePolicyIsDominant, timlinePolicyAllowed := policy.IsDominant(timelinePolicyResult) + if timelinePolicyIsDominant && !timlinePolicyAllowed { + return association, core.ErrorPermissionDenied{} + } + + messagePolicyResult := core.PolicyEvalResultDefault + if target.Policy != "" { + var params map[string]any = make(map[string]any) + if target.PolicyParams != nil { + err := json.Unmarshal([]byte(*target.PolicyParams), ¶ms) + if err != nil { + span.SetStatus(codes.Error, err.Error()) + span.RecordError(err) + goto SKIP_EVAL_MESSAGE_POLICY + } + } + + var err error + messagePolicyResult, err = s.policy.TestWithPolicyURL( + ctx, + target.Policy, + core.RequestContext{ + Self: target, + Params: params, + Document: doc, + }, + "message.association.attach", + ) + if err != nil { + span.SetStatus(codes.Error, err.Error()) + goto SKIP_EVAL_MESSAGE_POLICY + + } + } + SKIP_EVAL_MESSAGE_POLICY: + + result := s.policy.Summerize([]core.PolicyEvalResult{messagePolicyResult, timelinePolicyResult}, "association.attach") + if !result { + return association, core.ErrorPermissionDenied{} + } + case 'p': // profile + case 't': // timeline + case 's': // subscription + } + association, err = s.repo.Create(ctx, association) if err != nil { if errors.Is(err, core.ErrorAlreadyExists{}) { From bac05c36d558d45bbef6c470bedc365ecd9bc059 Mon Sep 17 00:00:00 2001 From: totegamma Date: Wed, 17 Jul 2024 00:38:19 +0900 Subject: [PATCH 15/22] add message/association deletion rule --- cmd/api/policy.go | 133 +++++++++++++++++++++++++++++++++++++++ x/association/service.go | 30 ++++++++- x/message/service.go | 77 ++++++++++++++--------- x/policy/service.go | 3 +- x/store/service.go | 2 +- x/timeline/service.go | 1 + 6 files changed, 213 insertions(+), 33 deletions(-) diff --git a/cmd/api/policy.go b/cmd/api/policy.go index 74249d07..372ab3b9 100644 --- a/cmd/api/policy.go +++ b/cmd/api/policy.go @@ -37,6 +37,139 @@ var globalPolicyJson = ` "const": "_invite" } }, + "association.delete": { + "condition": { + "op": "Or", + "args": [ + { + "op": "Eq", + "args": [ + { + "op": "LoadSelf", + "const": "author" + }, + { + "op": "LoadDocument", + "const": "signer" + } + ] + }, + { + "op": "Eq", + "args": [ + { + "op": "LoadSelf", + "const": "owner" + }, + { + "op": "LoadDocument", + "const": "signer" + } + ] + }, + { + "op": "RequesterHasTag", + "const": "_admin" + } + ] + } + }, + "message.delete": { + "condition": { + "op": "Or", + "args": [ + { + "op": "Eq", + "args": [ + { + "op": "LoadSelf", + "const": "author" + }, + { + "op": "LoadDocument", + "const": "signer" + } + ] + }, + { + "op": "RequesterHasTag", + "const": "_admin" + } + ] + } + }, + "profile.delete": { + "condition": { + "op": "Or", + "args": [ + { + "op": "Eq", + "args": [ + { + "op": "LoadSelf", + "const": "author" + }, + { + "op": "LoadDocument", + "const": "signer" + } + ] + }, + { + "op": "RequesterHasTag", + "const": "_admin" + } + ] + } + }, + "subscription.delete": { + "condition": { + "op": "Or", + "args": [ + { + "op": "Eq", + "args": [ + { + "op": "LoadSelf", + "const": "author" + }, + { + "op": "LoadDocument", + "const": "signer" + } + ] + }, + { + "op": "RequesterHasTag", + "const": "_admin" + } + ] + } + }, + "timeline.delete": { + "condition": { + "op": "Or", + "args": [ + { + "op": "Eq", + "args": [ + { + "op": "LoadSelf", + "const": "author" + }, + { + "op": "LoadDocument", + "const": "signer" + } + ] + }, + { + "op": "RequesterHasTag", + "const": "_admin" + } + ] + } + }, "timeline.distribute": { "condition": { "op": "Or", diff --git a/x/association/service.go b/x/association/service.go index 4e9d10c0..c620e393 100644 --- a/x/association/service.go +++ b/x/association/service.go @@ -433,8 +433,34 @@ func (s *service) Delete(ctx context.Context, mode core.CommitMode, document, si return core.Association{}, err } - if (targetAssociation.Author != requester) && (targetMessage.Author != requester) { - return core.Association{}, fmt.Errorf("you are not authorized to perform this action") + var params map[string]any = make(map[string]any) + if targetMessage.PolicyParams != nil { + err := json.Unmarshal([]byte(*targetMessage.PolicyParams), ¶ms) + if err != nil { + span.RecordError(err) + return core.Association{}, err + } + } + + result, err := s.policy.TestWithPolicyURL( + ctx, + targetMessage.Policy, + core.RequestContext{ + Self: targetAssociation, + Params: params, + Document: doc, + }, + "association.delete", + ) + + if err != nil { + span.RecordError(err) + return core.Association{}, err + } + + finally := s.policy.Summerize([]core.PolicyEvalResult{result}, "association.delete") + if !finally { + return core.Association{}, core.ErrorPermissionDenied{} } err = s.repo.Delete(ctx, doc.Target) diff --git a/x/message/service.go b/x/message/service.go index 1ad693a7..9a5c871d 100644 --- a/x/message/service.go +++ b/x/message/service.go @@ -71,11 +71,6 @@ func (s *service) isMessagePublic(ctx context.Context, message core.Message) (bo continue } - if timeline.Policy == "" { - timelinePolicyResults[i] = core.PolicyEvalResultDefault - continue - } - var params map[string]any = make(map[string]any) if timeline.PolicyParams != nil { err := json.Unmarshal([]byte(*timeline.PolicyParams), ¶ms) @@ -112,34 +107,32 @@ func (s *service) isMessagePublic(ctx context.Context, message core.Message) (bo // message policy check messagePolicyResult := core.PolicyEvalResultDefault - if message.Policy != "" { - var params map[string]any = make(map[string]any) - if message.PolicyParams != nil { - err := json.Unmarshal([]byte(*message.PolicyParams), ¶ms) - if err != nil { - span.SetStatus(codes.Error, err.Error()) - span.RecordError(err) - goto SKIP_EVAL_MESSAGE_POLICY - } - } - - var err error - messagePolicyResult, err = s.policy.TestWithPolicyURL( - ctx, - message.Policy, - core.RequestContext{ - Self: message, - Params: params, - }, - "message.read", - ) + var err error + var params map[string]any = make(map[string]any) + if message.PolicyParams != nil { + err := json.Unmarshal([]byte(*message.PolicyParams), ¶ms) if err != nil { span.SetStatus(codes.Error, err.Error()) + span.RecordError(err) goto SKIP_EVAL_MESSAGE_POLICY - } } + + messagePolicyResult, err = s.policy.TestWithPolicyURL( + ctx, + message.Policy, + core.RequestContext{ + Self: message, + Params: params, + }, + "message.read", + ) + if err != nil { + span.SetStatus(codes.Error, err.Error()) + goto SKIP_EVAL_MESSAGE_POLICY + + } SKIP_EVAL_MESSAGE_POLICY: // accumulate polies @@ -443,8 +436,34 @@ func (s *service) Delete(ctx context.Context, mode core.CommitMode, document, si return core.Message{}, err } - if deleteTarget.Author != doc.Signer { - return core.Message{}, fmt.Errorf("you are not authorized to perform this action") + var params map[string]any = make(map[string]any) + if deleteTarget.PolicyParams != nil { + err := json.Unmarshal([]byte(*deleteTarget.PolicyParams), ¶ms) + if err != nil { + span.RecordError(err) + return core.Message{}, err + } + } + + result, err := s.policy.TestWithPolicyURL( + ctx, + deleteTarget.Policy, + core.RequestContext{ + Self: deleteTarget, + Params: params, + Document: doc, + }, + "message.delete", + ) + + if err != nil { + span.RecordError(err) + return core.Message{}, err + } + + finally := s.policy.Summerize([]core.PolicyEvalResult{result}, "message.delete") + if !finally { + return core.Message{}, core.ErrorPermissionDenied{} } err = s.repo.Delete(ctx, doc.Target) diff --git a/x/policy/service.go b/x/policy/service.go index 6be556d8..21baf2d4 100644 --- a/x/policy/service.go +++ b/x/policy/service.go @@ -118,7 +118,6 @@ func (s service) test(ctx context.Context, policy core.Policy, context core.Requ statement, ok := policy.Statements[action] if !ok { - testutil.PrintJson(policy) span.SetAttributes(attribute.String("debug", "no rule")) return core.PolicyEvalResultDefault, nil } @@ -213,6 +212,7 @@ func (s service) eval(expr core.Expr, requestCtx core.RequestContext) (core.Eval Error: err.Error(), }, err } + args = append(args, eval) rhs, ok := eval.Result.(bool) if !ok { err := fmt.Errorf("bad argument type for OR. Expected bool but got %s\n", reflect.TypeOf(eval.Result)) @@ -375,6 +375,7 @@ func (s service) eval(expr core.Expr, requestCtx core.RequestContext) (core.Eval value, ok := resolveDotNotation(requestCtx.Params, key) if !ok { err := fmt.Errorf("key not found: %s\n", key) + testutil.PrintJson(requestCtx) return core.EvalResult{ Operator: "LoadParam", Error: err.Error(), diff --git a/x/store/service.go b/x/store/service.go index 33dc0199..90a4570c 100644 --- a/x/store/service.go +++ b/x/store/service.go @@ -170,7 +170,7 @@ func (s *service) Commit(ctx context.Context, mode core.CommitMode, document str case "delete": var doc core.DeleteDocument - err := json.Unmarshal([]byte(document), &doc) + err = json.Unmarshal([]byte(document), &doc) if err != nil { return nil, err } diff --git a/x/timeline/service.go b/x/timeline/service.go index d5f14f19..cb1ae913 100644 --- a/x/timeline/service.go +++ b/x/timeline/service.go @@ -384,6 +384,7 @@ func (s *service) PostItem(ctx context.Context, timeline string, item core.Timel core.RequestContext{ Self: tl, Requester: requesterEntity, + Params: params, }, "timeline.distribute", ) From 94502b7fca6cc23712bcfddd25ca86a4ac1eeb5e Mon Sep 17 00:00:00 2001 From: totegamma Date: Wed, 17 Jul 2024 23:12:48 +0900 Subject: [PATCH 16/22] add message/subscription create/update/delete policy --- cmd/api/main.go | 2 +- cmd/api/policy.go | 58 ++++++++++++++++++++++++++ core/interfaces.go | 2 +- core/mock/services.go | 30 +++++++------- wire.go | 4 +- wire_gen.go | 13 +++--- x/store/service.go | 2 +- x/subscription/service.go | 85 ++++++++++++++++++++++++++++++++++++--- x/timeline/service.go | 64 ++++++++++++++++++++++++++++- 9 files changed, 227 insertions(+), 33 deletions(-) diff --git a/cmd/api/main.go b/cmd/api/main.go index 19539f7e..239094f7 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -244,7 +244,7 @@ func main() { storeService := concurrent.SetupStoreService(db, rdb, mc, client, policy, conconf, config.Server.RepositoryPath) storeHandler := store.NewHandler(storeService) - subscriptionService := concurrent.SetupSubscriptionService(db) + subscriptionService := concurrent.SetupSubscriptionService(db, rdb, mc, client, policy, conconf) subscriptionHandler := subscription.NewHandler(subscriptionService) jobService := concurrent.SetupJobService(db) diff --git a/cmd/api/policy.go b/cmd/api/policy.go index 372ab3b9..db204c76 100644 --- a/cmd/api/policy.go +++ b/cmd/api/policy.go @@ -122,6 +122,35 @@ var globalPolicyJson = ` ] } }, + "subscription.create": { + "condition": { + "op": "Or", + "args": [ + { + "op": "IsRequesterLocalUser" + }, + { + "op": "RequesterHasTag", + "const": "subscription_creator" + } + ] + } + }, + "subscription.update": { + "condition": { + "op": "Eq", + "args": [ + { + "op": "LoadSelf", + "const": "author" + }, + { + "op": "LoadDocument", + "const": "signer" + } + ] + } + }, "subscription.delete": { "condition": { "op": "Or", @@ -146,6 +175,35 @@ var globalPolicyJson = ` ] } }, + "timeline.create": { + "condition": { + "op": "Or", + "args": [ + { + "op": "IsRequesterLocalUser" + }, + { + "op": "RequesterHasTag", + "const": "timeline_creator" + } + ] + } + }, + "timeline.update": { + "condition": { + "op": "Eq", + "args": [ + { + "op": "LoadSelf", + "const": "author" + }, + { + "op": "LoadDocument", + "const": "signer" + } + ] + } + }, "timeline.delete": { "condition": { "op": "Or", diff --git a/core/interfaces.go b/core/interfaces.go index 75d0db7c..52248fa5 100644 --- a/core/interfaces.go +++ b/core/interfaces.go @@ -135,7 +135,7 @@ type StoreService interface { } type SubscriptionService interface { - CreateSubscription(ctx context.Context, mode CommitMode, document, signature string) (Subscription, error) + UpsertSubscription(ctx context.Context, mode CommitMode, document, signature string) (Subscription, error) Subscribe(ctx context.Context, mode CommitMode, document string, signature string) (SubscriptionItem, error) Unsubscribe(ctx context.Context, mode CommitMode, document string) (SubscriptionItem, error) DeleteSubscription(ctx context.Context, mode CommitMode, document string) (Subscription, error) diff --git a/core/mock/services.go b/core/mock/services.go index 122dcf1a..d02443e6 100644 --- a/core/mock/services.go +++ b/core/mock/services.go @@ -1551,21 +1551,6 @@ func (mr *MockSubscriptionServiceMockRecorder) Clean(ctx, ccid any) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clean", reflect.TypeOf((*MockSubscriptionService)(nil).Clean), ctx, ccid) } -// CreateSubscription mocks base method. -func (m *MockSubscriptionService) CreateSubscription(ctx context.Context, mode core.CommitMode, document, signature string) (core.Subscription, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateSubscription", ctx, mode, document, signature) - ret0, _ := ret[0].(core.Subscription) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// CreateSubscription indicates an expected call of CreateSubscription. -func (mr *MockSubscriptionServiceMockRecorder) CreateSubscription(ctx, mode, document, signature any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSubscription", reflect.TypeOf((*MockSubscriptionService)(nil).CreateSubscription), ctx, mode, document, signature) -} - // DeleteSubscription mocks base method. func (m *MockSubscriptionService) DeleteSubscription(ctx context.Context, mode core.CommitMode, document string) (core.Subscription, error) { m.ctrl.T.Helper() @@ -1641,6 +1626,21 @@ func (mr *MockSubscriptionServiceMockRecorder) Unsubscribe(ctx, mode, document a return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unsubscribe", reflect.TypeOf((*MockSubscriptionService)(nil).Unsubscribe), ctx, mode, document) } +// UpsertSubscription mocks base method. +func (m *MockSubscriptionService) UpsertSubscription(ctx context.Context, mode core.CommitMode, document, signature string) (core.Subscription, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpsertSubscription", ctx, mode, document, signature) + ret0, _ := ret[0].(core.Subscription) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpsertSubscription indicates an expected call of UpsertSubscription. +func (mr *MockSubscriptionServiceMockRecorder) UpsertSubscription(ctx, mode, document, signature any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertSubscription", reflect.TypeOf((*MockSubscriptionService)(nil).UpsertSubscription), ctx, mode, document, signature) +} + // MockTimelineService is a mock of TimelineService interface. type MockTimelineService struct { ctrl *gomock.Controller diff --git a/wire.go b/wire.go index f8eaeb0d..d15bf0da 100644 --- a/wire.go +++ b/wire.go @@ -43,10 +43,10 @@ var jobServiceProvider = wire.NewSet(job.NewService, job.NewRepository) // Lv1 var entityServiceProvider = wire.NewSet(entity.NewService, entity.NewRepository, SetupJwtService, SetupSchemaService, SetupKeyService) -var subscriptionServiceProvider = wire.NewSet(subscription.NewService, subscription.NewRepository, SetupSchemaService) // Lv2 var timelineServiceProvider = wire.NewSet(timeline.NewService, timeline.NewRepository, SetupEntityService, SetupDomainService, SetupSchemaService, SetupSemanticidService, SetupSubscriptionService) +var subscriptionServiceProvider = wire.NewSet(subscription.NewService, subscription.NewRepository, SetupSchemaService, SetupEntityService) // Lv3 var profileServiceProvider = wire.NewSet(profile.NewService, profile.NewRepository, SetupEntityService, SetupKeyService, SetupSchemaService, SetupSemanticidService) @@ -159,7 +159,7 @@ func SetupStoreService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, clie return nil } -func SetupSubscriptionService(db *gorm.DB) core.SubscriptionService { +func SetupSubscriptionService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client client.Client, policy core.PolicyService, config core.Config) core.SubscriptionService { wire.Build(subscriptionServiceProvider) return nil } diff --git a/wire_gen.go b/wire_gen.go index 76bd9a54..4b62f65a 100644 --- a/wire_gen.go +++ b/wire_gen.go @@ -105,7 +105,7 @@ func SetupTimelineService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, c entityService := SetupEntityService(db, rdb, mc, client2, policy2, config) domainService := SetupDomainService(db, client2, config) semanticIDService := SetupSemanticidService(db) - subscriptionService := SetupSubscriptionService(db) + subscriptionService := SetupSubscriptionService(db, rdb, mc, client2, policy2, config) timelineService := timeline.NewService(repository, entityService, domainService, semanticIDService, subscriptionService, policy2, config) return timelineService } @@ -161,16 +161,17 @@ func SetupStoreService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, clie profileService := SetupProfileService(db, rdb, mc, client2, policy2, config) timelineService := SetupTimelineService(db, rdb, mc, client2, policy2, config) ackService := SetupAckService(db, rdb, mc, client2, policy2, config) - subscriptionService := SetupSubscriptionService(db) + subscriptionService := SetupSubscriptionService(db, rdb, mc, client2, policy2, config) semanticIDService := SetupSemanticidService(db) storeService := store.NewService(repository, keyService, entityService, messageService, associationService, profileService, timelineService, ackService, subscriptionService, semanticIDService, config, repositoryPath) return storeService } -func SetupSubscriptionService(db *gorm.DB) core.SubscriptionService { +func SetupSubscriptionService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, client2 client.Client, policy2 core.PolicyService, config core.Config) core.SubscriptionService { schemaService := SetupSchemaService(db) repository := subscription.NewRepository(db, schemaService) - subscriptionService := subscription.NewService(repository) + entityService := SetupEntityService(db, rdb, mc, client2, policy2, config) + subscriptionService := subscription.NewService(repository, entityService, policy2) return subscriptionService } @@ -202,11 +203,11 @@ var jobServiceProvider = wire.NewSet(job.NewService, job.NewRepository) // Lv1 var entityServiceProvider = wire.NewSet(entity.NewService, entity.NewRepository, SetupJwtService, SetupSchemaService, SetupKeyService) -var subscriptionServiceProvider = wire.NewSet(subscription.NewService, subscription.NewRepository, SetupSchemaService) - // Lv2 var timelineServiceProvider = wire.NewSet(timeline.NewService, timeline.NewRepository, SetupEntityService, SetupDomainService, SetupSchemaService, SetupSemanticidService, SetupSubscriptionService) +var subscriptionServiceProvider = wire.NewSet(subscription.NewService, subscription.NewRepository, SetupSchemaService, SetupEntityService) + // Lv3 var profileServiceProvider = wire.NewSet(profile.NewService, profile.NewRepository, SetupEntityService, SetupKeyService, SetupSchemaService, SetupSemanticidService) diff --git a/x/store/service.go b/x/store/service.go index 90a4570c..6ef47aa2 100644 --- a/x/store/service.go +++ b/x/store/service.go @@ -148,7 +148,7 @@ func (s *service) Commit(ctx context.Context, mode core.CommitMode, document str case "subscription": var sub core.Subscription - sub, err = s.subscription.CreateSubscription(ctx, mode, document, signature) + sub, err = s.subscription.UpsertSubscription(ctx, mode, document, signature) result = sub if !sub.DomainOwned { owners = []string{sub.Author} diff --git a/x/subscription/service.go b/x/subscription/service.go index ada89a86..ffde54d8 100644 --- a/x/subscription/service.go +++ b/x/subscription/service.go @@ -4,22 +4,34 @@ import ( "context" "encoding/json" "errors" + "strings" + "github.com/totegamma/concurrent/cdid" "github.com/totegamma/concurrent/core" - "strings" + "go.opentelemetry.io/otel/codes" ) type service struct { - repo Repository + repo Repository + entity core.EntityService + policy core.PolicyService } // NewRepository creates a new collection repository -func NewService(repo Repository) core.SubscriptionService { - return &service{repo: repo} +func NewService( + repo Repository, + entity core.EntityService, + policy core.PolicyService, +) core.SubscriptionService { + return &service{ + repo, + entity, + policy, + } } -// CreateSubscription creates new collection -func (s *service) CreateSubscription(ctx context.Context, mode core.CommitMode, document, signature string) (core.Subscription, error) { +// UpsertSubscription creates new collection +func (s *service) UpsertSubscription(ctx context.Context, mode core.CommitMode, document, signature string) (core.Subscription, error) { ctx, span := tracer.Start(ctx, "Subscription.Service.CreateSubscription") defer span.End() @@ -30,12 +42,43 @@ func (s *service) CreateSubscription(ctx context.Context, mode core.CommitMode, return core.Subscription{}, err } + signer, err := s.entity.Get(ctx, doc.Signer) + if err != nil { + span.RecordError(err) + return core.Subscription{}, err + } + if doc.ID == "" { // New hash := core.GetHash([]byte(document)) hash10 := [10]byte{} copy(hash10[:], hash[:10]) signedAt := doc.SignedAt doc.ID = cdid.New(hash10, signedAt).String() + + // check existance + _, err := s.repo.GetSubscription(ctx, doc.ID) + if err == nil { + return core.Subscription{}, errors.New("subscription already exists") + } + + policyResult, err := s.policy.TestWithPolicyURL( + ctx, + "", + core.RequestContext{ + Requester: signer, + Document: doc, + }, + "subscription.create", + ) + if err != nil { + span.RecordError(err) + } + + result := s.policy.Summerize([]core.PolicyEvalResult{policyResult}, "subscription.create") + if !result { + return core.Subscription{}, errors.New("You don't have subscription.create access") + } + } else { existance, err := s.repo.GetSubscription(ctx, doc.ID) if err != nil { @@ -44,6 +87,36 @@ func (s *service) CreateSubscription(ctx context.Context, mode core.CommitMode, } doc.DomainOwned = existance.DomainOwned // make sure the domain owned is immutable + + var params map[string]any = make(map[string]any) + if existance.PolicyParams != nil { + if err != nil { + span.SetStatus(codes.Error, err.Error()) + span.RecordError(err) + } + } + + policyResult, err := s.policy.TestWithPolicyURL( + ctx, + existance.Policy, + core.RequestContext{ + Requester: signer, + Self: existance, + Document: doc, + Params: params, + }, + "subscription.update", + ) + + if err != nil { + span.SetStatus(codes.Error, err.Error()) + span.RecordError(err) + } + + result := s.policy.Summerize([]core.PolicyEvalResult{policyResult}, "subscription.update") + if !result { + return core.Subscription{}, errors.New("You don't have subscription.update access") + } } var policyparams *string = nil diff --git a/x/timeline/service.go b/x/timeline/service.go index cb1ae913..c09b75a7 100644 --- a/x/timeline/service.go +++ b/x/timeline/service.go @@ -11,6 +11,7 @@ import ( "github.com/pkg/errors" "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" "github.com/totegamma/concurrent/cdid" "github.com/totegamma/concurrent/core" @@ -499,12 +500,43 @@ func (s *service) UpsertTimeline(ctx context.Context, mode core.CommitMode, docu } } - if doc.ID == "" { // New + signer, err := s.entity.Get(ctx, doc.Signer) + if err != nil { + span.RecordError(err) + return core.Timeline{}, err + } + + if doc.ID == "" { // Create hash := core.GetHash([]byte(document)) hash10 := [10]byte{} copy(hash10[:], hash[:10]) signedAt := doc.SignedAt doc.ID = cdid.New(hash10, signedAt).String() + + // check existence + _, err := s.repository.GetTimeline(ctx, doc.ID) + if err == nil { + return core.Timeline{}, fmt.Errorf("Timeline already exists: %s", doc.ID) + } + + policyResult, err := s.policy.TestWithPolicyURL( + ctx, + "", + core.RequestContext{ + Requester: signer, + Document: doc, + }, + "timeline.create", + ) + if err != nil { + return core.Timeline{}, err + } + + result := s.policy.Summerize([]core.PolicyEvalResult{policyResult}, "timeline.create") + if !result { + return core.Timeline{}, fmt.Errorf("You don't have timeline.create access") + } + } else { // Update id, err := s.NormalizeTimelineID(ctx, doc.ID) if err != nil { @@ -525,6 +557,36 @@ func (s *service) UpsertTimeline(ctx context.Context, mode core.CommitMode, docu } doc.DomainOwned = existance.DomainOwned // make sure the domain owned is immutable + + var params map[string]any = make(map[string]any) + if existance.PolicyParams != nil { + if err != nil { + span.SetStatus(codes.Error, err.Error()) + span.RecordError(err) + } + } + + policyResult, err := s.policy.TestWithPolicyURL( + ctx, + existance.Policy, + core.RequestContext{ + Requester: signer, + Self: existance, + Document: doc, + Params: params, + }, + "timeline.update", + ) + + if err != nil { + span.SetStatus(codes.Error, err.Error()) + span.RecordError(err) + } + + result := s.policy.Summerize([]core.PolicyEvalResult{policyResult}, "timeline.update") + if !result { + return core.Timeline{}, fmt.Errorf("You don't have timeline.update access") + } } var policyparams *string = nil From 3f7f6ff32305f7db76381fdc607c5ea322668a17 Mon Sep 17 00:00:00 2001 From: totegamma Date: Thu, 18 Jul 2024 00:39:44 +0900 Subject: [PATCH 17/22] add profile create/upddate/delete policy --- cmd/api/policy.go | 20 +++++++ wire_gen.go | 2 +- x/profile/service.go | 122 +++++++++++++++++++++++++++++++++++--- x/subscription/service.go | 35 ++++++++++- x/timeline/service.go | 31 +++++++++- 5 files changed, 196 insertions(+), 14 deletions(-) diff --git a/cmd/api/policy.go b/cmd/api/policy.go index db204c76..aae79098 100644 --- a/cmd/api/policy.go +++ b/cmd/api/policy.go @@ -98,6 +98,26 @@ var globalPolicyJson = ` ] } }, + "profile.create": { + "condition": { + "op": "IsRequesterLocalUser" + } + }, + "profile.update": { + "condition": { + "op": "Eq", + "args": [ + { + "op": "LoadSelf", + "const": "author" + }, + { + "op": "LoadDocument", + "const": "signer" + } + ] + } + }, "profile.delete": { "condition": { "op": "Or", diff --git a/wire_gen.go b/wire_gen.go index 4b62f65a..4e2934a8 100644 --- a/wire_gen.go +++ b/wire_gen.go @@ -83,7 +83,7 @@ func SetupProfileService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client, cl repository := profile.NewRepository(db, mc, schemaService) entityService := SetupEntityService(db, rdb, mc, client2, policy2, config) semanticIDService := SetupSemanticidService(db) - profileService := profile.NewService(repository, entityService, semanticIDService) + profileService := profile.NewService(repository, entityService, policy2, semanticIDService) return profileService } diff --git a/x/profile/service.go b/x/profile/service.go index fe3918ae..0abe7a38 100644 --- a/x/profile/service.go +++ b/x/profile/service.go @@ -4,19 +4,32 @@ import ( "context" "encoding/json" "errors" + "github.com/totegamma/concurrent/cdid" "github.com/totegamma/concurrent/core" + "go.opentelemetry.io/otel/codes" ) type service struct { repo Repository entity core.EntityService + policy core.PolicyService semanticid core.SemanticIDService } // NewService creates a new profile service -func NewService(repo Repository, entity core.EntityService, semanticid core.SemanticIDService) core.ProfileService { - return &service{repo, entity, semanticid} +func NewService( + repo Repository, + entity core.EntityService, + policy core.PolicyService, + semanticid core.SemanticIDService, +) core.ProfileService { + return &service{ + repo, + entity, + policy, + semanticid, + } } // Count returns the count number of messages @@ -108,11 +121,77 @@ func (s *service) Upsert(ctx context.Context, mode core.CommitMode, document, si } } + signer, err := s.entity.Get(ctx, doc.Signer) + if err != nil { + span.RecordError(err) + return core.Profile{}, err + } + if doc.ID == "" { hash := core.GetHash([]byte(document)) hash10 := [10]byte{} copy(hash10[:], hash[:10]) doc.ID = cdid.New(hash10, doc.SignedAt).String() + + _, err := s.repo.Get(ctx, doc.ID) + if err == nil { + span.RecordError(err) + return core.Profile{}, errors.New("profile already exists") + } + + policyResult, err := s.policy.TestWithPolicyURL( + ctx, + "", + core.RequestContext{ + Requester: signer, + Document: doc, + }, + "profile.create", + ) + if err != nil { + span.RecordError(err) + return core.Profile{}, err + } + + result := s.policy.Summerize([]core.PolicyEvalResult{policyResult}, "profile.create") + if !result { + return core.Profile{}, errors.New("policy failed") + } + + } else { + + existance, err := s.repo.Get(ctx, doc.ID) + if err != nil { + span.RecordError(err) + return core.Profile{}, err + } + + var params map[string]any = make(map[string]any) + if existance.PolicyParams != nil { + json.Unmarshal([]byte(*existance.PolicyParams), ¶ms) + } + + policyResult, err := s.policy.TestWithPolicyURL( + ctx, + existance.Policy, + core.RequestContext{ + Requester: signer, + Self: existance, + Document: doc, + Params: params, + }, + "profile.update", + ) + + if err != nil { + span.SetStatus(codes.Error, err.Error()) + span.RecordError(err) + } + + result := s.policy.Summerize([]core.PolicyEvalResult{policyResult}, "profile.update") + if !result { + return core.Profile{}, errors.New("policy failed") + } } var policyparams *string = nil @@ -148,30 +227,55 @@ func (s *service) Upsert(ctx context.Context, mode core.CommitMode, document, si } // Delete deletes profile -func (s *service) Delete(ctx context.Context, mode core.CommitMode, documentStr string) (core.Profile, error) { +func (s *service) Delete(ctx context.Context, mode core.CommitMode, document string) (core.Profile, error) { ctx, span := tracer.Start(ctx, "Profile.Service.Delete") defer span.End() - var document core.DeleteDocument - err := json.Unmarshal([]byte(documentStr), &document) + var doc core.DeleteDocument + err := json.Unmarshal([]byte(document), &doc) if err != nil { span.RecordError(err) return core.Profile{}, err } - deleteTarget, err := s.Get(ctx, document.Target) + deleteTarget, err := s.Get(ctx, doc.Target) if err != nil { span.RecordError(err) return core.Profile{}, err } - if deleteTarget.Author != document.Signer { - err = errors.New("unauthorized") + signer, err := s.entity.Get(ctx, doc.Signer) + if err != nil { span.RecordError(err) return core.Profile{}, err } - return s.repo.Delete(ctx, document.Target) + var params map[string]any = make(map[string]any) + if deleteTarget.PolicyParams != nil { + json.Unmarshal([]byte(*deleteTarget.PolicyParams), ¶ms) + } + + policyResult, err := s.policy.TestWithPolicyURL( + ctx, + deleteTarget.Policy, + core.RequestContext{ + Requester: signer, + Self: deleteTarget, + Document: doc, + }, + "profile.delete", + ) + if err != nil { + span.RecordError(err) + return core.Profile{}, err + } + + result := s.policy.Summerize([]core.PolicyEvalResult{policyResult}, "profile.delete") + if !result { + return core.Profile{}, errors.New("policy failed") + } + + return s.repo.Delete(ctx, doc.Target) } // Clean deletes all profiles by author diff --git a/x/subscription/service.go b/x/subscription/service.go index ffde54d8..72c188c1 100644 --- a/x/subscription/service.go +++ b/x/subscription/service.go @@ -170,11 +170,42 @@ func (s *service) DeleteSubscription(ctx context.Context, mode core.CommitMode, return core.Subscription{}, err } - if deleteTarget.Author != doc.Signer { - return core.Subscription{}, errors.New("you are not authorized to perform this action") + signer, err := s.entity.Get(ctx, doc.Signer) + if err != nil { + span.RecordError(err) + return core.Subscription{}, err + } + + var params map[string]any = make(map[string]any) + if deleteTarget.PolicyParams != nil { + json.Unmarshal([]byte(*deleteTarget.PolicyParams), ¶ms) + } + + policyResult, err := s.policy.TestWithPolicyURL( + ctx, + deleteTarget.Policy, + core.RequestContext{ + Requester: signer, + Self: deleteTarget, + Document: doc, + }, + "subscription.delete", + ) + if err != nil { + span.RecordError(err) + return core.Subscription{}, err + } + + result := s.policy.Summerize([]core.PolicyEvalResult{policyResult}, "subscription.delete") + if !result { + return core.Subscription{}, errors.New("policy failed") } err = s.repo.DeleteSubscription(ctx, doc.Target) + if err != nil { + span.RecordError(err) + return core.Subscription{}, err + } return deleteTarget, err } diff --git a/x/timeline/service.go b/x/timeline/service.go index c09b75a7..46d4102f 100644 --- a/x/timeline/service.go +++ b/x/timeline/service.go @@ -713,8 +713,35 @@ func (s *service) DeleteTimeline(ctx context.Context, mode core.CommitMode, docu return core.Timeline{}, err } - if deleteTarget.Author != doc.Signer { - return core.Timeline{}, fmt.Errorf("You are not authorized to perform this action") + signer, err := s.entity.Get(ctx, doc.Signer) + if err != nil { + span.RecordError(err) + return core.Timeline{}, err + } + + var params map[string]any = make(map[string]any) + if deleteTarget.PolicyParams != nil { + json.Unmarshal([]byte(*deleteTarget.PolicyParams), ¶ms) + } + + policyResult, err := s.policy.TestWithPolicyURL( + ctx, + deleteTarget.Policy, + core.RequestContext{ + Requester: signer, + Self: deleteTarget, + Document: doc, + }, + "timeline.delete", + ) + if err != nil { + span.RecordError(err) + return core.Timeline{}, err + } + + result := s.policy.Summerize([]core.PolicyEvalResult{policyResult}, "timeline.delete") + if !result { + return core.Timeline{}, errors.New("policy failed") } err = s.repository.DeleteTimeline(ctx, doc.Target) From a78271aafe03bb0675fa0d4003db77837f46a484 Mon Sep 17 00:00:00 2001 From: totegamma Date: Thu, 18 Jul 2024 01:31:37 +0900 Subject: [PATCH 18/22] add profile/timeline/subscription.association.attach --- cmd/api/policy.go | 4 +- wire.go | 2 +- wire_gen.go | 6 +- x/association/service.go | 209 +++++++++++++++++++++++++++++---------- 4 files changed, 166 insertions(+), 55 deletions(-) diff --git a/cmd/api/policy.go b/cmd/api/policy.go index aae79098..25c51bab 100644 --- a/cmd/api/policy.go +++ b/cmd/api/policy.go @@ -296,7 +296,9 @@ var globalPolicyJson = ` } }, "defaults": { - "association.attach": true + "message.association.attach": true, + "timeline.association.attach": true, + "subscription.association.attach": true } }` diff --git a/wire.go b/wire.go index d15bf0da..875a1040 100644 --- a/wire.go +++ b/wire.go @@ -57,7 +57,7 @@ var ackServiceProvider = wire.NewSet(ack.NewService, ack.NewRepository, SetupEnt var messageServiceProvider = wire.NewSet(message.NewService, message.NewRepository, SetupEntityService, SetupDomainService, SetupTimelineService, SetupKeyService, SetupSchemaService) // Lv5 -var associationServiceProvider = wire.NewSet(association.NewService, association.NewRepository, SetupEntityService, SetupDomainService, SetupTimelineService, SetupMessageService, SetupKeyService, SetupSchemaService) +var associationServiceProvider = wire.NewSet(association.NewService, association.NewRepository, SetupEntityService, SetupDomainService, SetupTimelineService, SetupMessageService, SetupKeyService, SetupSchemaService, SetupProfileService, SetupSubscriptionService) // Lv6 var storeServiceProvider = wire.NewSet( diff --git a/wire_gen.go b/wire_gen.go index 4e2934a8..1f7ddf44 100644 --- a/wire_gen.go +++ b/wire_gen.go @@ -92,10 +92,12 @@ func SetupAssociationService(db *gorm.DB, rdb *redis.Client, mc *memcache.Client repository := association.NewRepository(db, mc, schemaService) entityService := SetupEntityService(db, rdb, mc, client2, policy2, config) domainService := SetupDomainService(db, client2, config) + profileService := SetupProfileService(db, rdb, mc, client2, policy2, config) timelineService := SetupTimelineService(db, rdb, mc, client2, policy2, config) + subscriptionService := SetupSubscriptionService(db, rdb, mc, client2, policy2, config) messageService := SetupMessageService(db, rdb, mc, client2, policy2, config) keyService := SetupKeyService(db, rdb, mc, client2, config) - associationService := association.NewService(repository, client2, entityService, domainService, timelineService, messageService, keyService, policy2, config) + associationService := association.NewService(repository, client2, entityService, domainService, profileService, timelineService, subscriptionService, messageService, keyService, policy2, config) return associationService } @@ -219,7 +221,7 @@ var ackServiceProvider = wire.NewSet(ack.NewService, ack.NewRepository, SetupEnt var messageServiceProvider = wire.NewSet(message.NewService, message.NewRepository, SetupEntityService, SetupDomainService, SetupTimelineService, SetupKeyService, SetupSchemaService) // Lv5 -var associationServiceProvider = wire.NewSet(association.NewService, association.NewRepository, SetupEntityService, SetupDomainService, SetupTimelineService, SetupMessageService, SetupKeyService, SetupSchemaService) +var associationServiceProvider = wire.NewSet(association.NewService, association.NewRepository, SetupEntityService, SetupDomainService, SetupTimelineService, SetupMessageService, SetupKeyService, SetupSchemaService, SetupProfileService, SetupSubscriptionService) // Lv6 var storeServiceProvider = wire.NewSet(store.NewService, store.NewRepository, SetupKeyService, diff --git a/x/association/service.go b/x/association/service.go index c620e393..9d296014 100644 --- a/x/association/service.go +++ b/x/association/service.go @@ -19,15 +19,17 @@ import ( ) type service struct { - repo Repository - client client.Client - entity core.EntityService - domain core.DomainService - timeline core.TimelineService - message core.MessageService - key core.KeyService - policy core.PolicyService - config core.Config + repo Repository + client client.Client + entity core.EntityService + domain core.DomainService + profile core.ProfileService + timeline core.TimelineService + subscription core.SubscriptionService + message core.MessageService + key core.KeyService + policy core.PolicyService + config core.Config } // NewService creates a new association service @@ -36,7 +38,9 @@ func NewService( client client.Client, entity core.EntityService, domain core.DomainService, + profile core.ProfileService, timeline core.TimelineService, + subscription core.SubscriptionService, message core.MessageService, key core.KeyService, policy core.PolicyService, @@ -47,7 +51,9 @@ func NewService( client, entity, domain, + profile, timeline, + subscription, message, key, policy, @@ -96,6 +102,12 @@ func (s *service) Create(ctx context.Context, mode core.CommitMode, document str signedAt := doc.SignedAt id := "a" + cdid.New(hash10, signedAt).String() + signer, err := s.entity.Get(ctx, doc.Signer) + if err != nil { + span.RecordError(err) + return core.Association{}, err + } + owner, err := s.entity.Get(ctx, doc.Owner) if err != nil { span.RecordError(err) @@ -183,44 +195,143 @@ func (s *service) Create(ctx context.Context, mode core.CommitMode, document str return association, core.ErrorPermissionDenied{} } - messagePolicyResult := core.PolicyEvalResultDefault - if target.Policy != "" { - var params map[string]any = make(map[string]any) - if target.PolicyParams != nil { - err := json.Unmarshal([]byte(*target.PolicyParams), ¶ms) - if err != nil { - span.SetStatus(codes.Error, err.Error()) - span.RecordError(err) - goto SKIP_EVAL_MESSAGE_POLICY - } + var params map[string]any = make(map[string]any) + if target.PolicyParams != nil { + err := json.Unmarshal([]byte(*target.PolicyParams), ¶ms) + if err != nil { + span.SetStatus(codes.Error, err.Error()) + span.RecordError(err) } + } - var err error - messagePolicyResult, err = s.policy.TestWithPolicyURL( - ctx, - target.Policy, - core.RequestContext{ - Self: target, - Params: params, - Document: doc, - }, - "message.association.attach", - ) + messagePolicyResult, err := s.policy.TestWithPolicyURL( + ctx, + target.Policy, + core.RequestContext{ + Requester: signer, + Self: target, + Params: params, + Document: doc, + }, + "message.association.attach", + ) + if err != nil { + span.SetStatus(codes.Error, err.Error()) + + } + + result := s.policy.Summerize([]core.PolicyEvalResult{messagePolicyResult, timelinePolicyResult}, "message.association.attach") + if !result { + return association, core.ErrorPermissionDenied{} + } + + case 'p': // profile + target, err := s.profile.Get(ctx, association.Target) + if err != nil { + span.RecordError(err) + return association, err + } + + var params map[string]any = make(map[string]any) + if target.PolicyParams != nil { + err := json.Unmarshal([]byte(*target.PolicyParams), ¶ms) if err != nil { span.SetStatus(codes.Error, err.Error()) - goto SKIP_EVAL_MESSAGE_POLICY - + span.RecordError(err) } } - SKIP_EVAL_MESSAGE_POLICY: - result := s.policy.Summerize([]core.PolicyEvalResult{messagePolicyResult, timelinePolicyResult}, "association.attach") + policyEvalResult, err := s.policy.TestWithPolicyURL( + ctx, + target.Policy, + core.RequestContext{ + Requester: signer, + Self: target, + Params: params, + Document: doc, + }, + "profile.association.attach", + ) + if err != nil { + span.SetStatus(codes.Error, err.Error()) + } + + result := s.policy.Summerize([]core.PolicyEvalResult{policyEvalResult}, "profile.association.attach") if !result { return association, core.ErrorPermissionDenied{} } - case 'p': // profile + case 't': // timeline + target, err := s.timeline.GetTimeline(ctx, association.Target) + if err != nil { + span.RecordError(err) + return association, err + } + + var params map[string]any = make(map[string]any) + if target.PolicyParams != nil { + err := json.Unmarshal([]byte(*target.PolicyParams), ¶ms) + if err != nil { + span.SetStatus(codes.Error, err.Error()) + span.RecordError(err) + } + } + + policyEvalResult, err := s.policy.TestWithPolicyURL( + ctx, + target.Policy, + core.RequestContext{ + Requester: signer, + Self: target, + Params: params, + Document: doc, + }, + "timeline.association.attach", + ) + if err != nil { + span.SetStatus(codes.Error, err.Error()) + } + + result := s.policy.Summerize([]core.PolicyEvalResult{policyEvalResult}, "timeline.association.attach") + if !result { + return association, core.ErrorPermissionDenied{} + } + case 's': // subscription + target, err := s.subscription.GetSubscription(ctx, association.Target) + if err != nil { + span.RecordError(err) + return association, err + } + + var params map[string]any = make(map[string]any) + if target.PolicyParams != nil { + err := json.Unmarshal([]byte(*target.PolicyParams), ¶ms) + if err != nil { + span.SetStatus(codes.Error, err.Error()) + span.RecordError(err) + } + } + + policyEvalResult, err := s.policy.TestWithPolicyURL( + ctx, + target.Policy, + core.RequestContext{ + Requester: signer, + Self: target, + Params: params, + Document: doc, + }, + "subscription.association.attach", + ) + if err != nil { + span.SetStatus(codes.Error, err.Error()) + } + + result := s.policy.Summerize([]core.PolicyEvalResult{policyEvalResult}, "subscription.association.attach") + if !result { + return association, core.ErrorPermissionDenied{} + } } association, err = s.repo.Create(ctx, association) @@ -425,30 +536,19 @@ func (s *service) Delete(ctx context.Context, mode core.CommitMode, document, si return core.Association{}, err } - requester := doc.Signer - - targetMessage, err := s.message.Get(ctx, targetAssociation.Target, requester) + signer, err := s.entity.Get(ctx, doc.Signer) if err != nil { span.RecordError(err) return core.Association{}, err } - var params map[string]any = make(map[string]any) - if targetMessage.PolicyParams != nil { - err := json.Unmarshal([]byte(*targetMessage.PolicyParams), ¶ms) - if err != nil { - span.RecordError(err) - return core.Association{}, err - } - } - result, err := s.policy.TestWithPolicyURL( ctx, - targetMessage.Policy, + "", core.RequestContext{ - Self: targetAssociation, - Params: params, - Document: doc, + Requester: signer, + Self: targetAssociation, + Document: doc, }, "association.delete", ) @@ -490,6 +590,13 @@ func (s *service) Delete(ctx context.Context, mode core.CommitMode, document, si } if targetAssociation.Target[0] == 'm' && mode != core.CommitModeLocalOnlyExec { // distribute is needed only when targetType is messages + + targetMessage, err := s.message.Get(ctx, targetAssociation.Target, doc.Signer) + if err != nil { + span.RecordError(err) + return core.Association{}, err + } + for _, timeline := range targetMessage.Timelines { normalized, err := s.timeline.NormalizeTimelineID(ctx, timeline) From c9967aa896310eeacde4d9c537f9ce3623bb1b40 Mon Sep 17 00:00:00 2001 From: totegamma Date: Fri, 19 Jul 2024 02:54:28 +0900 Subject: [PATCH 19/22] add retract and sort owner handling --- core/documents.go | 6 +++ core/interfaces.go | 11 +++-- x/association/service.go | 102 +++++++++++++++++++-------------------- x/message/service.go | 90 +++++++++++++++++----------------- x/store/service.go | 23 +++------ x/timeline/handler.go | 34 ------------- x/timeline/service.go | 96 +++++++++++++++++++++++++++++++++--- 7 files changed, 199 insertions(+), 163 deletions(-) diff --git a/core/documents.go b/core/documents.go index e9795397..1fde4c0a 100644 --- a/core/documents.go +++ b/core/documents.go @@ -88,6 +88,12 @@ type TimelineDocument[T any] struct { // type: timeline DomainOwned bool `json:"domainOwned"` } +type RetractDocument struct { + DocumentBase[any] + Timeline string `json:"timeline"` + Target string `json:"target"` +} + // subscription type SubscriptionDocument[T any] struct { // type: subscription DocumentBase[T] diff --git a/core/interfaces.go b/core/interfaces.go index 52248fa5..e3b3046e 100644 --- a/core/interfaces.go +++ b/core/interfaces.go @@ -20,8 +20,8 @@ type AgentService interface { } type AssociationService interface { - Create(ctx context.Context, mode CommitMode, document, signature string) (Association, error) - Delete(ctx context.Context, mode CommitMode, document, signature string) (Association, error) + Create(ctx context.Context, mode CommitMode, document, signature string) (Association, []string, error) + Delete(ctx context.Context, mode CommitMode, document, signature string) (Association, []string, error) Clean(ctx context.Context, ccid string) error Get(ctx context.Context, id string) (Association, error) @@ -82,8 +82,8 @@ type MessageService interface { Get(ctx context.Context, id string, requester string) (Message, error) GetWithOwnAssociations(ctx context.Context, id string, requester string) (Message, error) Clean(ctx context.Context, ccid string) error - Create(ctx context.Context, mode CommitMode, document string, signature string) (Message, error) - Delete(ctx context.Context, mode CommitMode, document, signature string) (Message, error) + Create(ctx context.Context, mode CommitMode, document string, signature string) (Message, []string, error) + Delete(ctx context.Context, mode CommitMode, document, signature string) (Message, []string, error) Count(ctx context.Context) (int64, error) } @@ -158,7 +158,7 @@ type TimelineService interface { GetImmediateItemsFromSubscription(ctx context.Context, subscription string, since time.Time, limit int) ([]TimelineItem, error) GetItem(ctx context.Context, timeline string, id string) (TimelineItem, error) PostItem(ctx context.Context, timeline string, item TimelineItem, document, signature string) (TimelineItem, error) - RemoveItem(ctx context.Context, timeline string, id string) + Retract(ctx context.Context, mode CommitMode, document, signature string) (TimelineItem, []string, error) RemoveItemsByResourceID(ctx context.Context, resourceID string) error PublishEvent(ctx context.Context, event Event) error @@ -175,6 +175,7 @@ type TimelineService interface { ListTimelineSubscriptions(ctx context.Context) (map[string]int64, error) Count(ctx context.Context) (int64, error) NormalizeTimelineID(ctx context.Context, timeline string) (string, error) + GetOwners(ctx context.Context, timelines []string) ([]string, error) Realtime(ctx context.Context, request <-chan []string, response chan<- Event) } diff --git a/x/association/service.go b/x/association/service.go index 9d296014..3d0b76ac 100644 --- a/x/association/service.go +++ b/x/association/service.go @@ -85,7 +85,7 @@ func (s *service) Clean(ctx context.Context, ccid string) error { // PostAssociation creates a new association // If targetType is messages, it also posts the association to the target message's timelines // returns the created association -func (s *service) Create(ctx context.Context, mode core.CommitMode, document string, signature string) (core.Association, error) { +func (s *service) Create(ctx context.Context, mode core.CommitMode, document string, signature string) (core.Association, []string, error) { ctx, span := tracer.Start(ctx, "Association.Service.Create") defer span.End() @@ -93,7 +93,7 @@ func (s *service) Create(ctx context.Context, mode core.CommitMode, document str err := json.Unmarshal([]byte(document), &doc) if err != nil { span.RecordError(err) - return core.Association{}, err + return core.Association{}, []string{}, err } hash := core.GetHash([]byte(document)) @@ -105,19 +105,19 @@ func (s *service) Create(ctx context.Context, mode core.CommitMode, document str signer, err := s.entity.Get(ctx, doc.Signer) if err != nil { span.RecordError(err) - return core.Association{}, err + return core.Association{}, []string{}, err } owner, err := s.entity.Get(ctx, doc.Owner) if err != nil { span.RecordError(err) - return core.Association{}, err + return core.Association{}, []string{}, err } bodyStr, err := json.Marshal(doc.Body) if err != nil { span.RecordError(err) - return core.Association{}, err + return core.Association{}, []string{}, err } uniqueKey := doc.Signer + doc.Schema + doc.Target + doc.Variant + string(bodyStr) @@ -144,7 +144,7 @@ func (s *service) Create(ctx context.Context, mode core.CommitMode, document str target, err := s.message.Get(ctx, association.Target, doc.Signer) if err != nil { span.RecordError(err) - return association, err + return core.Association{}, []string{}, err } timelinePolicyResults := make([]core.PolicyEvalResult, len(target.Timelines)) @@ -155,19 +155,9 @@ func (s *service) Create(ctx context.Context, mode core.CommitMode, document str continue } - if timeline.Policy == "" { - timelinePolicyResults[i] = core.PolicyEvalResultDefault - continue - } - var params map[string]any = make(map[string]any) if timeline.PolicyParams != nil { - err := json.Unmarshal([]byte(*timeline.PolicyParams), ¶ms) - if err != nil { - span.SetStatus(codes.Error, err.Error()) - span.RecordError(err) - continue - } + json.Unmarshal([]byte(*timeline.PolicyParams), ¶ms) } result, err := s.policy.TestWithPolicyURL( @@ -192,16 +182,12 @@ func (s *service) Create(ctx context.Context, mode core.CommitMode, document str timelinePolicyResult := policy.AccumulateOr(timelinePolicyResults) timelinePolicyIsDominant, timlinePolicyAllowed := policy.IsDominant(timelinePolicyResult) if timelinePolicyIsDominant && !timlinePolicyAllowed { - return association, core.ErrorPermissionDenied{} + return association, []string{}, core.ErrorPermissionDenied{} } var params map[string]any = make(map[string]any) if target.PolicyParams != nil { - err := json.Unmarshal([]byte(*target.PolicyParams), ¶ms) - if err != nil { - span.SetStatus(codes.Error, err.Error()) - span.RecordError(err) - } + json.Unmarshal([]byte(*target.PolicyParams), ¶ms) } messagePolicyResult, err := s.policy.TestWithPolicyURL( @@ -222,14 +208,14 @@ func (s *service) Create(ctx context.Context, mode core.CommitMode, document str result := s.policy.Summerize([]core.PolicyEvalResult{messagePolicyResult, timelinePolicyResult}, "message.association.attach") if !result { - return association, core.ErrorPermissionDenied{} + return association, []string{}, core.ErrorPermissionDenied{} } case 'p': // profile target, err := s.profile.Get(ctx, association.Target) if err != nil { span.RecordError(err) - return association, err + return association, []string{}, err } var params map[string]any = make(map[string]any) @@ -258,14 +244,14 @@ func (s *service) Create(ctx context.Context, mode core.CommitMode, document str result := s.policy.Summerize([]core.PolicyEvalResult{policyEvalResult}, "profile.association.attach") if !result { - return association, core.ErrorPermissionDenied{} + return association, []string{}, core.ErrorPermissionDenied{} } case 't': // timeline target, err := s.timeline.GetTimeline(ctx, association.Target) if err != nil { span.RecordError(err) - return association, err + return association, []string{}, err } var params map[string]any = make(map[string]any) @@ -294,14 +280,14 @@ func (s *service) Create(ctx context.Context, mode core.CommitMode, document str result := s.policy.Summerize([]core.PolicyEvalResult{policyEvalResult}, "timeline.association.attach") if !result { - return association, core.ErrorPermissionDenied{} + return association, []string{}, core.ErrorPermissionDenied{} } case 's': // subscription target, err := s.subscription.GetSubscription(ctx, association.Target) if err != nil { span.RecordError(err) - return association, err + return association, []string{}, err } var params map[string]any = make(map[string]any) @@ -330,17 +316,17 @@ func (s *service) Create(ctx context.Context, mode core.CommitMode, document str result := s.policy.Summerize([]core.PolicyEvalResult{policyEvalResult}, "subscription.association.attach") if !result { - return association, core.ErrorPermissionDenied{} + return association, []string{}, core.ErrorPermissionDenied{} } } association, err = s.repo.Create(ctx, association) if err != nil { if errors.Is(err, core.ErrorAlreadyExists{}) { - return association, core.NewErrorAlreadyExists() + return association, []string{}, core.NewErrorAlreadyExists() } span.RecordError(err) - return association, err + return association, []string{}, err } } @@ -423,7 +409,7 @@ func (s *service) Create(ctx context.Context, mode core.CommitMode, document str targetMessage, err := s.message.Get(ctx, association.Target, doc.Signer) //NOTE: これはownerのドメインしか実行できない if err != nil { span.RecordError(err) - return association, err + return association, []string{}, err } for _, timeline := range targetMessage.Timelines { @@ -449,7 +435,7 @@ func (s *service) Create(ctx context.Context, mode core.CommitMode, document str if err != nil { slog.ErrorContext(ctx, "failed to publish message to Redis", slog.String("error", err.Error()), slog.String("module", "association")) span.RecordError(err) - return association, err + return association, []string{}, err } } else { documentObj := core.EventDocument{ @@ -467,13 +453,13 @@ func (s *service) Create(ctx context.Context, mode core.CommitMode, document str document, err := json.Marshal(documentObj) if err != nil { span.RecordError(err) - return association, err + return association, []string{}, err } signatureBytes, err := core.SignBytes([]byte(document), s.config.PrivateKey) if err != nil { span.RecordError(err) - return association, err + return association, []string{}, err } signature := hex.EncodeToString(signatureBytes) @@ -486,7 +472,7 @@ func (s *service) Create(ctx context.Context, mode core.CommitMode, document str packet, err := json.Marshal(packetObj) if err != nil { span.RecordError(err) - return association, err + return association, []string{}, err } s.client.Commit(ctx, domain, string(packet), nil, nil) @@ -495,7 +481,12 @@ func (s *service) Create(ctx context.Context, mode core.CommitMode, document str } } - return association, nil + affected, err := s.timeline.GetOwners(ctx, doc.Timelines) + if err != nil { + span.RecordError(err) + } + + return association, affected, nil } // Get returns an association by ID @@ -515,7 +506,7 @@ func (s *service) GetOwn(ctx context.Context, author string) ([]core.Association } // Delete deletes an association by ID -func (s *service) Delete(ctx context.Context, mode core.CommitMode, document, signature string) (core.Association, error) { +func (s *service) Delete(ctx context.Context, mode core.CommitMode, document, signature string) (core.Association, []string, error) { ctx, span := tracer.Start(ctx, "Association.Service.Delete") defer span.End() @@ -523,23 +514,23 @@ func (s *service) Delete(ctx context.Context, mode core.CommitMode, document, si err := json.Unmarshal([]byte(document), &doc) if err != nil { span.RecordError(err) - return core.Association{}, err + return core.Association{}, []string{}, err } targetAssociation, err := s.repo.Get(ctx, doc.Target) if err != nil { if errors.Is(err, core.ErrorNotFound{}) { - return core.Association{}, core.NewErrorAlreadyDeleted() + return core.Association{}, []string{}, core.NewErrorAlreadyDeleted() } span.RecordError(err) - return core.Association{}, err + return core.Association{}, []string{}, err } signer, err := s.entity.Get(ctx, doc.Signer) if err != nil { span.RecordError(err) - return core.Association{}, err + return core.Association{}, []string{}, err } result, err := s.policy.TestWithPolicyURL( @@ -555,18 +546,18 @@ func (s *service) Delete(ctx context.Context, mode core.CommitMode, document, si if err != nil { span.RecordError(err) - return core.Association{}, err + return core.Association{}, []string{}, err } finally := s.policy.Summerize([]core.PolicyEvalResult{result}, "association.delete") if !finally { - return core.Association{}, core.ErrorPermissionDenied{} + return core.Association{}, []string{}, core.ErrorPermissionDenied{} } err = s.repo.Delete(ctx, doc.Target) if err != nil { span.RecordError(err) - return core.Association{}, err + return core.Association{}, []string{}, err } err = s.timeline.RemoveItemsByResourceID(ctx, doc.Target) @@ -585,7 +576,7 @@ func (s *service) Delete(ctx context.Context, mode core.CommitMode, document, si if err != nil { slog.ErrorContext(ctx, "failed to publish message to Redis", slog.String("error", err.Error()), slog.String("module", "association")) span.RecordError(err) - return targetAssociation, err + return targetAssociation, []string{}, err } } @@ -594,7 +585,7 @@ func (s *service) Delete(ctx context.Context, mode core.CommitMode, document, si targetMessage, err := s.message.Get(ctx, targetAssociation.Target, doc.Signer) if err != nil { span.RecordError(err) - return core.Association{}, err + return core.Association{}, []string{}, err } for _, timeline := range targetMessage.Timelines { @@ -622,7 +613,7 @@ func (s *service) Delete(ctx context.Context, mode core.CommitMode, document, si if err != nil { slog.ErrorContext(ctx, "failed to publish message to Redis", slog.String("error", err.Error()), slog.String("module", "association")) span.RecordError(err) - return targetAssociation, err + return targetAssociation, []string{}, err } } else { documentObj := core.EventDocument{ @@ -635,13 +626,13 @@ func (s *service) Delete(ctx context.Context, mode core.CommitMode, document, si document, err := json.Marshal(documentObj) if err != nil { span.RecordError(err) - return targetAssociation, err + return targetAssociation, []string{}, err } signatureBytes, err := core.SignBytes([]byte(document), s.config.PrivateKey) if err != nil { span.RecordError(err) - return targetAssociation, err + return targetAssociation, []string{}, err } signature := hex.EncodeToString(signatureBytes) @@ -654,7 +645,7 @@ func (s *service) Delete(ctx context.Context, mode core.CommitMode, document, si packet, err := json.Marshal(packetObj) if err != nil { span.RecordError(err) - return targetAssociation, err + return targetAssociation, []string{}, err } s.client.Commit(ctx, domain, string(packet), nil, nil) @@ -662,7 +653,12 @@ func (s *service) Delete(ctx context.Context, mode core.CommitMode, document, si } } - return targetAssociation, nil + affected, err := s.timeline.GetOwners(ctx, targetAssociation.Timelines) + if err != nil { + span.RecordError(err) + } + + return targetAssociation, affected, nil } // GetByTarget returns associations by target diff --git a/x/message/service.go b/x/message/service.go index 9a5c871d..4e3d0419 100644 --- a/x/message/service.go +++ b/x/message/service.go @@ -111,12 +111,7 @@ func (s *service) isMessagePublic(ctx context.Context, message core.Message) (bo var err error var params map[string]any = make(map[string]any) if message.PolicyParams != nil { - err := json.Unmarshal([]byte(*message.PolicyParams), ¶ms) - if err != nil { - span.SetStatus(codes.Error, err.Error()) - span.RecordError(err) - goto SKIP_EVAL_MESSAGE_POLICY - } + json.Unmarshal([]byte(*message.PolicyParams), ¶ms) } messagePolicyResult, err = s.policy.TestWithPolicyURL( @@ -130,10 +125,7 @@ func (s *service) isMessagePublic(ctx context.Context, message core.Message) (bo ) if err != nil { span.SetStatus(codes.Error, err.Error()) - goto SKIP_EVAL_MESSAGE_POLICY - } -SKIP_EVAL_MESSAGE_POLICY: // accumulate polies result := s.policy.Summerize([]core.PolicyEvalResult{messagePolicyResult, timelinePolicyResult}, "message.read") @@ -228,31 +220,22 @@ func (s *service) GetWithOwnAssociations(ctx context.Context, id string, request } messagePolicyResult := core.PolicyEvalResultDefault - if message.Policy != "" { - var params map[string]any = make(map[string]any) - if message.PolicyParams != nil { - err := json.Unmarshal([]byte(*message.PolicyParams), ¶ms) - if err != nil { - span.SetStatus(codes.Error, err.Error()) - span.RecordError(err) - goto SKIP_EVAL_MESSAGE_POLICY - } - } + var params map[string]any = make(map[string]any) + if message.PolicyParams != nil { + json.Unmarshal([]byte(*message.PolicyParams), ¶ms) + } - requestContext := core.RequestContext{ - Self: message, - Params: params, - Requester: requesterEntity, - } + requestContext := core.RequestContext{ + Self: message, + Params: params, + Requester: requesterEntity, + } - messagePolicyResult, err = s.policy.TestWithPolicyURL(ctx, message.Policy, requestContext, "message.read") - if err != nil { - span.SetStatus(codes.Error, err.Error()) - goto SKIP_EVAL_MESSAGE_POLICY - } + messagePolicyResult, err = s.policy.TestWithPolicyURL(ctx, message.Policy, requestContext, "message.read") + if err != nil { + span.SetStatus(codes.Error, err.Error()) } -SKIP_EVAL_MESSAGE_POLICY: result := s.policy.Summerize([]core.PolicyEvalResult{messagePolicyResult, timelinePolicyResult}, "message.read") if !result { @@ -264,7 +247,7 @@ SKIP_EVAL_MESSAGE_POLICY: // Create creates a new message // It also posts the message to the timelines -func (s *service) Create(ctx context.Context, mode core.CommitMode, document string, signature string) (core.Message, error) { +func (s *service) Create(ctx context.Context, mode core.CommitMode, document string, signature string) (core.Message, []string, error) { ctx, span := tracer.Start(ctx, "Message.Service.Create") defer span.End() @@ -274,7 +257,7 @@ func (s *service) Create(ctx context.Context, mode core.CommitMode, document str err := json.Unmarshal([]byte(document), &doc) if err != nil { span.RecordError(err) - return created, err + return created, []string{}, err } hash := core.GetHash([]byte(document)) @@ -286,7 +269,7 @@ func (s *service) Create(ctx context.Context, mode core.CommitMode, document str signer, err := s.entity.Get(ctx, doc.Signer) if err != nil { span.RecordError(err) - return core.Message{}, err + return core.Message{}, []string{}, err } var policyparams *string = nil @@ -310,7 +293,7 @@ func (s *service) Create(ctx context.Context, mode core.CommitMode, document str created, err = s.repo.Create(ctx, message) if err != nil { span.RecordError(err) - return message, err + return message, []string{}, err } } @@ -338,14 +321,16 @@ func (s *service) Create(ctx context.Context, mode core.CommitMode, document str ispublic, err := s.isMessagePublic(ctx, created) if err != nil { span.RecordError(err) - return core.Message{}, err + return core.Message{}, []string{}, err } sendDocument := "" sendSignature := "" + var sendResource *core.Message if ispublic { sendDocument = document sendSignature = signature + sendResource = &created } for domain, timelines := range destinations { @@ -376,6 +361,7 @@ func (s *service) Create(ctx context.Context, mode core.CommitMode, document str Item: posted, Document: sendDocument, Signature: sendSignature, + Resource: sendResource, } err = s.timeline.PublishEvent(ctx, event) @@ -411,12 +397,17 @@ func (s *service) Create(ctx context.Context, mode core.CommitMode, document str // TODO: 実際に送信できたstreamの一覧を返すべき - return created, nil + affected, err := s.timeline.GetOwners(ctx, doc.Timelines) + if err != nil { + span.RecordError(err) + } + + return created, affected, nil } // Delete deletes a message by ID // It also emits a delete event to the sockets -func (s *service) Delete(ctx context.Context, mode core.CommitMode, document, signature string) (core.Message, error) { +func (s *service) Delete(ctx context.Context, mode core.CommitMode, document, signature string) (core.Message, []string, error) { ctx, span := tracer.Start(ctx, "Message.Service.Delete") defer span.End() @@ -424,16 +415,16 @@ func (s *service) Delete(ctx context.Context, mode core.CommitMode, document, si err := json.Unmarshal([]byte(document), &doc) if err != nil { span.RecordError(err) - return core.Message{}, err + return core.Message{}, []string{}, err } deleteTarget, err := s.repo.Get(ctx, doc.Target) if err != nil { if errors.Is(err, core.ErrorNotFound{}) { - return core.Message{}, core.NewErrorAlreadyDeleted() + return core.Message{}, []string{}, core.NewErrorAlreadyDeleted() } span.RecordError(err) - return core.Message{}, err + return core.Message{}, []string{}, err } var params map[string]any = make(map[string]any) @@ -441,7 +432,7 @@ func (s *service) Delete(ctx context.Context, mode core.CommitMode, document, si err := json.Unmarshal([]byte(*deleteTarget.PolicyParams), ¶ms) if err != nil { span.RecordError(err) - return core.Message{}, err + return core.Message{}, []string{}, err } } @@ -458,18 +449,18 @@ func (s *service) Delete(ctx context.Context, mode core.CommitMode, document, si if err != nil { span.RecordError(err) - return core.Message{}, err + return core.Message{}, []string{}, err } finally := s.policy.Summerize([]core.PolicyEvalResult{result}, "message.delete") if !finally { - return core.Message{}, core.ErrorPermissionDenied{} + return core.Message{}, []string{}, core.ErrorPermissionDenied{} } err = s.repo.Delete(ctx, doc.Target) if err != nil { span.RecordError(err) - return core.Message{}, err + return core.Message{}, []string{}, err } err = s.timeline.RemoveItemsByResourceID(ctx, doc.Target) @@ -480,7 +471,7 @@ func (s *service) Delete(ctx context.Context, mode core.CommitMode, document, si ispublic, err := s.isMessagePublic(ctx, deleteTarget) if err != nil { span.RecordError(err) - return core.Message{}, err + return core.Message{}, []string{}, err } var publicResource *core.Message = nil @@ -499,12 +490,17 @@ func (s *service) Delete(ctx context.Context, mode core.CommitMode, document, si err := s.timeline.PublishEvent(ctx, event) if err != nil { span.RecordError(err) - return deleteTarget, err + return deleteTarget, []string{}, err } } } - return deleteTarget, err + affected, err := s.timeline.GetOwners(ctx, deleteTarget.Timelines) + if err != nil { + span.RecordError(err) + } + + return deleteTarget, affected, err } func (s *service) Clean(ctx context.Context, ccid string) error { diff --git a/x/store/service.go b/x/store/service.go index 6ef47aa2..75b54856 100644 --- a/x/store/service.go +++ b/x/store/service.go @@ -86,16 +86,10 @@ func (s *service) Commit(ctx context.Context, mode core.CommitMode, document str switch base.Type { case "message": - var m core.Message - m, err = s.message.Create(ctx, mode, document, signature) - result = m - owners = []string{m.Author} + result, owners, err = s.message.Create(ctx, mode, document, signature) case "association": - var a core.Association - a, err = s.association.Create(ctx, mode, document, signature) - result = a - owners = []string{a.Author, a.Owner} + result, owners, err = s.association.Create(ctx, mode, document, signature) case "profile": var p core.Profile @@ -125,6 +119,9 @@ func (s *service) Commit(ctx context.Context, mode core.CommitMode, document str owners = []string{s.config.FQDN} } + case "retract": + result, owners, err = s.timeline.Retract(ctx, mode, document, signature) + case "event": result, err = s.timeline.Event(ctx, mode, document, signature) @@ -177,15 +174,9 @@ func (s *service) Commit(ctx context.Context, mode core.CommitMode, document str typ := doc.Target[0] switch typ { case 'm': // message - var dm core.Message - dm, err = s.message.Delete(ctx, mode, document, signature) - result = dm - owners = []string{dm.Author} + result, owners, err = s.message.Delete(ctx, mode, document, signature) case 'a': // association - var da core.Association - da, err = s.association.Delete(ctx, mode, document, signature) - result = da - owners = []string{da.Owner} + result, owners, err = s.association.Delete(ctx, mode, document, signature) case 'p': // profile var dp core.Profile dp, err = s.profile.Delete(ctx, mode, document) diff --git a/x/timeline/handler.go b/x/timeline/handler.go index ba42f54b..d1874f24 100644 --- a/x/timeline/handler.go +++ b/x/timeline/handler.go @@ -26,7 +26,6 @@ type Handler interface { Range(c echo.Context) error List(c echo.Context) error ListMine(c echo.Context) error - Remove(c echo.Context) error GetChunks(c echo.Context) error Realtime(c echo.Context) error } @@ -177,39 +176,6 @@ func (h handler) ListMine(c echo.Context) error { return c.JSON(http.StatusOK, echo.Map{"status": "ok", "content": list}) } -// Remove is remove timeline element from timeline -func (h handler) Remove(c echo.Context) error { - ctx, span := tracer.Start(c.Request().Context(), "Timeline.Handler.Remove") - defer span.End() - - timelineID := c.Param("timeline") - split := strings.Split(timelineID, "@") - if len(split) == 2 { - timelineID = split[0] - } - - objectID := c.Param("object") - - target, err := h.service.GetItem(ctx, timelineID, objectID) - if err != nil { - span.RecordError(err) - return c.JSON(http.StatusInternalServerError, echo.Map{"error": err.Error()}) - } - - requester, ok := ctx.Value(core.RequesterIdCtxKey).(string) - if !ok { - return c.JSON(http.StatusForbidden, echo.Map{"status": "error", "message": "requester not found"}) - } - - if *target.Author != requester && target.Owner != requester { - return c.JSON(http.StatusForbidden, echo.Map{"error": "You are not owner of this timeline element"}) - } - - h.service.RemoveItem(ctx, timelineID, objectID) - - return c.JSON(http.StatusOK, echo.Map{"status": "ok"}) -} - // GetChunks func (h handler) GetChunks(c echo.Context) error { ctx, span := tracer.Start(c.Request().Context(), "Timeline.Handler.GetChunks") diff --git a/x/timeline/service.go b/x/timeline/service.go index 46d4102f..d9644340 100644 --- a/x/timeline/service.go +++ b/x/timeline/service.go @@ -560,10 +560,7 @@ func (s *service) UpsertTimeline(ctx context.Context, mode core.CommitMode, docu var params map[string]any = make(map[string]any) if existance.PolicyParams != nil { - if err != nil { - span.SetStatus(codes.Error, err.Error()) - span.RecordError(err) - } + json.Unmarshal([]byte(*existance.PolicyParams), ¶ms) } policyResult, err := s.policy.TestWithPolicyURL( @@ -687,12 +684,70 @@ func (s *service) GetItem(ctx context.Context, timeline string, id string) (core return s.repository.GetItem(ctx, timeline, id) } -// Remove removes timeline element by ID -func (s *service) RemoveItem(ctx context.Context, timeline string, id string) { - ctx, span := tracer.Start(ctx, "Timeline.Service.RemoveItem") +// Retract removes timeline element by ID +func (s *service) Retract(ctx context.Context, mode core.CommitMode, document, signature string) (core.TimelineItem, []string, error) { + ctx, span := tracer.Start(ctx, "Timeline.Service.Retract") defer span.End() - s.repository.DeleteItem(ctx, timeline, id) + var doc core.RetractDocument + err := json.Unmarshal([]byte(document), &doc) + if err != nil { + return core.TimelineItem{}, []string{}, err + } + + existing, err := s.repository.GetItem(ctx, doc.Timeline, doc.Target) + if err != nil { + return core.TimelineItem{}, []string{}, err + } + + signer, err := s.entity.Get(ctx, doc.Signer) + if err != nil { + span.RecordError(err) + return core.TimelineItem{}, []string{}, err + } + + timeline, err := s.repository.GetTimeline(ctx, doc.ID) + if err != nil { + span.RecordError(err) + return core.TimelineItem{}, []string{}, err + } + + var params map[string]any = make(map[string]any) + if timeline.PolicyParams != nil { + json.Unmarshal([]byte(*timeline.PolicyParams), ¶ms) + } + + policyResult, err := s.policy.TestWithPolicyURL( + ctx, + timeline.Policy, + core.RequestContext{ + Requester: signer, + Self: timeline, + Resource: existing, + Document: doc, + Params: params, + }, + "timeline.retract", + ) + + if err != nil { + span.SetStatus(codes.Error, err.Error()) + span.RecordError(err) + } + + result := s.policy.Summerize([]core.PolicyEvalResult{policyResult}, "timeline.retract") + if !result { + return core.TimelineItem{}, []string{}, fmt.Errorf("You don't have timeline.retract access") + } + + s.repository.DeleteItem(ctx, doc.Timeline, doc.Target) + + affected := []string{timeline.Author} + if timeline.DomainOwned { + affected = []string{s.config.FQDN} + } + + return existing, affected, nil } // Delete deletes @@ -833,6 +888,31 @@ func (s *service) Realtime(ctx context.Context, request <-chan []string, respons } } +func (s *service) GetOwners(ctx context.Context, timelines []string) ([]string, error) { + ctx, span := tracer.Start(ctx, "Timeline.Service.GetOwners") + defer span.End() + + var owners_map map[string]bool = make(map[string]bool) + for _, timelineID := range timelines { + timeline, err := s.GetTimeline(ctx, timelineID) + if err != nil { + continue + } + if timeline.DomainOwned { + owners_map[s.config.FQDN] = true + } else { + owners_map[timeline.Author] = true + } + } + + owners := make([]string, 0) + for owner := range owners_map { + owners = append(owners, owner) + } + + return owners, nil +} + func (s *service) Clean(ctx context.Context, ccid string) error { ctx, span := tracer.Start(ctx, "Timeline.Service.Clean") defer span.End() From 0a9fd20b21fdd65298aa746b0aeff862c8017cfa Mon Sep 17 00:00:00 2001 From: totegamma Date: Fri, 19 Jul 2024 20:16:13 +0900 Subject: [PATCH 20/22] update policy --- cmd/api/policy.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/cmd/api/policy.go b/cmd/api/policy.go index 25c51bab..3b96da4d 100644 --- a/cmd/api/policy.go +++ b/cmd/api/policy.go @@ -38,6 +38,8 @@ var globalPolicyJson = ` } }, "association.delete": { + "dominant": true, + "defaultOnFalse": true, "condition": { "op": "Or", "args": [ @@ -75,6 +77,8 @@ var globalPolicyJson = ` } }, "message.delete": { + "dominant": true, + "defaultOnFalse": true, "condition": { "op": "Or", "args": [ @@ -99,11 +103,15 @@ var globalPolicyJson = ` } }, "profile.create": { + "dominant": true, + "defaultOnFalse": true, "condition": { "op": "IsRequesterLocalUser" } }, "profile.update": { + "dominant": true, + "defaultOnFalse": true, "condition": { "op": "Eq", "args": [ @@ -119,6 +127,8 @@ var globalPolicyJson = ` } }, "profile.delete": { + "dominant": true, + "defaultOnFalse": true, "condition": { "op": "Or", "args": [ @@ -143,6 +153,8 @@ var globalPolicyJson = ` } }, "subscription.create": { + "dominant": true, + "defaultOnFalse": true, "condition": { "op": "Or", "args": [ @@ -157,6 +169,8 @@ var globalPolicyJson = ` } }, "subscription.update": { + "dominant": true, + "defaultOnFalse": true, "condition": { "op": "Eq", "args": [ @@ -172,6 +186,8 @@ var globalPolicyJson = ` } }, "subscription.delete": { + "dominant": true, + "defaultOnFalse": true, "condition": { "op": "Or", "args": [ @@ -196,6 +212,8 @@ var globalPolicyJson = ` } }, "timeline.create": { + "dominant": true, + "defaultOnFalse": true, "condition": { "op": "Or", "args": [ @@ -210,6 +228,8 @@ var globalPolicyJson = ` } }, "timeline.update": { + "dominant": true, + "defaultOnFalse": true, "condition": { "op": "Eq", "args": [ @@ -225,6 +245,8 @@ var globalPolicyJson = ` } }, "timeline.delete": { + "dominant": true, + "defaultOnFalse": true, "condition": { "op": "Or", "args": [ @@ -249,6 +271,8 @@ var globalPolicyJson = ` } }, "timeline.distribute": { + "dominant": true, + "defaultOnFalse": true, "condition": { "op": "Or", "args": [ @@ -272,6 +296,8 @@ var globalPolicyJson = ` } }, "timeline.message.read": { + "dominant": true, + "defaultOnFalse": true, "condition": { "op": "Or", "args": [ From 05482a752b747985c9ef7fa9589f279af77f682c Mon Sep 17 00:00:00 2001 From: totegamma Date: Fri, 19 Jul 2024 20:53:43 +0900 Subject: [PATCH 21/22] fix association.service --- x/association/service.go | 111 ++++++++++++++++++++------------------- 1 file changed, 56 insertions(+), 55 deletions(-) diff --git a/x/association/service.go b/x/association/service.go index 3d0b76ac..96e94700 100644 --- a/x/association/service.go +++ b/x/association/service.go @@ -330,79 +330,80 @@ func (s *service) Create(ctx context.Context, mode core.CommitMode, document str } } - if doc.Target[0] == 'm' { - destinations := make(map[string][]string) - for _, timeline := range doc.Timelines { - normalized, err := s.timeline.NormalizeTimelineID(ctx, timeline) - if err != nil { - span.RecordError(err) - continue - } - split := strings.Split(normalized, "@") - if len(split) <= 1 { - span.RecordError(fmt.Errorf("invalid timeline id: %s", normalized)) - continue - } - domain := split[len(split)-1] - - if _, ok := destinations[domain]; !ok { - destinations[domain] = []string{} - } - destinations[domain] = append(destinations[domain], timeline) + destinations := make(map[string][]string) + for _, timeline := range doc.Timelines { + normalized, err := s.timeline.NormalizeTimelineID(ctx, timeline) + if err != nil { + span.RecordError(err) + continue } + split := strings.Split(normalized, "@") + if len(split) <= 1 { + span.RecordError(fmt.Errorf("invalid timeline id: %s", normalized)) + continue + } + domain := split[len(split)-1] - for domain, timelines := range destinations { - if domain == s.config.FQDN { - // localなら、timelineのエントリを生成→Eventを発行 - for _, timeline := range timelines { - posted, err := s.timeline.PostItem(ctx, timeline, core.TimelineItem{ - ResourceID: association.ID, - Owner: association.Owner, - Author: &association.Author, - }, document, signature) - if err != nil { - span.RecordError(err) - continue - } + if _, ok := destinations[domain]; !ok { + destinations[domain] = []string{} + } + destinations[domain] = append(destinations[domain], timeline) + } - event := core.Event{ - Timeline: timeline, - Item: posted, - Document: document, - Signature: signature, - Resource: association, - } + for domain, timelines := range destinations { + if domain == s.config.FQDN { + // localなら、timelineのエントリを生成→Eventを発行 + for _, timeline := range timelines { - err = s.timeline.PublishEvent(ctx, event) - if err != nil { - slog.ErrorContext(ctx, "failed to publish event", slog.String("error", err.Error()), slog.String("module", "timeline")) - span.RecordError(err) - continue - } + posted, err := s.timeline.PostItem(ctx, timeline, core.TimelineItem{ + ResourceID: association.ID, + Owner: association.Owner, + Author: &association.Author, + }, document, signature) + if err != nil { + span.RecordError(err) + continue } - } else if owner.Domain == s.config.FQDN && mode != core.CommitModeLocalOnlyExec { // ここでリソースを作成したなら、リモートにもリレー - // send to remote - packet := core.Commit{ + + event := core.Event{ + Timeline: timeline, + Item: posted, Document: document, Signature: signature, + Resource: association, } - packetStr, err := json.Marshal(packet) + err = s.timeline.PublishEvent(ctx, event) if err != nil { + slog.ErrorContext(ctx, "failed to publish event", slog.String("error", err.Error()), slog.String("module", "timeline")) span.RecordError(err) continue } + } + } else if owner.Domain == s.config.FQDN && mode != core.CommitModeLocalOnlyExec { // ここでリソースを作成したなら、リモートにもリレー + // send to remote + packet := core.Commit{ + Document: document, + Signature: signature, + } - _, err = s.domain.GetByFQDN(ctx, domain) - if err != nil { - span.RecordError(err) - continue - } + packetStr, err := json.Marshal(packet) + if err != nil { + span.RecordError(err) + continue + } - s.client.Commit(ctx, domain, string(packetStr), nil, nil) + _, err = s.domain.GetByFQDN(ctx, domain) + if err != nil { + span.RecordError(err) + continue } + + s.client.Commit(ctx, domain, string(packetStr), nil, nil) } + } + if doc.Target[0] == 'm' { // Associationだけの追加対応 // メッセージの場合は、ターゲットのタイムラインにも追加する if owner.Domain == s.config.FQDN && mode != core.CommitModeLocalOnlyExec { From 49010a41acb546e764e3e72fd9095e6e382ba16e Mon Sep 17 00:00:00 2001 From: totegamma Date: Fri, 19 Jul 2024 21:11:05 +0900 Subject: [PATCH 22/22] update docker-publish.yml --- .github/workflows/docker-publish.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 1086d3f6..e78da149 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -61,6 +61,9 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Get tag + run: echo "TAG=${GITHUB_REF##*/}" >> $GITHUB_ENV + # Extract metadata (tags, labels) for Docker # https://github.com/docker/metadata-action - name: Extract Docker metadata @@ -68,9 +71,9 @@ jobs: uses: docker/metadata-action@v4 with: images: ${{ matrix.image }} - - - name: Get tag - run: echo "TAG=${GITHUB_REF##*/}" >> $GITHUB_ENV + tags: | + type=raw,value=latest,enable=${{ !contains(env.TAG, 'beta') }} + type=semver,pattern={{raw}} # Build and push Docker image with Buildx (don't push on PR) # https://github.com/docker/build-push-action