alibi_plugins/sso/
provider_read.rs1use super::{
3 AuthContext, AuthError, AuthResult, AuthSchema, AuthUser, EndpointResponse, JsValue,
4 ServerEndpoint, SessionRequest, SsoPlugin, SsoProvider, Value, api, json,
5};
6impl SsoPlugin {
7 pub fn get_sso_provider_endpoint(provider_id: &str) -> AuthResult<ServerEndpoint<Value>> {
8 ServerEndpoint::new("sso", "getSSOProvider").with_query(&json!({"providerId":provider_id}))
9 }
10 pub(super) async fn read<S: AuthSchema>(
11 &self,
12 input: &JsValue,
13 request: &impl SessionRequest,
14 ctx: &AuthContext<S>,
15 ) -> AuthResult<EndpointResponse> {
16 let (user, _) = super::super::helpers::ordinary_session(request, ctx).await?;
17 let provider_id = input
18 .get("providerId")
19 .and_then(JsValue::as_str)
20 .ok_or_else(|| super::super::endpoint::validation("Invalid providerId"))?;
21 let provider = ctx
22 .database
23 .get_sso_provider(provider_id)
24 .await?
25 .ok_or_else(|| api(404, "Provider not found"))?;
26 let allowed = if let Some(organization_id) = provider
27 .organization_id
28 .as_deref()
29 .filter(|id| !id.is_empty())
30 {
31 if ctx
32 .get_metadata(super::super::organization::METADATA_ENABLED)
33 .is_some()
34 {
35 ctx.database
36 .get_member(organization_id, user.id().as_ref())
37 .await?
38 .is_some_and(|member| {
39 member
40 .role
41 .split(',')
42 .any(|role| matches!(role.trim(), "owner" | "admin"))
43 })
44 } else {
45 provider.user_id.as_deref() == Some(user.id().as_ref())
46 }
47 } else {
48 provider.user_id.as_deref() == Some(user.id().as_ref())
49 };
50 if !allowed {
51 return Err(api(403, "You don't have access to this provider"));
52 }
53 EndpointResponse::json(&sanitize(&provider, ctx)?)
54 }
55}
56pub(super) fn sanitize<S: AuthSchema>(
57 provider: &SsoProvider,
58 ctx: &AuthContext<S>,
59) -> AuthResult<Value> {
60 let saml = provider
61 .saml_config
62 .as_deref()
63 .and_then(|raw| serde_json::from_str::<Value>(raw).ok())
64 .filter(|value| !value.is_null());
65 if saml.is_some() {
66 return Err(AuthError::not_implemented(
67 "SAML provider sanitization is not implemented",
68 ));
69 }
70 let base = format!(
71 "{}{}",
72 ctx.config.base_url.trim_end_matches('/'),
73 ctx.config.base_path.trim_end_matches('/')
74 );
75 let mut output = json!({"providerId":provider.provider_id,"type":"oidc","issuer":provider.issuer,"domain":provider.domain,"organizationId":provider.organization_id.as_deref().filter(|id|!id.is_empty()),"domainVerified":provider.domain_verified.unwrap_or(false),"spMetadataUrl":format!("{base}/sso/saml2/sp/metadata?providerId={}",component(&provider.provider_id))});
76 if let Some(config) = provider
77 .oidc_config
78 .as_deref()
79 .and_then(|raw| serde_json::from_str::<Value>(raw).ok())
80 .filter(|value| !value.is_null())
81 {
82 let mut safe = serde_json::Map::new();
83 for field in [
84 "discoveryEndpoint",
85 "pkce",
86 "authorizationEndpoint",
87 "tokenEndpoint",
88 "userInfoEndpoint",
89 "jwksEndpoint",
90 "scopes",
91 "tokenEndpointAuthentication",
92 ] {
93 if let Some(value) = config.get(field) {
94 _ = safe.insert(field.into(), value.clone());
95 }
96 }
97 let client_id = config
98 .get("clientId")
99 .and_then(Value::as_str)
100 .ok_or_else(|| AuthError::internal("Stored OIDC configuration has no clientId"))?;
101 let units: Vec<u16> = client_id.encode_utf16().collect();
102 let last = units
103 .get(units.len().saturating_sub(4)..)
104 .unwrap_or_default();
105 let masked = if units.len() <= 4 {
106 "****".into()
107 } else {
108 format!("****{}", String::from_utf16_lossy(last))
109 };
110 _ = safe.insert("clientIdLastFour".into(), json!(masked));
111 let object = output
112 .as_object_mut()
113 .ok_or_else(|| AuthError::internal("SSO provider serialization failed"))?;
114 _ = object.insert("oidcConfig".into(), Value::Object(safe));
115 }
116 Ok(output)
117}
118fn component(value: &str) -> String {
119 urlencoding::encode(value)
120 .replace("%21", "!")
121 .replace("%27", "'")
122 .replace("%28", "(")
123 .replace("%29", ")")
124 .replace("%2A", "*")
125}
126pub(super) fn validate_query(value: Option<&JsValue>) -> AuthResult<JsValue> {
127 super::super::endpoint::validate_fields(
128 value,
129 "query",
130 &[super::super::authentication_helpers::JsonField::string(
131 "providerId",
132 true,
133 )],
134 )
135}