raos/authorize/validate.rs
1use url::Url;
2
3use crate::common::frontend::{OAuthError, OAuthValidationError};
4use crate::common::model::Client;
5use crate::common::model::CodeChallenge;
6use crate::{
7 authorize::{AuthorizationRequest, ResponseType},
8 manager::OAuthManager,
9};
10
11/// A validated authorization request from a client.
12/// This struct contains all the information needed to authorize a client's request.
13/// This struct is produced by validating an [AuthorizationRequest] from a client.
14#[derive(Debug)]
15pub struct ValidatedAuthorizationRequest {
16 /// The response type expected in the request.
17 pub response_type: ResponseType,
18 /// The client making the request, including all its information obtained from the [ClientProvider](crate::common::ClientProvider).
19 pub client: Client,
20 /// The code challenge and method used in the request.
21 pub code_challenge: CodeChallenge,
22 /// The redirect URI the client expects to be redirected to, or the only one the client has registered.
23 pub redirect_uri: Url,
24 /// The scopes requested by the client, after being filtered by the [ClientProvider](crate::common::ClientProvider).
25 pub scopes: Vec<String>,
26 /// The state of the request to be sent back to the client in the response.
27 pub state: Option<String>,
28}
29
30impl<U: 'static, E: 'static, Ex> OAuthManager<U, E, Ex> {
31 /// Validate an incoming authorization request from a client.
32 /// This function will validate the incoming request, and then return a [ValidatedAuthorizationRequest]
33 /// that contains the information needed to authorize the request.
34 ///
35 /// # Parameters
36 /// - `req` - The parsed incoming request from the client, represented by an [AuthorizationRequest]
37 ///
38 /// # Returns
39 /// A [ValidatedAuthorizationRequest] that contains the information needed to authorize the request.
40 ///
41 /// # Errors
42 /// This function can return an [OAuthError] if the request is invalid,
43 /// or if the [AuthorizationProvider](crate::authorize::AuthorizationProvider) or [ClientProvider](crate::common::ClientProvider) return an error.
44 ///
45 /// # Example
46 /// ```
47 /// # use raos::{
48 /// # authorize::{AuthorizationRequest, ResponseType},
49 /// # common::model::CodeChallenge,
50 /// # test::doctest::oauth_manager_from_application_state
51 /// # };
52 ///
53 /// let manager = oauth_manager_from_application_state();
54 /// let req = AuthorizationRequest {
55 /// response_type: ResponseType::Code,
56 /// client_id: "CLIENT_ID".to_string(),
57 /// code_challenge: CodeChallenge::Plain { code_challenge: "CODE_CHALLENGE".to_string() },
58 /// redirect_uri: Some("https://example.com".to_string()),
59 /// scope: Some("SCOPE".to_string()),
60 /// state: Some("STATE".to_string()),
61 /// };
62 ///
63 /// # tokio_test::block_on(async {
64 /// let result = manager.validate_authorization_request(req).await;
65 /// assert!(result.is_ok());
66 /// # });
67 /// ```
68 pub async fn validate_authorization_request(
69 &self,
70 req: AuthorizationRequest,
71 ) -> Result<ValidatedAuthorizationRequest, OAuthError<E>> {
72 let Some(client) = self
73 .client_provider
74 .get_client_by_id(&req.client_id)
75 .await
76 .map_err(OAuthError::ProviderImplementationError)?
77 else {
78 return Err(OAuthValidationError::ClientDoesNotExist.into());
79 };
80 if !client.is_valid() {
81 return Err(OAuthValidationError::InvalidClient.into());
82 }
83
84 if matches!(req.code_challenge, CodeChallenge::None)
85 && self.config.require_code_challenge.require_code_challenge(&client)
86 {
87 return Err(OAuthValidationError::CodeChallengeRequired.into());
88 }
89 if self.config.disallow_plain_code_challenge
90 && matches!(req.code_challenge, CodeChallenge::Plain { .. })
91 {
92 return Err(OAuthValidationError::CodeChallengeRequired.into());
93 }
94
95 let redirect_uri = if let Some(redirect_uri) = req.redirect_uri {
96 if client.has_redirect_uri(&redirect_uri) {
97 redirect_uri
98 } else {
99 return Err(OAuthValidationError::UnknownRedirectUri.into());
100 }
101 } else if client.redirect_uris.len() == 1 {
102 client
103 .redirect_uris
104 .first()
105 .expect("Unexpected error: .first() is None after .len() == 1 check")
106 .to_owned()
107 } else {
108 return Err(OAuthValidationError::NoRedirectUri.into());
109 };
110 let redirect_uri: Url =
111 redirect_uri.parse().map_err(|_| OAuthValidationError::InvalidRedirectUri)?;
112 if redirect_uri.fragment().is_some() {
113 return Err(OAuthValidationError::InvalidRedirectUri.into());
114 }
115
116 let scopes = if let Some(scope) = req.scope {
117 scope.split(' ').map(str::to_string).collect()
118 } else {
119 Vec::new()
120 };
121 let scopes = self
122 .client_provider
123 .allow_client_scopes(&client, scopes)
124 .await
125 .map_err(OAuthError::ProviderImplementationError)?;
126 if scopes.is_empty() {
127 return Err(OAuthValidationError::NoScopesProvided.into());
128 }
129
130 Ok(ValidatedAuthorizationRequest {
131 client,
132 redirect_uri,
133 scopes,
134 response_type: req.response_type,
135 code_challenge: req.code_challenge,
136 state: req.state,
137 })
138 }
139}