-
Notifications
You must be signed in to change notification settings - Fork 912
/
privacy.go
140 lines (120 loc) · 4.21 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
// 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 (
// Policies combines multiple policies into a single policy.
Policies []ent.Policy
// 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
)
// NewPolicies creates an ent.Policy from list of mixin.Schema
// and ent.Schema that implement the ent.Policy interface.
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
}
// 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
}