@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "semios_account"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[features]
|
||||
default = ["client"]
|
||||
client = []
|
||||
|
||||
[dependencies]
|
||||
cfg-if = "1"
|
||||
bitflags = { version = "2", features = ["serde"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
thiserror = "2"
|
||||
uuid = { version = "1", features = ["serde"] }
|
||||
@@ -0,0 +1,161 @@
|
||||
//! User authentication.
|
||||
|
||||
use bitflags::bitflags;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use uuid::Uuid;
|
||||
|
||||
macro_rules! decl_session_args {
|
||||
(pub struct $sn:ident { $(pub $n:ident : $t:ty,)* }) => {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct $sn {
|
||||
$(pub $n: $t),*
|
||||
}
|
||||
impl $sn {
|
||||
pub fn parse(args: &[String]) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
Self::_from_session_args(SessionArgs::parse(args)?)
|
||||
}
|
||||
|
||||
pub fn compose(&self) -> Vec<String> {
|
||||
self._to_session_args().compose()
|
||||
}
|
||||
|
||||
fn _from_session_args(sa: SessionArgs) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
Ok(Self {
|
||||
$(
|
||||
$n: sa.0.get(stringify!($n))
|
||||
.ok_or_else(|| Box::<dyn std::error::Error>::from(format!(
|
||||
"argument {} not found", stringify!($n),
|
||||
)))?
|
||||
.parse::<$t>()?
|
||||
),*
|
||||
})
|
||||
}
|
||||
|
||||
fn _to_session_args(&self) -> SessionArgs {
|
||||
let mut map = HashMap::new();
|
||||
$(
|
||||
map.insert(stringify!($n).into(), self.$n.to_string());
|
||||
)*
|
||||
SessionArgs(map)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Maximum count of auth methods enabled for one user.
|
||||
pub const USER_MAX_AUTH_METHODS: usize = 32;
|
||||
|
||||
/// Record of an auth method enabled for a user.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct UserAuthMethodRecord {
|
||||
/// Name of the auth method.
|
||||
pub name: String,
|
||||
|
||||
/// Flags of the auth method.
|
||||
pub flags: UserAuthMethodFlags,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
pub struct UserAuthMethodFlags: u32 {
|
||||
/// Indicates if the auth method is a "main" auth method.
|
||||
///
|
||||
/// A "main" auth method is unique for a user. It provides a master key for secret storage.
|
||||
const MAIN = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Information about an authentication method.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AuthMethodInfo {
|
||||
/// Name of the auth method.
|
||||
pub name: String,
|
||||
|
||||
/// True if the auth method supports providing the master key.
|
||||
pub provides_master_key: bool,
|
||||
|
||||
/// Supported operation modes.
|
||||
pub modes: Vec<ModeInfo>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ModeInfo {
|
||||
/// Name of the operation mode.
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
/// Command line of an authentication method provider.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum AuthCli {
|
||||
/// Queries information about the auth method.
|
||||
///
|
||||
/// The implementor should output an [`AuthMethod`] in JSON to stdout stream, and then exit.
|
||||
QueryInformation,
|
||||
|
||||
/// Begins an authentication session.
|
||||
Authenticate(AuthenticateArgs),
|
||||
|
||||
/// Begins an update session.
|
||||
Update(UpdateArgs),
|
||||
}
|
||||
impl AuthCli {
|
||||
pub fn parse(args: &[String]) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
match args.first().map(String::as_str) {
|
||||
Some("QueryInformation") => Ok(Self::QueryInformation),
|
||||
Some("Authenticate") => Ok(Self::Authenticate(AuthenticateArgs::parse(&args[1..])?)),
|
||||
Some("Update") => Ok(Self::Update(UpdateArgs::parse(&args[1..])?)),
|
||||
Some(unexpected) => Err(Box::from(format!("unexpected command `{unexpected}`"))),
|
||||
None => Err(Box::from("no command specified")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compose(&self) -> Vec<String> {
|
||||
match self {
|
||||
Self::QueryInformation => vec!["QueryInformation".into()],
|
||||
Self::Authenticate(args) => {
|
||||
let mut ret = vec!["Authenticate".into()];
|
||||
ret.append(&mut args.compose());
|
||||
ret
|
||||
}
|
||||
Self::Update(args) => {
|
||||
let mut ret = vec!["Update".into()];
|
||||
ret.append(&mut args.compose());
|
||||
ret
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decl_session_args! {
|
||||
pub struct AuthenticateArgs {
|
||||
pub user_uuid: Uuid,
|
||||
pub mode: String,
|
||||
}
|
||||
}
|
||||
|
||||
decl_session_args! {
|
||||
pub struct UpdateArgs {
|
||||
pub user_uuid: Uuid,
|
||||
pub mode: String,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct SessionArgs(HashMap<String, String>);
|
||||
impl SessionArgs {
|
||||
fn parse(args: &[String]) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let mut map = HashMap::with_capacity(args.len());
|
||||
for arg in args {
|
||||
let Some((key, val)) = arg.split_once('=') else {
|
||||
return Err(Box::from("invalid argument format"));
|
||||
};
|
||||
map.insert(key.into(), val.into());
|
||||
}
|
||||
Ok(Self(map))
|
||||
}
|
||||
|
||||
fn compose(&self) -> Vec<String> {
|
||||
self.0.iter().map(|(k, v)| format!("{k}={v}")).collect()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
use crate::{
|
||||
auth::UserAuthMethodRecord,
|
||||
error::Error,
|
||||
protocol::*,
|
||||
raw_client::Connection,
|
||||
record::{GroupInfo, UserInfo},
|
||||
secret::Secret,
|
||||
};
|
||||
use serde::{Serialize, de::DeserializeOwned};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Client {
|
||||
conn: Connection,
|
||||
buf: Vec<u8>,
|
||||
}
|
||||
impl Client {
|
||||
pub fn connect_default() -> std::io::Result<Self> {
|
||||
Self::connect(&crate::protocol::uri())
|
||||
}
|
||||
|
||||
pub fn connect(uri: &str) -> std::io::Result<Self> {
|
||||
Ok(Self {
|
||||
conn: Connection::connect(uri)?,
|
||||
buf: Vec::with_capacity(512),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn invoke<P: Serialize, R: DeserializeOwned>(
|
||||
&mut self,
|
||||
method: String,
|
||||
params: P,
|
||||
) -> Result<R, Error> {
|
||||
self.buf.clear();
|
||||
let params =
|
||||
serde_json::to_value(params).map_err(|e| Error::Communication(e.to_string()))?;
|
||||
let req = Request { method, params };
|
||||
serde_json::to_writer(&mut self.buf, &req)
|
||||
.map_err(|e| Error::Communication(e.to_string()))?;
|
||||
self.conn
|
||||
.send(&self.buf)
|
||||
.map_err(|e| Error::Communication(e.to_string()))?;
|
||||
self.conn
|
||||
.recv(&mut self.buf)
|
||||
.map_err(|e| Error::Communication(e.to_string()))?;
|
||||
let resp: Response =
|
||||
serde_json::from_slice(&self.buf).map_err(|e| Error::Communication(e.to_string()))?;
|
||||
resp.into_result()
|
||||
.map(|x| serde_json::from_value(x).map_err(|e| Error::Communication(e.to_string())))
|
||||
.flatten()
|
||||
}
|
||||
|
||||
pub fn get_user_info(&mut self, args: GetUserInfoArgs) -> Result<UserInfo, Error> {
|
||||
self.invoke(GET_USER_INFO.into(), args)
|
||||
}
|
||||
|
||||
pub fn get_group_info(&mut self, args: GetGroupInfoArgs) -> Result<GroupInfo, Error> {
|
||||
self.invoke(GET_GROUP_INFO.into(), args)
|
||||
}
|
||||
|
||||
pub fn get_secret(&mut self, args: GetSecretArgs) -> Result<Secret, Error> {
|
||||
self.invoke(GET_SECRET.into(), args)
|
||||
}
|
||||
|
||||
pub fn set_secret(&mut self, args: SetSecretArgs) -> Result<(), Error> {
|
||||
self.invoke(SET_SECRET.into(), args)
|
||||
}
|
||||
|
||||
pub fn provide_master_key(&mut self, args: ProvideMasterKeyArgs) -> Result<(), Error> {
|
||||
self.invoke(PROVIDE_MASTER_KEY.into(), args)
|
||||
}
|
||||
|
||||
pub fn clear_master_key(&mut self, args: ClearMasterKeyArgs) -> Result<(), Error> {
|
||||
self.invoke(CLEAR_MASTER_KEY.into(), args)
|
||||
}
|
||||
|
||||
pub fn get_user_auth_methods(
|
||||
&mut self,
|
||||
args: GetUserAuthMethodsArgs,
|
||||
) -> Result<Vec<UserAuthMethodRecord>, Error> {
|
||||
self.invoke(GET_USER_AUTH_METHODS.into(), args)
|
||||
}
|
||||
|
||||
pub fn get_auth_method_path(&mut self, args: GetAuthMethodPathArgs) -> Result<PathBuf, Error> {
|
||||
self.invoke(GET_AUTH_METHOD_PATH.into(), args)
|
||||
}
|
||||
|
||||
pub fn user_add_auth_method(&mut self, args: UserAddAuthMethodArgs) -> Result<(), Error> {
|
||||
self.invoke(USER_ADD_AUTH_METHOD.into(), args)
|
||||
}
|
||||
|
||||
pub fn create_user(&mut self, args: CreateUserArgs) -> Result<(), Error> {
|
||||
self.invoke(CREATE_USER.into(), args)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
//! Errors.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Display;
|
||||
|
||||
/// An error.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, thiserror::Error)]
|
||||
pub enum Error {
|
||||
/// Communication error.
|
||||
#[error("communication error: {0}")]
|
||||
Communication(String),
|
||||
|
||||
/// The specified method is not implemented.
|
||||
#[error("`{0}`: Not implemented")]
|
||||
NotImplemented(String),
|
||||
|
||||
/// Parameters are not suitable for the called method.
|
||||
#[error("invalid parameters: {0}")]
|
||||
InvalidParams(String),
|
||||
|
||||
/// The user has not enough permission to perform the action.
|
||||
#[error("permission denied")]
|
||||
PermissionDenied,
|
||||
|
||||
/// The operation operates on a user, but the specified user does not exist.
|
||||
#[error("no such user")]
|
||||
NoSuchUser,
|
||||
|
||||
/// The operation operates on a group, but the specified group does not exist.
|
||||
#[error("no such group")]
|
||||
NoSuchGroup,
|
||||
|
||||
/// The operation operates on a secret, but the specified secret does not exist.
|
||||
#[error("no such secret")]
|
||||
NoSuchSecret,
|
||||
|
||||
/// Attempted to launch an auth method that is not installed on current system.
|
||||
#[error("no such auth method")]
|
||||
NoSuchAuthMethod,
|
||||
|
||||
/// Attempted to authenticate/update authentication via an auth method that is not activated for the user.
|
||||
///
|
||||
/// **NOTE**: This does not imply that the auth method is installed on current system.
|
||||
#[error("the specified auth method is not activated for the user")]
|
||||
NotActivatedAuthMethod,
|
||||
|
||||
/// TODO
|
||||
#[error("already exists")]
|
||||
AlreadyExists,
|
||||
|
||||
/// An internal error.
|
||||
#[error("internal error: {0}")]
|
||||
Internal(String),
|
||||
}
|
||||
impl Error {
|
||||
pub fn make_internal(x: impl Display) -> Self {
|
||||
Self::Internal(x.to_string())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
pub mod auth;
|
||||
pub mod error;
|
||||
pub mod protocol;
|
||||
pub mod record;
|
||||
pub mod secret;
|
||||
pub mod wellknown;
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_family = "unix")] {
|
||||
#[path = "raw_client_unix.rs"]
|
||||
pub mod raw_client;
|
||||
} else {
|
||||
std::compile_error!("target not supported");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
pub mod client;
|
||||
@@ -0,0 +1,266 @@
|
||||
use crate::{
|
||||
error::Error,
|
||||
record::{GroupInfo, HostGid, HostUid, SecureTag},
|
||||
secret::{MasterKeyProvision, Secret},
|
||||
};
|
||||
use cfg_if::cfg_if;
|
||||
use serde::{Deserialize, Serialize, de::DeserializeOwned};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Max length of a single message.
|
||||
pub const MAX_MESSAGE_LEN: usize = 128 * 1024;
|
||||
|
||||
// ==- IPC URI -==
|
||||
cfg_if! {
|
||||
if #[cfg(target_os = "linux")] {
|
||||
pub const DEFAULT_URI: &str = "unix://@verified:org.semilabs.os/accountd";
|
||||
} else if #[cfg(target_family = "unix")] {
|
||||
pub const DEFAULT_URI: &str = "unix:///var/run/accountd.sock";
|
||||
} else {
|
||||
pub const DEFAULT_URI: &str = "";
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub enum Uri {
|
||||
Unix(std::path::PathBuf),
|
||||
UnixAbstract(String),
|
||||
}
|
||||
impl std::str::FromStr for Uri {
|
||||
type Err = std::io::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if let Some(unix) = s.strip_prefix("unix://") {
|
||||
if unix.starts_with('/') {
|
||||
Ok(Self::Unix(unix.into()))
|
||||
} else if unix.starts_with('@') {
|
||||
Ok(Self::UnixAbstract(unix[1..].into()))
|
||||
} else {
|
||||
Err(std::io::ErrorKind::AddrNotAvailable.into())
|
||||
}
|
||||
} else {
|
||||
Err(std::io::ErrorKind::AddrNotAvailable.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets IPC URI.
|
||||
pub fn uri() -> String {
|
||||
std::env::var("ACCOUNT_IPC_URI").unwrap_or_else(|_| DEFAULT_URI.into())
|
||||
}
|
||||
|
||||
// ==- Basic Definitions -==
|
||||
|
||||
/// A request.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Request {
|
||||
pub method: String,
|
||||
pub params: serde_json::Value,
|
||||
}
|
||||
impl Request {
|
||||
pub fn params<T: DeserializeOwned>(self) -> Result<T, Error> {
|
||||
serde_json::from_value(self.params).map_err(|e| Error::InvalidParams(e.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
/// A response.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Response {
|
||||
pub success: bool,
|
||||
pub value: serde_json::Value,
|
||||
}
|
||||
impl Response {
|
||||
pub fn from_result<R: Serialize>(result: Result<R, Error>) -> Self {
|
||||
let success = result.is_ok();
|
||||
let value = match result {
|
||||
Ok(x) => serde_json::to_value(x).expect("response not serialized"),
|
||||
Err(e) => serde_json::to_value(e).expect("response not serialized"),
|
||||
};
|
||||
Self { success, value }
|
||||
}
|
||||
|
||||
pub fn into_result<R: DeserializeOwned>(self) -> Result<R, Error> {
|
||||
if self.success {
|
||||
Ok(serde_json::from_value(self.value)
|
||||
.map_err(|x| Error::InvalidParams(x.to_string()))?)
|
||||
} else {
|
||||
Err(serde_json::from_value(self.value)
|
||||
.map_err(|x| Error::InvalidParams(x.to_string()))?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==- Method Names: Querying Server Information -==
|
||||
|
||||
pub const SERVER_VERSION: &str = "ServerVersion";
|
||||
|
||||
// ==- Methods: Querying Records -==
|
||||
|
||||
pub const LIST_USER: &str = "ListUser";
|
||||
pub const LIST_GROUP: &str = "ListGroup";
|
||||
pub const GET_USER_INFO: &str = "GetUserInfo";
|
||||
pub const GET_GROUP_INFO: &str = "GetGroupInfo";
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ListUserArgs {
|
||||
pub start: u32,
|
||||
pub len: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ListGroupArgs {
|
||||
pub start: u32,
|
||||
pub len: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GetUserInfoArgs {
|
||||
Uuid(Uuid),
|
||||
Username(String),
|
||||
HostUid(HostUid),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GetGroupInfoArgs {
|
||||
Uuid(Uuid),
|
||||
Groupname(String),
|
||||
HostGid(HostGid),
|
||||
}
|
||||
|
||||
// ==- Methods: Managed Secrets -==
|
||||
|
||||
pub const GET_SECRET: &str = "GetSecret";
|
||||
pub const SET_SECRET: &str = "SetSecret";
|
||||
pub const REMOVE_SECRET: &str = "RemoveSecret";
|
||||
pub const PROVIDE_MASTER_KEY: &str = "ProvideMasterKey";
|
||||
pub const CLEAR_MASTER_KEY: &str = "ClearMasterKey";
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct GetSecretArgs {
|
||||
pub user: Uuid,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SetSecretArgs {
|
||||
pub user: Uuid,
|
||||
pub secret: Secret,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RemoveSecretArgs {
|
||||
pub user: Uuid,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ProvideMasterKeyArgs {
|
||||
pub user: Uuid,
|
||||
pub provision: MasterKeyProvision,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ClearMasterKeyArgs {
|
||||
pub user: Uuid,
|
||||
}
|
||||
|
||||
// ==- Methods: User Management -==
|
||||
|
||||
pub const CREATE_USER: &str = "CreateUser";
|
||||
pub const REMOVE_USER: &str = "RemoveUser";
|
||||
pub const CREATE_GROUP: &str = "CreateGroup";
|
||||
pub const REMOVE_GROUP: &str = "RemoveGroup";
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CreateUserArgs {
|
||||
/// User name.
|
||||
pub username: String,
|
||||
|
||||
/// Full name.
|
||||
pub fullname: Option<String>,
|
||||
|
||||
/// Specify a host UID.
|
||||
pub host_uid: Option<HostUid>,
|
||||
|
||||
/// User description.
|
||||
#[serde(default)]
|
||||
pub description: String,
|
||||
|
||||
/// Secure tags.
|
||||
#[serde(default)]
|
||||
pub secure_tags: HashSet<SecureTag>,
|
||||
|
||||
/// Extra records.
|
||||
#[serde(default)]
|
||||
pub extra_records: HashMap<String, String>,
|
||||
|
||||
/// Defaults of the user.
|
||||
#[serde(default)]
|
||||
pub defaults: HashMap<String, String>,
|
||||
|
||||
/// Home directory of the user.
|
||||
pub home_directory: Option<String>,
|
||||
|
||||
/// Initial groups of the newly created user.
|
||||
#[serde(default)]
|
||||
pub groups: Vec<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RemoveUserArgs {
|
||||
/// UUID of the user to be removed.
|
||||
pub uuid: Uuid,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CreateGroupArgs {
|
||||
/// Group name.
|
||||
pub groupname: String,
|
||||
|
||||
/// Full group name.
|
||||
pub fullname: Option<String>,
|
||||
|
||||
/// Group ID on current host.
|
||||
pub host_gid: Option<HostGid>,
|
||||
|
||||
/// Group description.
|
||||
#[serde(default)]
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RemoveGroupArgs {
|
||||
/// UUID of the group to be removed.
|
||||
pub uuid: Uuid,
|
||||
}
|
||||
|
||||
// ==- Methods: Authentication -==
|
||||
|
||||
pub const GET_USER_AUTH_METHODS: &str = "GetUserAuthMethods";
|
||||
pub const GET_AUTH_METHOD_PATH: &str = "GetAuthMethodPath";
|
||||
pub const USER_ADD_AUTH_METHOD: &str = "UserAddAuthMethod";
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct GetUserAuthMethodsArgs {
|
||||
/// UUID of the user.
|
||||
pub user: Uuid,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct GetAuthMethodPathArgs {
|
||||
/// Name of the auth method.
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct UserAddAuthMethodArgs {
|
||||
/// UUID of the user.
|
||||
pub user: Uuid,
|
||||
|
||||
/// Name of the auth method.
|
||||
pub auth_method: String,
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
use crate::protocol::{MAX_MESSAGE_LEN, Uri};
|
||||
use cfg_if::cfg_if;
|
||||
use std::{
|
||||
io::{Read, Write},
|
||||
os::unix::net::{SocketAddr, UnixStream},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Connection(UnixStream);
|
||||
impl Connection {
|
||||
pub fn connect(uri: &str) -> std::io::Result<Self> {
|
||||
let uri: Uri = uri.parse()?;
|
||||
let sockaddr = match uri {
|
||||
Uri::Unix(path) => SocketAddr::from_pathname(path)?,
|
||||
Uri::UnixAbstract(_name) => {
|
||||
cfg_if! {
|
||||
if #[cfg(target_os = "linux")] {
|
||||
use std::os::linux::net::SocketAddrExt;
|
||||
SocketAddr::from_abstract_name(_name.as_bytes())?
|
||||
} else {
|
||||
return Err(std::io::ErrorKind::Unsupported.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(Self(UnixStream::connect_addr(&sockaddr)?))
|
||||
}
|
||||
|
||||
pub fn send(&mut self, data: &[u8]) -> std::io::Result<()> {
|
||||
if data.len() > MAX_MESSAGE_LEN {
|
||||
return Err(std::io::ErrorKind::FileTooLarge.into());
|
||||
}
|
||||
self.0.write_all(&(data.len() as u32).to_le_bytes())?;
|
||||
self.0.write(data)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn recv(&mut self, buf: &mut Vec<u8>) -> std::io::Result<()> {
|
||||
let mut len = [0u8; size_of::<u32>()];
|
||||
self.0.read_exact(&mut len)?;
|
||||
let len = u32::from_le_bytes(len) as usize;
|
||||
if len > MAX_MESSAGE_LEN {
|
||||
return Err(std::io::ErrorKind::FileTooLarge.into());
|
||||
}
|
||||
buf.resize(len, 0);
|
||||
self.0.read_exact(buf)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
//! Basic user information records.
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use uuid::Uuid;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(target_family = "unix")] {
|
||||
pub type HostUid = u32;
|
||||
pub type HostGid = u32;
|
||||
} else if #[cfg(target_family = "windows")] {
|
||||
pub type HostUid = String;
|
||||
pub type HostGid = String;
|
||||
} else {
|
||||
pub type HostUid = u64;
|
||||
pub type HostGid = u64;
|
||||
}
|
||||
}
|
||||
|
||||
/// Basical user information.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct UserInfo {
|
||||
/// User UUID.
|
||||
pub uuid: Uuid,
|
||||
|
||||
/// Username.
|
||||
pub username: String,
|
||||
|
||||
/// User ID on current host.
|
||||
pub host_uid: Option<HostUid>,
|
||||
|
||||
/// Full name.
|
||||
pub fullname: String,
|
||||
|
||||
/// User description.
|
||||
pub description: String,
|
||||
|
||||
/// Secure tags.
|
||||
pub secure_tags: HashSet<SecureTag>,
|
||||
|
||||
/// Extra records.
|
||||
pub extra_records: HashMap<String, String>,
|
||||
|
||||
/// Defaults of the user.
|
||||
pub defaults: HashMap<String, String>,
|
||||
|
||||
/// Home directory of the user.
|
||||
pub home_directory: Option<String>,
|
||||
|
||||
/// Time the user is created.
|
||||
pub creation_time: i64,
|
||||
|
||||
/// Time the user is last logged in.
|
||||
pub last_login_time: i64,
|
||||
}
|
||||
impl UserInfo {
|
||||
pub fn clear_host_specific(&mut self) {
|
||||
self.host_uid = None;
|
||||
}
|
||||
}
|
||||
|
||||
/// User secure tag.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Hash, PartialEq, Eq)]
|
||||
pub enum SecureTag {
|
||||
/// Indicates that the user can be logged in.
|
||||
Login,
|
||||
|
||||
/// Indicates that the user may create another user.
|
||||
CreateUser,
|
||||
|
||||
/// Indicates that the user may remove users.
|
||||
RemoveUser,
|
||||
|
||||
/// Read secrets of other users.
|
||||
ReadSecret,
|
||||
|
||||
/// Set or delete secrets of other users.
|
||||
WriteSecret,
|
||||
}
|
||||
|
||||
/// Basical group information.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct GroupInfo {
|
||||
/// Group UUID.
|
||||
pub uuid: Uuid,
|
||||
|
||||
/// Group name.
|
||||
pub groupname: String,
|
||||
|
||||
/// Group ID on current host.
|
||||
pub host_gid: Option<HostGid>,
|
||||
|
||||
/// Full group name.
|
||||
pub fullname: String,
|
||||
|
||||
/// Group description.
|
||||
pub description: String,
|
||||
|
||||
/// Time the group is created.
|
||||
pub creation_time: i64,
|
||||
}
|
||||
impl GroupInfo {
|
||||
pub fn clear_host_specific(&mut self) {
|
||||
self.host_gid = None;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
//! User managed secrets.
|
||||
//!
|
||||
//! Managed Secrets is a feature that allows applications host their secrets (e.g. password database keys) here,
|
||||
//! and get the secrets later with proper authentication, like Apple Keychain and KDE KWallet.
|
||||
|
||||
use bitflags::bitflags;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Maximum length of a secret name.
|
||||
pub const SECRET_NAME_LEN: usize = 1024;
|
||||
|
||||
/// Maximum length of a managed secret item.
|
||||
pub const SECRET_MAX_LEN: usize = 4096;
|
||||
|
||||
/// Maximum count of managed secrets owned by a user.
|
||||
pub const MAX_SECRET_COUNT: usize = 16384;
|
||||
|
||||
/// A managed secret item.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Secret {
|
||||
/// Name of the secret.
|
||||
pub name: String,
|
||||
|
||||
/// Data of the secret.
|
||||
pub data: Vec<u8>,
|
||||
|
||||
/// Security flags of the secret.
|
||||
pub security_flags: SecurityFlags,
|
||||
|
||||
/// Time the secret is created.
|
||||
pub creation_time: i64,
|
||||
|
||||
/// Time the secret will be expired.
|
||||
pub expiration_time: i64,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct SecurityFlags: u32 {
|
||||
const ENCRYPTED = 1;
|
||||
const WRITE_PROTECTED = 2;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct MasterKeyProvision {
|
||||
pub algorithm: String,
|
||||
pub key: Vec<u8>,
|
||||
pub expiration_time: i64,
|
||||
}
|
||||
impl MasterKeyProvision {
|
||||
pub const ALGORITHM_AES_256_GCM: &str = "AES-256-GCM";
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
//! "Well-known" strings.
|
||||
|
||||
/// Command-line login shell. Used in "defaults" field of a user.
|
||||
pub const CLI_LOGIN_SHELL: &str = "CliLoginShell";
|
||||
|
||||
/// Birth date. Used in "extra_records" field of a user.
|
||||
pub const BIRTH_DATE: &str = "BirthDate";
|
||||
|
||||
/// Gender. Used in "extra_records" field of a user.
|
||||
pub const GENDER: &str = "Gender";
|
||||
|
||||
/// Name of the GUI interactive operation mode.
|
||||
pub const OP_MODE_GUI: &str = "GUI";
|
||||
|
||||
/// Name of the CLI interactive operation mode.
|
||||
pub const OP_MODE_CLI: &str = "CLI";
|
||||
|
||||
/// Name of the API non-interactive operation mode.
|
||||
pub const OP_MODE_API: &str = "API";
|
||||
Reference in New Issue
Block a user