-
Notifications
You must be signed in to change notification settings - Fork 927
/
privacy.go
162 lines (139 loc) · 4.91 KB
/
privacy.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
// Copyright 2019-present Facebook Inc. All rights reserved.
// This source code is licensed under the Apache 2.0 license found
// in the LICENSE file in the root directory of this source tree.
// Package privacy provides sets of types and helpers for writing privacy
// rules in user schemas, and deal with their evaluation at runtime.
package privacy
import (
"context"
"errors"
"entgo.io/ent"
)
// List of policy decisions.
var (
// Allow may be returned by rules to indicate that the policy
// evaluation should terminate with an allow decision.
Allow = errors.New("ent/privacy: allow rule")
// Deny may be returned by rules to indicate that the policy
// evaluation should terminate with an deny decision.
Deny = errors.New("ent/privacy: deny rule")
// Skip may be returned by rules to indicate that the policy
// evaluation should continue to the next rule.
Skip = errors.New("ent/privacy: skip rule")
)
type (
// QueryRule defines the interface deciding whether a
// query is allowed and optionally modify it.
QueryRule interface {
EvalQuery(context.Context, ent.Query) error
}
// QueryPolicy combines multiple query rules into a single policy.
QueryPolicy []QueryRule
// MutationRule defines the interface deciding whether a
// mutation is allowed and optionally modify it.
MutationRule interface {
EvalMutation(context.Context, ent.Mutation) error
}
// MutationPolicy combines multiple mutation rules into a single policy.
MutationPolicy []MutationRule
// Policy groups query and mutation policies.
Policy struct {
Query QueryPolicy
Mutation MutationPolicy
}
)
// EvalQuery forwards evaluation to query a policy.
func (p Policy) EvalQuery(ctx context.Context, q ent.Query) error {
return p.Query.EvalQuery(ctx, q)
}
// EvalMutation forwards evaluation to mutate a policy.
func (p Policy) EvalMutation(ctx context.Context, m ent.Mutation) error {
return p.Mutation.EvalMutation(ctx, m)
}
// NewPolicies creates an ent.Policy from list of mixin.Schema
// and ent.Schema that implement the ent.Policy interface.
//
// Note that, this is a runtime function used by the ent generated
// code and should not be used in ent/schemas as a privacy rule.
func NewPolicies(schemas ...interface{ Policy() ent.Policy }) ent.Policy {
policies := make(Policies, 0, len(schemas))
for i := range schemas {
if policy := schemas[i].Policy(); policy != nil {
policies = append(policies, policy)
}
}
return policies
}
// Policies combines multiple policies into a single policy.
//
// Note that, this is a runtime type used by the ent generated
// code and should not be used in ent/schemas as a privacy rule.
type Policies []ent.Policy
// EvalQuery evaluates the query policies. If the Allow error is returned
// from one of the policies, it stops the evaluation with a nil error.
func (policies Policies) EvalQuery(ctx context.Context, q ent.Query) error {
return policies.eval(ctx, func(policy ent.Policy) error {
return policy.EvalQuery(ctx, q)
})
}
// EvalMutation evaluates the mutation policies. If the Allow error is returned
// from one of the policies, it stops the evaluation with a nil error.
func (policies Policies) EvalMutation(ctx context.Context, m ent.Mutation) error {
return policies.eval(ctx, func(policy ent.Policy) error {
return policy.EvalMutation(ctx, m)
})
}
func (policies Policies) eval(ctx context.Context, eval func(ent.Policy) error) error {
if decision, ok := DecisionFromContext(ctx); ok {
return decision
}
for _, policy := range policies {
switch decision := eval(policy); {
case decision == nil || errors.Is(decision, Skip):
case errors.Is(decision, Allow):
return nil
default:
return decision
}
}
return nil
}
// EvalQuery evaluates a query against a query policy.
func (policies QueryPolicy) EvalQuery(ctx context.Context, q ent.Query) error {
for _, policy := range policies {
switch decision := policy.EvalQuery(ctx, q); {
case decision == nil || errors.Is(decision, Skip):
default:
return decision
}
}
return nil
}
// EvalMutation evaluates a mutation against a mutation policy.
func (policies MutationPolicy) EvalMutation(ctx context.Context, m ent.Mutation) error {
for _, policy := range policies {
switch decision := policy.EvalMutation(ctx, m); {
case decision == nil || errors.Is(decision, Skip):
default:
return decision
}
}
return nil
}
type decisionCtxKey struct{}
// DecisionContext creates a new context from the given parent context with
// a policy decision attach to it.
func DecisionContext(parent context.Context, decision error) context.Context {
if decision == nil || errors.Is(decision, Skip) {
return parent
}
return context.WithValue(parent, decisionCtxKey{}, decision)
}
// DecisionFromContext retrieves the policy decision from the context.
func DecisionFromContext(ctx context.Context) (error, bool) {
decision, ok := ctx.Value(decisionCtxKey{}).(error)
if ok && errors.Is(decision, Allow) {
decision = nil
}
return decision, ok
}