fix(oauth): dynamic client registration should be optional (#463)
Signed-off-by: jokemanfire <hu.dingyang@zte.com.cn>
This commit is contained in:
parent
75a7e48441
commit
6cd779c5d7
2 changed files with 16 additions and 19 deletions
|
|
@ -14,7 +14,7 @@ use reqwest::{Client as HttpClient, IntoUrl, StatusCode, Url, header::AUTHORIZAT
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::sync::{Mutex, RwLock};
|
use tokio::sync::{Mutex, RwLock};
|
||||||
use tracing::{debug, error};
|
use tracing::{debug, error, warn};
|
||||||
|
|
||||||
const DEFAULT_EXCHANGE_URL: &str = "http://localhost";
|
const DEFAULT_EXCHANGE_URL: &str = "http://localhost";
|
||||||
|
|
||||||
|
|
@ -102,7 +102,7 @@ pub enum AuthError {
|
||||||
pub struct AuthorizationMetadata {
|
pub struct AuthorizationMetadata {
|
||||||
pub authorization_endpoint: String,
|
pub authorization_endpoint: String,
|
||||||
pub token_endpoint: String,
|
pub token_endpoint: String,
|
||||||
pub registration_endpoint: String,
|
pub registration_endpoint: Option<String>,
|
||||||
pub issuer: Option<String>,
|
pub issuer: Option<String>,
|
||||||
pub jwks_uri: Option<String>,
|
pub jwks_uri: Option<String>,
|
||||||
pub scopes_supported: Option<Vec<String>>,
|
pub scopes_supported: Option<Vec<String>>,
|
||||||
|
|
@ -273,7 +273,7 @@ impl AuthorizationManager {
|
||||||
return Ok(metadata);
|
return Ok(metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("No valid .well-known endpoint found, falling back to default endpoints");
|
warn!("No valid .well-known endpoint found, falling back to default endpoints");
|
||||||
|
|
||||||
// fallback to default endpoints
|
// fallback to default endpoints
|
||||||
let mut auth_base = self.base_url.clone();
|
let mut auth_base = self.base_url.clone();
|
||||||
|
|
@ -290,7 +290,7 @@ impl AuthorizationManager {
|
||||||
Ok(AuthorizationMetadata {
|
Ok(AuthorizationMetadata {
|
||||||
authorization_endpoint: create_endpoint("authorize"),
|
authorization_endpoint: create_endpoint("authorize"),
|
||||||
token_endpoint: create_endpoint("token"),
|
token_endpoint: create_endpoint("token"),
|
||||||
registration_endpoint: create_endpoint("register"),
|
registration_endpoint: None,
|
||||||
issuer: None,
|
issuer: None,
|
||||||
jwks_uri: None,
|
jwks_uri: None,
|
||||||
scopes_supported: None,
|
scopes_supported: None,
|
||||||
|
|
@ -323,12 +323,10 @@ impl AuthorizationManager {
|
||||||
let token_url = TokenUrl::new(metadata.token_endpoint.clone())
|
let token_url = TokenUrl::new(metadata.token_endpoint.clone())
|
||||||
.map_err(|e| AuthError::OAuthError(format!("Invalid token URL: {}", e)))?;
|
.map_err(|e| AuthError::OAuthError(format!("Invalid token URL: {}", e)))?;
|
||||||
|
|
||||||
// debug!("token url: {:?}", token_url);
|
|
||||||
let client_id = ClientId::new(config.client_id);
|
let client_id = ClientId::new(config.client_id);
|
||||||
let redirect_url = RedirectUrl::new(config.redirect_uri.clone())
|
let redirect_url = RedirectUrl::new(config.redirect_uri.clone())
|
||||||
.map_err(|e| AuthError::OAuthError(format!("Invalid re URL: {}", e)))?;
|
.map_err(|e| AuthError::OAuthError(format!("Invalid re URL: {}", e)))?;
|
||||||
|
|
||||||
debug!("client_id: {:?}", client_id);
|
|
||||||
let mut client_builder = BasicClient::new(client_id.clone())
|
let mut client_builder = BasicClient::new(client_id.clone())
|
||||||
.set_auth_uri(auth_url)
|
.set_auth_uri(auth_url)
|
||||||
.set_token_uri(token_url)
|
.set_token_uri(token_url)
|
||||||
|
|
@ -349,14 +347,16 @@ impl AuthorizationManager {
|
||||||
redirect_uri: &str,
|
redirect_uri: &str,
|
||||||
) -> Result<OAuthClientConfig, AuthError> {
|
) -> Result<OAuthClientConfig, AuthError> {
|
||||||
if self.metadata.is_none() {
|
if self.metadata.is_none() {
|
||||||
error!("No authorization support detected");
|
|
||||||
return Err(AuthError::NoAuthorizationSupport);
|
return Err(AuthError::NoAuthorizationSupport);
|
||||||
}
|
}
|
||||||
|
|
||||||
let metadata = self.metadata.as_ref().unwrap();
|
let metadata = self.metadata.as_ref().unwrap();
|
||||||
let registration_url = metadata.registration_endpoint.clone();
|
let Some(registration_url) = metadata.registration_endpoint.as_ref() else {
|
||||||
|
return Err(AuthError::RegistrationFailed(
|
||||||
|
"Dynamic client registration not supported".to_string(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
debug!("registration url: {:?}", registration_url);
|
|
||||||
// prepare registration request
|
// prepare registration request
|
||||||
let registration_request = ClientRegistrationRequest {
|
let registration_request = ClientRegistrationRequest {
|
||||||
client_name: name.to_string(),
|
client_name: name.to_string(),
|
||||||
|
|
@ -369,8 +369,6 @@ impl AuthorizationManager {
|
||||||
response_types: vec!["code".to_string()],
|
response_types: vec!["code".to_string()],
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!("registration request: {:?}", registration_request);
|
|
||||||
|
|
||||||
let response = match self
|
let response = match self
|
||||||
.http_client
|
.http_client
|
||||||
.post(registration_url)
|
.post(registration_url)
|
||||||
|
|
@ -380,7 +378,6 @@ impl AuthorizationManager {
|
||||||
{
|
{
|
||||||
Ok(response) => response,
|
Ok(response) => response,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Registration request failed: {}", e);
|
|
||||||
return Err(AuthError::RegistrationFailed(format!(
|
return Err(AuthError::RegistrationFailed(format!(
|
||||||
"HTTP request error: {}",
|
"HTTP request error: {}",
|
||||||
e
|
e
|
||||||
|
|
@ -395,7 +392,6 @@ impl AuthorizationManager {
|
||||||
Err(_) => "cannot get error details".to_string(),
|
Err(_) => "cannot get error details".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
error!("Registration failed: HTTP {} - {}", status, error_text);
|
|
||||||
return Err(AuthError::RegistrationFailed(format!(
|
return Err(AuthError::RegistrationFailed(format!(
|
||||||
"HTTP {}: {}",
|
"HTTP {}: {}",
|
||||||
status, error_text
|
status, error_text
|
||||||
|
|
@ -406,7 +402,6 @@ impl AuthorizationManager {
|
||||||
let reg_response = match response.json::<ClientRegistrationResponse>().await {
|
let reg_response = match response.json::<ClientRegistrationResponse>().await {
|
||||||
Ok(response) => response,
|
Ok(response) => response,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Failed to parse registration response: {}", e);
|
|
||||||
return Err(AuthError::RegistrationFailed(format!(
|
return Err(AuthError::RegistrationFailed(format!(
|
||||||
"analyze response error: {}",
|
"analyze response error: {}",
|
||||||
e
|
e
|
||||||
|
|
@ -471,7 +466,6 @@ impl AuthorizationManager {
|
||||||
pkce_verifier,
|
pkce_verifier,
|
||||||
csrf_token,
|
csrf_token,
|
||||||
});
|
});
|
||||||
debug!("set authorization state: {:?}", self.state.read().await);
|
|
||||||
|
|
||||||
Ok(auth_url.to_string())
|
Ok(auth_url.to_string())
|
||||||
}
|
}
|
||||||
|
|
@ -625,9 +619,9 @@ impl AuthorizationSession {
|
||||||
redirect_uri: &str,
|
redirect_uri: &str,
|
||||||
client_name: Option<&str>,
|
client_name: Option<&str>,
|
||||||
) -> Result<Self, AuthError> {
|
) -> Result<Self, AuthError> {
|
||||||
// set redirect uri
|
// Default client config
|
||||||
let config = OAuthClientConfig {
|
let config = OAuthClientConfig {
|
||||||
client_id: "mcp-client".to_string(), // temporary id, will be updated by dynamic registration
|
client_id: "mcp-client".to_string(),
|
||||||
client_secret: None,
|
client_secret: None,
|
||||||
scopes: scopes.iter().map(|s| s.to_string()).collect(),
|
scopes: scopes.iter().map(|s| s.to_string()).collect(),
|
||||||
redirect_uri: redirect_uri.to_string(),
|
redirect_uri: redirect_uri.to_string(),
|
||||||
|
|
@ -640,7 +634,10 @@ impl AuthorizationSession {
|
||||||
{
|
{
|
||||||
Ok(config) => config,
|
Ok(config) => config,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("Dynamic registration failed: {}", e);
|
warn!(
|
||||||
|
"Dynamic registration failed: {}, fallback to default config",
|
||||||
|
e
|
||||||
|
);
|
||||||
// fallback to default config
|
// fallback to default config
|
||||||
config
|
config
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -525,7 +525,7 @@ async fn oauth_authorization_server() -> impl IntoResponse {
|
||||||
authorization_endpoint: format!("http://{}/oauth/authorize", BIND_ADDRESS),
|
authorization_endpoint: format!("http://{}/oauth/authorize", BIND_ADDRESS),
|
||||||
token_endpoint: format!("http://{}/oauth/token", BIND_ADDRESS),
|
token_endpoint: format!("http://{}/oauth/token", BIND_ADDRESS),
|
||||||
scopes_supported: Some(vec!["profile".to_string(), "email".to_string()]),
|
scopes_supported: Some(vec!["profile".to_string(), "email".to_string()]),
|
||||||
registration_endpoint: format!("http://{}/oauth/register", BIND_ADDRESS),
|
registration_endpoint: Some(format!("http://{}/oauth/register", BIND_ADDRESS)),
|
||||||
issuer: Some(BIND_ADDRESS.to_string()),
|
issuer: Some(BIND_ADDRESS.to_string()),
|
||||||
jwks_uri: Some(format!("http://{}/oauth/jwks", BIND_ADDRESS)),
|
jwks_uri: Some(format!("http://{}/oauth/jwks", BIND_ADDRESS)),
|
||||||
additional_fields,
|
additional_fields,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue