drogue_bazaar/auth/openid/
config.rs

1use crate::{core::config::CommaSeparatedVec, reqwest::ClientFactory};
2use anyhow::Context;
3use core::fmt::Debug;
4use drogue_client::openid::OpenIdTokenProvider;
5use serde::Deserialize;
6use std::time::Duration;
7use url::Url;
8
9/// All required configuration when authentication is enabled.
10#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
11pub struct TokenConfig {
12    pub client_id: String,
13
14    pub client_secret: String,
15
16    pub issuer_url: Url,
17
18    #[serde(default)]
19    pub tls_insecure: bool,
20
21    #[serde(default)]
22    pub tls_ca_certificates: CommaSeparatedVec,
23
24    #[serde(default)]
25    #[serde(with = "humantime_serde")]
26    pub refresh_before: Option<Duration>,
27}
28
29impl TokenConfig {
30    pub async fn into_client(self, redirect: Option<String>) -> anyhow::Result<openid::Client> {
31        let mut client = ClientFactory::new();
32        client = client.add_ca_certs(self.tls_ca_certificates.0);
33
34        if self.tls_insecure {
35            client = client.make_insecure();
36        }
37
38        openid::Client::discover_with_client(
39            client.build()?,
40            self.client_id,
41            self.client_secret,
42            redirect,
43            self.issuer_url,
44        )
45        .await
46        .context("Discovering endpoint")
47    }
48
49    /// Create a new provider by discovering the OAuth2 client from the configuration
50    pub async fn discover_from(self) -> anyhow::Result<OpenIdTokenProvider> {
51        let refresh_before = self
52            .refresh_before
53            .and_then(|d| chrono::Duration::from_std(d).ok())
54            .unwrap_or_else(|| chrono::Duration::seconds(15));
55
56        Ok(OpenIdTokenProvider::new(
57            self.into_client(None).await?,
58            refresh_before,
59        ))
60    }
61}
62
63#[cfg(test)]
64mod test {
65    use super::*;
66    use crate::core::config::ConfigFromEnv;
67    use std::collections::HashMap;
68
69    #[test]
70    fn test_ca_certs() {
71        let mut envs = HashMap::new();
72
73        envs.insert("CLIENT_ID", "id");
74        envs.insert("CLIENT_SECRET", "secret");
75        envs.insert("ISSUER_URL", "http://foo.bar/baz/buz");
76        envs.insert("REALM", "drogue");
77        envs.insert("TLS_CA_CERTIFICATES", "/foo/bar/baz");
78
79        let config = TokenConfig::from_set(envs).unwrap();
80
81        assert_eq!(
82            TokenConfig {
83                client_id: "id".to_string(),
84                client_secret: "secret".to_string(),
85                issuer_url: Url::parse("http://foo.bar/baz/buz").unwrap(),
86                refresh_before: None,
87                tls_insecure: false,
88                tls_ca_certificates: vec!["/foo/bar/baz".to_string()].into(),
89            },
90            config
91        );
92    }
93
94    #[test]
95    fn test_ca_certs_multi() {
96        let mut envs = HashMap::new();
97
98        envs.insert("CLIENT_ID", "id");
99        envs.insert("CLIENT_SECRET", "secret");
100        envs.insert("ISSUER_URL", "http://foo.bar/baz/buz");
101        envs.insert("REALM", "drogue");
102        envs.insert("TLS_CA_CERTIFICATES", "/foo/bar/baz,/foo/bar/baz2");
103
104        let config = TokenConfig::from_set(envs).unwrap();
105
106        assert_eq!(
107            TokenConfig {
108                client_id: "id".to_string(),
109                client_secret: "secret".to_string(),
110                issuer_url: Url::parse("http://foo.bar/baz/buz").unwrap(),
111                refresh_before: None,
112                tls_insecure: false,
113                tls_ca_certificates: vec!["/foo/bar/baz".to_string(), "/foo/bar/baz2".to_string()]
114                    .into(),
115            },
116            config
117        );
118    }
119}