-
Notifications
You must be signed in to change notification settings - Fork 59
Add list of group IDs to /session/me
response
#1830
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a0b8735
7479c5d
8acee42
5f0ae14
2950162
0e61d09
52b9f46
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -335,33 +335,34 @@ async fn test_session_me(cptestctx: &ControlPlaneTestContext) { | |
.execute() | ||
.await | ||
.expect("failed to get current user") | ||
.parsed_body::<views::User>() | ||
.parsed_body::<views::SessionMe>() | ||
.unwrap(); | ||
|
||
assert_eq!( | ||
priv_user, | ||
views::User { | ||
views::SessionMe { | ||
id: USER_TEST_PRIVILEGED.id(), | ||
display_name: USER_TEST_PRIVILEGED.external_id.clone(), | ||
silo_id: DEFAULT_SILO.id(), | ||
group_ids: vec![], | ||
} | ||
); | ||
|
||
// make sure it returns different things for different users | ||
let unpriv_user = NexusRequest::object_get(testctx, "/session/me") | ||
.authn_as(AuthnMode::UnprivilegedUser) | ||
.execute() | ||
.await | ||
.expect("failed to get current user") | ||
.parsed_body::<views::User>() | ||
.parsed_body::<views::SessionMe>() | ||
.unwrap(); | ||
|
||
assert_eq!( | ||
unpriv_user, | ||
views::User { | ||
views::SessionMe { | ||
id: USER_TEST_UNPRIVILEGED.id(), | ||
display_name: USER_TEST_UNPRIVILEGED.external_id.clone(), | ||
silo_id: DEFAULT_SILO.id(), | ||
group_ids: vec![], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be cool to be able to add the user to a group, then refetch and see the group here. But I see that there is no endpoint for adding a user to a group and modification of group memberships only happens during JIT user create. A next-best thing would be if the built-in privileged and unprivileged users were in groups, so at least this vec would come back non-empty. @jmpesp any thoughts? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see. In 0e61d09 I modified There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's not a problem, though if the order of groups returned changes then that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a cute helper for that in 52b9f46 |
||
} | ||
); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,8 @@ | |
// License, v. 2.0. If a copy of the MPL was not distributed with this | ||
// file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
|
||
use std::fmt::Debug; | ||
|
||
use nexus_test_utils::http_testing::{AuthnMode, NexusRequest, RequestBuilder}; | ||
use omicron_common::api::external::IdentityMetadataCreateParams; | ||
use omicron_nexus::authn::silos::{ | ||
|
@@ -19,7 +21,9 @@ use nexus_test_utils::resource_helpers::{create_silo, object_create}; | |
use nexus_test_utils::ControlPlaneTestContext; | ||
use nexus_test_utils_macros::nexus_test; | ||
|
||
use dropshot::ResultsPage; | ||
use httptest::{matchers::*, responders::*, Expectation, Server}; | ||
use uuid::Uuid; | ||
|
||
// Valid SAML IdP entity descriptor from https://en.wikipedia.org/wiki/SAML_metadata#Identity_provider_metadata | ||
// note: no signing keys | ||
|
@@ -959,7 +963,7 @@ async fn test_post_saml_response(cptestctx: &ControlPlaneTestContext) { | |
|
||
signing_keypair: None, | ||
|
||
group_attribute_name: None, | ||
group_attribute_name: Some("groups".into()), | ||
}, | ||
) | ||
.await; | ||
|
@@ -984,7 +988,7 @@ async fn test_post_saml_response(cptestctx: &ControlPlaneTestContext) { | |
) | ||
.raw_body(Some( | ||
serde_urlencoded::to_string(SamlLoginPost { | ||
saml_response: base64::encode(SAML_RESPONSE), | ||
saml_response: base64::encode(SAML_RESPONSE_WITH_GROUPS), | ||
relay_state: None, | ||
}) | ||
.unwrap(), | ||
|
@@ -1006,19 +1010,47 @@ async fn test_post_saml_response(cptestctx: &ControlPlaneTestContext) { | |
.await | ||
.expect("expected success"); | ||
|
||
let _session_user: views::User = NexusRequest::new( | ||
let session_cookie_value = | ||
result.headers["Set-Cookie"].to_str().unwrap().to_string(); | ||
|
||
let groups: ResultsPage<views::Group> = NexusRequest::new( | ||
RequestBuilder::new(client, Method::GET, "/groups") | ||
.header(http::header::COOKIE, session_cookie_value.clone()) | ||
.expect_status(Some(StatusCode::OK)), | ||
) | ||
.execute() | ||
.await | ||
.expect("expected success") | ||
.parsed_body() | ||
.unwrap(); | ||
|
||
let silo_group_names: Vec<&str> = | ||
groups.items.iter().map(|g| g.display_name.as_str()).collect(); | ||
let silo_group_ids: Vec<Uuid> = groups.items.iter().map(|g| g.id).collect(); | ||
|
||
assert_same_items(silo_group_names, vec!["SRE", "Admins"]); | ||
|
||
let session_me: views::SessionMe = NexusRequest::new( | ||
RequestBuilder::new(client, Method::GET, "/session/me") | ||
.header( | ||
http::header::COOKIE, | ||
result.headers["Set-Cookie"].to_str().unwrap().to_string(), | ||
) | ||
.header(http::header::COOKIE, session_cookie_value) | ||
.expect_status(Some(StatusCode::OK)), | ||
) | ||
.execute() | ||
.await | ||
.expect("expected success") | ||
.parsed_body() | ||
.unwrap(); | ||
|
||
assert_eq!(session_me.display_name, "[email protected]"); | ||
assert_same_items(session_me.group_ids, silo_group_ids); | ||
} | ||
|
||
/// Order-agnostic vec equality | ||
fn assert_same_items<T: PartialEq + Debug>(v1: Vec<T>, v2: Vec<T>) { | ||
assert_eq!(v1.len(), v2.len(), "{:?} and {:?} don't match", v1, v2); | ||
for item in v1.iter() { | ||
assert!(v2.contains(item), "{:?} and {:?} don't match", v1, v2); | ||
} | ||
} | ||
|
||
// Test correct SAML response with relay state | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5202,7 +5202,7 @@ | |
"content": { | ||
"application/json": { | ||
"schema": { | ||
"$ref": "#/components/schemas/User" | ||
"$ref": "#/components/schemas/SessionMe" | ||
} | ||
} | ||
} | ||
|
@@ -11002,6 +11002,38 @@ | |
"technical_contact_email" | ||
] | ||
}, | ||
"SessionMe": { | ||
"description": "Client view of a [`User`] and their groups", | ||
"type": "object", | ||
"properties": { | ||
"display_name": { | ||
"description": "Human-readable name that can identify the user", | ||
"type": "string" | ||
}, | ||
"group_ids": { | ||
"type": "array", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this bounded in some way? in general I think we try to avoid responses that are effectively unbounded in terms of their size. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, and bad timing for the auto merge. I might revert it, or I might just make the necessary changes on top. I don't think there is a bound on the size of this list. The problem from the console point of view is that we really do want all the groups for the user. Our standard approach is to make a separate paginated endpoint. I was trying to avoid that, but it's natural enough: the console can On the other hand, this whole rigmarole is a point in favor of moving that logic server-side, letting the console ask for the current user's effective role on any resource. Another option is to actually limit the number of group memberships and not paginate this. |
||
"items": { | ||
"type": "string", | ||
"format": "uuid" | ||
} | ||
}, | ||
"id": { | ||
"type": "string", | ||
"format": "uuid" | ||
}, | ||
"silo_id": { | ||
"description": "Uuid of the silo to which this user belongs", | ||
"type": "string", | ||
"format": "uuid" | ||
} | ||
}, | ||
"required": [ | ||
"display_name", | ||
"group_ids", | ||
"id", | ||
"silo_id" | ||
] | ||
}, | ||
"Silo": { | ||
"description": "Client view of a ['Silo']", | ||
"type": "object", | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think
views::SessionMe::new(user, groups)
would make sense here, but I struggled to decide where to define that. The obviously place would be right next to the definition ofSessionMe
inviews.rs
, but that would be the first time anything in that file refers to DB models. Could be fine, or maybe it offends our sense of propriety. Generally we seem to prefer doing ourimpl From
for these views next to the DB model, which to be honest is not what I prefer. I like to think of the view as "knowing about" the model but the model not knowing about the view.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In fact, there does not seem to be a way to import
db
intonexus/types/src/external_api/views.rs
because it's a different crate and that would introduce a circular dependency. So it basically has to be defined here in the app, but I can'timpl
a constructor for it because it comes from another crate.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this seems to point to a
fn Nexus::session_me(..) -> views::SessionMe
, no?Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I considered that too. The problem is the app layer methods all return DB models, not views, and it's up to the
From
impls to turn those into views in the entrypoints. This is a pretty rare case where we're making a view out of two models. I don't think we do that very often. So if it can't go innexus/src/app
, it would basically be a helper that sits right next to the route handler right here, at which point I figure we might as well inline it (leave as is) since it's not going to be used anywhere else.