Skip to content

Commit 6e444f1

Browse files
committed
delete session by token instead of by ID
1 parent be8308d commit 6e444f1

File tree

6 files changed

+11
-83
lines changed

6 files changed

+11
-83
lines changed

nexus/auth/src/authn/external/session_cookie.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ pub trait SessionStore {
4545
) -> Option<Self::SessionModel>;
4646

4747
/// Mark session expired
48-
async fn session_expire(&self, id: ConsoleSessionUuid) -> Option<()>;
48+
async fn session_expire(&self, token: String) -> Option<()>;
4949

5050
/// Maximum time session can remain idle before expiring
5151
fn session_idle_timeout(&self) -> Duration;
@@ -133,7 +133,7 @@ where
133133
// expired
134134
let now = Utc::now();
135135
if session.time_last_used() + ctx.session_idle_timeout() < now {
136-
let expired_session = ctx.session_expire(session.id()).await;
136+
let expired_session = ctx.session_expire(token).await;
137137
if expired_session.is_none() {
138138
debug!(log, "failed to expire session")
139139
}
@@ -153,7 +153,7 @@ where
153153
// existed longer than absolute_timeout, it is expired and we can no
154154
// longer extend the session
155155
if session.time_created() + ctx.session_absolute_timeout() < now {
156-
let expired_session = ctx.session_expire(session.id()).await;
156+
let expired_session = ctx.session_expire(token).await;
157157
if expired_session.is_none() {
158158
debug!(log, "failed to expire session")
159159
}
@@ -273,9 +273,9 @@ mod test {
273273
}
274274
}
275275

276-
async fn session_expire(&self, id: ConsoleSessionUuid) -> Option<()> {
276+
async fn session_expire(&self, token: String) -> Option<()> {
277277
let mut sessions = self.sessions.lock().unwrap();
278-
sessions.retain(|s| s.id != id);
278+
sessions.retain(|s| s.token != token);
279279
Some(())
280280
}
281281

nexus/db-queries/src/db/datastore/console_session.rs

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ use nexus_db_schema::schema::console_session;
1717
use omicron_common::api::external::CreateResult;
1818
use omicron_common::api::external::DeleteResult;
1919
use omicron_common::api::external::Error;
20-
use omicron_common::api::external::InternalContext;
2120
use omicron_common::api::external::LookupResult;
2221
use omicron_common::api::external::LookupType;
2322
use omicron_common::api::external::ResourceType;
@@ -135,53 +134,6 @@ impl DataStore {
135134
})
136135
}
137136

138-
// putting "hard" in the name because we don't do this with any other model
139-
pub async fn session_hard_delete(
140-
&self,
141-
opctx: &OpContext,
142-
authz_session: &authz::ConsoleSession,
143-
) -> DeleteResult {
144-
// We don't do a typical authz check here. Instead, knowing that every
145-
// user is allowed to delete their own session, the query below filters
146-
// on the session's silo_user_id matching the current actor's id.
147-
//
148-
// We could instead model this more like other authz checks. That would
149-
// involve fetching the session record from the database, storing the
150-
// associated silo_user_id into the `authz::ConsoleSession`, and having
151-
// an Oso rule saying you can delete a session whose associated silo
152-
// user matches the authenticated actor. This would be a fair bit more
153-
// complicated and more work at runtime work than what we're doing here.
154-
// The tradeoff is that we're effectively encoding policy here, but it
155-
// seems worth it in this case.
156-
let actor = opctx
157-
.authn
158-
.actor_required()
159-
.internal_context("deleting current user's session")?;
160-
161-
// This check shouldn't be required in that there should be no overlap
162-
// between silo user ids and other types of identity ids. But it's easy
163-
// to check, and if we add another type of Actor, we'll be forced here
164-
// to consider if they should be able to have console sessions and log
165-
// out of them.
166-
let silo_user_id = actor
167-
.silo_user_id()
168-
.ok_or_else(|| Error::invalid_request("not a Silo user"))?;
169-
170-
use nexus_db_schema::schema::console_session::dsl;
171-
diesel::delete(dsl::console_session)
172-
.filter(dsl::silo_user_id.eq(silo_user_id))
173-
.filter(dsl::id.eq(authz_session.id().into_untyped_uuid()))
174-
.execute_async(&*self.pool_connection_authorized(opctx).await?)
175-
.await
176-
.map(|_rows_deleted| ())
177-
.map_err(|e| {
178-
Error::internal_error(&format!(
179-
"error deleting session: {:?}",
180-
e
181-
))
182-
})
183-
}
184-
185137
pub async fn session_hard_delete_by_token(
186138
&self,
187139
opctx: &OpContext,

nexus/db-queries/src/db/datastore/mod.rs

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -659,17 +659,6 @@ mod test {
659659
.unwrap();
660660
assert!(fetched.time_last_used > session.time_last_used);
661661

662-
// deleting it using `opctx` (which represents the test-privileged user)
663-
// should succeed but not do anything -- you can't delete someone else's
664-
// session
665-
let delete =
666-
datastore.session_hard_delete(&opctx, &authz_session).await;
667-
assert_eq!(delete, Ok(()));
668-
let fetched = datastore
669-
.session_lookup_by_token(&authn_opctx, token.clone())
670-
.await;
671-
assert!(fetched.is_ok());
672-
673662
// delete it and fetch should come back with nothing
674663
let silo_user_opctx = OpContext::for_background(
675664
logctx.log.new(o!()),
@@ -682,7 +671,7 @@ mod test {
682671
Arc::clone(&datastore) as Arc<dyn nexus_auth::storage::Storage>,
683672
);
684673
let delete = datastore
685-
.session_hard_delete(&silo_user_opctx, &authz_session)
674+
.session_hard_delete_by_token(&silo_user_opctx, token.clone())
686675
.await;
687676
assert_eq!(delete, Ok(()));
688677
let fetched = datastore
@@ -695,7 +684,7 @@ mod test {
695684

696685
// deleting an already nonexistent is considered a success
697686
let delete_again =
698-
datastore.session_hard_delete(&opctx, &authz_session).await;
687+
datastore.session_hard_delete_by_token(&opctx, token.clone()).await;
699688
assert_eq!(delete_again, Ok(()));
700689

701690
let delete_again =

nexus/src/app/session.rs

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -80,19 +80,6 @@ impl super::Nexus {
8080
self.db_datastore.session_update_last_used(opctx, &authz_session).await
8181
}
8282

83-
pub(crate) async fn session_hard_delete(
84-
&self,
85-
opctx: &OpContext,
86-
id: ConsoleSessionUuid,
87-
) -> DeleteResult {
88-
let authz_session = authz::ConsoleSession::new(
89-
authz::FLEET,
90-
id,
91-
LookupType::ById(id.into_untyped_uuid()),
92-
);
93-
self.db_datastore.session_hard_delete(opctx, &authz_session).await
94-
}
95-
9683
pub(crate) async fn session_hard_delete_by_token(
9784
&self,
9885
opctx: &OpContext,

nexus/src/context.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -477,9 +477,9 @@ impl SessionStore for ServerContext {
477477
self.nexus.session_update_last_used(&opctx, id).await.ok()
478478
}
479479

480-
async fn session_expire(&self, id: ConsoleSessionUuid) -> Option<()> {
480+
async fn session_expire(&self, token: String) -> Option<()> {
481481
let opctx = self.nexus.opctx_external_authn();
482-
self.nexus.session_hard_delete(opctx, id).await.ok()
482+
self.nexus.session_hard_delete_by_token(opctx, token).await.ok()
483483
}
484484

485485
fn session_idle_timeout(&self) -> Duration {

nexus/tests/integration_tests/authn_http.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -397,9 +397,9 @@ impl session_cookie::SessionStore for WhoamiServerState {
397397
}
398398
}
399399

400-
async fn session_expire(&self, id: ConsoleSessionUuid) -> Option<()> {
400+
async fn session_expire(&self, token: String) -> Option<()> {
401401
let mut sessions = self.sessions.lock().unwrap();
402-
sessions.retain(|s| s.id != id);
402+
sessions.retain(|s| s.token != token);
403403
Some(())
404404
}
405405

0 commit comments

Comments
 (0)