From 2e5a1b0c42adb9890b4126908b3f376cea070fdb Mon Sep 17 00:00:00 2001 From: Philippe Loctaux Date: Tue, 24 Oct 2023 09:18:07 +0200 Subject: [PATCH] oauth: token route: client_id is not required if basic auth is present --- crates/ezidam/src/oauth.rs | 2 +- crates/ezidam/src/routes/oauth/token.rs | 73 +++++++++++++++++-------- 2 files changed, 51 insertions(+), 24 deletions(-) diff --git a/crates/ezidam/src/oauth.rs b/crates/ezidam/src/oauth.rs index 1230838..ef4c8ba 100644 --- a/crates/ezidam/src/oauth.rs +++ b/crates/ezidam/src/oauth.rs @@ -38,7 +38,7 @@ pub struct TokenRequest<'r> { pub grant_type: GrantType, pub code: Option<&'r str>, pub redirect_uri: Option<&'r str>, - pub client_id: &'r str, + pub client_id: Option<&'r str>, pub client_secret: Option<&'r str>, pub scope: Option<&'r str>, pub refresh_token: Option<&'r str>, diff --git a/crates/ezidam/src/routes/oauth/token.rs b/crates/ezidam/src/routes/oauth/token.rs index a96485d..ffb8a5d 100644 --- a/crates/ezidam/src/routes/oauth/token.rs +++ b/crates/ezidam/src/routes/oauth/token.rs @@ -51,12 +51,14 @@ pub enum TokenError { RefreshTokenExpired, AuthorizationCodeUsed, AuthorizationCodeExpired, - HttpAuthDifferentClientId, AppError(apps::Error), - AppNotFound(String), + AppNotFoundFromAuthorizationCode(String), + AppNotFoundFromRefreshToken(String), + AppIdNotProvided, AppSecretNotProvided, Blocking(task::JoinError), SecretCompare(hash::Error), + AppIdWrong, AppSecretWrong, UserError(users::Error), UserNotFound, @@ -111,14 +113,19 @@ impl<'r> Responder<'r, 'static> for TokenError { Status::BadRequest, "Authorization code has expired".to_string(), ), - TokenError::HttpAuthDifferentClientId => ( - Status::BadRequest, - "HTTP Auth differs from provided client_id".to_string(), - ), TokenError::AppError(e) => (Status::InternalServerError, e.to_string()), - TokenError::AppNotFound(e) => { - (Status::NotFound, format!("Could not find application {e}")) - } + TokenError::AppNotFoundFromAuthorizationCode(e) => ( + Status::NotFound, + format!("Could not find application from authorization code {e}"), + ), + TokenError::AppNotFoundFromRefreshToken(e) => ( + Status::NotFound, + format!("Could not find application from refresh token {e}"), + ), + TokenError::AppIdNotProvided => ( + Status::BadRequest, + "Could not get client_id: not provided in any way".to_string(), + ), TokenError::AppSecretNotProvided => { (Status::BadRequest, "Secret was not provided".to_string()) } @@ -127,6 +134,7 @@ impl<'r> Responder<'r, 'static> for TokenError { Status::InternalServerError, format!("Failed to check app secret: {e}"), ), + TokenError::AppIdWrong => (Status::Forbidden, "Invalid client_id provided".to_string()), TokenError::AppSecretWrong => { (Status::Forbidden, "Invalid secret provided".to_string()) } @@ -171,8 +179,8 @@ pub async fn request_token( ) -> std::result::Result { let mut transaction = db.begin().await.map_err(TokenError::TransactionStart)?; - // Get user depending on grant type - let user = match token_request.grant_type { + // Get user and app depending on grant type + let (user, app) = match token_request.grant_type { GrantType::AuthorizationCode => { let authorization_code = token_request .code @@ -220,12 +228,20 @@ pub async fn request_token( return Err(TokenError::UserArchived(user.id().to_string())); } + // Get app from code + let app = App::get_one_from_authorization_code(&mut transaction, authorization_code) + .await + .map_err(TokenError::AppError)? + .ok_or(TokenError::AppNotFoundFromAuthorizationCode( + authorization_code.into(), + ))?; + // Mark code as used code.use_code(&mut transaction) .await .map_err(TokenError::AuthorizationError)?; - user + (user, app) } GrantType::RefreshToken => { let refresh_token = token_request @@ -264,27 +280,38 @@ pub async fn request_token( return Err(TokenError::RefreshTokenExpired); } + // Get app + let app = App::get_one_by_id(&mut transaction, refresh_token.app().as_ref()) + .await + .map_err(TokenError::AppError)? + .ok_or(TokenError::AppNotFoundFromRefreshToken(format!( + "Refresh token for user {}", + refresh_token.user() + )))?; + refresh_token .use_token(&mut transaction) .await .map_err(TokenError::RefreshTokenError)?; - user + (user, app) } }; - // If HTTP Basic Auth is provided, verify provided client id in form - if let Some(app_auth) = &app_auth { - if app_auth.id != token_request.client_id { - return Err(TokenError::HttpAuthDifferentClientId); + // Get client id + // https://www.rfc-editor.org/rfc/rfc6749#section-4.1.3 + let provided_client_id = match (&app_auth, token_request.client_id) { + (Some(http_auth), _) => http_auth.id.to_string(), + (None, Some(form)) => form.into(), + (None, None) => { + return Err(TokenError::AppIdNotProvided); } - } + }; - // Get app - let app = App::get_one_by_id(&mut transaction, token_request.client_id) - .await - .map_err(TokenError::AppError)? - .ok_or(TokenError::AppNotFound(token_request.client_id.into()))?; + // Verify client id + if app.id().as_ref() != provided_client_id { + return Err(TokenError::AppIdWrong); + } if app.is_confidential() { let provided_secret = match (app_auth, token_request.client_secret) {