Knossos Oauth 2 Flow Changes (#752)
* adjust type and response format * Replace Found with Ok for handled redirects * scope parse fix * change apps query from body to query * adjust tests for new response type * remove unused imports * Clippy fixes
This commit is contained in:
parent
a818199b5a
commit
97ccb7df94
@ -2,7 +2,7 @@ use super::ValidatedRedirectUri;
|
|||||||
use crate::auth::AuthenticationError;
|
use crate::auth::AuthenticationError;
|
||||||
use crate::models::error::ApiError;
|
use crate::models::error::ApiError;
|
||||||
use crate::models::ids::DecodingError;
|
use crate::models::ids::DecodingError;
|
||||||
use actix_web::http::StatusCode;
|
use actix_web::http::{header::LOCATION, StatusCode};
|
||||||
use actix_web::HttpResponse;
|
use actix_web::HttpResponse;
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
@ -63,7 +63,7 @@ impl actix_web::ResponseError for OAuthError {
|
|||||||
| OAuthErrorType::ScopesTooBroad
|
| OAuthErrorType::ScopesTooBroad
|
||||||
| OAuthErrorType::AccessDenied => {
|
| OAuthErrorType::AccessDenied => {
|
||||||
if self.valid_redirect_uri.is_some() {
|
if self.valid_redirect_uri.is_some() {
|
||||||
StatusCode::FOUND
|
StatusCode::OK
|
||||||
} else {
|
} else {
|
||||||
StatusCode::INTERNAL_SERVER_ERROR
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
}
|
}
|
||||||
@ -94,10 +94,9 @@ impl actix_web::ResponseError for OAuthError {
|
|||||||
redirect_uri = format!("{}&state={}", redirect_uri, state);
|
redirect_uri = format!("{}&state={}", redirect_uri, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
redirect_uri = urlencoding::encode(&redirect_uri).to_string();
|
HttpResponse::Ok()
|
||||||
HttpResponse::Found()
|
.append_header((LOCATION, redirect_uri.clone()))
|
||||||
.append_header(("Location".to_string(), redirect_uri))
|
.body(redirect_uri)
|
||||||
.finish()
|
|
||||||
} else {
|
} else {
|
||||||
HttpResponse::build(self.status_code()).json(ApiError {
|
HttpResponse::build(self.status_code()).json(ApiError {
|
||||||
error: &self.error_type.error_name(),
|
error: &self.error_type.error_name(),
|
||||||
|
|||||||
@ -7,19 +7,21 @@ use crate::database::models::oauth_client_item::OAuthClient as DBOAuthClient;
|
|||||||
use crate::database::models::oauth_token_item::OAuthAccessToken;
|
use crate::database::models::oauth_token_item::OAuthAccessToken;
|
||||||
use crate::database::models::{
|
use crate::database::models::{
|
||||||
generate_oauth_access_token_id, generate_oauth_client_authorization_id,
|
generate_oauth_access_token_id, generate_oauth_client_authorization_id,
|
||||||
OAuthClientAuthorizationId, OAuthClientId,
|
OAuthClientAuthorizationId,
|
||||||
};
|
};
|
||||||
use crate::database::redis::RedisPool;
|
use crate::database::redis::RedisPool;
|
||||||
use crate::models;
|
use crate::models;
|
||||||
|
use crate::models::ids::OAuthClientId;
|
||||||
use crate::models::pats::Scopes;
|
use crate::models::pats::Scopes;
|
||||||
use crate::queue::session::AuthQueue;
|
use crate::queue::session::AuthQueue;
|
||||||
|
use actix_web::http::header::LOCATION;
|
||||||
use actix_web::web::{scope, Data, Query, ServiceConfig};
|
use actix_web::web::{scope, Data, Query, ServiceConfig};
|
||||||
use actix_web::{get, post, web, HttpRequest, HttpResponse};
|
use actix_web::{get, post, web, HttpRequest, HttpResponse};
|
||||||
use chrono::Duration;
|
use chrono::Duration;
|
||||||
use rand::distributions::Alphanumeric;
|
use rand::distributions::Alphanumeric;
|
||||||
use rand::{Rng, SeedableRng};
|
use rand::{Rng, SeedableRng};
|
||||||
use rand_chacha::ChaCha20Rng;
|
use rand_chacha::ChaCha20Rng;
|
||||||
use reqwest::header::{CACHE_CONTROL, LOCATION, PRAGMA};
|
use reqwest::header::{CACHE_CONTROL, PRAGMA};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::postgres::PgPool;
|
use sqlx::postgres::PgPool;
|
||||||
|
|
||||||
@ -75,7 +77,7 @@ pub async fn init_oauth(
|
|||||||
.await?
|
.await?
|
||||||
.1;
|
.1;
|
||||||
|
|
||||||
let client_id = oauth_info.client_id;
|
let client_id = oauth_info.client_id.into();
|
||||||
let client = DBOAuthClient::get(client_id, &**pool).await?;
|
let client = DBOAuthClient::get(client_id, &**pool).await?;
|
||||||
|
|
||||||
if let Some(client) = client {
|
if let Some(client) = client {
|
||||||
@ -118,7 +120,7 @@ pub async fn init_oauth(
|
|||||||
{
|
{
|
||||||
init_oauth_code_flow(
|
init_oauth_code_flow(
|
||||||
user.id.into(),
|
user.id.into(),
|
||||||
client.id,
|
client.id.into(),
|
||||||
existing_authorization.id,
|
existing_authorization.id,
|
||||||
requested_scopes,
|
requested_scopes,
|
||||||
redirect_uris,
|
redirect_uris,
|
||||||
@ -141,7 +143,7 @@ pub async fn init_oauth(
|
|||||||
.map_err(|e| OAuthError::redirect(e, &oauth_info.state, &redirect_uri))?;
|
.map_err(|e| OAuthError::redirect(e, &oauth_info.state, &redirect_uri))?;
|
||||||
|
|
||||||
let access_request = OAuthClientAccessRequest {
|
let access_request = OAuthClientAccessRequest {
|
||||||
client_id: client.id,
|
client_id: client.id.into(),
|
||||||
client_name: client.name,
|
client_name: client.name,
|
||||||
client_icon: client.icon_url,
|
client_icon: client.icon_url,
|
||||||
flow_id,
|
flow_id,
|
||||||
@ -341,7 +343,7 @@ pub async fn accept_or_reject_client_scopes(
|
|||||||
|
|
||||||
init_oauth_code_flow(
|
init_oauth_code_flow(
|
||||||
user_id,
|
user_id,
|
||||||
client_id,
|
client_id.into(),
|
||||||
auth_id,
|
auth_id,
|
||||||
scopes,
|
scopes,
|
||||||
redirect_uris,
|
redirect_uris,
|
||||||
@ -396,7 +398,7 @@ async fn init_oauth_code_flow(
|
|||||||
) -> Result<HttpResponse, OAuthError> {
|
) -> Result<HttpResponse, OAuthError> {
|
||||||
let code = Flow::OAuthAuthorizationCodeSupplied {
|
let code = Flow::OAuthAuthorizationCodeSupplied {
|
||||||
user_id,
|
user_id,
|
||||||
client_id,
|
client_id: client_id.into(),
|
||||||
authorization_id,
|
authorization_id,
|
||||||
scopes,
|
scopes,
|
||||||
original_redirect_uri: redirect_uris.original.clone(),
|
original_redirect_uri: redirect_uris.original.clone(),
|
||||||
@ -413,9 +415,9 @@ async fn init_oauth_code_flow(
|
|||||||
let redirect_uri = append_params_to_uri(&redirect_uris.validated.0, &redirect_params);
|
let redirect_uri = append_params_to_uri(&redirect_uris.validated.0, &redirect_params);
|
||||||
|
|
||||||
// IETF RFC 6749 Section 4.1.2 (https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2)
|
// IETF RFC 6749 Section 4.1.2 (https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2)
|
||||||
Ok(HttpResponse::Found()
|
Ok(HttpResponse::Ok()
|
||||||
.append_header((LOCATION, redirect_uri))
|
.append_header((LOCATION, redirect_uri.clone()))
|
||||||
.finish())
|
.body(redirect_uri))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn append_params_to_uri(uri: &str, params: &[impl AsRef<str>]) -> String {
|
fn append_params_to_uri(uri: &str, params: &[impl AsRef<str>]) -> String {
|
||||||
|
|||||||
@ -4,6 +4,7 @@ use super::{
|
|||||||
};
|
};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_with::serde_as;
|
||||||
|
|
||||||
use crate::database::models::oauth_client_authorization_item::OAuthClientAuthorization as DBOAuthClientAuthorization;
|
use crate::database::models::oauth_client_authorization_item::OAuthClientAuthorization as DBOAuthClientAuthorization;
|
||||||
use crate::database::models::oauth_client_item::OAuthClient as DBOAuthClient;
|
use crate::database::models::oauth_client_item::OAuthClient as DBOAuthClient;
|
||||||
@ -64,9 +65,13 @@ pub struct OAuthClientAuthorization {
|
|||||||
pub created: DateTime<Utc>,
|
pub created: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[serde_as]
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
pub struct GetOAuthClientsRequest {
|
pub struct GetOAuthClientsRequest {
|
||||||
pub ids: Vec<OAuthClientId>,
|
#[serde_as(
|
||||||
|
as = "serde_with::StringWithSeparator::<serde_with::formats::CommaSeparator, String>"
|
||||||
|
)]
|
||||||
|
pub ids: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
|
|||||||
@ -132,7 +132,9 @@ impl Scopes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_from_oauth_scopes(scopes: &str) -> Result<Scopes, bitflags::parser::ParseError> {
|
pub fn parse_from_oauth_scopes(scopes: &str) -> Result<Scopes, bitflags::parser::ParseError> {
|
||||||
let scopes = scopes.replace(' ', "|").replace("%20", "|");
|
let scopes = scopes
|
||||||
|
.replace(['+', ' '], "|")
|
||||||
|
.replace("%20", "|");
|
||||||
bitflags::parser::from_str(&scopes)
|
bitflags::parser::from_str(&scopes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -15,7 +15,8 @@ use validator::Validate;
|
|||||||
|
|
||||||
use super::ApiError;
|
use super::ApiError;
|
||||||
use crate::{
|
use crate::{
|
||||||
auth::checks::ValidateAllAuthorized, models::oauth_clients::DeleteOAuthClientQueryParam,
|
auth::checks::ValidateAllAuthorized,
|
||||||
|
models::{ids::base62_impl::parse_base62, oauth_clients::DeleteOAuthClientQueryParam},
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
auth::{checks::ValidateAuthorized, get_user_from_headers},
|
auth::{checks::ValidateAuthorized, get_user_from_headers},
|
||||||
@ -111,13 +112,19 @@ pub async fn get_client(
|
|||||||
#[get("apps")]
|
#[get("apps")]
|
||||||
pub async fn get_clients(
|
pub async fn get_clients(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
info: web::Json<GetOAuthClientsRequest>,
|
info: web::Query<GetOAuthClientsRequest>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<RedisPool>,
|
redis: web::Data<RedisPool>,
|
||||||
session_queue: web::Data<AuthQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let clients =
|
let ids: Vec<_> = info
|
||||||
get_clients_inner(&info.into_inner().ids, req, pool, redis, session_queue).await?;
|
.ids
|
||||||
|
.iter()
|
||||||
|
.map(|id| parse_base62(id).map(ApiOAuthClientId))
|
||||||
|
.collect::<Result<_, _>>()?;
|
||||||
|
|
||||||
|
let clients = get_clients_inner(&ids, req, pool, redis, session_queue).await?;
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(clients))
|
Ok(HttpResponse::Ok().json(clients))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -125,7 +125,7 @@ pub async fn get_authorize_accept_flow_id(response: ServiceResponse) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_auth_code_from_redirect_params(response: &ServiceResponse) -> String {
|
pub async fn get_auth_code_from_redirect_params(response: &ServiceResponse) -> String {
|
||||||
assert_status(response, StatusCode::FOUND);
|
assert_status(response, StatusCode::OK);
|
||||||
let query_params = get_redirect_location_query_params(response);
|
let query_params = get_redirect_location_query_params(response);
|
||||||
query_params.get("code").unwrap().to_string()
|
query_params.get("code").unwrap().to_string()
|
||||||
}
|
}
|
||||||
@ -140,7 +140,13 @@ pub async fn get_access_token(response: ServiceResponse) -> String {
|
|||||||
pub fn get_redirect_location_query_params(
|
pub fn get_redirect_location_query_params(
|
||||||
response: &ServiceResponse,
|
response: &ServiceResponse,
|
||||||
) -> actix_web::web::Query<HashMap<String, String>> {
|
) -> actix_web::web::Query<HashMap<String, String>> {
|
||||||
let redirect_location = response.headers().get(LOCATION).unwrap().to_str().unwrap();
|
let redirect_location = response
|
||||||
|
.headers()
|
||||||
|
.get(LOCATION)
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap()
|
||||||
|
.to_string();
|
||||||
actix_web::web::Query::<HashMap<String, String>>::from_query(
|
actix_web::web::Query::<HashMap<String, String>>::from_query(
|
||||||
redirect_location.split_once('?').unwrap().1,
|
redirect_location.split_once('?').unwrap().1,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -42,7 +42,7 @@ async fn oauth_flow_happy_path() {
|
|||||||
|
|
||||||
// Accept the authorization request
|
// Accept the authorization request
|
||||||
let resp = env.v3.oauth_accept(&flow_id, FRIEND_USER_PAT).await;
|
let resp = env.v3.oauth_accept(&flow_id, FRIEND_USER_PAT).await;
|
||||||
assert_status(&resp, StatusCode::FOUND);
|
assert_status(&resp, StatusCode::OK);
|
||||||
let query = get_redirect_location_query_params(&resp);
|
let query = get_redirect_location_query_params(&resp);
|
||||||
|
|
||||||
let auth_code = query.get("code").unwrap();
|
let auth_code = query.get("code").unwrap();
|
||||||
@ -105,7 +105,7 @@ async fn oauth_authorize_for_already_authorized_scopes_returns_auth_code() {
|
|||||||
USER_USER_PAT,
|
USER_USER_PAT,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
assert_status(&resp, StatusCode::FOUND);
|
assert_status(&resp, StatusCode::OK);
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
@ -231,10 +231,10 @@ async fn reject_authorize_ends_authorize_flow() {
|
|||||||
let flow_id = get_authorize_accept_flow_id(resp).await;
|
let flow_id = get_authorize_accept_flow_id(resp).await;
|
||||||
|
|
||||||
let resp = env.v3.oauth_reject(&flow_id, USER_USER_PAT).await;
|
let resp = env.v3.oauth_reject(&flow_id, USER_USER_PAT).await;
|
||||||
assert_status(&resp, StatusCode::FOUND);
|
assert_status(&resp, StatusCode::OK);
|
||||||
|
|
||||||
let resp = env.v3.oauth_accept(&flow_id, USER_USER_PAT).await;
|
let resp = env.v3.oauth_accept(&flow_id, USER_USER_PAT).await;
|
||||||
assert_any_status_except(&resp, StatusCode::FOUND);
|
assert_any_status_except(&resp, StatusCode::OK);
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
@ -249,7 +249,7 @@ async fn accept_authorize_after_already_accepting_fails() {
|
|||||||
.await;
|
.await;
|
||||||
let flow_id = get_authorize_accept_flow_id(resp).await;
|
let flow_id = get_authorize_accept_flow_id(resp).await;
|
||||||
let resp = env.v3.oauth_accept(&flow_id, USER_USER_PAT).await;
|
let resp = env.v3.oauth_accept(&flow_id, USER_USER_PAT).await;
|
||||||
assert_status(&resp, StatusCode::FOUND);
|
assert_status(&resp, StatusCode::OK);
|
||||||
|
|
||||||
let resp = env.v3.oauth_accept(&flow_id, USER_USER_PAT).await;
|
let resp = env.v3.oauth_accept(&flow_id, USER_USER_PAT).await;
|
||||||
assert_status(&resp, StatusCode::BAD_REQUEST);
|
assert_status(&resp, StatusCode::BAD_REQUEST);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user