diff --git a/src/client.rs b/src/client.rs index 96e73df..9efbf05 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,7 +1,7 @@ use uuid::Uuid; use crate::message::{MessageId,TaggedMessage,VerificationError,Relevance}; -use crate::state::{StateType,StateValue,StatePath,StatePermission,StatePermissionKey}; -use crate::{RoomId,Group,Role,User,GroupPower}; +use crate::state::{self,StateType,StateValue,StatePath,StatePermission,StatePermissionKey}; +use crate::{RoomId,Group,Role,User,GroupPower,ServerAddr}; use serde::{Deserialize, Serialize}; use time::OffsetDateTime; @@ -13,19 +13,19 @@ use time::OffsetDateTime; -#[derive(Serialize,Deserialize,Clone,Debug)] -/// This exists solely to allow the DB to finish tagging the message. It is every field of a -/// message except for the ID which can be created using a Postgres serial type -pub struct InsertableClientMessage { - pub message: ClientMessage, - #[serde(with="time::serde::timestamp")] - pub client_timestamp: OffsetDateTime, - #[serde(with="time::serde::timestamp")] - pub server_timestamp: OffsetDateTime, - #[serde(with = "serde_bytes")] - pub signature: Box<[u8]>, - pub user: User -} +//#[derive(Serialize,Deserialize,Clone,Debug)] +///// This exists solely to allow the DB to finish tagging the message. It is every field of a +///// message except for the ID which can be created using a Postgres serial type +//pub struct InsertableClientMessage { +// pub message: ClientMessage, +// #[serde(with="time::serde::timestamp")] +// pub client_timestamp: OffsetDateTime, +// #[serde(with="time::serde::timestamp")] +// pub server_timestamp: OffsetDateTime, +// #[serde(with = "serde_bytes")] +// pub signature: Box<[u8]>, +// pub user: User +//} #[derive(Serialize,Deserialize,Clone,Debug)] @@ -39,17 +39,26 @@ pub struct SignedClientMessage { // Can I reserialize the message and check the signature that way?? // That would be very brittle but there isn't a good way to extract the // bytes using serde. I'll write something better at some point + // + // Should this be optional? #[serde(with = "serde_bytes")] - pub signature: Box<[u8]> + pub signature: Box<[u8]>, + + // Which server should process the request. Most messages are forwardable but some are not, + // like auth requests and media uploads. + // + // Setting this to None makes it a local message + pub target: Option } impl SignedClientMessage { - pub fn tag(self, username: User) -> InsertableClientMessage { - InsertableClientMessage { + pub fn tag(self, username: User, servername: ServerAddr) -> TaggedMessage { + TaggedMessage { message: self.message, client_timestamp: self.timestamp, server_timestamp: OffsetDateTime::now_utc(), signature: self.signature, user: username, + target: self.target.unwrap_or(servername) } } pub fn verify(&self) -> Result { @@ -59,7 +68,6 @@ impl SignedClientMessage { #[derive(Serialize,Deserialize,Clone,Debug)] pub enum ClientMessage { - Auth{ username: String, password: String @@ -79,7 +87,13 @@ pub enum ClientMessage { // Should it be one message type or mutiple? Message { body: String, - room_id: RoomId + room_id: RoomId, + // Can I overload this to have an optional ID? And reject any + // incoming messages that specify an ID? How else do I return an ID + // only for this message + // + // This is a hacky way to do it but idk how else to + id: Option }, // Private message/invite mechanism MessagePost { @@ -100,8 +114,8 @@ pub enum ClientMessage { StateCreate { room_id: RoomId, path: StatePath, - ty: StateType, - permissions: Option> + content: StateValue, + permissions: Option }, StateWrite { room_id: RoomId, @@ -127,9 +141,17 @@ pub enum ClientMessage { path: StatePath }, PermissionAdd { + room_id: RoomId, + path: StatePath, permission: StatePermission }, + PermissionRead { + room_id: RoomId, + path: StatePath, + }, PermissionDelete { + room_id: RoomId, + path: StatePath, permission: StatePermissionKey }, // Groups really should have a way to add permissions by user @@ -197,7 +219,10 @@ pub enum ClientMessage { bytes: Vec }, // Join and subscribe - Join { + RoomJoin { + room_id: RoomId, + }, + RoomCreate { room_id: RoomId, }, SubscribeMessages { @@ -213,11 +238,34 @@ pub enum ClientMessage { } } impl ClientMessage<> { + pub fn is_forwardable(&self) -> bool { + use ClientMessage::*; + match self { + MediaUpload{bytes: _} + | Auth{ + username: _, + password: _ + } + | UserCreate { + username: _, + password: _, + } + // In the future challenges might be forwardable but right now they are + // only used for signups and auth is local. + | ChallengeAnswer { + response: _ + } => false, + + _ => true + } + + } pub fn get_relevance(&self) -> Option { use ClientMessage::*; Some(match self { Message { body: _, + id: _, room_id // These don't necessarily need cloned, it might make sense to make an owned // version of Relevance and a reference version of it @@ -256,14 +304,26 @@ impl ClientMessage<> { } } -#[derive(Serialize,Deserialize)] +#[derive(Serialize,Deserialize,Debug)] pub enum ServerError { InvalidPermission, // Server can specify the required challenge - ChallengeInvitecode, + ChallengeInviteCode, NotAuthenticated, AlreadyAuthenticated, - NotInChallenge + AuthenticationFailed, + NotInChallenge, + NotJoined(RoomId), + RoomExists(RoomId), + RoomNotFound(RoomId), + ValueNotFound(StatePath), + MismatchedTypes, + DeleteNotEmpty(StatePath), + RemoveLastOwner, + MessageNotForwardable, + UserAlreadyExists, + Generic + } @@ -276,6 +336,8 @@ pub enum ServerMessage { Error(ServerError), // Returned on read State(StatePath,StateValue), + // Returned on permission read + StatePermission(StatePath,crate::state::PermissionTable), // Returned on subscribe, forwards state change message to client StateChange(TaggedMessage), OkMessage(MessageId), diff --git a/src/federation.rs b/src/federation.rs index 626b0c7..9d8278a 100644 --- a/src/federation.rs +++ b/src/federation.rs @@ -50,10 +50,12 @@ pub enum ServerResponse { Error(FederationError), State(StateValue), Media(Uuid,#[serde(with = "serde_bytes")] Vec), - ValidGroups(User,Vec) + ValidGroups(User,Vec), + Ok } #[derive(Serialize,Deserialize)] pub enum FederationError { InvalidPermission, + ServerError(crate::client::ServerError) } diff --git a/src/lib.rs b/src/lib.rs index bdd48ec..653e082 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,22 +8,23 @@ pub mod message; // Room coordinates and originating server #[derive(Serialize,Deserialize,Clone,Debug,Hash,PartialEq,Eq)] pub struct RoomId{ - coordinates: Vec, - server: Option + pub coordinates: Vec, + //pub server: Option } #[derive(Serialize,Deserialize,Clone,Hash,Eq,PartialEq,Debug)] pub struct User { - name: String, + pub name: String, // Should be a fqdn probably, dunno if its worth enforcing that here - server: String + pub server: String } #[derive(Serialize,Deserialize,Clone,Hash,Eq,PartialEq,Debug)] pub struct Group { - name: String, + pub name: String, // Should be a fqdn probably, dunno if its worth enforcing that here - server: String + // Dont need probably because this is already covered in the target field + //pub server: String } // Might want to standardize this, idk which powers would be useful though @@ -36,3 +37,7 @@ pub struct GroupPower(String); pub struct Role { name: String } + + +#[derive(Serialize,Deserialize,Clone,Hash,Eq,PartialEq,Debug)] +pub struct ServerAddr(pub String); diff --git a/src/message.rs b/src/message.rs index 96bda87..82870b2 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1,7 +1,7 @@ use crate::RoomId; use crate::client::ClientMessage; use crate::state::StatePath; -use crate::User; +use crate::{User,ServerAddr}; use time::OffsetDateTime; use serde::{Deserialize, Serialize}; @@ -16,6 +16,7 @@ pub struct MessageId(u64); #[derive(Serialize,Deserialize,Clone,Debug)] pub struct TaggedMessage { + // How do I return message IDs pub message: ClientMessage, #[serde(with="time::serde::timestamp")] pub client_timestamp: OffsetDateTime, @@ -23,7 +24,8 @@ pub struct TaggedMessage { pub server_timestamp: OffsetDateTime, #[serde(with = "serde_bytes")] pub signature: Box<[u8]>, - pub user: User + pub user: User, + pub target: ServerAddr } impl TaggedMessage { pub fn verify(&self) -> Result { @@ -42,6 +44,22 @@ pub enum Relevance { Post(User) } +//#[derive(Serialize,Deserialize,Clone,Debug)] +//pub struct Message { +// pub body: String, +// pub room_id: RoomId, +// pub target: ServerAddr, +// pub message_id: MessageId, +// #[serde(with="time::serde::timestamp")] +// pub client_timestamp: OffsetDateTime, +// #[serde(with="time::serde::timestamp")] +// pub server_timestamp: OffsetDateTime, +// #[serde(with = "serde_bytes")] +// pub signature: Box<[u8]>, +// pub user: User, +// +//} + //#[derive(Serialize,Deserialize,Debug,Clone)] //pub struct Message { // body: String, diff --git a/src/state.rs b/src/state.rs index 624420e..3cdf0e5 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,9 +1,25 @@ use serde::{Deserialize,Serialize}; +use std::collections::{HashMap,HashSet}; use crate::{User,Group,Role}; -#[derive(Serialize,Deserialize,Clone,Debug)] +#[derive(Serialize,Deserialize,Clone,Debug,PartialEq)] pub enum StateValue { String(String), - Binary(Vec) + Binary(Vec), + // A directory in a readable format to allow for something like `ls` + // on clients + Directory(HashMap), +} + +impl StateValue { + pub fn push_other(&mut self, other: &StateValue) -> Result<(),()> { + match (self,other) { + (StateValue::String(s),StateValue::String(o)) => s.push_str(o), + (StateValue::Binary(b),StateValue::Binary(o)) => b.extend_from_slice(o), + _ => return Err(()) + } + Ok(()) + + } } #[derive(Serialize,Deserialize,Debug,Clone)] pub enum StateType { @@ -18,20 +34,68 @@ pub enum StatePermissionKey { User(User), Group(Group), Role(Group,Role), + // Only the server can edit these. Potentially users with admin permissions + // too. + Server, + // Give everyone a permission. Useful for making a file server readable + Everyone, + // Give permissions to the room operator/admin. This is kept track of + // in the room state. Can operators remove other operators? + Operator, //This is a string for now but could get compiled later into an actual regex //should be able to match on name and server UserRegex(String) } -#[derive(Serialize,Deserialize,Clone,Debug)] +#[derive(Serialize,Deserialize,Clone,Debug,PartialEq,Eq)] pub enum StatePermissionValue { None, Read, ReadWrite, // Can only set value, does not allow appending - Write + Write, + Owner +} +impl StatePermissionValue { + // Binary relation + // Maybe could have overloaded a comparison operator for this + // + // Owner + // | + // | + // ReadWrite + // / \ + // Read Write + pub fn allows(&self, other: &StatePermissionValue) -> bool { + use StatePermissionValue::*; + match (self,other) { + (Read,Read) => true, + (ReadWrite,Read) => true, + (Write,Write) => true, + (ReadWrite,Write) => true, + // Owner allows everything + (Owner,_) => true, + _ => false + } + } +} +#[derive(Serialize,Deserialize,Clone,Debug,PartialEq,Eq)] +pub struct StatePermission(pub StatePermissionKey,pub StatePermissionValue); + +#[derive(Serialize,Deserialize,Clone,Debug,PartialEq)] +pub struct PermissionTable(pub HashMap); + +impl PermissionTable { + pub fn get_allowed(&self, needed: &StatePermissionValue) -> HashSet { + let mut set = HashSet::new(); + for (key,perm) in self.0.iter() { + if perm.allows(&needed) { + set.insert(key.clone()); + } + } + set + + } } -#[derive(Serialize,Deserialize,Clone,Debug)] -pub struct StatePermission(StatePermissionKey,StatePermissionValue); #[derive(Serialize,Deserialize,Clone,Debug,Hash,PartialEq,Eq)] -pub struct StatePath(Vec); +pub struct StatePath(pub Vec);