Skip to content

Commit ce74d63

Browse files
committed
add an integration test for multiple users and groups
1 parent f73efa2 commit ce74d63

File tree

1 file changed

+192
-0
lines changed
  • nexus/tests/integration_tests

1 file changed

+192
-0
lines changed

nexus/tests/integration_tests/scim.rs

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1993,3 +1993,195 @@ async fn test_scim_user_admin_group_priv_conflict(
19931993
.await
19941994
.expect("expected 200");
19951995
}
1996+
1997+
#[nexus_test]
1998+
async fn test_scim_list_users_with_groups(cptestctx: &ControlPlaneTestContext) {
1999+
let client = &cptestctx.external_client;
2000+
let nexus = &cptestctx.server.server_context().nexus;
2001+
let opctx = OpContext::for_tests(
2002+
cptestctx.logctx.log.new(o!()),
2003+
nexus.datastore().clone(),
2004+
);
2005+
2006+
const SILO_NAME: &str = "saml-scim-silo";
2007+
create_silo(&client, SILO_NAME, true, shared::SiloIdentityMode::SamlScim)
2008+
.await;
2009+
2010+
grant_iam(
2011+
client,
2012+
&format!("/v1/system/silos/{SILO_NAME}"),
2013+
shared::SiloRole::Admin,
2014+
opctx.authn.actor().unwrap().silo_user_id().unwrap(),
2015+
AuthnMode::PrivilegedUser,
2016+
)
2017+
.await;
2018+
2019+
let created_token: views::ScimClientBearerTokenValue =
2020+
object_create_no_body(
2021+
client,
2022+
&format!("/v1/system/scim/tokens?silo={}", SILO_NAME),
2023+
)
2024+
.await;
2025+
2026+
// Create 5 users
2027+
let mut users = Vec::new();
2028+
for i in 1..=5 {
2029+
let user: scim2_rs::User = NexusRequest::new(
2030+
RequestBuilder::new(client, Method::POST, "/scim/v2/Users")
2031+
.header(http::header::CONTENT_TYPE, "application/scim+json")
2032+
.header(
2033+
http::header::AUTHORIZATION,
2034+
format!("Bearer oxide-scim-{}", created_token.bearer_token),
2035+
)
2036+
.allow_non_dropshot_errors()
2037+
.raw_body(Some(
2038+
serde_json::to_string(&serde_json::json!({
2039+
"userName": format!("user{}", i),
2040+
"externalId": format!("user{}@example.com", i),
2041+
}))
2042+
.unwrap(),
2043+
))
2044+
.expect_status(Some(StatusCode::CREATED)),
2045+
)
2046+
.execute_and_parse_unwrap()
2047+
.await;
2048+
users.push(user);
2049+
}
2050+
2051+
// Create 3 groups with various membership patterns:
2052+
// - group1: user1, user2, user3
2053+
// - group2: user1, user4
2054+
// - group3: no members
2055+
let group1: scim2_rs::Group = NexusRequest::new(
2056+
RequestBuilder::new(client, Method::POST, "/scim/v2/Groups")
2057+
.header(http::header::CONTENT_TYPE, "application/scim+json")
2058+
.header(
2059+
http::header::AUTHORIZATION,
2060+
format!("Bearer oxide-scim-{}", created_token.bearer_token),
2061+
)
2062+
.allow_non_dropshot_errors()
2063+
.raw_body(Some(
2064+
serde_json::to_string(&serde_json::json!({
2065+
"displayName": "group1",
2066+
"externalId": "[email protected]",
2067+
"members": [
2068+
{"value": users[0].id},
2069+
{"value": users[1].id},
2070+
{"value": users[2].id},
2071+
],
2072+
}))
2073+
.unwrap(),
2074+
))
2075+
.expect_status(Some(StatusCode::CREATED)),
2076+
)
2077+
.execute_and_parse_unwrap()
2078+
.await;
2079+
2080+
let group2: scim2_rs::Group = NexusRequest::new(
2081+
RequestBuilder::new(client, Method::POST, "/scim/v2/Groups")
2082+
.header(http::header::CONTENT_TYPE, "application/scim+json")
2083+
.header(
2084+
http::header::AUTHORIZATION,
2085+
format!("Bearer oxide-scim-{}", created_token.bearer_token),
2086+
)
2087+
.allow_non_dropshot_errors()
2088+
.raw_body(Some(
2089+
serde_json::to_string(&serde_json::json!({
2090+
"displayName": "group2",
2091+
"externalId": "[email protected]",
2092+
"members": [
2093+
{"value": users[0].id},
2094+
{"value": users[3].id},
2095+
],
2096+
}))
2097+
.unwrap(),
2098+
))
2099+
.expect_status(Some(StatusCode::CREATED)),
2100+
)
2101+
.execute_and_parse_unwrap()
2102+
.await;
2103+
2104+
let _group3: scim2_rs::Group = NexusRequest::new(
2105+
RequestBuilder::new(client, Method::POST, "/scim/v2/Groups")
2106+
.header(http::header::CONTENT_TYPE, "application/scim+json")
2107+
.header(
2108+
http::header::AUTHORIZATION,
2109+
format!("Bearer oxide-scim-{}", created_token.bearer_token),
2110+
)
2111+
.allow_non_dropshot_errors()
2112+
.raw_body(Some(
2113+
serde_json::to_string(&serde_json::json!({
2114+
"displayName": "group3",
2115+
"externalId": "[email protected]",
2116+
}))
2117+
.unwrap(),
2118+
))
2119+
.expect_status(Some(StatusCode::CREATED)),
2120+
)
2121+
.execute_and_parse_unwrap()
2122+
.await;
2123+
2124+
// List all users and verify group memberships
2125+
let response: scim2_rs::ListResponse = NexusRequest::new(
2126+
RequestBuilder::new(client, Method::GET, "/scim/v2/Users")
2127+
.header(http::header::CONTENT_TYPE, "application/scim+json")
2128+
.header(
2129+
http::header::AUTHORIZATION,
2130+
format!("Bearer oxide-scim-{}", created_token.bearer_token),
2131+
)
2132+
.allow_non_dropshot_errors()
2133+
.expect_status(Some(StatusCode::OK)),
2134+
)
2135+
.execute_and_parse_unwrap()
2136+
.await;
2137+
2138+
let returned_users: Vec<scim2_rs::User> = serde_json::from_value(
2139+
serde_json::to_value(&response.resources).unwrap(),
2140+
)
2141+
.unwrap();
2142+
2143+
// Find our created users in the response
2144+
let find_user = |user_id: &str| {
2145+
returned_users
2146+
.iter()
2147+
.find(|u| u.id == user_id)
2148+
.expect("user should be in list")
2149+
};
2150+
2151+
// user1 should be in group1 and group2
2152+
let user1 = find_user(&users[0].id);
2153+
assert!(user1.groups.is_some());
2154+
let user1_groups = user1.groups.as_ref().unwrap();
2155+
assert_eq!(user1_groups.len(), 2);
2156+
let user1_group_ids: std::collections::HashSet<_> = user1_groups
2157+
.iter()
2158+
.map(|g| g.value.as_ref().unwrap().as_str())
2159+
.collect();
2160+
assert!(user1_group_ids.contains(group1.id.as_str()));
2161+
assert!(user1_group_ids.contains(group2.id.as_str()));
2162+
2163+
// user2 should be in group1 only
2164+
let user2 = find_user(&users[1].id);
2165+
assert!(user2.groups.is_some());
2166+
let user2_groups = user2.groups.as_ref().unwrap();
2167+
assert_eq!(user2_groups.len(), 1);
2168+
assert_eq!(user2_groups[0].value.as_ref().unwrap(), &group1.id);
2169+
2170+
// user3 should be in group1 only
2171+
let user3 = find_user(&users[2].id);
2172+
assert!(user3.groups.is_some());
2173+
let user3_groups = user3.groups.as_ref().unwrap();
2174+
assert_eq!(user3_groups.len(), 1);
2175+
assert_eq!(user3_groups[0].value.as_ref().unwrap(), &group1.id);
2176+
2177+
// user4 should be in group2 only
2178+
let user4 = find_user(&users[3].id);
2179+
assert!(user4.groups.is_some());
2180+
let user4_groups = user4.groups.as_ref().unwrap();
2181+
assert_eq!(user4_groups.len(), 1);
2182+
assert_eq!(user4_groups[0].value.as_ref().unwrap(), &group2.id);
2183+
2184+
// user5 should have no groups
2185+
let user5 = find_user(&users[4].id);
2186+
assert!(user5.groups.is_none());
2187+
}

0 commit comments

Comments
 (0)