chiark / gitweb /
new AccountScope
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Sat, 17 Oct 2020 16:15:37 +0000 (17:15 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Sat, 17 Oct 2020 16:15:37 +0000 (17:15 +0100)
Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
src/accounts.rs [new file with mode: 0644]
src/bin/otter.rs
src/cmdlistener.rs
src/commands.rs
src/global.rs
src/imports.rs
src/lib.rs
src/utils.rs

diff --git a/src/accounts.rs b/src/accounts.rs
new file mode 100644 (file)
index 0000000..66e392b
--- /dev/null
@@ -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 }
+  }
+}
index cf28f2d2d5d37f82e69eb5202e0c7857e5bfc785..59600205e5e3c1239403d3c5f8d52f663e4ef32a 100644 (file)
@@ -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<ManagementScope>,
+  scope: Option<AccountScope>,
   socket_path: Option<String>,
   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))
index 76b8abf2a548420da5ca52bab6632956d41133a4..39370b232c08cf8e314c8002f9701cbc99972e65 100644 (file)
@@ -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::<RawToken>::authorise(),
+        AS::Server => Authorised::<RawToken>::authorise(),
         _ => panic!(),
       };
       ig.player_access_register_fixed(
@@ -419,7 +421,7 @@ impl UpdateHandler {
 struct CommandStream<'d> {
   euid : Result<Uid, ConnectionEuidDiscoverEerror>,
   desc : &'d str,
-  scope : Option<ManagementScope>,
+  scope : Option<AccountScope>,
   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> = (T, ManagementScope);
+  type AS<T> = (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<A> (PhantomData<A>);
   //struct AuthorisedScope<A> (Authorised<A>, 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<T> Authorised<T> {
     pub fn authorise() -> Authorised<T> { Authorised(PhantomData) }
   }
 
-  impl<T> From<(Authorised<T>, ManagementScope)> for AuthorisedSatisfactory {
-    fn from((_,s): (Authorised<T>, ManagementScope)) -> Self { Self(s) }
+  impl<T> From<(Authorised<T>, AccountScope)> for AuthorisedSatisfactory {
+    fn from((_,s): (Authorised<T>, AccountScope)) -> Self { Self(s) }
   }
-  impl<T,U> From<((Authorised<T>, Authorised<U>), ManagementScope)> for AuthorisedSatisfactory {
-    fn from(((..),s): ((Authorised<T>, Authorised<U>), ManagementScope)) -> Self { Self(s) }
+  impl<T,U> From<((Authorised<T>, Authorised<U>), AccountScope)> for AuthorisedSatisfactory {
+    fn from(((..),s): ((Authorised<T>, Authorised<U>), AccountScope)) -> Self { Self(s) }
   }
 
   impl From<anyhow::Error> for AuthorisationError {
index 479e36b72165c1cf1ffd8d82e41b4ee19a0fb373..6a81118d63e37108c9879c29d90f84ffbadb24d1 100644 (file)
@@ -7,7 +7,7 @@ use crate::imports::*;
 #[derive(Debug,Serialize,Deserialize)]
 pub enum MgmtCommand {
   Noop,
-  SetScope(ManagementScope),
+  SetScope(AccountScope),
   CreateGame { name: String, insns: Vec<MgmtGameInstruction> },
   ListGames { all: Option<bool>, },
   AlterGame {
index fb8c5e92b9b3d69708e612eb26e0f898a73c22e2..676888f8ea9fe8a4b2a68e55b34a0fe7f0695b64 100644 (file)
@@ -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<Mutex<InstanceContainer>>);
@@ -57,13 +52,6 @@ pub struct ModifyingPieces(());
 pub struct Pieces (pub(in crate::global) ActualPieces);
 type ActualPieces = DenseSlotMap<PieceId,PieceState>;
 
-#[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<Arc<InstanceName>> {
     let games = GLOBAL.games.read().unwrap();
     let out : Vec<Arc<InstanceName>> =
@@ -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,
   }
 }
 
index 5e6b3cfb603dcbff83fcec475943046bcbc482e6..3c8a9213a3dba2821ce245e4d3a361584ca60d9b 100644 (file)
@@ -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};
 
index 96163a0a76be090b9eb90abed37eb2e3626122c7..51c5a11c066af8f358acf0c9317596e3912f5468 100644 (file)
@@ -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;
index 9913f4eef6b424f878ab5605ed58d5a18f996586..e088fcbb60eb7aa2b7f16e6c21f7536498df4caf 100644 (file)
@@ -9,3 +9,16 @@ pub trait OrdExt : Ord + Sized + Clone {
 }
 impl<T> OrdExt for T where T : Ord + Sized + Clone {
 }
+
+pub trait SplitAtDelim<Delim> {
+  fn split_at_delim(&self, delim: Delim) -> (&Self, &Self);
+}
+
+impl SplitAtDelim<char> for str {
+  fn split_at_delim(&self, delim: char) -> (&Self, &Self) {
+    match self.find(delim) {
+      Some(index) => self.split_at(index),
+      None => (self, ""),
+    }
+  }
+}