Skip to main content

reqsign_core/
api.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use crate::time::Timestamp;
19use crate::{BoxedFuture, Context, MaybeSend, Result};
20use std::fmt::Debug;
21use std::future::Future;
22use std::ops::Deref;
23use std::time::Duration;
24
25/// SigningCredential is the trait used by signer as the signing credential.
26pub trait SigningCredential: Clone + Debug + Send + Sync + Unpin + 'static {
27    /// Check if the signing credential is valid.
28    ///
29    /// Note: this will be removed in favor of `is_valid_at` in a future
30    /// release.
31    fn is_valid(&self) -> bool;
32
33    /// Check if the signing credential will be valid at the given timestamp.
34    fn is_valid_at(&self, _ts: Timestamp) -> bool {
35        self.is_valid()
36    }
37}
38
39impl<T: SigningCredential> SigningCredential for Option<T> {
40    fn is_valid(&self) -> bool {
41        let Some(ctx) = self else {
42            return false;
43        };
44
45        ctx.is_valid()
46    }
47}
48
49/// ProvideCredential is the trait used by signer to load the credential from the environment.
50///`
51/// Service may require different credential to sign the request, for example, AWS require
52/// access key and secret key, while Google Cloud Storage require token.
53pub trait ProvideCredential: Debug + Send + Sync + Unpin + 'static {
54    /// Credential returned by this loader.
55    ///
56    /// Typically, it will be a credential.
57    type Credential: Send + Sync + Unpin + 'static;
58
59    /// Load signing credential from current env.
60    fn provide_credential(
61        &self,
62        ctx: &Context,
63    ) -> impl Future<Output = Result<Option<Self::Credential>>> + MaybeSend;
64}
65
66/// ProvideCredentialDyn is the dyn version of [`ProvideCredential`].
67pub trait ProvideCredentialDyn: Debug + Send + Sync + Unpin + 'static {
68    /// Credential returned by this loader.
69    type Credential: Send + Sync + Unpin + 'static;
70
71    /// Dyn version of [`ProvideCredential::provide_credential`].
72    fn provide_credential_dyn<'a>(
73        &'a self,
74        ctx: &'a Context,
75    ) -> BoxedFuture<'a, Result<Option<Self::Credential>>>;
76}
77
78impl<T> ProvideCredentialDyn for T
79where
80    T: ProvideCredential + ?Sized,
81{
82    type Credential = T::Credential;
83
84    fn provide_credential_dyn<'a>(
85        &'a self,
86        ctx: &'a Context,
87    ) -> BoxedFuture<'a, Result<Option<Self::Credential>>> {
88        Box::pin(self.provide_credential(ctx))
89    }
90}
91
92impl<T> ProvideCredential for std::sync::Arc<T>
93where
94    T: ProvideCredentialDyn + ?Sized,
95{
96    type Credential = T::Credential;
97
98    async fn provide_credential(&self, ctx: &Context) -> Result<Option<Self::Credential>> {
99        self.deref().provide_credential_dyn(ctx).await
100    }
101}
102
103/// SignRequest is the trait used by signer to build the signing request.
104pub trait SignRequest: Debug + Send + Sync + Unpin + 'static {
105    /// Credential used by this builder.
106    ///
107    /// Typically, it will be a credential.
108    type Credential: Send + Sync + Unpin + 'static;
109
110    /// Construct the signing request.
111    ///
112    /// ## Credential
113    ///
114    /// The `credential` parameter is the credential required by the signer to sign the request.
115    ///
116    /// ## Expires In
117    ///
118    /// The `expires_in` parameter specifies the expiration time for the result.
119    /// If the signer does not support expiration, it should return an error.
120    ///
121    /// Implementation details determine how to handle the expiration logic. For instance,
122    /// AWS uses a query string that includes an `Expires` parameter.
123    fn sign_request<'a>(
124        &'a self,
125        ctx: &'a Context,
126        req: &'a mut http::request::Parts,
127        credential: Option<&'a Self::Credential>,
128        expires_in: Option<Duration>,
129    ) -> impl Future<Output = Result<()>> + MaybeSend + 'a;
130}
131
132/// SignRequestDyn is the dyn version of [`SignRequest`].
133pub trait SignRequestDyn: Debug + Send + Sync + Unpin + 'static {
134    /// Credential used by this builder.
135    type Credential: Send + Sync + Unpin + 'static;
136
137    /// Dyn version of [`SignRequest::sign_request`].
138    fn sign_request_dyn<'a>(
139        &'a self,
140        ctx: &'a Context,
141        req: &'a mut http::request::Parts,
142        credential: Option<&'a Self::Credential>,
143        expires_in: Option<Duration>,
144    ) -> BoxedFuture<'a, Result<()>>;
145}
146
147impl<T> SignRequestDyn for T
148where
149    T: SignRequest + ?Sized,
150{
151    type Credential = T::Credential;
152
153    fn sign_request_dyn<'a>(
154        &'a self,
155        ctx: &'a Context,
156        req: &'a mut http::request::Parts,
157        credential: Option<&'a Self::Credential>,
158        expires_in: Option<Duration>,
159    ) -> BoxedFuture<'a, Result<()>> {
160        Box::pin(self.sign_request(ctx, req, credential, expires_in))
161    }
162}
163
164impl<T> SignRequest for std::sync::Arc<T>
165where
166    T: SignRequestDyn + ?Sized,
167{
168    type Credential = T::Credential;
169
170    async fn sign_request(
171        &self,
172        ctx: &Context,
173        req: &mut http::request::Parts,
174        credential: Option<&Self::Credential>,
175        expires_in: Option<Duration>,
176    ) -> Result<()> {
177        self.deref()
178            .sign_request_dyn(ctx, req, credential, expires_in)
179            .await
180    }
181}
182
183/// A chain of credential providers that will be tried in order.
184///
185/// This is a generic implementation that can be used by any service to chain multiple
186/// credential providers together. The chain will try each provider in order until one
187/// returns credentials or all providers have been exhausted.
188///
189/// # Example
190///
191/// ```no_run
192/// use reqsign_core::{ProvideCredentialChain, Context, ProvideCredential, Result};
193///
194/// #[derive(Debug)]
195/// struct MyCredential {
196///     token: String,
197/// }
198///
199/// #[derive(Debug)]
200/// struct EnvironmentProvider;
201///
202/// impl ProvideCredential for EnvironmentProvider {
203///     type Credential = MyCredential;
204///
205///     async fn provide_credential(&self, ctx: &Context) -> Result<Option<Self::Credential>> {
206///         // Implementation
207///         Ok(None)
208///     }
209/// }
210///
211/// # async fn example(ctx: Context) {
212/// let chain = ProvideCredentialChain::new()
213///     .push(EnvironmentProvider);
214///
215/// let credentials = chain.provide_credential(&ctx).await;
216/// # }
217/// ```
218pub struct ProvideCredentialChain<C> {
219    providers: Vec<Box<dyn ProvideCredentialDyn<Credential = C>>>,
220}
221
222impl<C> ProvideCredentialChain<C>
223where
224    C: Send + Sync + Unpin + 'static,
225{
226    /// Create a new empty credential provider chain.
227    pub fn new() -> Self {
228        Self {
229            providers: Vec::new(),
230        }
231    }
232
233    /// Add a credential provider to the chain.
234    pub fn push(mut self, provider: impl ProvideCredential<Credential = C> + 'static) -> Self {
235        self.providers.push(Box::new(provider));
236        self
237    }
238
239    /// Add a credential provider to the front of the chain.
240    ///
241    /// This provider will be tried first before all existing providers.
242    pub fn push_front(
243        mut self,
244        provider: impl ProvideCredential<Credential = C> + 'static,
245    ) -> Self {
246        self.providers.insert(0, Box::new(provider));
247        self
248    }
249
250    /// Create a credential provider chain from a vector of providers.
251    pub fn from_vec(providers: Vec<Box<dyn ProvideCredentialDyn<Credential = C>>>) -> Self {
252        Self { providers }
253    }
254
255    /// Get the number of providers in the chain.
256    pub fn len(&self) -> usize {
257        self.providers.len()
258    }
259
260    /// Check if the chain is empty.
261    pub fn is_empty(&self) -> bool {
262        self.providers.is_empty()
263    }
264}
265
266impl<C> Default for ProvideCredentialChain<C>
267where
268    C: Send + Sync + Unpin + 'static,
269{
270    fn default() -> Self {
271        Self::new()
272    }
273}
274
275impl<C> Debug for ProvideCredentialChain<C>
276where
277    C: Send + Sync + Unpin + 'static,
278{
279    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
280        f.debug_struct("ProvideCredentialChain")
281            .field("providers_count", &self.providers.len())
282            .finish()
283    }
284}
285
286impl<C> ProvideCredential for ProvideCredentialChain<C>
287where
288    C: Send + Sync + Unpin + 'static,
289{
290    type Credential = C;
291
292    async fn provide_credential(&self, ctx: &Context) -> Result<Option<Self::Credential>> {
293        for provider in &self.providers {
294            log::debug!("Trying credential provider: {provider:?}");
295
296            match provider.provide_credential_dyn(ctx).await {
297                Ok(Some(cred)) => {
298                    log::debug!("Successfully loaded credential from provider: {provider:?}");
299                    return Ok(Some(cred));
300                }
301                Ok(None) => {
302                    log::debug!("No credential found in provider: {provider:?}");
303                    continue;
304                }
305                Err(e) => {
306                    log::warn!("Error loading credential from provider {provider:?}: {e:?}");
307                    // Continue to next provider on error
308                    continue;
309                }
310            }
311        }
312
313        Ok(None)
314    }
315}