mirror of
https://tangled.org/tranquil.farm/tranquil-pds
synced 2026-09-08 19:27:04 +00:00
fix(oauth): fail properly on non-expanded include scopes
This commit is contained in:
@@ -1432,7 +1432,16 @@ pub async fn consent_get(
|
||||
requested_scope_str.to_string()
|
||||
};
|
||||
|
||||
let expanded_scope_str = expand_include_scopes(&effective_scope_str).await;
|
||||
let expanded_scope_str = match expand_include_scopes(&effective_scope_str).await {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return json_error(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"invalid_scope",
|
||||
&format!("Failed to expand permission set: {e}"),
|
||||
);
|
||||
}
|
||||
};
|
||||
let requested_scopes: Vec<&str> = expanded_scope_str.split_whitespace().collect();
|
||||
let consent_client_id = ClientId::from(request_data.parameters.client_id.clone());
|
||||
let preferences = state
|
||||
|
||||
@@ -155,7 +155,11 @@ pub async fn handle_authorization_code_grant(
|
||||
|
||||
let final_scope = if let Some(ref scope) = raw_scope {
|
||||
if scope.contains("include:") {
|
||||
Some(expand_include_scopes(scope).await)
|
||||
Some(
|
||||
expand_include_scopes(scope)
|
||||
.await
|
||||
.map_err(|e| OAuthError::InvalidScope(format!("Failed to expand permission set: {e}")))?,
|
||||
)
|
||||
} else {
|
||||
raw_scope
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
pub use tranquil_scopes::{
|
||||
AccountAction, AccountAttr, AccountScope, BlobScope, IdentityAttr, IdentityScope, IncludeScope,
|
||||
ParsedScope, RepoAction, RepoScope, RpcScope, SCOPE_DEFINITIONS, ScopeCategory,
|
||||
ScopeDefinition, ScopeError, ScopePermissions, expand_include_scopes, format_scope_for_display,
|
||||
get_required_scopes, get_scope_definition, is_valid_scope, parse_scope, parse_scope_string,
|
||||
ScopeDefinition, ScopeError, ScopeExpansionError, ScopePermissions, expand_include_scopes,
|
||||
format_scope_for_display, get_required_scopes, get_scope_definition, is_valid_scope,
|
||||
parse_scope, parse_scope_string,
|
||||
};
|
||||
|
||||
@@ -13,5 +13,5 @@ pub use parser::{
|
||||
AccountAction, AccountAttr, AccountScope, BlobScope, IdentityAttr, IdentityScope, IncludeScope,
|
||||
ParsedScope, RepoAction, RepoScope, RpcScope, parse_scope, parse_scope_string,
|
||||
};
|
||||
pub use permission_set::expand_include_scopes;
|
||||
pub use permission_set::{ScopeExpansionError, expand_include_scopes};
|
||||
pub use permissions::ScopePermissions;
|
||||
|
||||
@@ -4,7 +4,7 @@ use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::LazyLock;
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::{debug, warn};
|
||||
use tracing::debug;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ScopeExpansionError {
|
||||
@@ -73,26 +73,27 @@ struct PermissionEntry {
|
||||
aud: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn expand_include_scopes(scope_string: &str) -> String {
|
||||
pub async fn expand_include_scopes(
|
||||
scope_string: &str,
|
||||
) -> Result<String, ScopeExpansionError> {
|
||||
let futures: Vec<_> = scope_string
|
||||
.split_whitespace()
|
||||
.map(|scope| async move {
|
||||
match scope.strip_prefix("include:") {
|
||||
Some(rest) => {
|
||||
let (nsid_base, aud) = parse_include_scope(rest);
|
||||
expand_permission_set(nsid_base, aud)
|
||||
.await
|
||||
.unwrap_or_else(|e| {
|
||||
warn!(nsid = nsid_base, error = %e, "Failed to expand permission set, keeping original");
|
||||
scope.to_string()
|
||||
})
|
||||
expand_permission_set(nsid_base, aud).await
|
||||
}
|
||||
None => scope.to_string(),
|
||||
None => Ok(scope.to_string()),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
futures::future::join_all(futures).await.join(" ")
|
||||
futures::future::join_all(futures)
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<String>, ScopeExpansionError>>()
|
||||
.map(|v| v.join(" "))
|
||||
}
|
||||
|
||||
fn parse_include_scope(rest: &str) -> (&str, Option<&str>) {
|
||||
@@ -553,17 +554,37 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_expand_include_scopes_passthrough_non_include() {
|
||||
let result = expand_include_scopes("atproto transition:generic").await;
|
||||
let result = expand_include_scopes("atproto transition:generic")
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(result, "atproto transition:generic");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_expand_include_scopes_mixed_with_regular() {
|
||||
let result = expand_include_scopes("atproto repo:app.bsky.feed.post?action=create").await;
|
||||
let result = expand_include_scopes("atproto repo:app.bsky.feed.post?action=create")
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(result.contains("atproto"));
|
||||
assert!(result.contains("repo:app.bsky.feed.post?action=create"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_expand_include_scopes_fails_on_unresolvable_nsid() {
|
||||
let result =
|
||||
expand_include_scopes("atproto include:nonexistent.fake.permissionSet").await;
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_expand_include_scopes_fails_even_with_valid_scopes_present() {
|
||||
let result = expand_include_scopes(
|
||||
"atproto include:nonexistent.fake.permissionSet repo:app.bsky.feed.post?action=create",
|
||||
)
|
||||
.await;
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_cache_population_and_retrieval() {
|
||||
let cache_key = "test.cached.scope";
|
||||
|
||||
Reference in New Issue
Block a user