From: Ian Jackson Date: Wed, 21 Oct 2020 21:49:25 +0000 (+0100) Subject: wip new account etc. X-Git-Tag: otter-0.2.0~612 X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=commitdiff_plain;h=b99d025408d5d51fada37f14eb1e7e317f9380ad;p=otter.git wip new account etc. Signed-off-by: Ian Jackson --- diff --git a/src/accounts.rs b/src/accounts.rs index 0676f5e5..88457698 100644 --- a/src/accounts.rs +++ b/src/accounts.rs @@ -7,8 +7,6 @@ use crate::imports::*; use parking_lot::{RwLock, const_rwlock, MappedRwLockReadGuard, MappedRwLockWriteGuard}; -pub type AccountName = ScopedName; - #[derive(Debug,Clone,Deserialize,Serialize)] #[derive(Eq,PartialEq,Ord,PartialOrd,Hash)] pub enum AccountScope { @@ -21,28 +19,83 @@ type AS = AccountScope; #[derive(Debug,Clone)] #[derive(Eq,PartialEq,Ord,PartialOrd,Hash)] #[derive(DeserializeFromStr,SerializeDisplay)] -pub struct ScopedName { +pub struct AccountName { pub scope: AccountScope, - pub scoped_name: String, + pub subaccount: 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 { +impl AccountScope { + /// Return value is parseable and filesystem- and html-safe + #[throws(E)] + pub fn display_name<'out, + NS: IntoIterator, + E, + F: FnMut(&str) -> Result<(),E> + > + (&'out self, ns: NS, f: F) + { + const ENCODE : percent_encoding::AsciiSet = + percent_encoding::NON_ALPHANUMERIC.remove(b':'); + + let mut out = String::new(); + match &self { AS::Server => { - write!(f, "server")? + f("server")?; }, AS::Unix { user } => { - assert_eq!(user.find('.'), None); - write!(f, "unix:{}", user)?; + f("unix")?; + f(":")?; + f(user)?; }, }; - match self.scoped_name.as_str() { - "" => {}, - suffix => write!(f, ":{}", &suffix)?, + for n in ns { + for frag in utf8_percent_encode(n, &ENCODE) { + f(frag)?; + } + } + } + + #[throws(InvalidScopedName)] + pub fn parse_name(s: &str) -> (AccountScope, [String; N]) { + let mut split = s.split(':'); + + let scope = { + let next = ||{ + split.next() + .ok_or(InvalidScopedName::MissingScopeComponent) + }; + let kind = next(); + match kind { + "server" => { + AccountScope::Server + }, + "unix" => { + let user = next().to_owned(); + AccountScope::Unix { user } + }, + _ => { + throw!(InvalidScopedName::UnknownScopeKind) + }, + } }; + + let strings = ArrayVec::new(); + while let Some(s) = split.next() { + let s = percent_decode_str(s).decode_utf8()?.into(); + strings.try_push(s) + .map_err(|_| InvalidScopedName::TooManyComponents)?; + } + let strings = strings.into_inner() + .map_err(|_| InvalidScopedName::TooFewComponents)?; + (scope, strings) + } +} + +impl Display for AccountName { + #[throws(fmt::Error)] + /// Return value is parseable, and filesystem- and html-safeb + fn fmt(&self, f: &mut fmt::Formatter) { + self.scope.display_name(&[ &self.subaccount ], |s| f.write(s)) } } @@ -50,29 +103,21 @@ impl Display for ScopedName { pub enum InvalidScopedName { #[error("Unknown scope kind (expected unix or server)")] UnknownScopeKind, + #[error("Missing scope component (scope scheme, or scope element)")] + MissingScopeComponent, + #[error("Too few components for scope")] + TooFewComponents, + #[error("Too many components for scope")] + TooManyComponents, } -impl FromStr for ScopedName { +impl FromStr for AccountName { 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 } + let (scope, [scoped_name]) = AccountScope::parse_name(s)?; + AccountName { scope, scoped_name } } } @@ -148,7 +193,7 @@ pub mod loaded_acl { #[derive(Debug,Clone)] pub struct EffectiveAcl<'i, P: Perm> { pub owner_account: Option<&'i str>, - pub acl: LoadedAcl

, + pub acl: &'i LoadedAcl

, } #[derive(Debug,Clone)] diff --git a/src/cmdlistener.rs b/src/cmdlistener.rs index 0f1075d8..7f43c844 100644 --- a/src/cmdlistener.rs +++ b/src/cmdlistener.rs @@ -53,8 +53,8 @@ struct CommandStream<'d> { #[derive(Debug,Clone)] struct AccountSpecified { - name: ScopedName, - cooked: String, + account: AccountName, + cooked: String, // account.to_string() auth: Authorisation, } @@ -569,6 +569,22 @@ impl CommandStream<'_> { self.desc, ae.0); MgmtError::AuthorisationError } + + + #[throws(MgmtError)] + pub fn check_acl(&mut self, + ig: &mut Unauthorised, + p: PermSet) -> &mut InstanceGuard { + let auth = { + let acl = self.by(Authorisation::authorise_any()).acl; + let eacl = EffectiveAcl { + owner_account : &self.account?.to_string(), + acl : &acl, + }; + eacl.check(p)?; + }; + self.by_mut(auth); + } } #[throws(MgmtError)] diff --git a/src/commands.rs b/src/commands.rs index a02b601e..c24546f5 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -12,17 +12,17 @@ pub enum MgmtCommand { UpdateAccont(AccountDetails), DeleteAccount(AccountDetails), - SetAccount(ScopedName), + SetAccount(AccountName), CreateGame { - game: ScopedName, + game: InstanceName, insns: Vec, }, ListGames { all: Option, // in same scope by default }, AlterGame { - game: ScopedName, + game: InstanceName, insns: Vec, how: MgmtGameUpdateMode, }, @@ -36,7 +36,7 @@ pub enum MgmtCommand { #[derive(Debug,Serialize,Deserialize)] pub struct AccountDetails { - pub account: ScopedName, + pub account: AccountName, pub nick: String, pub timezone: Option, #[serde(flatten)] @@ -76,7 +76,7 @@ pub enum MgmtGameInstruction { AddPlayer(MgmtPlayerDetails), UpdatePlayer(MgmtPlayerDetails), - RemovePlayer(ScopedName), + RemovePlayer(AccountName), BlockPlayer(String), } @@ -84,7 +84,7 @@ pub enum MgmtGameInstruction { #[derive(Debug,Serialize,Deserialize)] pub struct MgmtPlayerDetails { - pub account: ScopedName, + pub account: AccountName, pub timezone: Option, pub nick: Option, } @@ -113,7 +113,7 @@ pub struct MgmtGameResponseGameInfo { #[derive(Debug,Clone,Serialize,Deserialize)] pub struct MgmtPlayerInfo { - pub account: ScopedName, + pub account: AccountName, pub nick: String, } diff --git a/src/global.rs b/src/global.rs index b24c748a..effc7803 100644 --- a/src/global.rs +++ b/src/global.rs @@ -25,7 +25,11 @@ pub struct RawTokenVal(str); // ---------- public data structure ---------- -pub type InstanceName = ScopedName; +#[derive(Clone,Debug,Hash,Eq,PartialEq,Ord,PartialOrd)] +pub struct InstanceName { + account: AccountName, + game: String, +} #[derive(Debug,Clone)] pub struct InstanceRef (Arc>); @@ -38,7 +42,7 @@ pub struct Instance { pub iplayers : SecondarySlotMap, pub tokens_players : TokenRegistry, pub tokens_clients : TokenRegistry, - pub acl: Acl, + pub acl: LoadedAcl, } pub struct PlayerRecord { @@ -252,18 +256,6 @@ impl InstanceRef { } } -impl Unauthorised, A> { - #[throws(MgmtError)] - pub fn check_acl(&mut self, p: PermSet) - -> &mut InstanceGuard { - let auth = { - let acl = self.by(Authorisation::authorise_any()).acl; - acl.check(p)?; - }; - self.by_mut(auth); - } -} - impl Unauthorised { #[throws(InstanceLockError)] pub fn lock(&self) -> Unauthorised, A> { @@ -276,13 +268,14 @@ impl Instance { /// Returns `None` if a game with this name already exists #[allow(clippy::new_ret_no_self)] #[throws(MgmtError)] - pub fn new(name: InstanceName, gs: GameState, _: Authorisation) + pub fn new(name: InstanceName, gs: GameState, + acl: LoadedAcl, _: Authorisation) -> InstanceRef { let name = Arc::new(name); let g = Instance { name : name.clone(), - gs, + gs, acl, ipieces : PiecesLoaded(Default::default()), clients : Default::default(), iplayers : Default::default(), @@ -323,20 +316,23 @@ impl Instance { #[throws(MgmtError)] pub fn lookup_by_name_unauth(name: &InstanceName) - -> Unauthorised { - Unauthorised::of( + -> (Unauthorised, + &InstanceName) + { + (Unauthorised::of( GLOBAL.games.read().unwrap() .get(name) .ok_or(MgmtError::GameNotFound)? .clone() .into() - ) + ), + name) } #[throws(MgmtError)] pub fn lookup_by_name(name: &InstanceName, auth: Authorisation) -> InstanceRef { - Self::lookup_by_name_unauth(name)?.by(auth) + Self::lookup_by_name_unauth(name)?.0.by(auth) } #[throws(InternalError)] @@ -365,13 +361,13 @@ impl Instance { ); } - pub fn list_names(scope: Option<&AccountScope>, - _: Authorisation) + pub fn list_names(account: Option<&AccountName>, + _: Authorisation) -> Vec> { let games = GLOBAL.games.read().unwrap(); let out : Vec> = games.keys() - .filter(|k| scope == None || scope == Some(&k.scope)) + .filter(|k| account == None || account == Some(&k.account)) .cloned() .collect(); out @@ -388,6 +384,28 @@ impl DerefMut for InstanceGuard<'_> { fn deref_mut(&mut self) -> &mut Instance { &mut self.c.g } } +impl FromStr for InstanceName { + type Err = InvalidScopedName; + #[throws(InvalidScopedName)] + fn from_str(s: &str) -> Self { + let (scope, [subaccount, game]) = AccountScope::parse_name(s)?; + InstanceName { + account: AccountName { scope, subaccount }, + game, + } + } +} + +impl Display for InstanceName { + #[throws(fmt::Error)] + fn fmt(&self, f: &mut fmt::Formatter) { + self.account.scope.display_name( + &[ self.account.subaccount.as_str(), self.game.as_str() ], + |s| f.write_str(s) + )? + } +} + // ---------- Player and token functionality ---------- impl InstanceGuard<'_> { @@ -685,12 +703,9 @@ enum SavefilenameParseResult { } fn savefilename(name: &InstanceName, prefix: &str, suffix: &str) -> String { - 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(&format!("{}", name), &ENCODE )) + .chain(iter::once( name.to_string().as_str() )) .chain([ suffix ].iter().map(Deref::deref)) .collect() } @@ -706,10 +721,10 @@ fn savefilename_parse(leaf: &[u8]) -> SavefilenameParseResult { }; let after_ftype_prefix = rhs; let rhs = str::from_utf8(rhs)?; - if rhs.rfind('.').is_some() { return TempToDelete } + let rcomp = rhs.rsplitn(2, ':').next().unwrap(); + if rcomp.find('.').is_some() { return TempToDelete } - let name : String = percent_decode_str(rhs).decode_utf8()?.into(); - let name = ScopedName::from_str(&name)?; + let name = InstanceName::from_str(&rhs)?; GameFile { access_leaf : [ b"a-", after_ftype_prefix ].concat(), diff --git a/src/imports.rs b/src/imports.rs index e5e1093b..40a726b2 100644 --- a/src/imports.rs +++ b/src/imports.rs @@ -125,7 +125,7 @@ pub use crate::debugreader::DebugReader; pub use crate::shapelib; pub use crate::tz::*; pub use crate::accounts::*; -pub use crate::accounts::loaded_acl::{self,LoadedAcl,PermSet}; +pub use crate::accounts::loaded_acl::{self,LoadedAcl,EffectiveAcl,PermSet}; pub use zcoord::{self, ZCoord}; diff --git a/src/lib.rs b/src/lib.rs index 51c5a11c..76df4670 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ #![feature(proc_macro_hygiene, decl_macro)] #![feature(slice_strip)] +#![feature(min_const_generics)] #![allow(clippy::redundant_closure_call)]