oauth: token route: client_id is not required if basic auth is present

This commit is contained in:
Philippe Loctaux 2023-10-24 09:18:07 +02:00
parent 3d79fc1817
commit 2e5a1b0c42
2 changed files with 51 additions and 24 deletions

View file

@ -38,7 +38,7 @@ pub struct TokenRequest<'r> {
pub grant_type: GrantType, pub grant_type: GrantType,
pub code: Option<&'r str>, pub code: Option<&'r str>,
pub redirect_uri: 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 client_secret: Option<&'r str>,
pub scope: Option<&'r str>, pub scope: Option<&'r str>,
pub refresh_token: Option<&'r str>, pub refresh_token: Option<&'r str>,

View file

@ -51,12 +51,14 @@ pub enum TokenError {
RefreshTokenExpired, RefreshTokenExpired,
AuthorizationCodeUsed, AuthorizationCodeUsed,
AuthorizationCodeExpired, AuthorizationCodeExpired,
HttpAuthDifferentClientId,
AppError(apps::Error), AppError(apps::Error),
AppNotFound(String), AppNotFoundFromAuthorizationCode(String),
AppNotFoundFromRefreshToken(String),
AppIdNotProvided,
AppSecretNotProvided, AppSecretNotProvided,
Blocking(task::JoinError), Blocking(task::JoinError),
SecretCompare(hash::Error), SecretCompare(hash::Error),
AppIdWrong,
AppSecretWrong, AppSecretWrong,
UserError(users::Error), UserError(users::Error),
UserNotFound, UserNotFound,
@ -111,14 +113,19 @@ impl<'r> Responder<'r, 'static> for TokenError {
Status::BadRequest, Status::BadRequest,
"Authorization code has expired".to_string(), "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::AppError(e) => (Status::InternalServerError, e.to_string()),
TokenError::AppNotFound(e) => { TokenError::AppNotFoundFromAuthorizationCode(e) => (
(Status::NotFound, format!("Could not find application {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 => { TokenError::AppSecretNotProvided => {
(Status::BadRequest, "Secret was not provided".to_string()) (Status::BadRequest, "Secret was not provided".to_string())
} }
@ -127,6 +134,7 @@ impl<'r> Responder<'r, 'static> for TokenError {
Status::InternalServerError, Status::InternalServerError,
format!("Failed to check app secret: {e}"), format!("Failed to check app secret: {e}"),
), ),
TokenError::AppIdWrong => (Status::Forbidden, "Invalid client_id provided".to_string()),
TokenError::AppSecretWrong => { TokenError::AppSecretWrong => {
(Status::Forbidden, "Invalid secret provided".to_string()) (Status::Forbidden, "Invalid secret provided".to_string())
} }
@ -171,8 +179,8 @@ pub async fn request_token(
) -> std::result::Result<TokenResponse, TokenError> { ) -> std::result::Result<TokenResponse, TokenError> {
let mut transaction = db.begin().await.map_err(TokenError::TransactionStart)?; let mut transaction = db.begin().await.map_err(TokenError::TransactionStart)?;
// Get user depending on grant type // Get user and app depending on grant type
let user = match token_request.grant_type { let (user, app) = match token_request.grant_type {
GrantType::AuthorizationCode => { GrantType::AuthorizationCode => {
let authorization_code = token_request let authorization_code = token_request
.code .code
@ -220,12 +228,20 @@ pub async fn request_token(
return Err(TokenError::UserArchived(user.id().to_string())); 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 // Mark code as used
code.use_code(&mut transaction) code.use_code(&mut transaction)
.await .await
.map_err(TokenError::AuthorizationError)?; .map_err(TokenError::AuthorizationError)?;
user (user, app)
} }
GrantType::RefreshToken => { GrantType::RefreshToken => {
let refresh_token = token_request let refresh_token = token_request
@ -264,27 +280,38 @@ pub async fn request_token(
return Err(TokenError::RefreshTokenExpired); 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 refresh_token
.use_token(&mut transaction) .use_token(&mut transaction)
.await .await
.map_err(TokenError::RefreshTokenError)?; .map_err(TokenError::RefreshTokenError)?;
user (user, app)
} }
}; };
// If HTTP Basic Auth is provided, verify provided client id in form // Get client id
if let Some(app_auth) = &app_auth { // https://www.rfc-editor.org/rfc/rfc6749#section-4.1.3
if app_auth.id != token_request.client_id { let provided_client_id = match (&app_auth, token_request.client_id) {
return Err(TokenError::HttpAuthDifferentClientId); (Some(http_auth), _) => http_auth.id.to_string(),
} (None, Some(form)) => form.into(),
(None, None) => {
return Err(TokenError::AppIdNotProvided);
} }
};
// Get app // Verify client id
let app = App::get_one_by_id(&mut transaction, token_request.client_id) if app.id().as_ref() != provided_client_id {
.await return Err(TokenError::AppIdWrong);
.map_err(TokenError::AppError)? }
.ok_or(TokenError::AppNotFound(token_request.client_id.into()))?;
if app.is_confidential() { if app.is_confidential() {
let provided_secret = match (app_auth, token_request.client_secret) { let provided_secret = match (app_auth, token_request.client_secret) {