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 {
#[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<Item=&'out &'out str>,
+ 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<const N: usize>(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))
}
}
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 }
}
}
#[derive(Debug,Clone)]
pub struct EffectiveAcl<'i, P: Perm> {
pub owner_account: Option<&'i str>,
- pub acl: LoadedAcl<P>,
+ pub acl: &'i LoadedAcl<P>,
}
#[derive(Debug,Clone)]
#[derive(Debug,Clone)]
struct AccountSpecified {
- name: ScopedName,
- cooked: String,
+ account: AccountName,
+ cooked: String, // account.to_string()
auth: Authorisation<AccountScope>,
}
self.desc, ae.0);
MgmtError::AuthorisationError
}
+
+
+ #[throws(MgmtError)]
+ pub fn check_acl(&mut self,
+ ig: &mut Unauthorised<InstanceRef, InstanceName>,
+ p: PermSet<TablePermission>) -> &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)]
UpdateAccont(AccountDetails),
DeleteAccount(AccountDetails),
- SetAccount(ScopedName),
+ SetAccount(AccountName),
CreateGame {
- game: ScopedName,
+ game: InstanceName,
insns: Vec<MgmtGameInstruction>,
},
ListGames {
all: Option<bool>, // in same scope by default
},
AlterGame {
- game: ScopedName,
+ game: InstanceName,
insns: Vec<MgmtGameInstruction>,
how: MgmtGameUpdateMode,
},
#[derive(Debug,Serialize,Deserialize)]
pub struct AccountDetails {
- pub account: ScopedName,
+ pub account: AccountName,
pub nick: String,
pub timezone: Option<String>,
#[serde(flatten)]
AddPlayer(MgmtPlayerDetails),
UpdatePlayer(MgmtPlayerDetails),
- RemovePlayer(ScopedName),
+ RemovePlayer(AccountName),
BlockPlayer(String),
}
#[derive(Debug,Serialize,Deserialize)]
pub struct MgmtPlayerDetails {
- pub account: ScopedName,
+ pub account: AccountName,
pub timezone: Option<String>,
pub nick: Option<String>,
}
#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct MgmtPlayerInfo {
- pub account: ScopedName,
+ pub account: AccountName,
pub nick: String,
}
// ---------- 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<Mutex<InstanceContainer>>);
pub iplayers : SecondarySlotMap<PlayerId, PlayerRecord>,
pub tokens_players : TokenRegistry<PlayerId>,
pub tokens_clients : TokenRegistry<ClientId>,
- pub acl: Acl<TablePermission>,
+ pub acl: LoadedAcl<TablePermission>,
}
pub struct PlayerRecord {
}
}
-impl<A> Unauthorised<InstanceGuard<'_>, A> {
- #[throws(MgmtError)]
- pub fn check_acl(&mut self, p: PermSet<TablePermission>)
- -> &mut InstanceGuard {
- let auth = {
- let acl = self.by(Authorisation::authorise_any()).acl;
- acl.check(p)?;
- };
- self.by_mut(auth);
- }
-}
-
impl<A> Unauthorised<InstanceRef, A> {
#[throws(InstanceLockError)]
pub fn lock(&self) -> Unauthorised<InstanceGuard<'_>, A> {
/// 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<InstanceName>)
+ pub fn new(name: InstanceName, gs: GameState,
+ acl: LoadedAcl<TablePermission>, _: Authorisation<InstanceName>)
-> 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(),
#[throws(MgmtError)]
pub fn lookup_by_name_unauth(name: &InstanceName)
- -> Unauthorised<InstanceRef, InstanceName> {
- Unauthorised::of(
+ -> (Unauthorised<InstanceRef, InstanceName>,
+ &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<InstanceName>)
-> InstanceRef {
- Self::lookup_by_name_unauth(name)?.by(auth)
+ Self::lookup_by_name_unauth(name)?.0.by(auth)
}
#[throws(InternalError)]
);
}
- pub fn list_names(scope: Option<&AccountScope>,
- _: Authorisation<AccountScope>)
+ pub fn list_names(account: Option<&AccountName>,
+ _: Authorisation<AccountName>)
-> Vec<Arc<InstanceName>> {
let games = GLOBAL.games.read().unwrap();
let out : Vec<Arc<InstanceName>> =
games.keys()
- .filter(|k| scope == None || scope == Some(&k.scope))
+ .filter(|k| account == None || account == Some(&k.account))
.cloned()
.collect();
out
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<'_> {
}
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()
}
};
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(),
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};
#![feature(proc_macro_hygiene, decl_macro)]
#![feature(slice_strip)]
+#![feature(min_const_generics)]
#![allow(clippy::redundant_closure_call)]