1use std::sync::Arc;
2
3use gatekeep::{
4 AuditSink, Authorizer, Context, Decision, DecisionAuditOccurrence, DecisiveClause, DenyShape,
5 Effect, FactResolver, IdentityReasonCatalog, Lattice, NoopAuditSink, NoopPolicyObserver,
6 Policy, PolicyId, PolicyObserver, PreparedPolicy, ReasonCatalog,
7};
8use serde::Serialize;
9
10use crate::{DenialResponseConfig, GatekeepAxumError, GatekeepRejection};
11
12#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct Authorized<O> {
15 pub outcome: O,
17 pub decision: Decision<O>,
19 pub audit_occurrence: DecisionAuditOccurrence,
22}
23
24pub struct Gatekeeper<R, A = NoopAuditSink, C = IdentityReasonCatalog, W = NoopPolicyObserver> {
26 authorizer: Authorizer<R, A, W>,
27 reason_catalog: Arc<C>,
28 denial_response: DenialResponseConfig,
29}
30
31impl<R, A, C, W> Clone for Gatekeeper<R, A, C, W> {
32 fn clone(&self) -> Self {
33 Self {
34 authorizer: self.authorizer.clone(),
35 reason_catalog: Arc::clone(&self.reason_catalog),
36 denial_response: self.denial_response.clone(),
37 }
38 }
39}
40
41impl<R> Gatekeeper<R> {
42 #[must_use]
48 pub fn unaudited(resolver: R) -> Self {
49 Self {
50 authorizer: Authorizer::unaudited(resolver),
51 reason_catalog: Arc::new(IdentityReasonCatalog),
52 denial_response: DenialResponseConfig::default(),
53 }
54 }
55}
56
57impl<R, A> Gatekeeper<R, A> {
58 #[must_use]
60 pub fn new(resolver: R, audit_sink: A) -> Self {
61 Self {
62 authorizer: Authorizer::new(resolver, audit_sink),
63 reason_catalog: Arc::new(IdentityReasonCatalog),
64 denial_response: DenialResponseConfig::default(),
65 }
66 }
67}
68
69impl<R, A, C, W> Gatekeeper<R, A, C, W> {
70 #[must_use]
72 pub fn with_audit_sink<NextAudit>(
73 self,
74 audit_sink: NextAudit,
75 ) -> Gatekeeper<R, NextAudit, C, W> {
76 Gatekeeper {
77 authorizer: self.authorizer.with_audit_sink(audit_sink),
78 reason_catalog: self.reason_catalog,
79 denial_response: self.denial_response,
80 }
81 }
82
83 #[must_use]
85 pub fn with_reason_catalog<NextCatalog>(
86 self,
87 reason_catalog: NextCatalog,
88 ) -> Gatekeeper<R, A, NextCatalog, W> {
89 Gatekeeper {
90 authorizer: self.authorizer,
91 reason_catalog: Arc::new(reason_catalog),
92 denial_response: self.denial_response,
93 }
94 }
95
96 #[must_use]
98 pub fn with_observer<NextObserver>(
99 self,
100 observer: NextObserver,
101 ) -> Gatekeeper<R, A, C, NextObserver> {
102 Gatekeeper {
103 authorizer: self.authorizer.with_observer(observer),
104 reason_catalog: self.reason_catalog,
105 denial_response: self.denial_response,
106 }
107 }
108
109 #[must_use]
111 pub fn with_denial_response(mut self, denial_response: DenialResponseConfig) -> Self {
112 self.denial_response = denial_response;
113 self
114 }
115
116 #[must_use]
119 pub fn with_clock<F>(mut self, clock: F) -> Self
120 where
121 F: gatekeep::Clock + 'static,
122 {
123 self.authorizer = self.authorizer.with_clock(clock);
124 self
125 }
126}
127
128impl<R, A, C, W> Gatekeeper<R, A, C, W>
129where
130 R: FactResolver,
131 A: AuditSink,
132 C: ReasonCatalog + Send + Sync,
133 W: PolicyObserver,
134{
135 pub async fn authorize<O>(
144 &self,
145 policy_id: PolicyId,
146 policy: &Policy<O>,
147 context: Context,
148 ) -> Result<Authorized<O>, GatekeepRejection<R::Error, A::Error>>
149 where
150 O: Lattice + Serialize + Send + Sync,
151 {
152 let result = self
153 .authorizer
154 .authorize_policy(policy_id, policy, &context)
155 .await
156 .map_err(GatekeepRejection::from_error)?;
157 self.present(result, &context)
158 }
159
160 pub async fn authorize_prepared<O>(
165 &self,
166 policy: &PreparedPolicy<O>,
167 context: Context,
168 ) -> Result<Authorized<O>, GatekeepRejection<R::Error, A::Error>>
169 where
170 O: Lattice + Serialize + Send + Sync,
171 {
172 let result = self
173 .authorizer
174 .authorize(policy, &context)
175 .await
176 .map_err(GatekeepRejection::from_error)?;
177 self.present(result, &context)
178 }
179}
180
181impl<R, A, C: ReasonCatalog, W> Gatekeeper<R, A, C, W> {
182 fn present<O: Serialize + Clone, Resolve, Audit>(
183 &self,
184 result: gatekeep::AuthorizationDecision<O>,
185 context: &Context,
186 ) -> Result<Authorized<O>, GatekeepRejection<Resolve, Audit>> {
187 let gatekeep::AuthorizationDecision {
188 decision,
189 audit_occurrence,
190 } = result;
191 match decision.effect.clone() {
192 Effect::Permit(outcome) => Ok(Authorized {
193 outcome,
194 decision,
195 audit_occurrence,
196 }),
197 Effect::Deny => {
198 let reason = decision
199 .denial_reason()
200 .map_err(GatekeepAxumError::Trace)
201 .map_err(GatekeepRejection::from_error)?;
202 let response = self.denial_response.denied(
203 denial_shape(&decision),
204 reason.as_ref(),
205 context.locale(),
206 self.reason_catalog.as_ref(),
207 );
208 Err(response.into())
209 }
210 }
211 }
212}
213
214const fn denial_shape<O>(decision: &Decision<O>) -> DenyShape {
215 match &decision.trace.decisive {
216 DecisiveClause::Deny { shape, .. } => *shape,
217 DecisiveClause::Permit { .. } => DenyShape::Forbidden,
218 }
219}
220
221impl<R, A, C, W> Gatekeeper<R, A, C, W>
222where
223 R: gatekeep::ResourcePolicy,
224 A: AuditSink,
225 C: ReasonCatalog + Send + Sync,
226 W: PolicyObserver,
227{
228 pub async fn authorize_resource(
233 &self,
234 action: &R::Action,
235 principal: &R::Principal,
236 resource: &R::Resource,
237 context: Context,
238 ) -> Result<Authorized<R::Outcome>, GatekeepRejection<R::Error, A::Error>> {
239 let result = self
240 .authorizer
241 .authorize_resource(action, principal, resource, &context)
242 .await
243 .map_err(GatekeepRejection::from_error)?;
244 self.present(result, &context)
245 }
246}