Skip to content

Commit

Permalink
Integrate session into pmrac::Platform
Browse files Browse the repository at this point in the history
- Also provide a linked session type that will provide methods that use
  the platform reference (i.e. works like a handle/ctrl).
- Discovered that it may be best to save a new token without updating
  the timestamp further to maintain certain expectations.
- Have no idea how to address saving.  Perhaps make it so that calling
  save returns the token (i.e. make the argument just `self`).  Will
  revisit this later.
  • Loading branch information
metatoaster committed Oct 11, 2024
1 parent cb9dd35 commit daec733
Show file tree
Hide file tree
Showing 7 changed files with 206 additions and 9 deletions.
1 change: 1 addition & 0 deletions pmrac/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod error;
pub mod password;
pub mod platform;
pub mod session;
pub mod user;

pub use platform::Platform;
101 changes: 99 additions & 2 deletions pmrac/src/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ use pmrcore::{
agent::Agent,
genpolicy::Policy,
role::Role,
session::{
self,
SessionFactory,
SessionToken,
},
user,
workflow::State,
},
Expand All @@ -22,6 +27,7 @@ use crate::{
Password,
PasswordStatus,
},
session::Session,
};

#[derive(Clone, Default)]
Expand All @@ -31,12 +37,15 @@ pub struct Builder {
// automatically purges all but the most recent passwords
password_autopurge: bool,
pmrrbac_builder: PmrRbacBuilder,
session_factory: Arc<SessionFactory>,
}

#[derive(Clone)]
pub struct Platform {
ac_platform: Arc<dyn ACPlatform>,
password_autopurge: bool,
pmrrbac_builder: PmrRbacBuilder,
session_factory: Arc<SessionFactory>,
}

impl Builder {
Expand All @@ -62,26 +71,42 @@ impl Builder {
self
}

pub fn session_factory(mut self, val: SessionFactory) -> Self {
self.session_factory = Arc::new(val);
self
}

pub fn build(self) -> Platform {
Platform {
ac_platform: self.ac_platform.expect("missing required argument ac_platform"),
ac_platform: self.ac_platform
.expect("missing required argument ac_platform"),
password_autopurge: self.password_autopurge,
pmrrbac_builder: self.pmrrbac_builder
pmrrbac_builder: self.pmrrbac_builder,
session_factory: self.session_factory,
}
}
}

impl Platform {
pub(crate) fn ac_platform(&self) -> &dyn ACPlatform {
self.ac_platform.as_ref()
}
}

impl Platform {
pub fn new(
ac_platform: impl ACPlatform + 'static,
password_autopurge: bool,
pmrrbac_builder: PmrRbacBuilder,
session_factory: SessionFactory,
) -> Self {
let ac_platform = Arc::new(ac_platform);
let session_factory = Arc::new(session_factory);
Self {
ac_platform,
password_autopurge,
pmrrbac_builder,
session_factory,
}
}
}
Expand Down Expand Up @@ -330,6 +355,78 @@ impl Platform {
}
}

// Session management

impl<'a> Platform {
pub async fn new_user_session(
&'a self,
user: User<'a>,
origin: String,
) -> Result<Session<'a>, Error> {
// this wouldn't work in stable as trait upcasting is nightly
// let session = Session::new(
// &self,
// SessionBackend::save_session(
// self.ac_platform.as_ref(),
// &self.session_factory,
// user.id(),
// origin,
// )
// .await?,
// user,
// );
// ... so just inline the trivial `new_user_session` here, at
// least until this is fully stablized.
let session = self.session_factory.create(user.id(), origin);
self.ac_platform.save_session(&session).await?;
let session = Session::new(&self, session, user);
Ok(session)
}

pub async fn load_session(
&'a self,
token: SessionToken,
) -> Result<Session<'a>, Error> {
let session = self.ac_platform.load_session(token).await?;
let user = self.get_user(session.user_id).await?;
Ok(Session::new(
&self,
session,
user,
))
}

/// simply return a list of sessions without the token for the user_id
pub async fn get_user_sessions(
&self,
user_id: i64,
) -> Result<Vec<session::Session>, Error> {
Ok(self.ac_platform.get_user_sessions(user_id).await?)
}

/*
// Not implementing the following, as Session<'_> will provide them.
pub async fn save_session(
&self,
session: &Session,
) -> Result<(), Error> {
}
pub async fn purge_session(
&self,
token: SessionToken,
) -> Result<(), Error> {
}
pub async fn purge_user_sessions(
&self,
user_id: i64,
token: Option<SessionToken>,
) -> Result<(), Error> {
}
*/
}

// Enforcement

impl Platform {
Expand Down
14 changes: 14 additions & 0 deletions pmrac/src/session.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use pmrcore::ac::session;

use crate::{
Platform,
user::User,
};

pub struct Session<'a> {
platform: &'a Platform,
session: session::Session,
user: User<'a>,
}

mod impls;
40 changes: 40 additions & 0 deletions pmrac/src/session/impls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use pmrcore::ac::session;

use crate::{
Platform,
error::Error,
user::User,
};
use super::Session;

impl<'a> Session<'a> {
pub(crate) fn new(
platform: &'a Platform,
session: session::Session,
user: User<'a>,
) -> Self {
Self {
platform,
session,
user,
}
}

pub fn user(&self) -> &User<'a> {
&self.user
}

// access to every field, which may or may not be what we want.
pub fn session(&self) -> &session::Session {
&self.session
}

// consider making the argument `self` to consume, and not worry
// about dealing with the timestamp at all?
pub async fn save(&self) -> Result<i64, Error> {
Ok(self.platform
.ac_platform()
.save_session(&self.session)
.await?)
}
}
50 changes: 47 additions & 3 deletions pmrac/tests/test_platform.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use pmrcore::ac::{
agent::Agent,
role::Role,
session::SessionFactory,
workflow::State,
};
use pmrac::{
Expand All @@ -17,9 +18,12 @@ use pmrac::{
};
use pmrrbac::Builder as PmrRbacBuilder;

use test_pmr::ac::{
create_sqlite_backend,
create_sqlite_platform,
use test_pmr::{
ac::{
create_sqlite_backend,
create_sqlite_platform,
},
chrono::Utc,
};

async fn basic_lifecycle(purge: bool) -> anyhow::Result<()> {
Expand Down Expand Up @@ -375,3 +379,43 @@ async fn policy_enforcement() -> anyhow::Result<()> {

Ok(())
}

#[async_std::test]
async fn sessions() -> anyhow::Result<()> {
let platform = Builder::new()
.ac_platform(create_sqlite_backend().await?)
.session_factory(
SessionFactory::new()
.ts_source(|| Utc::now().timestamp())
)
.build();
let user = platform.create_user("test_user").await?;
let user_id = user.id();

// note that a session can be created like this, without a password,
// typically this lets new users set up their own initial password.
let session = platform.new_user_session(
user,
"localhost".to_string(),
).await?;

// FIXME loading may potentially update last_access_ts later?
let new_session = platform.load_session(session.session().token).await?;
assert_eq!(session.user().id(), new_session.user().id());
assert_eq!(session.session(), new_session.session());

platform.new_user_session(
// typically this is done as part of some login workflow
platform.get_user(user_id).await?,
"localhost".to_string(),
).await?;
assert_eq!(2, platform.get_user_sessions(user_id).await?.len());

// test that saving does bump
session.save().await?;
let updated_session = platform.load_session(session.session().token).await?;
assert_eq!(session.session().origin, updated_session.session().origin);
assert_ne!(session.session().last_active_ts, updated_session.session().last_active_ts);

Ok(())
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion pmrmodel/src/model/db/sqlite/ac/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,13 @@ VALUES ( ?1, ?2, ?3, ?4, ?5 )
ON CONFLICT(token)
DO UPDATE SET
origin = ?3,
last_active_ts = ?5
last_active_ts = ?6
"#,
token_str,
session.user_id,
session.origin,
session.created_ts,
session.last_active_ts,
last_active_ts,
)
.execute(&*backend.pool)
Expand Down

0 comments on commit daec733

Please sign in to comment.