-
Notifications
You must be signed in to change notification settings - Fork 45
Silo admin endpoints for user logout + listing tokens and sessions #8479
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
base: main
Are you sure you want to change the base?
Changes from all commits
dbb6937
b00d8ba
0419e33
041313d
42d7fd3
d80a4d6
653bd29
bdc9d59
e475578
b7d6372
17110eb
898ef72
0a436e9
10f014e
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 |
---|---|---|
|
@@ -450,6 +450,26 @@ resource ConsoleSessionList { | |
has_relation(fleet: Fleet, "parent_fleet", collection: ConsoleSessionList) | ||
if collection.fleet = fleet; | ||
|
||
# Allow silo admins to delete user sessions and list user tokens | ||
resource SiloUserAuthnList { | ||
permissions = [ "modify", "list_children" ]; | ||
relations = { parent_silo: Silo }; | ||
|
||
# A silo admin can modify (e.g., delete) a user's sessions. | ||
"modify" if "admin" on "parent_silo"; | ||
|
||
# A silo admin can list a user's tokens and sessions. | ||
"list_children" if "admin" on "parent_silo"; | ||
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. This is a little funny because I'm using 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. Hmm, that seems resonable to me, though I'm far from the most knowledgeable about how we tend to model stuff in Polar... @davepacheco might have an opinion? |
||
} | ||
has_relation(silo: Silo, "parent_silo", authn_list: SiloUserAuthnList) | ||
if authn_list.silo_user.silo = silo; | ||
|
||
# give users 'modify' and 'list_children' on their own tokens and sessions | ||
has_permission(actor: AuthenticatedActor, "modify", authn_list: SiloUserAuthnList) | ||
if actor.equals_silo_user(authn_list.silo_user); | ||
has_permission(actor: AuthenticatedActor, "list_children", authn_list: SiloUserAuthnList) | ||
if actor.equals_silo_user(authn_list.silo_user); | ||
|
||
# Describes the policy for creating and managing device authorization requests. | ||
resource DeviceAuthRequestList { | ||
permissions = [ "create_child" ]; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,19 +9,25 @@ use crate::authn; | |
use crate::authz; | ||
use crate::context::OpContext; | ||
use crate::db::model::ConsoleSession; | ||
use crate::db::pagination::paginated; | ||
use async_bb8_diesel::AsyncRunQueryDsl; | ||
use chrono::Utc; | ||
use diesel::prelude::*; | ||
use nexus_db_errors::ErrorHandler; | ||
use nexus_db_errors::public_error_from_diesel; | ||
use nexus_db_lookup::LookupPath; | ||
use nexus_db_schema::schema::console_session; | ||
use omicron_common::api::external::CreateResult; | ||
use omicron_common::api::external::DataPageParams; | ||
use omicron_common::api::external::DeleteResult; | ||
use omicron_common::api::external::Error; | ||
use omicron_common::api::external::ListResultVec; | ||
use omicron_common::api::external::LookupResult; | ||
use omicron_common::api::external::LookupType; | ||
use omicron_common::api::external::ResourceType; | ||
use omicron_common::api::external::UpdateResult; | ||
use omicron_uuid_kinds::GenericUuid; | ||
use uuid::Uuid; | ||
|
||
impl DataStore { | ||
/// Look up session by token. The token is a kind of password, so simply | ||
|
@@ -154,4 +160,52 @@ impl DataStore { | |
)) | ||
}) | ||
} | ||
|
||
/// List console sessions for a specific user | ||
pub async fn silo_user_session_list( | ||
&self, | ||
opctx: &OpContext, | ||
authn_list: authz::SiloUserAuthnList, | ||
pagparams: &DataPageParams<'_, Uuid>, | ||
) -> ListResultVec<ConsoleSession> { | ||
opctx.authorize(authz::Action::ListChildren, &authn_list).await?; | ||
|
||
let user_id = authn_list.silo_user().id(); | ||
|
||
use nexus_db_schema::schema::console_session::dsl; | ||
paginated(dsl::console_session, dsl::id, &pagparams) | ||
.filter(dsl::silo_user_id.eq(user_id)) | ||
// TODO: unlike with tokens, we do not have expiration time here, | ||
// so we can't filter out expired sessions by comparing to now. In | ||
// the authn code, this works by dynamically comparing the created | ||
// and last used times against now + idle/absolute TTL. We may | ||
// have to do that here but it's kind of sad. It might be nicer to | ||
// make sessions work more like tokens and put idle and absolute | ||
// expiration time right there in the table at session create time. | ||
Comment on lines
+178
to
+184
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. This is the major outstanding issue. I will probably make the fix a separate PR on top of this one because it's an actual change, not just an addition. 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. Mm, I like the idea of the proposed refactor --- generally it seems nicer to represent things as deadlines, I think. I agree that it's a big enough change that it could be done subsequently. In the meantime, might we want to cruft together a thing that filters such sessions out of the returned Do you think it's worth opening an issue to track that, in addition to/instead of the TODO comment? |
||
.select(ConsoleSession::as_select()) | ||
.load_async(&*self.pool_connection_authorized(opctx).await?) | ||
.await | ||
.map_err(|e| public_error_from_diesel(e, ErrorHandler::Server)) | ||
} | ||
|
||
/// Delete all session for the user | ||
pub async fn silo_user_sessions_delete( | ||
&self, | ||
opctx: &OpContext, | ||
authn_list: &authz::SiloUserAuthnList, | ||
) -> Result<(), Error> { | ||
// authz policy enforces that the opctx actor is a silo admin on the | ||
// target user's own silo in particular | ||
opctx.authorize(authz::Action::Modify, authn_list).await?; | ||
|
||
let user_id = authn_list.silo_user().id(); | ||
|
||
use nexus_db_schema::schema::console_session; | ||
diesel::delete(console_session::table) | ||
.filter(console_session::silo_user_id.eq(user_id)) | ||
.execute_async(&*self.pool_connection_authorized(opctx).await?) | ||
.await | ||
.map_err(|e| public_error_from_diesel(e, ErrorHandler::Server)) | ||
.map(|_x| ()) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -214,6 +214,33 @@ impl DataStore { | |
.map_err(|e| public_error_from_diesel(e, ErrorHandler::Server)) | ||
} | ||
|
||
/// List device access tokens for a specific user | ||
pub async fn silo_user_token_list( | ||
&self, | ||
opctx: &OpContext, | ||
user_authn_list: authz::SiloUserAuthnList, | ||
pagparams: &DataPageParams<'_, Uuid>, | ||
) -> ListResultVec<DeviceAccessToken> { | ||
opctx.authorize(authz::Action::ListChildren, &user_authn_list).await?; | ||
|
||
let silo_user_id = user_authn_list.silo_user().id(); | ||
|
||
use nexus_db_schema::schema::device_access_token::dsl; | ||
paginated(dsl::device_access_token, dsl::id, &pagparams) | ||
.filter(dsl::silo_user_id.eq(silo_user_id)) | ||
// we don't have time_deleted on tokens. unfortunately this is not | ||
// indexed well. maybe it can be! | ||
.filter( | ||
dsl::time_expires | ||
.is_null() | ||
.or(dsl::time_expires.gt(Utc::now())), | ||
) | ||
.select(DeviceAccessToken::as_select()) | ||
.load_async(&*self.pool_connection_authorized(opctx).await?) | ||
.await | ||
.map_err(|e| public_error_from_diesel(e, ErrorHandler::Server)) | ||
} | ||
|
||
pub async fn current_user_token_delete( | ||
&self, | ||
opctx: &OpContext, | ||
|
@@ -241,4 +268,26 @@ impl DataStore { | |
|
||
Ok(()) | ||
} | ||
|
||
/// Delete all tokens for the user | ||
pub async fn silo_user_tokens_delete( | ||
&self, | ||
opctx: &OpContext, | ||
authn_list: &authz::SiloUserAuthnList, | ||
) -> Result<(), Error> { | ||
// authz policy enforces that the opctx actor is a silo admin on the | ||
// target user's own silo in particular | ||
opctx.authorize(authz::Action::Modify, authn_list).await?; | ||
|
||
use nexus_db_schema::schema::device_access_token; | ||
diesel::delete(device_access_token::table) | ||
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. Occurs to me we could do a soft-delete-like thing by mutating the expiration time to |
||
.filter( | ||
device_access_token::silo_user_id | ||
.eq(authn_list.silo_user().id()), | ||
) | ||
.execute_async(&*self.pool_connection_authorized(opctx).await?) | ||
.await | ||
.map_err(|e| public_error_from_diesel(e, ErrorHandler::Server)) | ||
.map(|_x| ()) | ||
} | ||
} |
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.
Hmm, how would that work? Is that a question you want to answer before merging this branch?
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.
Status quo is the single resource version (I meant resource from the POV of authz). I think it would be simple to have two instead, I would just make a second one and name one tokens and one sessions, and use each as appropriate. Since that can be done any time we need to distinguish the two (we may never have to) I am inclined to solve this by deleting the comment.