chiark / gitweb /
wip new account etc.
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Wed, 21 Oct 2020 21:49:25 +0000 (22:49 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Wed, 21 Oct 2020 21:49:25 +0000 (22:49 +0100)
Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
src/accounts.rs
src/cmdlistener.rs
src/commands.rs
src/global.rs
src/imports.rs
src/lib.rs

index 0676f5e55f6faab39871241cd95663f278b8e272..8845769835b9bc48fd814cfd8cf587700f0617a3 100644 (file)
@@ -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<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))
   }
 }
 
@@ -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<P>,
+    pub acl: &'i LoadedAcl<P>,
   }
 
   #[derive(Debug,Clone)]
index 0f1075d8ea3372fcafd2709f7ee7fa3f43f91766..7f43c844263c9a4a395d6972f67dd54971d703fe 100644 (file)
@@ -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<AccountScope>,
 }
 
@@ -569,6 +569,22 @@ impl CommandStream<'_> {
           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)]
index a02b601e80fcde2b3f00128b078d340b1d81df36..c24546f5ba253bd6d972ab94bbc855cf72c367c4 100644 (file)
@@ -12,17 +12,17 @@ pub enum MgmtCommand {
   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,
   },
@@ -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<String>,
   #[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<String>,
   pub nick: Option<String>,
 }
@@ -113,7 +113,7 @@ pub struct MgmtGameResponseGameInfo {
 
 #[derive(Debug,Clone,Serialize,Deserialize)]
 pub struct MgmtPlayerInfo {
-  pub account: ScopedName,
+  pub account: AccountName,
   pub nick: String,
 }
 
index b24c748adf341fe1d191dd9a16614efb17f0f33e..effc7803d9ea87f71b26be8502c030a56e576ede 100644 (file)
@@ -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<Mutex<InstanceContainer>>);
@@ -38,7 +42,7 @@ pub struct Instance {
   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 {
@@ -252,18 +256,6 @@ impl InstanceRef {
   }
 }
 
-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> {
@@ -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<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(),
@@ -323,20 +316,23 @@ impl Instance {
 
   #[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)]
@@ -365,13 +361,13 @@ impl Instance {
       );
   }
 
-  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
@@ -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(),
index e5e1093b3229ef248295475be90c88eae82de992..40a726b21d062abb3da8c85c4cab766ef695cd14 100644 (file)
@@ -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};
 
index 51c5a11c066af8f358acf0c9317596e3912f5468..76df4670890ce36be314c86eed5dc1d4094201e5 100644 (file)
@@ -4,6 +4,7 @@
 
 #![feature(proc_macro_hygiene, decl_macro)]
 #![feature(slice_strip)]
+#![feature(min_const_generics)]
 
 #![allow(clippy::redundant_closure_call)]