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}