diff options
| author | KunoiSayami <[email protected]> | 2025-03-25 22:43:40 +0800 |
|---|---|---|
| committer | KunoiSayami <[email protected]> | 2025-03-25 22:43:40 +0800 |
| commit | 8b091d4ddc499be6bc4ce4d430a0c09f433f9884 (patch) | |
| tree | 79504607082a248a1ad751bb4c88a4eb02c9f8e0 | |
| parent | 645cda1b2faecb44b9c819634c412443f27d943c (diff) | |
Signed-off-by: KunoiSayami <[email protected]>
| -rw-r--r-- | Cargo.lock | 109 | ||||
| -rw-r--r-- | src/authentication.rs | 598 | ||||
| -rw-r--r-- | src/main.rs | 623 | ||||
| -rw-r--r-- | src/test.rs | 9 |
4 files changed, 680 insertions, 659 deletions
@@ -241,9 +241,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.16" +version = "1.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" +checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a" dependencies = [ "shlex", ] @@ -650,9 +650,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "form_urlencoded" @@ -758,14 +758,14 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", "libc", - "wasi 0.13.3+wasi-0.2.2", - "windows-targets 0.52.6", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", ] [[package]] @@ -782,9 +782,9 @@ checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "handlebars" -version = "6.3.1" +version = "6.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d752747ddabc4c1a70dd28e72f2e3c218a816773e0d7faf67433f1acfa6cba7c" +checksum = "759e2d5aea3287cb1190c8ec394f42866cb5bf74fcbf213f354e3c856ea26098" dependencies = [ "derive_builder", "log", @@ -863,14 +863,15 @@ checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" [[package]] name = "iana-time-zone" -version = "0.1.61" +version = "0.1.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "b2fd658b06e56721792c5df4475705b6cda790e9298d19d2f8af083457bcd127" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", "windows-core", ] @@ -1071,9 +1072,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d699bc6dfc879fb1bf9bdff0d4c56f0884fc6f0d0eb0fba397a6d00cd9a6b85e" +checksum = "c102670231191d07d37a35af3eb77f1f0dbf7a71be51a962dcd57ea607be7260" dependencies = [ "jiff-static", "log", @@ -1084,9 +1085,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d16e75759ee0aa64c57a56acbf43916987b20c77373cb7e808979e02b93c9f9" +checksum = "4cdde31a9d349f1b1f51a0b3714a5940ac022976f4b49485fc04be052b183b4c" dependencies = [ "proc-macro2", "quote", @@ -1165,9 +1166,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.26" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" dependencies = [ "serde", ] @@ -1444,9 +1445,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.15" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" +checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6" dependencies = [ "memchr", "thiserror 2.0.12", @@ -1455,9 +1456,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.15" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" +checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5" dependencies = [ "pest", "pest_generator", @@ -1465,9 +1466,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.15" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" +checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841" dependencies = [ "pest", "pest_meta", @@ -1478,9 +1479,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.15" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" +checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0" dependencies = [ "once_cell", "pest", @@ -1569,6 +1570,12 @@ dependencies = [ ] [[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1625,14 +1632,14 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.1", + "getrandom 0.3.2", ] [[package]] name = "redis" -version = "0.29.1" +version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8034fb926579ff49d3fe58d288d5dcb580bf11e9bccd33224b45adebf0fd0c23" +checksum = "b110459d6e323b7cda23980c46c77157601199c9da6241552b284cd565a7a133" dependencies = [ "arc-swap", "bytes", @@ -1736,9 +1743,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7178faa4b75a30e269c71e61c353ce2748cf3d76f0c44c393f4e60abf49b825" +checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96" dependencies = [ "bitflags", "errno", @@ -1749,9 +1756,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.23" +version = "0.23.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" +checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c" dependencies = [ "once_cell", "ring", @@ -1778,9 +1785,9 @@ checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" [[package]] name = "rustls-webpki" -version = "0.102.8" +version = "0.103.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "0aa4eeac2588ffff23e9d7a7e9b3f971c5fb5b7ebc9452745e0c232c64f83b2f" dependencies = [ "ring", "rustls-pki-types", @@ -2235,12 +2242,12 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488960f40a3fd53d72c2a29a58722561dee8afdd175bd88e3db4677d7b2ba600" +checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" dependencies = [ "fastrand", - "getrandom 0.3.1", + "getrandom 0.3.2", "once_cell", "rustix", "windows-sys 0.59.0", @@ -2544,7 +2551,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" dependencies = [ - "getrandom 0.3.1", + "getrandom 0.3.2", ] [[package]] @@ -2577,9 +2584,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" -version = "0.13.3+wasi-0.2.2" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] @@ -2659,9 +2666,9 @@ dependencies = [ [[package]] name = "whoami" -version = "1.5.2" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" +checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" dependencies = [ "redox_syscall", "wasite", @@ -2700,9 +2707,9 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" [[package]] name = "windows-sys" @@ -2863,9 +2870,9 @@ dependencies = [ [[package]] name = "wit-bindgen-rt" -version = "0.33.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags", ] @@ -2908,18 +2915,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.23" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" +checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.23" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" +checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" dependencies = [ "proc-macro2", "quote", diff --git a/src/authentication.rs b/src/authentication.rs new file mode 100644 index 0000000..4d343c0 --- /dev/null +++ b/src/authentication.rs @@ -0,0 +1,598 @@ +#[cfg(feature = "pam")] +use crate::datastructures::AuthorizerType; +use crate::datastructures::{Config, Cookie, FormData, WrapConfigure}; +use anyhow::Result; +use clap::ArgMatches; +use handlebars::Handlebars; +use itertools::Itertools as _; +use redis::AsyncCommands; +use serde::Serialize; +use sqlx::sqlite::SqliteConnectOptions; +use sqlx::{ConnectOptions, Connection, SqliteConnection}; +use std::env; +use std::io::{BufRead, Write}; +use std::str::FromStr; +use tempfile::TempDir; +use tokio_stream::StreamExt as _; + +pub(crate) struct IOModule<R, W> { + reader: R, + writer: W, +} + +impl<R, W> IOModule<R, W> { + pub fn new(reader: R, writer: W) -> Self { + Self { reader, writer } + } +} + +impl<R: BufRead, W: Write> IOModule<R, W> { + // Processing the `authenticate-post` called by cgit. + pub(crate) async fn cmd_authenticate_post( + &mut self, + matches: &ArgMatches, + cfg: Config, + ) -> Result<()> { + // Read stdin from upstream. + let mut buffer = String::new(); + self.reader.read_to_string(&mut buffer)?; + + //log::debug!("{}", buffer); + let data = FormData::from(buffer); + + let cfg = WrapConfigure::from(cfg); + log::trace!("Method is {}", cfg.get_authorizer().method()); + let ret = verify_login(&cfg, &data).await; + + if let Err(ref e) = ret { + eprintln!("{e:?}"); + #[cfg(test)] + eprintln!( + "If database locked error occurs frequently, \ + please use environment DISK_WAIT_TIME to specify longer time." + ); + log::error!("{e:?}") + } + + if ret.unwrap_or(false) { + let redis_conn = redis::Client::open("redis://127.0.0.1/")?; + let cookie = Cookie::generate(data.get_user()); + let mut conn = redis_conn.get_multiplexed_async_connection().await?; + + conn.set_ex::<_, _, String>( + format!("cgit_auth_{}", cookie.get_key()), + cookie.get_body(), + cfg.get_config().cookie_ttl, + ) + .await?; + + let cookie_value = cookie.to_string(); + + let is_secure = matches + .get_one::<String>("https") + .map(|s| s.as_str()) + .is_some_and(|x| matches!(x, "yes" | "on" | "1")); + let domain = matches + .get_one::<String>("http-host") + .map(|s| s.as_str()) + .unwrap_or("*"); + let location = matches + .get_one::<String>("http-referer") + .map(|s| s.as_str()) + .unwrap_or("/"); + let cookie_suffix = if is_secure { "; secure" } else { "" }; + writeln!(&mut self.writer, "Status: 302 Found")?; + writeln!(&mut self.writer, "Cache-Control: no-cache, no-store")?; + writeln!(&mut self.writer, "Location: {location}")?; + writeln!( + &mut self.writer, + "Set-Cookie: cgit_auth={cookie_value}; Domain={domain}; Max-Age={}; HttpOnly{cookie_suffix}", + cfg.get_config().cookie_ttl * 10, + )?; + } else { + writeln!(&mut self.writer, "Status: 403 Forbidden")?; + writeln!(&mut self.writer, "Cache-Control: no-cache, no-store")?; + } + + writeln!(&mut self.writer)?; + Ok(()) + } +} + +// Processing the `authenticate-cookie` called by cgit. +pub(crate) async fn cmd_authenticate_cookie(matches: &ArgMatches, cfg: Config) -> Result<bool> { + let cookies = matches + .get_one::<String>("http-cookie") + .map(|s| s.as_str()) + .unwrap_or(""); + let repo = matches + .get_one::<String>("repo") + .map(|s| s.as_str()) + .unwrap_or(""); + /*let current_url = matches.value_of("current-url").unwrap_or("");*/ + + let mut bypass = false; + + if cfg.bypass_root /*&& current_url.eq("/")*/ && repo.is_empty() { + bypass = true; + } + + if bypass || (!repo.is_empty() && !cfg.check_repo_protect(repo)) { + return Ok(true); + } + + if cookies.is_empty() { + return Ok(false); + } + + let redis_conn = redis::Client::open("redis://127.0.0.1/")?; + let mut conn = redis_conn.get_multiplexed_async_connection().await?; + + let redis_key = format!("cgit_repo_{repo}"); + if !repo.is_empty() && !conn.exists(&redis_key).await? { + let sql_conn = SqliteConnectOptions::from_str(cfg.get_database_location())? + .read_only(true) + .immutable(true) + .disable_statement_logging() + .connect() + .await; + if let Err(ref e) = sql_conn { + log::error!( + "Got error while open sqlite connection: {e:?}\nDatabase location: {}", + cfg.get_database_location() + ); + } + let mut sql_conn = sql_conn?; + if let Some((users,)) = + sqlx::query_as::<_, (String,)>(r#"SELECT "users" FROM "repos" WHERE "repo" = ? "#) + .bind(repo) + .fetch_optional(&mut sql_conn) + .await? + { + let users = users.split_whitespace().collect::<Vec<&str>>(); + let _: () = conn.sadd(&redis_key, users).await?; + } + } + + if let Ok(Some(cookie)) = Cookie::load_from_request(cookies) { + log::debug!("Cookie is {cookie:?}"); + if let Ok(r) = conn + .get::<_, String>(format!("cgit_auth_{}", cookie.get_key())) + .await + { + conn.expire::<_, bool>( + format!("cgit_auth_{}", cookie.get_key()), + cfg.cookie_ttl as i64, + ) + .await?; + if cookie.eq_body(r.as_str()) { + if repo.is_empty() { + return Ok(true); + } + if conn + .sismember::<_, _, i32>(&redis_key, cookie.get_user()) + .await? + == 1 + { + return Ok(true); + } + } + } + log::debug!("{cookie:?}"); + } + + Ok(false) +} + +pub(crate) async fn cmd_init(cfg: Config) -> Result<()> { + let loc = std::path::Path::new(cfg.get_database_location()); + let exists = loc.exists(); + if !exists { + std::fs::File::create(loc)?; + } + + let mut conn = SqliteConnection::connect(cfg.get_database_location()).await?; + + if exists { + let rows = sqlx::query(r#"SELECT name FROM sqlite_master WHERE type='table' AND name=?"#) + .bind("auth_meta") + .fetch_all(&mut conn) + .await?; + + if !rows.is_empty() { + return Ok(()); + } + } + + sqlx::query(crate::database::current::CREATE_TABLES) + .execute(&mut conn) + .await?; + println!("Initialize the database successfully"); + + drop(conn); + + cfg.write_database_commit_timestamp().await?; + Ok(()) +} + +#[cfg(not(feature = "pam"))] +pub(crate) async fn verify_login(cfg: &WrapConfigure, data: &FormData) -> Result<bool> { + cfg.hook().await?; + data.authorize(cfg.get_authorizer()).await +} + +#[cfg(feature = "pam")] +pub(crate) async fn verify_login(cfg: &WrapConfigure, data: &FormData) -> Result<bool> { + if let AuthorizerType::Password = cfg.get_authorizer().method() { + cfg.hook().await?; + } + data.authorize(cfg.get_authorizer()).await +} + +#[derive(Serialize)] +pub struct Meta<'a> { + action: &'a str, + redirect: &'a str, + version: &'a str, +} + +// Processing the `body` called by cgit. +pub(crate) async fn cmd_body(matches: &ArgMatches, _cfg: Config) { + let source = include_str!("authentication_page.html"); + let handlebars = Handlebars::new(); + let meta = Meta { + action: matches + .get_one::<String>("login-url") + .map(|s| s.as_str()) + .unwrap_or_default(), + redirect: matches + .get_one::<String>("current-url") + .map(|s| s.as_str()) + .unwrap_or_default(), + version: env!("CARGO_PKG_VERSION"), + }; + handlebars + .render_template_to_write(source, &meta, std::io::stdout()) + .unwrap(); +} + +pub(crate) async fn cmd_add_user(matches: &ArgMatches, cfg: Config) -> Result<()> { + let re = regex::Regex::new(r"^\w+$").unwrap(); + let user = matches + .get_one::<String>("user") + .map(|s| s.as_str()) + .unwrap_or_default(); + let passwd = matches + .get_one::<String>("password") + .map(|s| s.to_string()) + .unwrap_or_default(); + if user.is_empty() || passwd.is_empty() { + return Err(anyhow::Error::msg("Invalid user or password length")); + } + + if user.len() >= 20 { + return Err(anyhow::Error::msg("Username length should less than 21")); + } + + if !re.is_match(user) { + return Err(anyhow::Error::msg( + "Username must pass regex check\"^\\w+$\"", + )); + } + + let mut conn = SqliteConnection::connect(cfg.get_database_location()).await?; + + let items = sqlx::query(r#"SELECT 1 FROM "accounts" WHERE "user" = ? "#) + .bind(user) + .fetch_all(&mut conn) + .await?; + + if !items.is_empty() { + return Err(anyhow::Error::msg("User already exists!")); + } + + let uid = uuid::Uuid::new_v4().to_string(); + + sqlx::query(r#"INSERT INTO "accounts" VALUES (?, ?, ?) "#) + .bind(user) + .bind(FormData::gen_string_argon2_hash(&passwd)?) + .bind(&uid) + .execute(&mut conn) + .await?; + + println!("Insert {user} ({uid}) to database"); + + drop(conn); + + cfg.write_database_commit_timestamp().await?; + Ok(()) +} + +pub(crate) async fn cmd_list_user(cfg: Config) -> Result<()> { + let mut conn = SqliteConnection::connect(cfg.get_database_location()).await?; + + let (count,) = sqlx::query_as::<_, (i32,)>(r#"SELECT COUNT(*) FROM "accounts""#) + .fetch_one(&mut conn) + .await?; + + if count > 0 { + let mut iter = + sqlx::query_as::<_, (String,)>(r#"SELECT "user" FROM "accounts""#).fetch(&mut conn); + + println!( + "There is {count} user{} in database", + if count > 1 { "s" } else { "" } + ); + while let Some(Ok((row,))) = iter.next().await { + println!("{row}") + } + } else { + println!("There is not user exists.") + } + + Ok(()) +} + +pub(crate) async fn cmd_delete_user(matches: &ArgMatches, cfg: Config) -> Result<()> { + let user = matches + .get_one::<String>("user") + .map(|s| s.as_str()) + .unwrap_or(""); + if user.is_empty() { + return Err(anyhow::Error::msg("Please input a valid username")); + } + + let mut conn = SqliteConnection::connect(cfg.get_database_location()).await?; + + let items = sqlx::query_as::<_, (i32,)>(r#"SELECT 1 FROM "accounts" WHERE "user" = ?"#) + .bind(user) + .fetch_all(&mut conn) + .await?; + + if items.is_empty() { + return Err(anyhow::Error::msg(format!("User {user} not found"))); + } + + sqlx::query(r#"DELETE FROM "accounts" WHERE "user" = ?"#) + .bind(user) + .execute(&mut conn) + .await?; + + println!("Delete {user} from database"); + + cfg.write_database_commit_timestamp().await?; + Ok(()) +} + +pub(crate) async fn cmd_reset_database(matches: &ArgMatches, cfg: Config) -> Result<()> { + if !matches.contains_id("confirm") { + return Err(anyhow::anyhow!( + "Please add --confirm argument to process reset", + )); + } + + let mut conn = SqliteConnection::connect(cfg.get_database_location()).await?; + + sqlx::query(crate::database::current::DROP_TABLES) + .execute(&mut conn) + .await?; + + sqlx::query(crate::database::current::CREATE_TABLES) + .execute(&mut conn) + .await?; + + println!("Reset database successfully"); + + cfg.write_database_commit_timestamp().await?; + Ok(()) +} + +pub(crate) async fn cmd_upgrade_database(cfg: Config) -> Result<()> { + let tmp_dir = TempDir::new()?; + + let v2_path = tmp_dir.path().join("v2.db"); + let v3_path = tmp_dir.path().join("v3.db"); + + drop(std::fs::File::create(&v3_path).expect("Create v3 database failure")); + + std::fs::copy(cfg.get_database_location(), &v2_path) + .expect("Copy v2 database to tempdir failure"); + + let mut origin_conn = SqliteConnectOptions::from_str(v2_path.as_path().to_str().unwrap())? + .read_only(true) + .immutable(true) + .connect() + .await?; + + let (v,) = sqlx::query_as::<_, (String,)>( + r#"SELECT "value" FROM "auth_meta" WHERE "key" = 'version' "#, + ) + .fetch_optional(&mut origin_conn) + .await? + .unwrap(); + + #[allow(deprecated)] + if v.eq(crate::database::previous::VERSION) { + let mut conn = SqliteConnection::connect(v3_path.as_path().to_str().unwrap()).await?; + + sqlx::query(crate::database::current::CREATE_TABLES) + .execute(&mut conn) + .await?; + + let mut iter = sqlx::query_as::<_, (String, String, String)>(r#"SELECT * FROM "accounts""#) + .fetch(&mut origin_conn); + + while let Some(Ok((user, passwd, uid))) = iter.next().await { + sqlx::query(r#"INSERT INTO "accounts" VALUES (?, ?, ?)"#) + .bind(user.as_str()) + .bind(passwd) + .bind(uid.as_str()) + .execute(&mut conn) + .await?; + log::debug!("Process user: {user} ({uid})"); + } + drop(conn); + + std::fs::copy(&v3_path, cfg.get_database_location()) + .expect("Copy back to database location failure"); + println!("Upgrade database successful"); + } else { + eprintln!( + "Got database version {v} but {} required", + crate::database::previous::VERSION + ) + } + drop(origin_conn); + tmp_dir.close()?; + + cfg.write_database_commit_timestamp().await?; + Ok(()) +} + +pub(crate) async fn cmd_repo_user_control( + matches: &ArgMatches, + cfg: Config, + is_delete: bool, +) -> Result<()> { + let repo = matches + .get_one::<String>("repo") + .map(|s| s.as_str()) + .unwrap_or(""); + let user = matches + .get_one::<String>("user") + .map(|s| s.as_str()) + .unwrap_or_default(); + + let clear_all = is_delete && matches.contains_id("clear-all"); + + if repo.is_empty() + || (is_delete && !clear_all && user.is_empty()) + || (!is_delete && user.is_empty()) + { + return Err(anyhow::Error::msg("Invalid repository or username")); + } + + let redis_client = redis::Client::open("redis://127.0.0.1/")?; + let mut redis_conn = redis_client.get_multiplexed_async_connection().await?; + + let mut conn = SqliteConnection::connect(cfg.get_database_location()).await?; + + if sqlx::query(r#"SELECT "users" FROM "repos" WHERE "repo" = ?"#) + .bind(repo) + .fetch_optional(&mut conn) + .await? + .is_none() + { + if is_delete { + println!("Row is empty."); + return Ok(()); + } + sqlx::query(r#"INSERT INTO "repos" VALUES (?, '')"#) + .bind(repo) + .execute(&mut conn) + .await?; + } + + let (users,) = + sqlx::query_as::<_, (String,)>(r#"SELECT "users" FROM "repos" WHERE "repo" = ?"#) + .bind(repo) + .fetch_optional(&mut conn) + .await? + .unwrap(); + let mut users = users.split_whitespace().collect::<Vec<&str>>(); + + if let Some(index) = users.clone().into_iter().position(|x| x.eq(user)) { + if is_delete { + if clear_all { + users.clear(); + } else { + users.remove(index); + } + } else { + return Err(anyhow::Error::msg("User already in repository ACL")); + } + } + + if !is_delete { + users.push(user); + } + + sqlx::query(r#"UPDATE "repos" SET "users" = ? WHERE "repo" = ?"#) + .bind(users.join(" ")) + .bind(repo) + .execute(&mut conn) + .await?; + + let redis_key = format!("cgit_repo_{repo}"); + if redis_conn.exists::<_, i32>(&redis_key).await? == 0 { + redis_conn.sadd::<_, _, i32>(&redis_key, users).await?; + } else if is_delete { + if clear_all { + redis_conn.del::<_, i32>(&redis_key).await?; + } else { + redis_conn.srem::<_, _, i32>(&redis_key, user).await?; + } + } else { + redis_conn.sadd::<_, _, i32>(&redis_key, user).await?; + } + + if !clear_all { + println!( + "{} user {user} {} repository {repo} ACL successful", + if is_delete { "Delete" } else { "Add" }, + if is_delete { "from" } else { "to" }, + ); + } else { + println!("Clear all users from repository {repo} ACL"); + } + + Ok(()) +} + +pub(crate) async fn cmd_list_repos_acl(arg_matches: &ArgMatches, cfg: Config) -> Result<()> { + let repo = arg_matches + .get_one::<String>("repo") + .map(|s| s.as_str()) + .unwrap_or(""); + + let mut conn = SqliteConnectOptions::from_str(cfg.get_database_location())? + .read_only(true) + .immutable(true) + .connect() + .await?; + + if repo.is_empty() { + let (length,) = sqlx::query_as::<_, (i32,)>(r#"SELECT COUNT(*) FROM "repos""#) + .fetch_optional(&mut conn) + .await? + .unwrap_or((0,)); + + println!( + "There is total {length} {} in database", + if length == 1 { + "repository" + } else { + "repositories" + }, + ); + + let mut iter = + sqlx::query_as::<_, (String, String)>(r#"SELECT * FROM "repos""#).fetch(&mut conn); + while let Some(Ok((repo, users))) = iter.next().await { + println!("{repo}: {}", users.split_whitespace().join(",")) + } + } else { + let ret = + sqlx::query_as::<_, (String, String)>(r#"SELECT * FROM "repos" WHERE "repo" = ?"#) + .bind(repo) + .fetch_optional(&mut conn) + .await?; + if let Some((repo, users)) = ret { + println!("{repo}: {}", users.split_whitespace().join(",")) + } else { + println!("Repository {repo} not register in database") + } + } + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index f392792..318a94c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,599 +15,19 @@ ** along with this program. If not, see <https://www.gnu.org/licenses/>. */ +mod authentication; mod database; mod datastructures; #[cfg(test)] mod test; -#[cfg(feature = "pam")] -use crate::datastructures::AuthorizerType; -use crate::datastructures::{Config, Cookie, FormData, TestSuite, WrapConfigure}; +use crate::datastructures::{Config, TestSuite}; use anyhow::Result; use clap::{Arg, ArgMatches, Command}; -use handlebars::Handlebars; -use itertools::Itertools as _; use log4rs::append::file::FileAppender; use log4rs::config::{Appender, Root}; use log4rs::encode::pattern::PatternEncoder; -use redis::AsyncCommands; -use serde::Serialize; -use sqlx::sqlite::SqliteConnectOptions; -use sqlx::{ConnectOptions, Connection, SqliteConnection}; use std::env; -use std::io::{BufRead, Write}; -use std::str::FromStr; -use tempfile::TempDir; -use tokio_stream::StreamExt as _; - -struct IOModule<R, W> { - reader: R, - writer: W, -} - -impl<R: BufRead, W: Write> IOModule<R, W> { - // Processing the `authenticate-post` called by cgit. - async fn cmd_authenticate_post(&mut self, matches: &ArgMatches, cfg: Config) -> Result<()> { - // Read stdin from upstream. - let mut buffer = String::new(); - self.reader.read_to_string(&mut buffer)?; - - //log::debug!("{}", buffer); - let data = datastructures::FormData::from(buffer); - - let cfg = WrapConfigure::from(cfg); - log::trace!("Method is {}", cfg.get_authorizer().method()); - let ret = verify_login(&cfg, &data).await; - - if let Err(ref e) = ret { - eprintln!("{e:?}"); - #[cfg(test)] - eprintln!( - "If database locked error occurs frequently, \ - please use environment DISK_WAIT_TIME to specify longer time." - ); - log::error!("{e:?}") - } - - if ret.unwrap_or(false) { - let redis_conn = redis::Client::open("redis://127.0.0.1/")?; - let cookie = Cookie::generate(data.get_user()); - let mut conn = redis_conn.get_multiplexed_async_connection().await?; - - conn.set_ex::<_, _, String>( - format!("cgit_auth_{}", cookie.get_key()), - cookie.get_body(), - cfg.get_config().cookie_ttl, - ) - .await?; - - let cookie_value = cookie.to_string(); - - let is_secure = matches - .get_one::<String>("https") - .map(|s| s.as_str()) - .is_some_and(|x| matches!(x, "yes" | "on" | "1")); - let domain = matches - .get_one::<String>("http-host") - .map(|s| s.as_str()) - .unwrap_or("*"); - let location = matches - .get_one::<String>("http-referer") - .map(|s| s.as_str()) - .unwrap_or("/"); - let cookie_suffix = if is_secure { "; secure" } else { "" }; - writeln!(&mut self.writer, "Status: 302 Found")?; - writeln!(&mut self.writer, "Cache-Control: no-cache, no-store")?; - writeln!(&mut self.writer, "Location: {}", location)?; - writeln!( - &mut self.writer, - "Set-Cookie: cgit_auth={cookie_value}; Domain={domain}; Max-Age={}; HttpOnly{cookie_suffix}", - cfg.get_config().cookie_ttl * 10, - )?; - } else { - writeln!(&mut self.writer, "Status: 403 Forbidden")?; - writeln!(&mut self.writer, "Cache-Control: no-cache, no-store")?; - } - - writeln!(&mut self.writer)?; - Ok(()) - } -} - -// Processing the `authenticate-cookie` called by cgit. -async fn cmd_authenticate_cookie(matches: &ArgMatches, cfg: Config) -> Result<bool> { - let cookies = matches - .get_one::<String>("http-cookie") - .map(|s| s.as_str()) - .unwrap_or(""); - let repo = matches - .get_one::<String>("repo") - .map(|s| s.as_str()) - .unwrap_or(""); - /*let current_url = matches.value_of("current-url").unwrap_or("");*/ - - let mut bypass = false; - - if cfg.bypass_root /*&& current_url.eq("/")*/ && repo.is_empty() { - bypass = true; - } - - if bypass || (!repo.is_empty() && !cfg.check_repo_protect(repo)) { - return Ok(true); - } - - if cookies.is_empty() { - return Ok(false); - } - - let redis_conn = redis::Client::open("redis://127.0.0.1/")?; - let mut conn = redis_conn.get_multiplexed_async_connection().await?; - - let redis_key = format!("cgit_repo_{repo}"); - if !repo.is_empty() && !conn.exists(&redis_key).await? { - let sql_conn = SqliteConnectOptions::from_str(cfg.get_database_location())? - .read_only(true) - .immutable(true) - .disable_statement_logging() - .connect() - .await; - if let Err(ref e) = sql_conn { - log::error!( - "Got error while open sqlite connection: {e:?}\nDatabase location: {}", - cfg.get_database_location() - ); - } - let mut sql_conn = sql_conn?; - if let Some((users,)) = - sqlx::query_as::<_, (String,)>(r#"SELECT "users" FROM "repos" WHERE "repo" = ? "#) - .bind(repo) - .fetch_optional(&mut sql_conn) - .await? - { - let users = users.split_whitespace().collect::<Vec<&str>>(); - let _: () = conn.sadd(&redis_key, users).await?; - } - } - - if let Ok(Some(cookie)) = Cookie::load_from_request(cookies) { - log::debug!("Cookie is {cookie:?}"); - if let Ok(r) = conn - .get::<_, String>(format!("cgit_auth_{}", cookie.get_key())) - .await - { - conn.expire::<_, bool>( - format!("cgit_auth_{}", cookie.get_key()), - cfg.cookie_ttl as i64, - ) - .await?; - if cookie.eq_body(r.as_str()) { - if repo.is_empty() { - return Ok(true); - } - if conn - .sismember::<_, _, i32>(&redis_key, cookie.get_user()) - .await? - == 1 - { - return Ok(true); - } - } - } - log::debug!("{cookie:?}"); - } - - Ok(false) -} - -async fn cmd_init(cfg: Config) -> Result<()> { - let loc = std::path::Path::new(cfg.get_database_location()); - let exists = loc.exists(); - if !exists { - std::fs::File::create(loc)?; - } - - let mut conn = SqliteConnection::connect(cfg.get_database_location()).await?; - - if exists { - let rows = sqlx::query(r#"SELECT name FROM sqlite_master WHERE type='table' AND name=?"#) - .bind("auth_meta") - .fetch_all(&mut conn) - .await?; - - if !rows.is_empty() { - return Ok(()); - } - } - - sqlx::query(database::current::CREATE_TABLES) - .execute(&mut conn) - .await?; - println!("Initialize the database successfully"); - - drop(conn); - - cfg.write_database_commit_timestamp().await?; - Ok(()) -} - -#[cfg(not(feature = "pam"))] -async fn verify_login(cfg: &WrapConfigure, data: &FormData) -> Result<bool> { - cfg.hook().await?; - data.authorize(cfg.get_authorizer()).await -} - -#[cfg(feature = "pam")] -async fn verify_login(cfg: &WrapConfigure, data: &FormData) -> Result<bool> { - if let AuthorizerType::Password = cfg.get_authorizer().method() { - cfg.hook().await?; - } - data.authorize(cfg.get_authorizer()).await -} - -#[derive(Serialize)] -pub struct Meta<'a> { - action: &'a str, - redirect: &'a str, - version: &'a str, -} - -// Processing the `body` called by cgit. -async fn cmd_body(matches: &ArgMatches, _cfg: Config) { - let source = include_str!("authentication_page.html"); - let handlebars = Handlebars::new(); - let meta = Meta { - action: matches - .get_one::<String>("login-url") - .map(|s| s.as_str()) - .unwrap_or_default(), - redirect: matches - .get_one::<String>("current-url") - .map(|s| s.as_str()) - .unwrap_or_default(), - version: env!("CARGO_PKG_VERSION"), - }; - handlebars - .render_template_to_write(source, &meta, std::io::stdout()) - .unwrap(); -} - -async fn cmd_add_user(matches: &ArgMatches, cfg: Config) -> Result<()> { - let re = regex::Regex::new(r"^\w+$").unwrap(); - let user = matches - .get_one::<String>("user") - .map(|s| s.as_str()) - .unwrap_or_default(); - let passwd = matches - .get_one::<String>("password") - .map(|s| s.to_string()) - .unwrap_or_default(); - if user.is_empty() || passwd.is_empty() { - return Err(anyhow::Error::msg("Invalid user or password length")); - } - - if user.len() >= 20 { - return Err(anyhow::Error::msg("Username length should less than 21")); - } - - if !re.is_match(user) { - return Err(anyhow::Error::msg( - "Username must pass regex check\"^\\w+$\"", - )); - } - - let mut conn = SqliteConnection::connect(cfg.get_database_location()).await?; - - let items = sqlx::query(r#"SELECT 1 FROM "accounts" WHERE "user" = ? "#) - .bind(user) - .fetch_all(&mut conn) - .await?; - - if !items.is_empty() { - return Err(anyhow::Error::msg("User already exists!")); - } - - let uid = uuid::Uuid::new_v4().to_string(); - - sqlx::query(r#"INSERT INTO "accounts" VALUES (?, ?, ?) "#) - .bind(user) - .bind(FormData::gen_string_argon2_hash(&passwd)?) - .bind(&uid) - .execute(&mut conn) - .await?; - - println!("Insert {user} ({uid}) to database"); - - drop(conn); - - cfg.write_database_commit_timestamp().await?; - Ok(()) -} - -async fn cmd_list_user(cfg: Config) -> Result<()> { - let mut conn = SqliteConnection::connect(cfg.get_database_location()).await?; - - let (count,) = sqlx::query_as::<_, (i32,)>(r#"SELECT COUNT(*) FROM "accounts""#) - .fetch_one(&mut conn) - .await?; - - if count > 0 { - let mut iter = - sqlx::query_as::<_, (String,)>(r#"SELECT "user" FROM "accounts""#).fetch(&mut conn); - - println!( - "There is {count} user{} in database", - if count > 1 { "s" } else { "" } - ); - while let Some(Ok((row,))) = iter.next().await { - println!("{row}") - } - } else { - println!("There is not user exists.") - } - - Ok(()) -} - -async fn cmd_delete_user(matches: &ArgMatches, cfg: Config) -> Result<()> { - let user = matches - .get_one::<String>("user") - .map(|s| s.as_str()) - .unwrap_or(""); - if user.is_empty() { - return Err(anyhow::Error::msg("Please input a valid username")); - } - - let mut conn = SqliteConnection::connect(cfg.get_database_location()).await?; - - let items = sqlx::query_as::<_, (i32,)>(r#"SELECT 1 FROM "accounts" WHERE "user" = ?"#) - .bind(user) - .fetch_all(&mut conn) - .await?; - - if items.is_empty() { - return Err(anyhow::Error::msg(format!("User {user} not found"))); - } - - sqlx::query(r#"DELETE FROM "accounts" WHERE "user" = ?"#) - .bind(user) - .execute(&mut conn) - .await?; - - println!("Delete {user} from database"); - - cfg.write_database_commit_timestamp().await?; - Ok(()) -} - -async fn cmd_reset_database(matches: &ArgMatches, cfg: Config) -> Result<()> { - if !matches.contains_id("confirm") { - return Err(anyhow::Error::msg( - "Please add --confirm argument to process reset", - )); - } - - let mut conn = SqliteConnection::connect(cfg.get_database_location()).await?; - - sqlx::query(database::current::DROP_TABLES) - .execute(&mut conn) - .await?; - - sqlx::query(database::current::CREATE_TABLES) - .execute(&mut conn) - .await?; - - println!("Reset database successfully"); - - cfg.write_database_commit_timestamp().await?; - Ok(()) -} - -async fn cmd_upgrade_database(cfg: Config) -> Result<()> { - let tmp_dir = TempDir::new()?; - - let v2_path = tmp_dir.path().join("v2.db"); - let v3_path = tmp_dir.path().join("v3.db"); - - drop(std::fs::File::create(&v3_path).expect("Create v3 database failure")); - - std::fs::copy(cfg.get_database_location(), &v2_path) - .expect("Copy v2 database to tempdir failure"); - - let mut origin_conn = SqliteConnectOptions::from_str(v2_path.as_path().to_str().unwrap())? - .read_only(true) - .immutable(true) - .connect() - .await?; - - let (v,) = sqlx::query_as::<_, (String,)>( - r#"SELECT "value" FROM "auth_meta" WHERE "key" = 'version' "#, - ) - .fetch_optional(&mut origin_conn) - .await? - .unwrap(); - - #[allow(deprecated)] - if v.eq(database::previous::VERSION) { - let mut conn = SqliteConnection::connect(v3_path.as_path().to_str().unwrap()).await?; - - sqlx::query(database::current::CREATE_TABLES) - .execute(&mut conn) - .await?; - - let mut iter = sqlx::query_as::<_, (String, String, String)>(r#"SELECT * FROM "accounts""#) - .fetch(&mut origin_conn); - - while let Some(Ok((user, passwd, uid))) = iter.next().await { - sqlx::query(r#"INSERT INTO "accounts" VALUES (?, ?, ?)"#) - .bind(user.as_str()) - .bind(passwd) - .bind(uid.as_str()) - .execute(&mut conn) - .await?; - log::debug!("Process user: {user} ({uid})"); - } - drop(conn); - - std::fs::copy(&v3_path, cfg.get_database_location()) - .expect("Copy back to database location failure"); - println!("Upgrade database successful"); - } else { - eprintln!( - "Got database version {v} but {} required", - database::previous::VERSION - ) - } - drop(origin_conn); - tmp_dir.close()?; - - cfg.write_database_commit_timestamp().await?; - Ok(()) -} - -async fn cmd_repo_user_control(matches: &ArgMatches, cfg: Config, is_delete: bool) -> Result<()> { - let repo = matches - .get_one::<String>("repo") - .map(|s| s.as_str()) - .unwrap_or(""); - let user = matches - .get_one::<String>("user") - .map(|s| s.as_str()) - .unwrap_or_default(); - - let clear_all = is_delete && matches.contains_id("clear-all"); - - if repo.is_empty() - || (is_delete && !clear_all && user.is_empty()) - || (!is_delete && user.is_empty()) - { - return Err(anyhow::Error::msg("Invalid repository or username")); - } - - let redis_client = redis::Client::open("redis://127.0.0.1/")?; - let mut redis_conn = redis_client.get_multiplexed_async_connection().await?; - - let mut conn = SqliteConnection::connect(cfg.get_database_location()).await?; - - if sqlx::query(r#"SELECT "users" FROM "repos" WHERE "repo" = ?"#) - .bind(repo) - .fetch_optional(&mut conn) - .await? - .is_none() - { - if is_delete { - println!("Row is empty."); - return Ok(()); - } - sqlx::query(r#"INSERT INTO "repos" VALUES (?, ?)"#) - .bind(repo) - .bind("") - .execute(&mut conn) - .await?; - } - - let (users,) = - sqlx::query_as::<_, (String,)>(r#"SELECT "users" FROM "repos" WHERE "repo" = ?"#) - .bind(repo) - .fetch_optional(&mut conn) - .await? - .unwrap(); - let mut users = users.split_whitespace().collect::<Vec<&str>>(); - - if let Some(index) = users.clone().into_iter().position(|x| x.eq(user)) { - if is_delete { - if clear_all { - users.clear(); - } else { - users.remove(index); - } - } else { - return Err(anyhow::Error::msg("User already in repository ACL")); - } - } - - if !is_delete { - users.push(user); - } - - sqlx::query(r#"UPDATE "repos" SET "users" = ? WHERE "repo" = ?"#) - .bind(users.join(" ")) - .bind(repo) - .execute(&mut conn) - .await?; - - let redis_key = format!("cgit_repo_{repo}"); - if redis_conn.exists::<_, i32>(&redis_key).await? == 0 { - redis_conn.sadd::<_, _, i32>(&redis_key, users).await?; - } else if is_delete { - if clear_all { - redis_conn.del::<_, i32>(&redis_key).await?; - } else { - redis_conn.srem::<_, _, i32>(&redis_key, user).await?; - } - } else { - redis_conn.sadd::<_, _, i32>(&redis_key, user).await?; - } - - if !clear_all { - println!( - "{} user {user} {} repository {repo} ACL successful", - if is_delete { "Delete" } else { "Add" }, - if is_delete { "from" } else { "to" }, - ); - } else { - println!("Clear all users from repository {repo} ACL"); - } - - Ok(()) -} - -async fn cmd_list_repos_acl(arg_matches: &ArgMatches, cfg: Config) -> Result<()> { - let repo = arg_matches - .get_one::<String>("repo") - .map(|s| s.as_str()) - .unwrap_or(""); - - let mut conn = SqliteConnectOptions::from_str(cfg.get_database_location())? - .read_only(true) - .immutable(true) - .connect() - .await?; - - if repo.is_empty() { - let (length,) = sqlx::query_as::<_, (i32,)>(r#"SELECT COUNT(*) FROM "repos""#) - .fetch_optional(&mut conn) - .await? - .unwrap_or((0,)); - - println!( - "There is total {length} {} in database", - if length == 1 { - "repository" - } else { - "repositories" - }, - ); - - let mut iter = - sqlx::query_as::<_, (String, String)>(r#"SELECT * FROM "repos""#).fetch(&mut conn); - while let Some(Ok((repo, users))) = iter.next().await { - println!("{repo}: {}", users.split_whitespace().join(",")) - } - } else { - let ret = - sqlx::query_as::<_, (String, String)>(r#"SELECT * FROM "repos" WHERE "repo" = ?"#) - .bind(repo) - .fetch_optional(&mut conn) - .await?; - if let Some((repo, users)) = ret { - println!("{repo}: {}", users.split_whitespace().join(",")) - } else { - println!("Repository {repo} not register in database") - } - } - - Ok(()) -} async fn async_main(arg_matches: ArgMatches) -> Result<i32> { let cfg = if env::args().any(|x| x.eq("--test")) { @@ -617,7 +37,7 @@ async fn async_main(arg_matches: ArgMatches) -> Result<i32> { }; match arg_matches.subcommand() { Some(("authenticate-cookie", matches)) => { - if let Ok(should_pass) = cmd_authenticate_cookie(matches, cfg).await { + if let Ok(should_pass) = authentication::cmd_authenticate_cookie(matches, cfg).await { if should_pass { return Ok(1); } @@ -628,46 +48,45 @@ async fn async_main(arg_matches: ArgMatches) -> Result<i32> { let input = stdin.lock(); let output = std::io::stdout(); - let mut module = IOModule { - reader: input, - writer: output, - }; + let mut module = authentication::IOModule::new(input, output); module.cmd_authenticate_post(matches, cfg).await?; } Some(("body", matches)) => { - cmd_body(matches, cfg).await; + authentication::cmd_body(matches, cfg).await; } Some(("user", matches)) => match matches.subcommand() { Some(("add", matches)) => { - cmd_add_user(matches, cfg).await?; + authentication::cmd_add_user(matches, cfg).await?; } Some(("del", matches)) => { - cmd_delete_user(matches, cfg).await?; + authentication::cmd_delete_user(matches, cfg).await?; } Some(("list", _matches)) => { - cmd_list_user(cfg).await?; + authentication::cmd_list_user(cfg).await?; } _ => {} }, Some(("database", matches)) => match matches.subcommand() { Some(("init", _matches)) => { - cmd_init(cfg).await?; + authentication::cmd_init(cfg).await?; } Some(("upgrade", _matches)) => { - cmd_upgrade_database(cfg).await?; + authentication::cmd_upgrade_database(cfg).await?; } Some(("reset", matches)) => { - cmd_reset_database(matches, cfg).await?; + authentication::cmd_reset_database(matches, cfg).await?; } _ => {} }, Some(("repo", matches)) => match matches.subcommand() { - Some(("add", matches)) => cmd_repo_user_control(matches, cfg, false).await?, + Some(("add", matches)) => { + authentication::cmd_repo_user_control(matches, cfg, false).await? + } Some(("del", matches)) => { - cmd_repo_user_control(matches, cfg, true).await?; + authentication::cmd_repo_user_control(matches, cfg, true).await?; } Some(("list", matches)) => { - cmd_list_repos_acl(matches, cfg).await?; + authentication::cmd_list_repos_acl(matches, cfg).await?; } _ => {} }, @@ -814,20 +233,18 @@ fn main() -> Result<()> { let logfile = match logfile { Ok(f) => f, Err(e) => { - return Err(anyhow::Error::msg(format!( + return Err(anyhow::anyhow!( "Got error while append to {logfile_path}: {e:?}", - ))); + )); } }; let config = log4rs::Config::builder() .appender(Appender::builder().build("logfile", Box::new(logfile))) - .logger( + .loggers([ log4rs::config::Logger::builder().build("handlebars::render", log::LevelFilter::Warn), - ) - .logger( log4rs::config::Logger::builder().build("handlebars::context", log::LevelFilter::Warn), - ) + ]) .build( Root::builder() .appender("logfile") diff --git a/src/test.rs b/src/test.rs index 3d54d71..e6e1bbc 100644 --- a/src/test.rs +++ b/src/test.rs @@ -18,7 +18,9 @@ #[cfg(test)] mod core { use crate::{ - IOModule, cmd_add_user, cmd_authenticate_cookie, cmd_init, cmd_repo_user_control, + authentication::{ + IOModule, cmd_add_user, cmd_authenticate_cookie, cmd_init, cmd_repo_user_control, + }, datastructures::{Config, TestSuite, rand_str}, get_arg_matches, }; @@ -99,10 +101,7 @@ mod core { "/?p=login", ])); let mut output = Vec::new(); - let mut module = IOModule { - reader: &correct_input[..], - writer: &mut output, - }; + let mut module = IOModule::new(&correct_input[..], &mut output); let cfg = Config::generate_test_config(); |
