graph_oauth/identity/credentials/
client_credentials_authorization_url.rs

1use reqwest::IntoUrl;
2
3use url::Url;
4use uuid::Uuid;
5
6use graph_error::{AuthorizationFailure, IdentityResult};
7
8use crate::identity::{credentials::app_config::AppConfig, Authority, AzureCloudInstance};
9use crate::oauth_serializer::{AuthParameter, AuthSerializer};
10use crate::{ClientAssertionCredentialBuilder, ClientSecretCredentialBuilder};
11
12#[cfg(feature = "openssl")]
13use crate::identity::{ClientCertificateCredentialBuilder, X509Certificate};
14
15#[derive(Default, Debug, Clone, Serialize, Deserialize)]
16pub struct ClientCredentialAdminConsentResponse {
17    pub admin_consent: bool,
18    pub tenant: String,
19}
20
21#[derive(Clone)]
22pub struct ClientCredentialsAuthorizationUrlParameters {
23    /// The client (application) ID of the service principal
24    pub(crate) app_config: AppConfig,
25    pub(crate) state: Option<String>,
26}
27
28impl ClientCredentialsAuthorizationUrlParameters {
29    pub fn new(
30        client_id: impl AsRef<str>,
31        redirect_uri: impl IntoUrl,
32    ) -> IdentityResult<ClientCredentialsAuthorizationUrlParameters> {
33        let redirect_uri_result = Url::parse(redirect_uri.as_str());
34        let redirect_uri = redirect_uri.into_url().or(redirect_uri_result)?;
35
36        Ok(ClientCredentialsAuthorizationUrlParameters {
37            app_config: AppConfig::builder(client_id.as_ref())
38                .redirect_uri(redirect_uri)
39                .build(),
40            state: None,
41        })
42    }
43
44    pub fn builder<T: AsRef<str>>(
45        client_id: T,
46    ) -> ClientCredentialsAuthorizationUrlParameterBuilder {
47        ClientCredentialsAuthorizationUrlParameterBuilder::new(client_id)
48    }
49
50    pub fn with_client_secret(
51        self,
52        client_secret: impl AsRef<str>,
53    ) -> ClientSecretCredentialBuilder {
54        ClientSecretCredentialBuilder::new_with_client_secret(client_secret, self.app_config)
55    }
56
57    pub fn with_client_assertion(
58        self,
59        signed_assertion: impl AsRef<str>,
60    ) -> ClientAssertionCredentialBuilder {
61        ClientAssertionCredentialBuilder::new_with_signed_assertion(
62            signed_assertion,
63            self.app_config,
64        )
65    }
66
67    #[cfg(feature = "openssl")]
68    pub fn with_client_x509_certificate(
69        self,
70        _client_secret: impl AsRef<str>,
71        x509: &X509Certificate,
72    ) -> IdentityResult<ClientCertificateCredentialBuilder> {
73        ClientCertificateCredentialBuilder::new_with_certificate(x509, self.app_config)
74    }
75
76    pub fn url(&self) -> IdentityResult<Url> {
77        self.url_with_host(&self.app_config.azure_cloud_instance)
78    }
79
80    pub fn url_with_host(&self, azure_cloud_instance: &AzureCloudInstance) -> IdentityResult<Url> {
81        let mut serializer = AuthSerializer::new();
82        let client_id = self.app_config.client_id.to_string();
83        if client_id.trim().is_empty() || self.app_config.client_id.is_nil() {
84            return AuthorizationFailure::result(AuthParameter::ClientId.alias());
85        }
86
87        if self.app_config.redirect_uri.is_none() {
88            return AuthorizationFailure::result(AuthParameter::RedirectUri.alias());
89        }
90
91        if let Some(redirect_uri) = self.app_config.redirect_uri.as_ref() {
92            serializer.redirect_uri(redirect_uri.as_str());
93        }
94
95        serializer.client_id(client_id.as_str());
96
97        if let Some(state) = self.state.as_ref() {
98            serializer.state(state.as_ref());
99        }
100
101        let mut uri = azure_cloud_instance.admin_consent_uri(&self.app_config.authority)?;
102        let query = serializer.encode_query(
103            vec![AuthParameter::State],
104            vec![AuthParameter::ClientId, AuthParameter::RedirectUri],
105        )?;
106        uri.set_query(Some(query.as_str()));
107        Ok(uri)
108    }
109}
110
111#[derive(Clone)]
112pub struct ClientCredentialsAuthorizationUrlParameterBuilder {
113    credential: ClientCredentialsAuthorizationUrlParameters,
114}
115
116impl ClientCredentialsAuthorizationUrlParameterBuilder {
117    pub fn new(client_id: impl AsRef<str>) -> Self {
118        Self {
119            credential: ClientCredentialsAuthorizationUrlParameters {
120                app_config: AppConfig::new(client_id.as_ref()),
121                state: None,
122            },
123        }
124    }
125
126    pub(crate) fn new_with_app_config(app_config: AppConfig) -> Self {
127        Self {
128            credential: ClientCredentialsAuthorizationUrlParameters {
129                app_config,
130                state: None,
131            },
132        }
133    }
134
135    pub fn with_client_id<T: AsRef<str>>(&mut self, client_id: T) -> IdentityResult<&mut Self> {
136        self.credential.app_config.client_id = Uuid::try_parse(client_id.as_ref())?;
137        Ok(self)
138    }
139
140    pub fn with_redirect_uri(&mut self, redirect_uri: Url) -> &mut Self {
141        self.credential.app_config.redirect_uri = Some(redirect_uri);
142        self
143    }
144
145    /// Convenience method. Same as calling [with_authority(Authority::TenantId("tenant_id"))]
146    pub fn with_tenant<T: AsRef<str>>(&mut self, tenant: T) -> &mut Self {
147        self.credential.app_config.authority = Authority::TenantId(tenant.as_ref().to_owned());
148        self
149    }
150
151    pub fn with_authority<T: Into<Authority>>(&mut self, authority: T) -> &mut Self {
152        self.credential.app_config.authority = authority.into();
153        self
154    }
155
156    pub fn with_state<T: AsRef<str>>(&mut self, state: T) -> &mut Self {
157        self.credential.state = Some(state.as_ref().to_owned());
158        self
159    }
160
161    pub fn build(&self) -> ClientCredentialsAuthorizationUrlParameters {
162        self.credential.clone()
163    }
164
165    pub fn url(&self) -> IdentityResult<Url> {
166        self.credential.url()
167    }
168
169    pub fn with_client_secret(
170        self,
171        client_secret: impl AsRef<str>,
172    ) -> ClientSecretCredentialBuilder {
173        ClientSecretCredentialBuilder::new_with_client_secret(
174            client_secret,
175            self.credential.app_config,
176        )
177    }
178
179    pub fn with_client_assertion(
180        self,
181        signed_assertion: impl AsRef<str>,
182    ) -> ClientAssertionCredentialBuilder {
183        ClientAssertionCredentialBuilder::new_with_signed_assertion(
184            signed_assertion,
185            self.credential.app_config,
186        )
187    }
188
189    #[cfg(feature = "openssl")]
190    pub fn with_client_x509_certificate(
191        self,
192        _client_secret: impl AsRef<str>,
193        x509: &X509Certificate,
194    ) -> IdentityResult<ClientCertificateCredentialBuilder> {
195        ClientCertificateCredentialBuilder::new_with_certificate(x509, self.credential.app_config)
196    }
197}