From: Ian Jackson Date: Sat, 17 Oct 2020 16:15:37 +0000 (+0100) Subject: new AccountScope X-Git-Tag: otter-0.2.0~624 X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=commitdiff_plain;h=3bbb593997c7a788fc0c3c51cc8b44d4bd7c8629;p=otter.git new AccountScope Signed-off-by: Ian Jackson --- diff --git a/src/accounts.rs b/src/accounts.rs new file mode 100644 index 00000000..66e392b6 --- /dev/null +++ b/src/accounts.rs @@ -0,0 +1,72 @@ +// Copyright 2020 Ian Jackson +// SPDX-License-Identifier: AGPL-3.0-or-later +// There is NO WARRANTY. + +use crate::imports::*; + +#[derive(Debug,Clone,Deserialize,Serialize)] +#[derive(Eq,PartialEq,Ord,PartialOrd,Hash)] +pub enum AccountScope { + Server, + Unix { user : String }, +} + +type AS = AccountScope; + +#[derive(Debug)] +#[derive(Eq,PartialEq,Ord,PartialOrd,Hash)] +#[derive(DeserializeFromStr,SerializeDisplay)] +pub struct ScopedName { + pub scope: AccountScope, + pub scoped_name: String, +} + +impl Display for ScopedName { + #[throws(fmt::Error)] + /// Return value is parseable but not filesystem- or html-safe + fn fmt(&self, f: &mut fmt::Formatter) { + match &self.scope { + AS::Server => { + write!(f, "server")? + }, + AS::Unix { user } => { + assert_eq!(user.find('.'), None); + write!(f, "unix:{}", user)?; + }, + }; + match self.scoped_name.as_str() { + "" => {}, + suffix => write!(f, ":{}", &suffix)?, + }; + } +} + +#[derive(Error,Debug)] +pub enum InvalidScopedName { + #[error("Unknown scope kind (expected unix or server)")] + UnknownScopeKind, +} + +impl FromStr for ScopedName { + type Err = InvalidScopedName; + + #[throws(InvalidScopedName)] + fn from_str(s: &str) -> Self { + let (kind, rhs) = s.split_at_delim(':'); + let (scope, scoped_name) = match kind { + "server" => { + (AccountScope::Server, rhs) + }, + "unix" => { + let (user, rhs) = s.split_at_delim(':'); + let user = user.to_owned(); + (AccountScope::Unix { user }, rhs) + }, + _ => { + throw!(InvalidScopedName::UnknownScopeKind) + }, + }; + let scoped_name = scoped_name.to_string(); + ScopedName { scope, scoped_name } + } +} diff --git a/src/bin/otter.rs b/src/bin/otter.rs index cf28f2d2..59600205 100644 --- a/src/bin/otter.rs +++ b/src/bin/otter.rs @@ -14,6 +14,7 @@ use std::cell::Cell; type E = anyhow::Error; type Insn = MgmtGameInstruction; type Resp = MgmtGameResponse; +type AS = AccountScope; use argparse::action::ParseResult::Parsed; @@ -59,7 +60,7 @@ const EXIT_DISASTER : i32 = 16; #[derive(Debug,Default)] struct MainOpts { - scope: Option, + scope: Option, socket_path: Option, verbose: i32, } @@ -147,10 +148,10 @@ fn main() { let mut scope = ap.refer(&mut ma.opts.scope); scope.add_option(&["--scope-server"], - StoreConst(Some(ManagementScope::Server)), + StoreConst(Some(AS::Server)), "use Server scope"); scope.metavar("USER").add_option(&["--scope-unix-user"], - MapStore(|user| Ok(Some(ManagementScope::Unix { + MapStore(|user| Ok(Some(AS::Unix { user: user.into() }))), "use specified unix user scope"); @@ -174,7 +175,7 @@ fn main() { let user = env::var("USER").map_err(|e| ArgumentParseError( format!("--scope-unix needs USER env var: {}", &e) ))?; - *scope = Some(ManagementScope::Unix { user }); + *scope = Some(AS::Unix { user }); } if ma.config_filename.is_some() || ma.opts.socket_path.is_none() { ServerConfig::read(ma.config_filename.as_ref().map(String::as_str)) diff --git a/src/cmdlistener.rs b/src/cmdlistener.rs index 76b8abf2..39370b23 100644 --- a/src/cmdlistener.rs +++ b/src/cmdlistener.rs @@ -25,6 +25,8 @@ use MgmtError::*; type ME = MgmtError; from_instance_lock_error!{MgmtError} +type AS = AccountScope; + const USERLIST : &str = "/etc/userlist"; const CREATE_PIECES_MAX : u32 = 300; @@ -69,7 +71,7 @@ fn execute(cs: &mut CommandStream, cmd: MgmtCommand) -> MgmtResponse { max_z: default(), }; - let name = InstanceName { + let name = ScopedName { scope : cs.get_scope()?.clone(), scoped_name : name, }; @@ -94,7 +96,7 @@ fn execute(cs: &mut CommandStream, cmd: MgmtCommand) -> MgmtResponse { ListGames { all } => { let scope = if all == Some(true) { let _authorise : AuthorisedSatisfactory = - authorise_scope(cs, &ManagementScope::Server)?; + authorise_scope(cs, &AS::Server)?; None } else { let scope = cs.get_scope()?; @@ -106,7 +108,7 @@ fn execute(cs: &mut CommandStream, cmd: MgmtCommand) -> MgmtResponse { }, MgmtCommand::AlterGame { name, insns, how} => { - let name = InstanceName { + let name = ScopedName { scope: cs.get_scope()?.clone(), scoped_name: name }; @@ -228,9 +230,9 @@ fn execute_game_insn(cs: &CommandStream, SetFixedPlayerAccess { player, token } => { let authorised : AuthorisedSatisfactory = - authorise_scope(cs, &ManagementScope::Server)?; + authorise_scope(cs, &AS::Server)?; let authorised = match authorised.into_inner() { - ManagementScope::Server => Authorised::::authorise(), + AS::Server => Authorised::::authorise(), _ => panic!(), }; ig.player_access_register_fixed( @@ -419,7 +421,7 @@ impl UpdateHandler { struct CommandStream<'d> { euid : Result, desc : &'d str, - scope : Option, + scope : Option, chan : MgmtChannel, who: Who, } @@ -443,7 +445,7 @@ impl CommandStream<'_> { } #[throws(MgmtError)] - fn get_scope(&self) -> &ManagementScope { + fn get_scope(&self) -> &AccountScope { self.scope.as_ref().ok_or(NoScope)? } } @@ -554,31 +556,31 @@ impl CommandStream<'_> { } #[throws(MgmtError)] -fn authorise_scope(cs: &CommandStream, wanted: &ManagementScope) +fn authorise_scope(cs: &CommandStream, wanted: &AccountScope) -> AuthorisedSatisfactory { do_authorise_scope(cs, wanted) .map_err(|e| cs.map_auth_err(e))? } #[throws(AuthorisationError)] -fn do_authorise_scope(cs: &CommandStream, wanted: &ManagementScope) +fn do_authorise_scope(cs: &CommandStream, wanted: &AccountScope) -> AuthorisedSatisfactory { - type AS = (T, ManagementScope); + type AS = (T, AccountScope); match &wanted { - ManagementScope::Server => { + AccountScope::Server => { let y : AS< Authorised<(Passwd,Uid)>, > = { let ok = cs.authorised_uid(None,None)?; (ok, - ManagementScope::Server) + AccountScope::Server) }; return y.into() }, - ManagementScope::Unix { user: wanted } => { + AccountScope::Unix { user: wanted } => { let y : AS< Authorised<(Passwd,Uid)>, > = { @@ -633,7 +635,7 @@ fn do_authorise_scope(cs: &CommandStream, wanted: &ManagementScope) let info = xinfo.as_deref(); let ok = cs.authorised_uid(authorised_for, info)?; (ok, - ManagementScope::Unix { user: pwent.name }) + AccountScope::Unix { user: pwent.name }) }; y.into() }, @@ -655,21 +657,21 @@ mod authproofs { pub struct Authorised (PhantomData); //struct AuthorisedScope (Authorised, ManagementScope); - pub struct AuthorisedSatisfactory (ManagementScope); + pub struct AuthorisedSatisfactory (AccountScope); impl AuthorisedSatisfactory { - pub fn into_inner(self) -> ManagementScope { self.0 } + pub fn into_inner(self) -> AccountScope { self.0 } } impl Authorised { pub fn authorise() -> Authorised { Authorised(PhantomData) } } - impl From<(Authorised, ManagementScope)> for AuthorisedSatisfactory { - fn from((_,s): (Authorised, ManagementScope)) -> Self { Self(s) } + impl From<(Authorised, AccountScope)> for AuthorisedSatisfactory { + fn from((_,s): (Authorised, AccountScope)) -> Self { Self(s) } } - impl From<((Authorised, Authorised), ManagementScope)> for AuthorisedSatisfactory { - fn from(((..),s): ((Authorised, Authorised), ManagementScope)) -> Self { Self(s) } + impl From<((Authorised, Authorised), AccountScope)> for AuthorisedSatisfactory { + fn from(((..),s): ((Authorised, Authorised), AccountScope)) -> Self { Self(s) } } impl From for AuthorisationError { diff --git a/src/commands.rs b/src/commands.rs index 479e36b7..6a81118d 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -7,7 +7,7 @@ use crate::imports::*; #[derive(Debug,Serialize,Deserialize)] pub enum MgmtCommand { Noop, - SetScope(ManagementScope), + SetScope(AccountScope), CreateGame { name: String, insns: Vec }, ListGames { all: Option, }, AlterGame { diff --git a/src/global.rs b/src/global.rs index fb8c5e92..676888f8 100644 --- a/src/global.rs +++ b/src/global.rs @@ -25,12 +25,7 @@ pub struct RawTokenVal(str); // ---------- public data structure ---------- -#[derive(Debug,Serialize,Deserialize)] -#[derive(Eq,PartialEq,Ord,PartialOrd,Hash)] -pub struct InstanceName { - pub scope: ManagementScope, - pub scoped_name: String, -} +pub type InstanceName = ScopedName; #[derive(Debug,Clone)] pub struct InstanceRef (Arc>); @@ -57,13 +52,6 @@ pub struct ModifyingPieces(()); pub struct Pieces (pub(in crate::global) ActualPieces); type ActualPieces = DenseSlotMap; -#[derive(Debug,Clone,Deserialize,Serialize)] -#[derive(Eq,PartialEq,Ord,PartialOrd,Hash)] -pub enum ManagementScope { - Server, - Unix { user : String /* username, so filename-safe */ }, -} - #[derive(Debug)] pub struct Client { pub player : PlayerId, @@ -339,7 +327,7 @@ impl Instance { ); } - pub fn list_names(scope: Option<&ManagementScope>) + pub fn list_names(scope: Option<&AccountScope>) -> Vec> { let games = GLOBAL.games.read().unwrap(); let out : Vec> = @@ -609,14 +597,12 @@ enum SavefilenameParseResult { } fn savefilename(name: &InstanceName, prefix: &str, suffix: &str) -> String { - let scope_prefix = { use ManagementScope::*; match &name.scope { - Server => format!(""), - Unix{user} => { format!("{}:", user) }, - } }; - [ config().save_directory.as_str(), &"/", prefix, scope_prefix.as_ref() ] + const ENCODE : percent_encoding::AsciiSet = + percent_encoding::NON_ALPHANUMERIC.remove(b':'); + + [ config().save_directory.as_str(), &"/", prefix ] .iter().map(Deref::deref) - .chain( utf8_percent_encode(&name.scoped_name, - &percent_encoding::NON_ALPHANUMERIC) ) + .chain( utf8_percent_encode(&format!("{}", name), &ENCODE )) .chain([ suffix ].iter().map(Deref::deref)) .collect() } @@ -632,21 +618,14 @@ fn savefilename_parse(leaf: &[u8]) -> SavefilenameParseResult { }; let after_ftype_prefix = rhs; let rhs = str::from_utf8(rhs)?; - let (rhs, scope) = match rhs.find(':') { - None => { - (rhs, ManagementScope::Server) - }, - Some(colon) => { - let (lhs, rhs) = rhs.split_at(colon); - assert_eq!(rhs.chars().next(), Some(':')); - (rhs, ManagementScope::Unix { user: lhs.to_owned() }) - }, - }; if rhs.rfind('.').is_some() { return TempToDelete } - let scoped_name = percent_decode_str(rhs).decode_utf8()?.into(); + + let name : String = percent_decode_str(rhs).decode_utf8()?.into(); + let name = ScopedName::from_str(&name)?; + GameFile { access_leaf : [ b"a-", after_ftype_prefix ].concat(), - name : InstanceName { scope, scoped_name }, + name, } } diff --git a/src/imports.rs b/src/imports.rs index 5e6b3cfb..3c8a9213 100644 --- a/src/imports.rs +++ b/src/imports.rs @@ -121,6 +121,7 @@ pub use crate::spec::*; pub use crate::debugreader::DebugReader; pub use crate::shapelib; pub use crate::tz::*; +pub use crate::accounts::*; pub use zcoord::{self, ZCoord}; diff --git a/src/lib.rs b/src/lib.rs index 96163a0a..51c5a11c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,4 +25,5 @@ pub mod mgmtchannel; pub mod debugreader; pub mod shapelib; pub mod tz; +pub mod accounts; #[path="slotmap-slot-idx.rs"] pub mod slotmap_slot_idx; diff --git a/src/utils.rs b/src/utils.rs index 9913f4ee..e088fcbb 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -9,3 +9,16 @@ pub trait OrdExt : Ord + Sized + Clone { } impl OrdExt for T where T : Ord + Sized + Clone { } + +pub trait SplitAtDelim { + fn split_at_delim(&self, delim: Delim) -> (&Self, &Self); +} + +impl SplitAtDelim for str { + fn split_at_delim(&self, delim: char) -> (&Self, &Self) { + match self.find(delim) { + Some(index) => self.split_at(index), + None => (self, ""), + } + } +}