Skip to content

Commit d9bb30f

Browse files
Add upstream_oauth2.providers.[].client_secret_file config option
This patch factors out the previously introduced config wrapper for client secrets to also use it for upstream oauth providers. See a7e7c3c
1 parent 119ebba commit d9bb30f

File tree

5 files changed

+110
-73
lines changed

5 files changed

+110
-73
lines changed

crates/cli/src/sync.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,8 @@ pub async fn config_sync(
201201
}
202202

203203
let encrypted_client_secret =
204-
if let Some(client_secret) = provider.client_secret.as_deref() {
205-
Some(encrypter.encrypt_to_string(client_secret.as_bytes())?)
204+
if let Some(client_secret) = provider.client_secret {
205+
Some(encrypter.encrypt_to_string(client_secret.value().await?.as_bytes())?)
206206
} else if let Some(mut siwa) = provider.sign_in_with_apple.clone() {
207207
// if private key file is defined and not private key (raw), we populate the
208208
// private key to hold the content of the private key file.

crates/config/src/sections/clients.rs

Lines changed: 2 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66

77
use std::ops::Deref;
88

9-
use anyhow::bail;
10-
use camino::Utf8PathBuf;
119
use mas_iana::oauth::OAuthClientAuthenticationMethod;
1210
use mas_jose::jwk::PublicJsonWebKeySet;
1311
use schemars::JsonSchema;
@@ -16,7 +14,7 @@ use serde_with::serde_as;
1614
use ulid::Ulid;
1715
use url::Url;
1816

19-
use super::ConfigurationSection;
17+
use super::{ConfigurationSection, ClientSecret, ClientSecretRaw};
2018

2119
#[derive(JsonSchema, Serialize, Deserialize, Clone, Debug)]
2220
#[serde(rename_all = "snake_case")]
@@ -31,66 +29,6 @@ impl From<PublicJsonWebKeySet> for JwksOrJwksUri {
3129
}
3230
}
3331

34-
/// Client secret config option.
35-
///
36-
/// It either holds the client secret value directly or references a file where
37-
/// the client secret is stored.
38-
#[derive(Clone, Debug)]
39-
pub enum ClientSecret {
40-
File(Utf8PathBuf),
41-
Value(String),
42-
}
43-
44-
/// Client secret fields as serialized in JSON.
45-
#[derive(JsonSchema, Serialize, Deserialize, Clone, Debug)]
46-
struct ClientSecretRaw {
47-
/// Path to the file containing the client secret. The client secret is used
48-
/// by the `client_secret_basic`, `client_secret_post` and
49-
/// `client_secret_jwt` authentication methods.
50-
#[schemars(with = "Option<String>")]
51-
#[serde(skip_serializing_if = "Option::is_none")]
52-
client_secret_file: Option<Utf8PathBuf>,
53-
54-
/// Alternative to `client_secret_file`: Reads the client secret directly
55-
/// from the config.
56-
#[serde(skip_serializing_if = "Option::is_none")]
57-
client_secret: Option<String>,
58-
}
59-
60-
impl TryFrom<ClientSecretRaw> for Option<ClientSecret> {
61-
type Error = anyhow::Error;
62-
63-
fn try_from(value: ClientSecretRaw) -> Result<Self, Self::Error> {
64-
match (value.client_secret, value.client_secret_file) {
65-
(None, None) => Ok(None),
66-
(None, Some(path)) => Ok(Some(ClientSecret::File(path))),
67-
(Some(client_secret), None) => Ok(Some(ClientSecret::Value(client_secret))),
68-
(Some(_), Some(_)) => {
69-
bail!("Cannot specify both `client_secret` and `client_secret_file`")
70-
}
71-
}
72-
}
73-
}
74-
75-
impl From<Option<ClientSecret>> for ClientSecretRaw {
76-
fn from(value: Option<ClientSecret>) -> Self {
77-
match value {
78-
Some(ClientSecret::File(path)) => ClientSecretRaw {
79-
client_secret_file: Some(path),
80-
client_secret: None,
81-
},
82-
Some(ClientSecret::Value(client_secret)) => ClientSecretRaw {
83-
client_secret_file: None,
84-
client_secret: Some(client_secret),
85-
},
86-
None => ClientSecretRaw {
87-
client_secret_file: None,
88-
client_secret: None,
89-
},
90-
}
91-
}
92-
}
93-
9432
/// Authentication method used by clients
9533
#[derive(JsonSchema, Serialize, Deserialize, Copy, Clone, Debug)]
9634
#[serde(rename_all = "snake_case")]
@@ -273,8 +211,7 @@ impl ClientConfig {
273211
/// Returns an error when the client secret could not be read from file.
274212
pub async fn client_secret(&self) -> anyhow::Result<Option<String>> {
275213
Ok(match &self.client_secret {
276-
Some(ClientSecret::File(path)) => Some(tokio::fs::read_to_string(path).await?),
277-
Some(ClientSecret::Value(client_secret)) => Some(client_secret.clone()),
214+
Some(client_secret) => Some(client_secret.value().await?),
278215
None => None,
279216
})
280217
}

crates/config/src/sections/mod.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
55
// Please see LICENSE files in the repository root for full details.
66

7+
use anyhow::bail;
8+
use camino::Utf8PathBuf;
79
use rand::Rng;
810
use schemars::JsonSchema;
911
use serde::{Deserialize, Serialize};
@@ -303,3 +305,82 @@ impl ConfigurationSection for SyncConfig {
303305
Ok(())
304306
}
305307
}
308+
309+
/// Client secret config option.
310+
///
311+
/// It either holds the client secret value directly or references a file where
312+
/// the client secret is stored.
313+
#[derive(Clone, Debug)]
314+
pub enum ClientSecret {
315+
/// Path to the file containing the client secret.
316+
File(Utf8PathBuf),
317+
318+
/// Client secret value.
319+
Value(String),
320+
}
321+
322+
/// Client secret fields as serialized in JSON.
323+
#[derive(JsonSchema, Serialize, Deserialize, Clone, Debug)]
324+
pub struct ClientSecretRaw {
325+
/// Path to the file containing the client secret. The client secret is used
326+
/// by the `client_secret_basic`, `client_secret_post` and
327+
/// `client_secret_jwt` authentication methods.
328+
#[schemars(with = "Option<String>")]
329+
#[serde(skip_serializing_if = "Option::is_none")]
330+
client_secret_file: Option<Utf8PathBuf>,
331+
332+
/// Alternative to `client_secret_file`: Reads the client secret directly
333+
/// from the config.
334+
#[serde(skip_serializing_if = "Option::is_none")]
335+
client_secret: Option<String>,
336+
}
337+
338+
impl ClientSecret {
339+
/// Returns the client secret.
340+
///
341+
/// If `client_secret_file` was given, the secret is read from that file.
342+
///
343+
/// # Errors
344+
///
345+
/// Returns an error when the client secret could not be read from file.
346+
pub async fn value(&self) -> anyhow::Result<String> {
347+
Ok(match self {
348+
ClientSecret::File(path) => tokio::fs::read_to_string(path).await?,
349+
ClientSecret::Value(client_secret) => client_secret.clone(),
350+
})
351+
}
352+
}
353+
354+
impl TryFrom<ClientSecretRaw> for Option<ClientSecret> {
355+
type Error = anyhow::Error;
356+
357+
fn try_from(value: ClientSecretRaw) -> Result<Self, Self::Error> {
358+
match (value.client_secret, value.client_secret_file) {
359+
(None, None) => Ok(None),
360+
(None, Some(path)) => Ok(Some(ClientSecret::File(path))),
361+
(Some(client_secret), None) => Ok(Some(ClientSecret::Value(client_secret))),
362+
(Some(_), Some(_)) => {
363+
bail!("Cannot specify both `client_secret` and `client_secret_file`")
364+
}
365+
}
366+
}
367+
}
368+
369+
impl From<Option<ClientSecret>> for ClientSecretRaw {
370+
fn from(value: Option<ClientSecret>) -> Self {
371+
match value {
372+
Some(ClientSecret::File(path)) => ClientSecretRaw {
373+
client_secret_file: Some(path),
374+
client_secret: None,
375+
},
376+
Some(ClientSecret::Value(client_secret)) => ClientSecretRaw {
377+
client_secret_file: None,
378+
client_secret: Some(client_secret),
379+
},
380+
None => ClientSecretRaw {
381+
client_secret_file: None,
382+
client_secret: None,
383+
},
384+
}
385+
}
386+
}

crates/config/src/sections/upstream_oauth2.rs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ use camino::Utf8PathBuf;
1010
use mas_iana::jose::JsonWebSignatureAlg;
1111
use schemars::JsonSchema;
1212
use serde::{Deserialize, Serialize, de::Error};
13-
use serde_with::skip_serializing_none;
13+
use serde_with::{serde_as, skip_serializing_none};
1414
use ulid::Ulid;
1515
use url::Url;
1616

17-
use crate::ConfigurationSection;
17+
use crate::{ConfigurationSection, ClientSecret, ClientSecretRaw};
1818

1919
/// Upstream OAuth 2.0 providers configuration
2020
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
@@ -475,6 +475,7 @@ impl OnBackchannelLogout {
475475
}
476476

477477
/// Configuration for one upstream OAuth 2 provider.
478+
#[serde_as]
478479
#[skip_serializing_none]
479480
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
480481
pub struct Provider {
@@ -541,8 +542,10 @@ pub struct Provider {
541542
///
542543
/// Used by the `client_secret_basic`, `client_secret_post`, and
543544
/// `client_secret_jwt` methods
544-
#[serde(skip_serializing_if = "Option::is_none")]
545-
pub client_secret: Option<String>,
545+
#[schemars(with = "ClientSecretRaw")]
546+
#[serde_as(as = "serde_with::TryFromInto<ClientSecretRaw>")]
547+
#[serde(flatten)]
548+
pub client_secret: Option<ClientSecret>,
546549

547550
/// The method to authenticate the client with the provider
548551
pub token_endpoint_auth_method: TokenAuthMethod,
@@ -656,3 +659,19 @@ pub struct Provider {
656659
#[serde(default, skip_serializing_if = "OnBackchannelLogout::is_default")]
657660
pub on_backchannel_logout: OnBackchannelLogout,
658661
}
662+
663+
impl Provider {
664+
/// Returns the client secret.
665+
///
666+
/// If `client_secret_file` was given, the secret is read from that file.
667+
///
668+
/// # Errors
669+
///
670+
/// Returns an error when the client secret could not be read from file.
671+
pub async fn client_secret(&self) -> anyhow::Result<Option<String>> {
672+
Ok(match &self.client_secret {
673+
Some(client_secret) => Some(client_secret.value().await?),
674+
None => None,
675+
})
676+
}
677+
}

crates/syn2mas/src/synapse_reader/config/oidc.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use chrono::{DateTime, Utc};
99
use mas_config::{
1010
UpstreamOAuth2ClaimsImports, UpstreamOAuth2DiscoveryMode, UpstreamOAuth2ImportAction,
1111
UpstreamOAuth2OnBackchannelLogout, UpstreamOAuth2PkceMethod, UpstreamOAuth2ResponseMode,
12-
UpstreamOAuth2TokenAuthMethod,
12+
UpstreamOAuth2TokenAuthMethod, ClientSecret,
1313
};
1414
use mas_iana::jose::JsonWebSignatureAlg;
1515
use oauth2_types::scope::{OPENID, Scope, ScopeToken};
@@ -328,7 +328,7 @@ impl OidcProvider {
328328
human_name: self.idp_name,
329329
brand_name: self.idp_brand,
330330
client_id,
331-
client_secret: self.client_secret,
331+
client_secret: self.client_secret.map(ClientSecret::Value),
332332
token_endpoint_auth_method,
333333
sign_in_with_apple: None,
334334
token_endpoint_auth_signing_alg: None,

0 commit comments

Comments
 (0)