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