Skip to content

Commit bdeeedb

Browse files
authored
Merge pull request #18 from dotindustries/feature/bre-56-add-object-schema-to-context
feat: add object schema to context
2 parents 70b7438 + 8e8ddff commit bdeeedb

34 files changed

+1998
-576
lines changed

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@ grpc-install:
3030

3131
buf-install:
3232
printf $(COLOR) "Install/update buf..."
33-
go install github.com/bufbuild/buf/cmd/buf@v1.6.0
33+
go install github.com/bufbuild/buf/cmd/buf@latest
3434

3535
api-linter-install:
3636
printf $(COLOR) "Install/update api-linter..."
37-
go install github.com/googleapis/api-linter/cmd/api-linter@v1.32.3
37+
go install github.com/googleapis/api-linter/cmd/api-linter@latest
3838

3939
##### Linters #####
4040
api-linter:

apps/api/api/add.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ func (b *BreaseHandler) CreateRule(ctx context.Context, c *connect.Request[v1.Cr
1717
ctxID := c.Msg.ContextId
1818
rule := c.Msg.Rule
1919

20-
if !auth.HasPermission(ctx, auth.PermissionWrite) {
20+
if !auth.HasPermission(ctx, auth.PermissionCreateRule) {
2121
return nil, connect.NewError(connect.CodePermissionDenied, fmt.Errorf("permission denied"))
2222
}
2323

apps/api/api/delete.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313

1414
func (b *BreaseHandler) DeleteRule(ctx context.Context, c *connect.Request[contextv1.DeleteRuleRequest]) (*connect.Response[emptypb.Empty], error) {
1515
orgID := auth.CtxString(ctx, auth.ContextOrgKey)
16-
if !auth.HasPermission(ctx, auth.PermissionWrite) {
16+
if !auth.HasPermission(ctx, auth.PermissionCreateRule) {
1717
return nil, connect.NewError(connect.CodePermissionDenied, fmt.Errorf("permission denied"))
1818
}
1919
err := b.db.RemoveRule(ctx, orgID, c.Msg.ContextId, c.Msg.RuleId)

apps/api/api/evaluate.go

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"connectrpc.com/connect"
77
"context"
88
"fmt"
9+
"github.com/kaptinlin/jsonschema"
10+
"go.dot.industries/brease/cache"
911
"go.dot.industries/brease/code"
1012
"google.golang.org/protobuf/types/known/structpb"
1113

@@ -14,10 +16,34 @@ import (
1416
)
1517

1618
func (b *BreaseHandler) Evaluate(ctx context.Context, c *connect.Request[contextv1.EvaluateRequest]) (*connect.Response[contextv1.EvaluateResponse], error) {
17-
orgID := auth.CtxString(ctx, auth.ContextOrgKey)
18-
if !auth.HasPermission(ctx, auth.PermissionEvaluate) && !auth.HasPermission(ctx, auth.PermissionRead) {
19-
return nil, connect.NewError(connect.CodePermissionDenied, fmt.Errorf("permission denied"))
19+
orgID, _, _, cErr := permissionCheck(ctx, auth.PermissionEvaluate, auth.PermissionReadRule)
20+
if cErr != nil {
21+
return nil, cErr
2022
}
23+
24+
schema, err := b.db.GetObjectSchema(ctx, orgID, c.Msg.ContextId)
25+
if err != nil {
26+
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to fetch object schema: %v", err))
27+
}
28+
if schema != "" {
29+
compiledSchema, schemaErr := b.compiledObjectSchema(ctx, schema)
30+
if schemaErr != nil {
31+
return nil, connect.NewError(connect.CodeInvalidArgument, errors.Wrap(schemaErr, fmt.Errorf("invalid json schema")))
32+
}
33+
res := compiledSchema.ValidateStruct(c.Msg.Object)
34+
if !res.Valid {
35+
es := res.ToList()
36+
errStr := ""
37+
for _, e := range es.Errors {
38+
if len(errStr) > 0 {
39+
errStr += ", "
40+
}
41+
errStr += e
42+
}
43+
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("invalid object shape: %s", errStr))
44+
}
45+
}
46+
2147
codeBlock, err := b.findCode(ctx, c.Msg, orgID)
2248
if err != nil {
2349
return nil, err
@@ -33,6 +59,20 @@ func (b *BreaseHandler) Evaluate(ctx context.Context, c *connect.Request[context
3359
}), nil
3460
}
3561

62+
func (b *BreaseHandler) compiledObjectSchema(ctx context.Context, schema string) (*jsonschema.Schema, error) {
63+
compiled := b.cache.Get(ctx, cache.SimpleHash(schema))
64+
if compiled != nil && compiled != "" {
65+
return compiled.(*jsonschema.Schema), nil
66+
}
67+
compiledSchema, err := b.jsonSchemaCompiler.Compile([]byte(schema))
68+
if err != nil {
69+
return nil, err
70+
}
71+
// if we can't set it to cache, at worst it's gonna cause a delay on the next call
72+
_ = b.cache.Set(ctx, cache.SimpleHash(schema), compiledSchema)
73+
return compiledSchema, err
74+
}
75+
3676
func (b *BreaseHandler) run(ctx context.Context, codeBlock string, object *structpb.Struct) ([]*rulev1.EvaluationResult, error) {
3777
compiledScript, err := b.findScript(ctx, codeBlock)
3878
if err != nil {

apps/api/api/handler.go

Lines changed: 15 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,12 @@ package api
33
import (
44
"buf.build/gen/go/dot/brease/grpc/go/brease/auth/v1/authv1grpc"
55
"buf.build/gen/go/dot/brease/grpc/go/brease/context/v1/contextv1grpc"
6-
authv1 "buf.build/gen/go/dot/brease/protocolbuffers/go/brease/auth/v1"
7-
contextv1 "buf.build/gen/go/dot/brease/protocolbuffers/go/brease/context/v1"
8-
rulev1 "buf.build/gen/go/dot/brease/protocolbuffers/go/brease/rule/v1"
9-
"connectrpc.com/connect"
10-
"context"
6+
"github.com/kaptinlin/jsonschema"
117
"go.dot.industries/brease/auth"
128
"go.dot.industries/brease/cache"
139
"go.dot.industries/brease/code"
1410
"go.dot.industries/brease/storage"
1511
"go.uber.org/zap"
16-
"google.golang.org/grpc/metadata"
17-
"google.golang.org/protobuf/types/known/emptypb"
1812
)
1913

2014
type OpenApiHandler interface {
@@ -23,128 +17,28 @@ type OpenApiHandler interface {
2317
}
2418

2519
type BreaseHandler struct {
26-
db storage.Database
27-
logger *zap.Logger
28-
assembler *code.Assembler
29-
compiler *code.Compiler
30-
token auth.Token
31-
OpenApi OpenApiHandler
20+
db storage.Database
21+
logger *zap.Logger
22+
assembler *code.Assembler
23+
compiler *code.Compiler
24+
token auth.Token
25+
jsonSchemaCompiler *jsonschema.Compiler
26+
cache cache.Cache
3227
}
3328

3429
func NewHandler(db storage.Database, c cache.Cache, logger *zap.Logger) *BreaseHandler {
3530
if db == nil {
3631
panic("database cannot be nil")
3732
}
3833
bh := &BreaseHandler{
39-
db: db,
40-
logger: logger,
41-
assembler: code.NewAssembler(logger, c),
42-
compiler: code.NewCompiler(logger),
43-
token: auth.NewToken(logger),
34+
db: db,
35+
logger: logger,
36+
assembler: code.NewAssembler(logger, c),
37+
compiler: code.NewCompiler(logger),
38+
token: auth.NewToken(logger),
39+
jsonSchemaCompiler: jsonschema.NewCompiler(),
40+
cache: c,
4441
}
4542

46-
bh.OpenApi = &openApiHandler{handler: bh}
47-
4843
return bh
4944
}
50-
51-
type openApiHandler struct {
52-
handler *BreaseHandler
53-
}
54-
55-
func (o *openApiHandler) GetToken(ctx context.Context, empty *emptypb.Empty) (*authv1.TokenPair, error) {
56-
ctx = o.forwardMetadata(ctx)
57-
r, err := o.handler.GetToken(ctx, connect.NewRequest(empty))
58-
if err != nil {
59-
return nil, err
60-
}
61-
return r.Msg, nil
62-
}
63-
64-
func (o *openApiHandler) RefreshToken(ctx context.Context, request *authv1.RefreshTokenRequest) (*authv1.TokenPair, error) {
65-
ctx = o.forwardMetadata(ctx)
66-
r, err := o.handler.RefreshToken(ctx, connect.NewRequest(request))
67-
if err != nil {
68-
return nil, err
69-
}
70-
return r.Msg, nil
71-
}
72-
73-
func (o *openApiHandler) ListRules(ctx context.Context, request *contextv1.ListRulesRequest) (*contextv1.ListRulesResponse, error) {
74-
ctx = o.forwardMetadata(ctx)
75-
r, err := o.handler.ListRules(ctx, connect.NewRequest(request))
76-
if err != nil {
77-
return nil, err
78-
}
79-
return r.Msg, nil
80-
}
81-
82-
func (o *openApiHandler) GetRule(ctx context.Context, request *contextv1.GetRuleRequest) (*rulev1.VersionedRule, error) {
83-
ctx = o.forwardMetadata(ctx)
84-
r, err := o.handler.GetRule(ctx, connect.NewRequest(request))
85-
if err != nil {
86-
return nil, err
87-
}
88-
return r.Msg, nil
89-
}
90-
91-
func (o *openApiHandler) GetRuleVersions(ctx context.Context, request *contextv1.ListRuleVersionsRequest) (*contextv1.ListRuleVersionsResponse, error) {
92-
ctx = o.forwardMetadata(ctx)
93-
r, err := o.handler.GetRuleVersions(ctx, connect.NewRequest(request))
94-
if err != nil {
95-
return nil, err
96-
}
97-
return r.Msg, nil
98-
}
99-
100-
func (o *openApiHandler) CreateRule(ctx context.Context, request *contextv1.CreateRuleRequest) (*rulev1.VersionedRule, error) {
101-
ctx = o.forwardMetadata(ctx)
102-
r, err := o.handler.CreateRule(ctx, connect.NewRequest(request))
103-
if err != nil {
104-
return nil, err
105-
}
106-
return r.Msg, nil
107-
}
108-
109-
func (o *openApiHandler) UpdateRule(ctx context.Context, request *contextv1.UpdateRuleRequest) (*rulev1.VersionedRule, error) {
110-
ctx = o.forwardMetadata(ctx)
111-
r, err := o.handler.UpdateRule(ctx, connect.NewRequest(request))
112-
if err != nil {
113-
return nil, err
114-
}
115-
return r.Msg, nil
116-
}
117-
118-
func (o *openApiHandler) DeleteRule(ctx context.Context, request *contextv1.DeleteRuleRequest) (*emptypb.Empty, error) {
119-
ctx = o.forwardMetadata(ctx)
120-
r, err := o.handler.DeleteRule(ctx, connect.NewRequest(request))
121-
if err != nil {
122-
return nil, err
123-
}
124-
return r.Msg, nil
125-
}
126-
127-
func (o *openApiHandler) Evaluate(ctx context.Context, request *contextv1.EvaluateRequest) (*contextv1.EvaluateResponse, error) {
128-
ctx = o.forwardMetadata(ctx)
129-
r, err := o.handler.Evaluate(ctx, connect.NewRequest(request))
130-
if err != nil {
131-
return nil, err
132-
}
133-
return r.Msg, nil
134-
}
135-
136-
func (o *openApiHandler) forwardMetadata(ctx context.Context) context.Context {
137-
md, ok := metadata.FromIncomingContext(ctx)
138-
if !ok {
139-
return ctx
140-
}
141-
142-
if orgIds := md.Get(auth.ContextOrgKey); len(orgIds) > 0 {
143-
ctx = context.WithValue(ctx, auth.ContextOrgKey, orgIds[0])
144-
}
145-
if userIds := md.Get(auth.ContextUserIDKey); len(userIds) > 0 {
146-
ctx = context.WithValue(ctx, auth.ContextUserIDKey, userIds[0])
147-
}
148-
149-
return ctx
150-
}

apps/api/api/openapi_handler.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package api
2+
3+
import (
4+
authv1 "buf.build/gen/go/dot/brease/protocolbuffers/go/brease/auth/v1"
5+
contextv1 "buf.build/gen/go/dot/brease/protocolbuffers/go/brease/context/v1"
6+
rulev1 "buf.build/gen/go/dot/brease/protocolbuffers/go/brease/rule/v1"
7+
"connectrpc.com/connect"
8+
"context"
9+
"github.com/janvaclavik/govar"
10+
"go.dot.industries/brease/auth"
11+
"google.golang.org/grpc/metadata"
12+
"google.golang.org/protobuf/types/known/emptypb"
13+
)
14+
15+
type openApiHandler struct {
16+
handler *BreaseHandler
17+
}
18+
19+
func (o *openApiHandler) GetObjectSchema(ctx context.Context, request *contextv1.GetObjectSchemaRequest) (*contextv1.GetObjectSchemaResponse, error) {
20+
ctx = o.forwardMetadata(ctx)
21+
r, err := o.handler.GetObjectSchema(ctx, connect.NewRequest(request))
22+
if err != nil {
23+
return nil, err
24+
}
25+
return r.Msg, nil
26+
}
27+
28+
func (o *openApiHandler) ReplaceObjectSchema(ctx context.Context, request *contextv1.ReplaceObjectSchemaRequest) (*contextv1.ReplaceObjectSchemaResponse, error) {
29+
ctx = o.forwardMetadata(ctx)
30+
r, err := o.handler.ReplaceObjectSchema(ctx, connect.NewRequest(request))
31+
if err != nil {
32+
return nil, err
33+
}
34+
return r.Msg, nil
35+
}
36+
37+
func (o *openApiHandler) GetToken(ctx context.Context, empty *emptypb.Empty) (*authv1.TokenPair, error) {
38+
ctx = o.forwardMetadata(ctx)
39+
r, err := o.handler.GetToken(ctx, connect.NewRequest(empty))
40+
if err != nil {
41+
return nil, err
42+
}
43+
return r.Msg, nil
44+
}
45+
46+
func (o *openApiHandler) RefreshToken(ctx context.Context, request *authv1.RefreshTokenRequest) (*authv1.TokenPair, error) {
47+
ctx = o.forwardMetadata(ctx)
48+
r, err := o.handler.RefreshToken(ctx, connect.NewRequest(request))
49+
if err != nil {
50+
return nil, err
51+
}
52+
return r.Msg, nil
53+
}
54+
55+
func (o *openApiHandler) ListRules(ctx context.Context, request *contextv1.ListRulesRequest) (*contextv1.ListRulesResponse, error) {
56+
ctx = o.forwardMetadata(ctx)
57+
govar.Dump(ctx)
58+
r, err := o.handler.ListRules(ctx, connect.NewRequest(request))
59+
if err != nil {
60+
return nil, err
61+
}
62+
return r.Msg, nil
63+
}
64+
65+
func (o *openApiHandler) GetRule(ctx context.Context, request *contextv1.GetRuleRequest) (*rulev1.VersionedRule, error) {
66+
ctx = o.forwardMetadata(ctx)
67+
r, err := o.handler.GetRule(ctx, connect.NewRequest(request))
68+
if err != nil {
69+
return nil, err
70+
}
71+
return r.Msg, nil
72+
}
73+
74+
func (o *openApiHandler) GetRuleVersions(ctx context.Context, request *contextv1.ListRuleVersionsRequest) (*contextv1.ListRuleVersionsResponse, error) {
75+
ctx = o.forwardMetadata(ctx)
76+
r, err := o.handler.GetRuleVersions(ctx, connect.NewRequest(request))
77+
if err != nil {
78+
return nil, err
79+
}
80+
return r.Msg, nil
81+
}
82+
83+
func (o *openApiHandler) CreateRule(ctx context.Context, request *contextv1.CreateRuleRequest) (*rulev1.VersionedRule, error) {
84+
ctx = o.forwardMetadata(ctx)
85+
r, err := o.handler.CreateRule(ctx, connect.NewRequest(request))
86+
if err != nil {
87+
return nil, err
88+
}
89+
return r.Msg, nil
90+
}
91+
92+
func (o *openApiHandler) UpdateRule(ctx context.Context, request *contextv1.UpdateRuleRequest) (*rulev1.VersionedRule, error) {
93+
ctx = o.forwardMetadata(ctx)
94+
r, err := o.handler.UpdateRule(ctx, connect.NewRequest(request))
95+
if err != nil {
96+
return nil, err
97+
}
98+
return r.Msg, nil
99+
}
100+
101+
func (o *openApiHandler) DeleteRule(ctx context.Context, request *contextv1.DeleteRuleRequest) (*emptypb.Empty, error) {
102+
ctx = o.forwardMetadata(ctx)
103+
r, err := o.handler.DeleteRule(ctx, connect.NewRequest(request))
104+
if err != nil {
105+
return nil, err
106+
}
107+
return r.Msg, nil
108+
}
109+
110+
func (o *openApiHandler) Evaluate(ctx context.Context, request *contextv1.EvaluateRequest) (*contextv1.EvaluateResponse, error) {
111+
ctx = o.forwardMetadata(ctx)
112+
r, err := o.handler.Evaluate(ctx, connect.NewRequest(request))
113+
if err != nil {
114+
return nil, err
115+
}
116+
return r.Msg, nil
117+
}
118+
119+
func (o *openApiHandler) forwardMetadata(ctx context.Context) context.Context {
120+
md, ok := metadata.FromIncomingContext(ctx)
121+
if !ok {
122+
return ctx
123+
}
124+
125+
if orgIds := md.Get(auth.ContextOrgKey); len(orgIds) > 0 {
126+
ctx = context.WithValue(ctx, auth.ContextOrgKey, orgIds[0])
127+
}
128+
if userIds := md.Get(auth.ContextUserIDKey); len(userIds) > 0 {
129+
ctx = context.WithValue(ctx, auth.ContextUserIDKey, userIds[0])
130+
}
131+
132+
return ctx
133+
}

0 commit comments

Comments
 (0)