shield_oidc/
subprovider.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
use bon::Builder;
use openidconnect::{
    core::{
        CoreClient, CoreJsonWebKey, CoreJsonWebKeyType, CoreJsonWebKeyUse, CoreJwsSigningAlgorithm,
        CoreProviderMetadata,
    },
    reqwest::async_http_client,
    AuthUrl, ClientId, ClientSecret, IssuerUrl, JsonWebKeySet, RedirectUrl, TokenUrl, UserInfoUrl,
};
use shield::{ConfigurationError, Subprovider};

use crate::provider::OIDC_PROVIDER_ID;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum OidcProviderVisibility {
    Public,
    Unlisted,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum OidcProviderPkceCodeChallenge {
    None,
    Plain,
    S256,
}

#[derive(Builder, Clone, Debug)]
#[builder(on(String, into), state_mod(vis = "pub(crate)"))]
pub struct OidcSubprovider {
    pub id: String,
    pub name: String,
    pub slug: Option<String>,
    #[builder(default = OidcProviderVisibility::Public)]
    pub visibility: OidcProviderVisibility,
    pub client_id: String,
    pub client_secret: Option<String>,
    pub scopes: Option<Vec<String>>,
    pub redirect_url: Option<String>,
    pub discovery_url: Option<String>,
    pub issuer_url: Option<String>,
    pub authorization_url: Option<String>,
    pub authorization_url_params: Option<String>,
    pub token_url: Option<String>,
    pub token_url_params: Option<String>,
    pub introspection_url: Option<String>,
    pub introspection_url_params: Option<String>,
    pub revocation_url: Option<String>,
    pub revocation_url_params: Option<String>,
    pub user_info_url: Option<String>,
    pub json_web_key_set_url: Option<String>,
    pub json_web_key_set: Option<
        JsonWebKeySet<
            CoreJwsSigningAlgorithm,
            CoreJsonWebKeyType,
            CoreJsonWebKeyUse,
            CoreJsonWebKey,
        >,
    >,
    #[builder(default = OidcProviderPkceCodeChallenge::S256)]
    pub pkce_code_challenge: OidcProviderPkceCodeChallenge,
}

impl OidcSubprovider {
    pub async fn oidc_client(&self) -> Result<CoreClient, ConfigurationError> {
        let mut client = if let Some(discovery_url) = &self.discovery_url {
            let provider_metadata = CoreProviderMetadata::discover_async(
                // TODO: Consider stripping `/.well-known/openid-configuration` so `openidconnect` doesn't error.
                IssuerUrl::new(discovery_url.clone())
                    .map_err(|err| ConfigurationError::Invalid(err.to_string()))?,
                async_http_client,
            )
            .await
            .map_err(|err| ConfigurationError::Invalid(err.to_string()))?;

            CoreClient::from_provider_metadata(
                provider_metadata,
                ClientId::new(self.client_id.clone()),
                self.client_secret.clone().map(ClientSecret::new),
            )
        } else {
            CoreClient::new(
                ClientId::new(self.client_id.clone()),
                self.client_secret.clone().map(ClientSecret::new),
                IssuerUrl::new(
                    self.issuer_url
                        .clone()
                        .ok_or(ConfigurationError::Missing("issuer URL".to_owned()))?,
                )
                .map_err(|err| ConfigurationError::Invalid(err.to_string()))?,
                self.authorization_url
                    .as_ref()
                    .ok_or(ConfigurationError::Missing("authorization URL".to_owned()))
                    .and_then(|authorization_url| {
                        AuthUrl::new(authorization_url.clone())
                            .map_err(|err| ConfigurationError::Invalid(err.to_string()))
                    })?,
                match &self.token_url {
                    Some(token_url) => Some(
                        TokenUrl::new(token_url.clone())
                            .map_err(|err| ConfigurationError::Invalid(err.to_string()))?,
                    ),
                    None => None,
                },
                match &self.user_info_url {
                    Some(user_info_url) => Some(
                        UserInfoUrl::new(user_info_url.clone())
                            .map_err(|err| ConfigurationError::Invalid(err.to_string()))?,
                    ),
                    None => None,
                },
                self.json_web_key_set
                    .clone()
                    .ok_or(ConfigurationError::Missing("JSON Web Key Set".to_owned()))?,
            )
        };

        if let Some(redirect_url) = &self.redirect_url {
            client = client.set_redirect_uri(
                RedirectUrl::new(redirect_url.clone())
                    .map_err(|err| ConfigurationError::Invalid(err.to_string()))?,
            );
        }

        // TODO: Common client options.

        Ok(client)
    }
}

impl Subprovider for OidcSubprovider {
    fn provider_id(&self) -> String {
        OIDC_PROVIDER_ID.to_owned()
    }

    fn id(&self) -> Option<String> {
        Some(self.id.clone())
    }

    fn name(&self) -> String {
        self.name.clone()
    }

    fn form(&self) -> Option<shield::Form> {
        None
    }
}