oauth2_broker/flows/
client_credentials.rs1use crate::{
13 _prelude::*,
14 auth::{TokenFamily, TokenRecord},
15 error::ConfigError,
16 flows::{
17 Broker,
18 common::{self, CachedTokenRequest},
19 },
20 http::TokenHttpClient,
21 oauth::{BasicFacade, OAuth2Facade, TransportErrorMapper},
22 obs::{self, FlowKind, FlowOutcome, FlowSpan},
23 provider::{GrantType, ProviderStrategy},
24 store::{BrokerStore, StoreKey},
25};
26
27impl<C, M> Broker<C, M>
28where
29 C: TokenHttpClient + ?Sized,
30 M: TransportErrorMapper<C::TransportError> + ?Sized,
31{
32 pub async fn client_credentials(&self, request: CachedTokenRequest) -> Result<TokenRecord> {
34 const KIND: FlowKind = FlowKind::ClientCredentials;
35
36 let span = FlowSpan::new(KIND, "client_credentials");
37
38 obs::record_flow_outcome(KIND, FlowOutcome::Attempt);
39
40 let result = span
41 .instrument(async move {
42 self.ensure_client_credentials_supported()?;
43
44 let tenant = request.tenant.clone();
45 let principal = request.principal.clone();
46 let store_scope = request.scope.clone();
47 let requested_scope = store_scope.clone();
48 let mut family = TokenFamily::new(tenant, principal);
49
50 family.provider = Some(self.descriptor.id.clone());
51
52 let key = StoreKey::new(&family, &store_scope);
53 let guard = common::flow_guard(self, &key);
54 let _singleflight = guard.lock().await;
55 let now = OffsetDateTime::now_utc();
56
57 if let Some(current) =
58 <dyn BrokerStore>::fetch(self.store.as_ref(), &family, &store_scope)
59 .await
60 .map_err(Error::from)?
61 .filter(|record| !request.should_refresh(record, now))
62 {
63 return Ok(current);
64 }
65
66 let grant = GrantType::ClientCredentials;
67 let mut form = {
68 let mut map = BTreeMap::new();
69
70 map.insert("grant_type".into(), grant.as_str().into());
71
72 map
73 };
74
75 if let Some(scope_value) =
76 common::format_scope(&requested_scope, self.descriptor.quirks.scope_delimiter)
77 {
78 form.insert("scope".into(), scope_value);
79 }
80
81 <dyn ProviderStrategy>::augment_token_request(
82 self.strategy.as_ref(),
83 grant,
84 &mut form,
85 );
86
87 let extra_params: Vec<(String, String)> = form
88 .into_iter()
89 .filter(|(key, _)| key != "grant_type" && key != "scope")
90 .collect();
91 let scope_params = requested_scope.iter().collect::<Vec<_>>();
92 let facade: BasicFacade<C, M> = BasicFacade::from_descriptor(
93 &self.descriptor,
94 &self.client_id,
95 self.client_secret.as_deref(),
96 None,
97 self.http_client.clone(),
98 self.transport_mapper.clone(),
99 )?;
100 let record = facade
101 .exchange_client_credentials(
102 self.strategy.as_ref(),
103 family,
104 scope_params.as_slice(),
105 extra_params.as_slice(),
106 )
107 .await?;
108
109 <dyn BrokerStore>::save(self.store.as_ref(), record.clone())
110 .await
111 .map_err(Error::from)?;
112
113 Ok(record)
114 })
115 .await;
116
117 match &result {
118 Ok(_) => obs::record_flow_outcome(KIND, FlowOutcome::Success),
119 Err(_) => obs::record_flow_outcome(KIND, FlowOutcome::Failure),
120 }
121
122 result
123 }
124
125 fn ensure_client_credentials_supported(&self) -> Result<()> {
126 if self.descriptor.supports(GrantType::ClientCredentials) {
127 Ok(())
128 } else {
129 Err(ConfigError::UnsupportedGrant {
130 descriptor: self.descriptor.id.to_string(),
131 grant: "client_credentials",
132 }
133 .into())
134 }
135 }
136}