feat: add host id allocation
Signed-off-by: sisungo <[email protected]>
This commit is contained in:
@@ -0,0 +1,38 @@
|
|||||||
|
use crate::client;
|
||||||
|
use clap::Parser;
|
||||||
|
use semios_account::{
|
||||||
|
protocol::{CreateGroupArgs, HostIdRule},
|
||||||
|
record::HostGid,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Parser)]
|
||||||
|
pub struct Cli {
|
||||||
|
#[arg(short = 'D', long, default_value_t)]
|
||||||
|
description: String,
|
||||||
|
|
||||||
|
#[arg(short = 'N', long)]
|
||||||
|
fullname: Option<String>,
|
||||||
|
|
||||||
|
#[arg(short = 'G', long)]
|
||||||
|
host_gid: Option<HostGid>,
|
||||||
|
|
||||||
|
groupname: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main(cli: Cli) -> anyhow::Result<()> {
|
||||||
|
let mut client = client()?;
|
||||||
|
|
||||||
|
let host_gid = match cli.host_gid {
|
||||||
|
Some(x) => HostIdRule::Manual(x),
|
||||||
|
None => HostIdRule::Auto(1000, HostGid::MAX),
|
||||||
|
};
|
||||||
|
|
||||||
|
client.create_group(CreateGroupArgs {
|
||||||
|
groupname: cli.groupname,
|
||||||
|
fullname: cli.fullname,
|
||||||
|
host_gid,
|
||||||
|
description: cli.description,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ use crate::client;
|
|||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use semios_account::{
|
use semios_account::{
|
||||||
protocol::CreateUserArgs,
|
protocol::{CreateUserArgs, HostIdRule},
|
||||||
record::{HostUid, SecureTag},
|
record::{HostUid, SecureTag},
|
||||||
wellknown,
|
wellknown,
|
||||||
};
|
};
|
||||||
@@ -67,10 +67,15 @@ pub fn main(cli: Cli) -> anyhow::Result<()> {
|
|||||||
let mut defaults = HashMap::new();
|
let mut defaults = HashMap::new();
|
||||||
defaults.insert(wellknown::CLI_LOGIN_SHELL.into(), cli.cli_shell);
|
defaults.insert(wellknown::CLI_LOGIN_SHELL.into(), cli.cli_shell);
|
||||||
|
|
||||||
|
let host_uid = match cli.host_uid {
|
||||||
|
Some(x) => HostIdRule::Manual(x),
|
||||||
|
None => HostIdRule::Auto(1000, HostUid::MAX),
|
||||||
|
};
|
||||||
|
|
||||||
client.create_user(CreateUserArgs {
|
client.create_user(CreateUserArgs {
|
||||||
username: cli.username,
|
username: cli.username,
|
||||||
fullname: cli.fullname,
|
fullname: cli.fullname,
|
||||||
host_uid: cli.host_uid,
|
host_uid,
|
||||||
description: cli.description,
|
description: cli.description,
|
||||||
secure_tags,
|
secure_tags,
|
||||||
extra_records: Default::default(),
|
extra_records: Default::default(),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
mod authenticate;
|
mod authenticate;
|
||||||
|
mod create_group;
|
||||||
mod create_user;
|
mod create_user;
|
||||||
mod info;
|
mod info;
|
||||||
mod update_auth;
|
mod update_auth;
|
||||||
@@ -15,6 +16,9 @@ pub enum Cli {
|
|||||||
/// Create new user
|
/// Create new user
|
||||||
CreateUser(create_user::Cli),
|
CreateUser(create_user::Cli),
|
||||||
|
|
||||||
|
/// Create new group
|
||||||
|
CreateGroup(create_group::Cli),
|
||||||
|
|
||||||
/// Authenticate user
|
/// Authenticate user
|
||||||
Authenticate(authenticate::Cli),
|
Authenticate(authenticate::Cli),
|
||||||
|
|
||||||
@@ -28,6 +32,7 @@ fn main() {
|
|||||||
let result = match cli {
|
let result = match cli {
|
||||||
Cli::Info(cli) => info::main(cli),
|
Cli::Info(cli) => info::main(cli),
|
||||||
Cli::CreateUser(cli) => create_user::main(cli),
|
Cli::CreateUser(cli) => create_user::main(cli),
|
||||||
|
Cli::CreateGroup(cli) => create_group::main(cli),
|
||||||
Cli::Authenticate(cli) => authenticate::main(cli),
|
Cli::Authenticate(cli) => authenticate::main(cli),
|
||||||
Cli::UpdateAuth(cli) => update_auth::main(cli),
|
Cli::UpdateAuth(cli) => update_auth::main(cli),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use semios_account::{
|
|||||||
record::{GroupInfo, SecureTag, UserInfo},
|
record::{GroupInfo, SecureTag, UserInfo},
|
||||||
secret::Secret,
|
secret::Secret,
|
||||||
};
|
};
|
||||||
use std::{fmt::Debug, path::PathBuf, pin::Pin, sync::Arc};
|
use std::{fmt::Debug, path::PathBuf, pin::Pin, range::RangeInclusive, sync::Arc};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub async fn launch_server(app_state: Arc<AppState>) -> anyhow::Result<()> {
|
pub async fn launch_server(app_state: Arc<AppState>) -> anyhow::Result<()> {
|
||||||
@@ -215,11 +215,21 @@ async fn create_user(
|
|||||||
args: CreateUserArgs,
|
args: CreateUserArgs,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
require_secure_tags(&state, caller, &[SecureTag::CreateUser]).await?;
|
require_secure_tags(&state, caller, &[SecureTag::CreateUser]).await?;
|
||||||
|
let host_uid = match args.host_uid {
|
||||||
|
HostIdRule::Unallocated => None,
|
||||||
|
HostIdRule::Manual(x) => Some(x),
|
||||||
|
HostIdRule::Auto(a, b) => Some(
|
||||||
|
state
|
||||||
|
.local_db
|
||||||
|
.allocate_host_uid(RangeInclusive::from(a..=b))
|
||||||
|
.ok_or(Error::AllocHostId)?,
|
||||||
|
),
|
||||||
|
};
|
||||||
let user_info = UserInfo {
|
let user_info = UserInfo {
|
||||||
uuid: Uuid::new_v7(uuid::Timestamp::now(uuid::ContextV7::new())),
|
uuid: Uuid::new_v7(uuid::Timestamp::now(uuid::ContextV7::new())),
|
||||||
fullname: args.fullname.unwrap_or_else(|| args.username.clone()),
|
fullname: args.fullname.unwrap_or_else(|| args.username.clone()),
|
||||||
username: args.username,
|
username: args.username,
|
||||||
host_uid: args.host_uid,
|
host_uid,
|
||||||
description: args.description,
|
description: args.description,
|
||||||
secure_tags: args.secure_tags,
|
secure_tags: args.secure_tags,
|
||||||
extra_records: args.extra_records,
|
extra_records: args.extra_records,
|
||||||
@@ -241,11 +251,21 @@ async fn create_group(
|
|||||||
args: CreateGroupArgs,
|
args: CreateGroupArgs,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
require_secure_tags(&state, caller, &[SecureTag::CreateUser]).await?;
|
require_secure_tags(&state, caller, &[SecureTag::CreateUser]).await?;
|
||||||
|
let host_gid = match args.host_gid {
|
||||||
|
HostIdRule::Unallocated => None,
|
||||||
|
HostIdRule::Manual(x) => Some(x),
|
||||||
|
HostIdRule::Auto(a, b) => Some(
|
||||||
|
state
|
||||||
|
.local_db
|
||||||
|
.allocate_host_gid(RangeInclusive::from(a..=b))
|
||||||
|
.ok_or(Error::AllocHostId)?,
|
||||||
|
),
|
||||||
|
};
|
||||||
let group_info = GroupInfo {
|
let group_info = GroupInfo {
|
||||||
uuid: Uuid::new_v7(uuid::Timestamp::now(uuid::ContextV7::new())),
|
uuid: Uuid::new_v7(uuid::Timestamp::now(uuid::ContextV7::new())),
|
||||||
fullname: args.fullname.unwrap_or_else(|| args.groupname.clone()),
|
fullname: args.fullname.unwrap_or_else(|| args.groupname.clone()),
|
||||||
groupname: args.groupname,
|
groupname: args.groupname,
|
||||||
host_gid: args.host_gid,
|
host_gid,
|
||||||
description: args.description,
|
description: args.description,
|
||||||
creation_time: timestamp_s(),
|
creation_time: timestamp_s(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use rusqlite::params;
|
use rusqlite::{OptionalExtension, ToSql, params, types::FromSql};
|
||||||
use semios_account::{
|
use semios_account::{
|
||||||
auth::{UserAuthMethodFlags, UserAuthMethodRecord},
|
auth::{UserAuthMethodFlags, UserAuthMethodRecord},
|
||||||
error::Error,
|
error::Error,
|
||||||
@@ -6,7 +6,7 @@ use semios_account::{
|
|||||||
secret::{Secret, SecurityFlags},
|
secret::{Secret, SecurityFlags},
|
||||||
};
|
};
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use std::{path::Path, sync::Mutex};
|
use std::{path::Path, range::RangeInclusive, sync::Mutex};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
/// Schema of the database.
|
/// Schema of the database.
|
||||||
@@ -285,6 +285,18 @@ impl LocalDb {
|
|||||||
self._find_uuid_by("group", "group_host_gid", &host_gid.to_string())
|
self._find_uuid_by("group", "group_host_gid", &host_gid.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn allocate_host_uid(&self, range: RangeInclusive<HostUid>) -> Option<HostUid> {
|
||||||
|
self._allocate_host_id_max("user", "user_host_uid", range)
|
||||||
|
.ok()
|
||||||
|
.map(|x| x.map(|y| y + 1).unwrap_or(range.start))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn allocate_host_gid(&self, range: RangeInclusive<HostGid>) -> Option<HostGid> {
|
||||||
|
self._allocate_host_id_max("group", "group_host_gid", range)
|
||||||
|
.ok()
|
||||||
|
.map(|x| x.map(|y| y + 1).unwrap_or(range.start))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_first_run(&self) -> bool {
|
pub fn is_first_run(&self) -> bool {
|
||||||
!self._kv_get("initialized").is_some()
|
!self._kv_get("initialized").is_some()
|
||||||
}
|
}
|
||||||
@@ -349,6 +361,25 @@ impl LocalDb {
|
|||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn _allocate_host_id_max<T: FromSql + ToSql + PartialEq>(
|
||||||
|
&self,
|
||||||
|
table: &'static str,
|
||||||
|
column: &'static str,
|
||||||
|
range: RangeInclusive<T>,
|
||||||
|
) -> rusqlite::Result<Option<T>> {
|
||||||
|
let lock = self.0.lock().unwrap();
|
||||||
|
let mut stmt = lock.prepare_cached(&format!(
|
||||||
|
"SELECT MAX(CAST(\"{column}\" AS INTEGER))
|
||||||
|
FROM \"{table}\"
|
||||||
|
WHERE CAST(\"{column}\" AS INTEGER) BETWEEN ?1 AND ?2"
|
||||||
|
))?;
|
||||||
|
let max: Option<T> = stmt.query_one(params![range.start, range.last], |row| row.get(0))?;
|
||||||
|
if max == Some(range.last) {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
Ok(max)
|
||||||
|
}
|
||||||
|
|
||||||
fn _kv_get(&self, key: &str) -> Option<String> {
|
fn _kv_get(&self, key: &str) -> Option<String> {
|
||||||
let lock = self.0.lock().unwrap();
|
let lock = self.0.lock().unwrap();
|
||||||
let mut stmt = lock
|
let mut stmt = lock
|
||||||
|
|||||||
@@ -92,4 +92,8 @@ impl Client {
|
|||||||
pub fn create_user(&mut self, args: CreateUserArgs) -> Result<(), Error> {
|
pub fn create_user(&mut self, args: CreateUserArgs) -> Result<(), Error> {
|
||||||
self.invoke(CREATE_USER.into(), args)
|
self.invoke(CREATE_USER.into(), args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_group(&mut self, args: CreateGroupArgs) -> Result<(), Error> {
|
||||||
|
self.invoke(CREATE_GROUP.into(), args)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,10 @@ pub enum Error {
|
|||||||
#[error("the specified auth method is not activated for the user")]
|
#[error("the specified auth method is not activated for the user")]
|
||||||
NotActivatedAuthMethod,
|
NotActivatedAuthMethod,
|
||||||
|
|
||||||
|
/// Cannot allocate host ID
|
||||||
|
#[error("cannot allocate host id")]
|
||||||
|
AllocHostId,
|
||||||
|
|
||||||
/// TODO
|
/// TODO
|
||||||
#[error("already exists")]
|
#[error("already exists")]
|
||||||
AlreadyExists,
|
AlreadyExists,
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ pub struct CreateUserArgs {
|
|||||||
pub fullname: Option<String>,
|
pub fullname: Option<String>,
|
||||||
|
|
||||||
/// Specify a host UID.
|
/// Specify a host UID.
|
||||||
pub host_uid: Option<HostUid>,
|
pub host_uid: HostIdRule<HostUid>,
|
||||||
|
|
||||||
/// User description.
|
/// User description.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -225,7 +225,7 @@ pub struct CreateGroupArgs {
|
|||||||
pub fullname: Option<String>,
|
pub fullname: Option<String>,
|
||||||
|
|
||||||
/// Group ID on current host.
|
/// Group ID on current host.
|
||||||
pub host_gid: Option<HostGid>,
|
pub host_gid: HostIdRule<HostGid>,
|
||||||
|
|
||||||
/// Group description.
|
/// Group description.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -238,6 +238,14 @@ pub struct RemoveGroupArgs {
|
|||||||
pub uuid: Uuid,
|
pub uuid: Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum HostIdRule<T> {
|
||||||
|
Unallocated,
|
||||||
|
Auto(T, T),
|
||||||
|
Manual(T),
|
||||||
|
}
|
||||||
|
|
||||||
// ==- Methods: Authentication -==
|
// ==- Methods: Authentication -==
|
||||||
|
|
||||||
pub const GET_USER_AUTH_METHODS: &str = "GetUserAuthMethods";
|
pub const GET_USER_AUTH_METHODS: &str = "GetUserAuthMethods";
|
||||||
|
|||||||
Reference in New Issue
Block a user