soma_gateway/gateway/
oauth.rs1use thiserror::Error;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub enum GatewayOAuthSurface {
5 AdminOAuthOperation,
6 CodeModeAdmin,
7 CodeModeShared,
8 ProtectedPublicRoute,
9 RelayToolCall,
10}
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct IdentityMatrixRow {
14 pub surface: GatewayOAuthSurface,
15 pub authenticated_caller: &'static str,
16 pub upstream_credential_subject: &'static str,
17 pub relay_cache_subject: &'static str,
18 pub protected_public_route_caller_subject: &'static str,
19 pub source_of_truth: &'static str,
20 pub caller_supplied_subject_accepted: bool,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct GatewaySubject {
25 pub subject: String,
26 pub caller_supplied: bool,
27}
28
29#[derive(Debug, Error, PartialEq, Eq)]
30pub enum GatewayOAuthError {
31 #[error("caller supplied subject is accepted only for admin OAuth operations")]
32 CallerSuppliedSubjectDenied,
33}
34
35pub fn identity_matrix() -> Vec<IdentityMatrixRow> {
36 vec![
37 IdentityMatrixRow {
38 surface: GatewayOAuthSurface::AdminOAuthOperation,
39 authenticated_caller: "admin principal",
40 upstream_credential_subject: "caller selected subject",
41 relay_cache_subject: "caller selected subject",
42 protected_public_route_caller_subject: "not applicable",
43 source_of_truth: "admin OAuth params",
44 caller_supplied_subject_accepted: true,
45 },
46 IdentityMatrixRow {
47 surface: GatewayOAuthSurface::CodeModeAdmin,
48 authenticated_caller: "admin principal",
49 upstream_credential_subject: "shared gateway subject",
50 relay_cache_subject: "shared gateway subject",
51 protected_public_route_caller_subject: "not applicable",
52 source_of_truth: "gateway config",
53 caller_supplied_subject_accepted: false,
54 },
55 IdentityMatrixRow {
56 surface: GatewayOAuthSurface::CodeModeShared,
57 authenticated_caller: "shared gateway principal",
58 upstream_credential_subject: "shared gateway subject",
59 relay_cache_subject: "shared gateway subject",
60 protected_public_route_caller_subject: "not applicable",
61 source_of_truth: "gateway config",
62 caller_supplied_subject_accepted: false,
63 },
64 IdentityMatrixRow {
65 surface: GatewayOAuthSurface::ProtectedPublicRoute,
66 authenticated_caller: "public route caller",
67 upstream_credential_subject: "shared gateway subject",
68 relay_cache_subject: "shared gateway subject",
69 protected_public_route_caller_subject: "stripped before upstream",
70 source_of_truth: "protected route config",
71 caller_supplied_subject_accepted: false,
72 },
73 IdentityMatrixRow {
74 surface: GatewayOAuthSurface::RelayToolCall,
75 authenticated_caller: "downstream MCP session",
76 upstream_credential_subject: "resolved gateway subject",
77 relay_cache_subject: "resolved gateway subject",
78 protected_public_route_caller_subject: "not applicable",
79 source_of_truth: "gateway-authenticated principal",
80 caller_supplied_subject_accepted: false,
81 },
82 ]
83}
84
85pub fn resolve_subject(
86 surface: GatewayOAuthSurface,
87 configured_subject: &str,
88 caller_supplied_subject: Option<&str>,
89) -> Result<GatewaySubject, GatewayOAuthError> {
90 if let Some(subject) = caller_supplied_subject {
91 if surface == GatewayOAuthSurface::AdminOAuthOperation {
92 return Ok(GatewaySubject {
93 subject: subject.to_owned(),
94 caller_supplied: true,
95 });
96 }
97 return Err(GatewayOAuthError::CallerSuppliedSubjectDenied);
98 }
99 Ok(GatewaySubject {
100 subject: configured_subject.to_owned(),
101 caller_supplied: false,
102 })
103}
104
105pub fn strip_public_authorization_header<'a>(
106 headers: impl IntoIterator<Item = (&'a str, &'a str)>,
107) -> Vec<(&'a str, &'a str)> {
108 headers
109 .into_iter()
110 .filter(|(name, _)| !name.eq_ignore_ascii_case("authorization"))
111 .collect()
112}
113
114#[cfg(test)]
115#[path = "oauth_tests.rs"]
116mod tests;