Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion crates/rustauth-sso/src/openapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ pub fn update_provider_body_schema() -> BodySchema {
BodyField::new("providerId", JsonSchemaType::String),
BodyField::optional("issuer", JsonSchemaType::String).format("uri"),
BodyField::optional("domain", JsonSchemaType::String),
BodyField::optional("organizationId", JsonSchemaType::String),
BodyField::optional("oidcConfig", JsonSchemaType::Object).description(
"OIDC provider configuration. Manual skipDiscovery endpoints may be validated against trusted origins when strict_oidc_manual_endpoint_origins is enabled.",
),
Expand Down
23 changes: 1 addition & 22 deletions crates/rustauth-sso/src/routes/provider_update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use crate::options::{
OidcConfig, OidcMapping, SamlMapping, SsoAuditEvent, SsoAuditEventKind, SsoAuditSeverity,
SsoOptions, TokenEndpointAuthentication,
};
use crate::org::{can_manage_provider, can_register_for_organization};
use crate::org::can_manage_provider;
use crate::store::{SsoProviderStore, UpdateSsoProviderInput};
use crate::utils;

Expand All @@ -31,7 +31,6 @@ struct UpdateProviderBody {
provider_id: String,
issuer: Option<String>,
domain: Option<String>,
organization_id: Option<String>,
oidc_config: Option<UpdateOidcConfig>,
saml_config: Option<UpdateSamlConfig>,
}
Expand All @@ -40,7 +39,6 @@ impl UpdateProviderBody {
fn has_update_fields(&self) -> bool {
self.issuer.is_some()
|| self.domain.is_some()
|| self.organization_id.is_some()
|| self.oidc_config.is_some()
|| self.saml_config.is_some()
}
Expand Down Expand Up @@ -157,24 +155,6 @@ pub(super) fn endpoint(options: Arc<SsoOptions>) -> AsyncAuthEndpoint {
);
}
}
if let Some(organization_id) = &body.organization_id {
if !can_register_for_organization(
&context,
adapter.as_ref(),
&user_id,
organization_id,
)
.await?
{
return utils::json(
http::StatusCode::BAD_REQUEST,
&json!({
"code": "ORGANIZATION_ADMIN_REQUIRED",
"message": "You must be an admin or owner of the organization"
}),
);
}
}
let mut oidc_trust_boundary_changed = false;
let merged_oidc_config = if let Some(update) = body.oidc_config {
#[cfg(not(feature = "oidc"))]
Expand Down Expand Up @@ -294,7 +274,6 @@ pub(super) fn endpoint(options: Arc<SsoOptions>) -> AsyncAuthEndpoint {
UpdateSsoProviderInput {
issuer: body.issuer,
domain: body.domain,
organization_id: body.organization_id,
oidc_config: merged_oidc_config.map(Some),
saml_config: merged_saml_config.map(Some),
domain_verified: reset_domain_verified.then_some(false),
Expand Down
5 changes: 0 additions & 5 deletions crates/rustauth-sso/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,9 +325,6 @@ impl<'a> SsoProviderStore<'a> {
if let Some(domain) = input.domain {
query = query.data("domain", DbValue::String(domain));
}
if let Some(organization_id) = input.organization_id {
query = query.data("organization_id", DbValue::String(organization_id));
}
if let Some(oidc_config) = input.oidc_config {
query = query.data("oidc_config", optional_string(oidc_config));
}
Expand Down Expand Up @@ -405,8 +402,6 @@ pub struct UpdateSsoProviderInput {
pub issuer: Option<String>,
/// Updated domains.
pub domain: Option<String>,
/// Updated organization id.
pub organization_id: Option<String>,
/// Updated serialized OIDC config; `Some(None)` clears it.
pub oidc_config: Option<Option<String>>,
/// Updated serialized SAML config; `Some(None)` clears it.
Expand Down
52 changes: 5 additions & 47 deletions crates/rustauth-sso/tests/sso/endpoints/provider_update.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::*;

#[tokio::test]
async fn update_provider_applies_owner_scope_and_resets_domain_verification(
async fn update_provider_ignores_organization_id_and_resets_domain_verification(
) -> Result<(), Box<dyn std::error::Error>> {
let (adapter, router) =
router_with_options(SsoOptions::default().domain_verification_enabled(true))?;
Expand Down Expand Up @@ -29,66 +29,24 @@ async fn update_provider_applies_owner_scope_and_resets_domain_verification(
assert_eq!(body["providerId"], "okta");
assert_eq!(body["issuer"], "https://login.example.com");
assert_eq!(body["domain"], "corp.example.com");
assert_eq!(body["organizationId"], "org_1");
assert!(body["organizationId"].is_null());
assert_eq!(body["domainVerified"], false);

let records = adapter.records("sso_provider").await;
assert_eq!(
records[0].get("domain"),
Some(&DbValue::String("corp.example.com".to_owned()))
);
assert_eq!(
records[0].get("organization_id"),
Some(&DbValue::String("org_1".to_owned()))
);

Ok(())
}

#[tokio::test]
async fn update_provider_rejects_organization_id_without_admin_role(
) -> Result<(), Box<dyn std::error::Error>> {
let (adapter, router) = router_with_options_and_extra_plugins(
SsoOptions::default(),
vec![AuthPlugin::new("organization")],
)?;
let cookie = seed_session(&adapter).await?;
seed_organization(&adapter, "org_1", "acme").await?;
router
.handle_async(json_request(
Method::POST,
"/sso/register",
r#"{"providerId":"okta","issuer":"https://idp.example.com","domain":"example.com"}"#,
Some(&cookie),
)?)
.await?;

let response = router
.handle_async(json_request(
Method::POST,
"/sso/update-provider",
r#"{"providerId":"okta","organizationId":"org_1"}"#,
Some(&cookie),
)?)
.await?;

assert_eq!(response.status(), StatusCode::BAD_REQUEST);
assert_eq!(json_body(response)?["code"], "ORGANIZATION_ADMIN_REQUIRED");
let records = adapter.records("sso_provider").await;
assert_eq!(records[0].get("organization_id"), Some(&DbValue::Null));

Ok(())
}

#[tokio::test]
async fn update_provider_rejects_organization_id_for_plain_member(
async fn update_provider_rejects_organization_id_only_as_no_update_fields(
) -> Result<(), Box<dyn std::error::Error>> {
let (adapter, router) = router_with_options_and_extra_plugins(
SsoOptions::default(),
vec![AuthPlugin::new("organization")],
)?;
let (adapter, router) = router_with_options(SsoOptions::default())?;
let cookie = seed_session(&adapter).await?;
seed_org_member(&adapter, "member_update", "org_1", "user_1", "member").await?;
router
.handle_async(json_request(
Method::POST,
Expand All @@ -108,7 +66,7 @@ async fn update_provider_rejects_organization_id_for_plain_member(
.await?;

assert_eq!(response.status(), StatusCode::BAD_REQUEST);
assert_eq!(json_body(response)?["code"], "ORGANIZATION_ADMIN_REQUIRED");
assert_eq!(json_body(response)?["code"], "NO_UPDATE_FIELDS");
let records = adapter.records("sso_provider").await;
assert_eq!(records[0].get("organization_id"), Some(&DbValue::Null));

Expand Down
10 changes: 10 additions & 0 deletions crates/rustauth-sso/tests/sso/openapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ fn sso_openapi_exposes_public_route_metadata() -> Result<(), Box<dyn std::error:
["schema"]["properties"]["oidcConfig"]
.is_object()
);
assert!(
openapi["paths"]["/sso/register"]["post"]["requestBody"]["content"]["application/json"]
["schema"]["properties"]["organizationId"]
.is_object()
);
assert!(
openapi["paths"]["/sso/register"]["post"]["requestBody"]["content"]["application/json"]
["schema"]["properties"]["oidcConfig"]["description"]
Expand Down Expand Up @@ -80,6 +85,11 @@ fn sso_openapi_exposes_public_route_metadata() -> Result<(), Box<dyn std::error:
["application/json"]["schema"]["properties"]["samlConfig"]
.is_object()
);
assert!(
openapi["paths"]["/sso/update-provider"]["post"]["requestBody"]["content"]
["application/json"]["schema"]["properties"]["organizationId"]
.is_null()
);
#[cfg(feature = "saml")]
{
assert_eq!(
Expand Down
Loading