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
}
|