aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKunoiSayami <[email protected]>2021-05-15 21:33:15 +0800
committerKunoiSayami <[email protected]>2021-05-15 21:33:15 +0800
commit2f051ba28328f57552fd8758bdd8877e2b0d86ef (patch)
treed7c9dcedf1e2e22588996bd1bc5817e1304feefc
parent8d68ac0b56fa6be0eb5215f346d513b9a2ef37b8 (diff)
feat(core): Add remaining repository protect configure methodv0.4.2
* refactor(test): Move test out from core file
-rw-r--r--Cargo.lock2
-rw-r--r--Cargo.toml2
-rw-r--r--src/datastructures.rs17
-rw-r--r--src/main.rs419
-rw-r--r--src/test.rs354
5 files changed, 456 insertions, 338 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 964e52e..5bb51a1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -377,7 +377,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cgit-simple-authentication"
-version = "0.4.1"
+version = "0.4.2"
dependencies = [
"anyhow",
"argon2",
diff --git a/Cargo.toml b/Cargo.toml
index b0a36b0..d80c199 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "cgit-simple-authentication"
-version = "0.4.1"
+version = "0.4.2"
authors = ["KunoiSayami <[email protected]>"]
edition = "2018"
diff --git a/src/datastructures.rs b/src/datastructures.rs
index 2f5230c..83f3bc6 100644
--- a/src/datastructures.rs
+++ b/src/datastructures.rs
@@ -88,13 +88,13 @@ pub struct Config {
///
/// In /etc/cgitrc:
/// ```conf
- /// cgit-simple-auth-protect=none
+ /// cgit-simple-auth-full-protect=false
/// ```
///
/// In repo.conf
/// ```conf
/// repo.url=test
- /// repo.protected=true
+ /// repo.protect=true
/// ```
///
/// Default behavior is protect all repository
@@ -145,7 +145,7 @@ impl Config {
"cookie-ttl" => cookie_ttl = value.parse().unwrap_or(DEFAULT_COOKIE_TTL),
"database" => database = value,
"bypass-root" => bypass_root = value.to_lowercase().eq("true"),
- "protect" => protect_all = !value.to_lowercase().eq("none"),
+ "full-protect" => protect_all = !value.to_lowercase().eq("false"),
_ => {}
}
}
@@ -195,10 +195,11 @@ impl Config {
continue;
}
- if key.eq("repo.protect") {
- if value.trim().to_lowercase().eq("true") && !current_repo.is_empty() {
- protect_repos.push(current_repo.to_string());
- }
+ if key.eq("repo.protect")
+ && value.trim().to_lowercase().eq("true")
+ && !current_repo.is_empty()
+ {
+ protect_repos.push(current_repo.to_string());
}
if key.eq("include") {
@@ -296,7 +297,7 @@ impl TestSuite for Config {
cookie_ttl: DEFAULT_COOKIE_TTL,
test: true,
protect_all: false,
- protected_repos: vec!["test".to_string()],
+ protected_repos: vec!["test".to_string(), "repo".to_string()],
}
}
}
diff --git a/src/main.rs b/src/main.rs
index b422db1..b42d827 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -20,6 +20,7 @@
mod database;
mod datastructures;
+mod test;
use crate::datastructures::{Config, Cookie, FormData, TestSuite};
use anyhow::Result;
@@ -104,10 +105,11 @@ impl<R: BufRead, W: Write> IOModule<R, W> {
async fn cmd_authenticate_cookie(matches: &ArgMatches<'_>, cfg: Config) -> Result<bool> {
let cookies = matches.value_of("http-cookie").unwrap_or("");
let repo = matches.value_of("repo").unwrap_or("");
+ let current_url = matches.value_of("current-url").unwrap_or("");
let mut bypass = false;
- if cfg.bypass_root && matches.value_of("current-url").unwrap_or("").eq("/") {
+ if cfg.bypass_root && current_url.eq("/") && repo.is_empty() {
bypass = true;
}
@@ -123,21 +125,19 @@ async fn cmd_authenticate_cookie(matches: &ArgMatches<'_>, cfg: Config) -> Resul
let mut conn = redis_conn.get_async_connection().await?;
let redis_key = format!("cgit_repo_{}", repo);
- if !repo.is_empty() {
- if !conn.exists(&redis_key).await? {
- let mut sql_conn = SqliteConnectOptions::from_str(cfg.get_database_location())?
- .read_only(true)
- .connect()
- .await?;
- 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>>();
- conn.sadd(&redis_key, users).await?;
- }
+ if !repo.is_empty() && !conn.exists(&redis_key).await? {
+ let mut sql_conn = SqliteConnectOptions::from_str(cfg.get_database_location())?
+ .read_only(true)
+ .connect()
+ .await?;
+ 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>>();
+ conn.sadd(&redis_key, users).await?;
}
}
@@ -504,16 +504,14 @@ async fn cmd_repo_user_control(
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 if is_delete {
+ if clear_all {
+ redis_conn.del::<_, i32>(&redis_key).await?;
} else {
- redis_conn.sadd::<_, _, i32>(&redis_key, user).await?;
+ redis_conn.srem::<_, _, i32>(&redis_key, user).await?;
}
+ } else {
+ redis_conn.sadd::<_, _, i32>(&redis_key, user).await?;
}
if !clear_all {
@@ -531,6 +529,67 @@ async fn cmd_repo_user_control(
Ok(())
}
+async fn cmd_list_repos_acl(arg_matches: &ArgMatches<'_>, cfg: Config) -> Result<()> {
+ let repo = arg_matches.value_of("repo").unwrap_or("");
+
+ let mut conn = SqliteConnectOptions::from_str(cfg.get_database_location())?
+ .read_only(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 {} {} in database",
+ length,
+ 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()
+ .into_iter()
+ .collect::<Vec<&str>>()
+ .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()
+ .into_iter()
+ .collect::<Vec<&str>>()
+ .join(",")
+ )
+ } else {
+ println!("Repository {} not register in database", repo)
+ }
+ }
+
+ Ok(())
+}
+
async fn async_main(arg_matches: ArgMatches<'_>) -> Result<i32> {
let cfg = if std::env::args().any(|x| x.eq("--test")) {
Config::generate_test_config()
@@ -581,7 +640,9 @@ async fn async_main(arg_matches: ArgMatches<'_>) -> Result<i32> {
("repodel", Some(matches)) => {
cmd_repo_user_control(matches, cfg, true).await?;
}
- // TODO: other repository rated command
+ ("repos", Some(matches)) => {
+ cmd_list_repos_acl(matches, cfg).await?;
+ }
_ => {}
}
Ok(0)
@@ -692,11 +753,10 @@ fn main() -> Result<()> {
.encoder(Box::new(PatternEncoder::new(
"{d(%Y-%m-%d %H:%M:%S)}- {h({l})} - {m}{n}",
)))
- .build(env::var("RUST_LOG_FILE").unwrap_or("/tmp/auth.log".to_string()))?;
+ .build(env::var("LOG_FILE").unwrap_or_else(|_| "/var/cache/cgit/auth.log".to_string()))?;
let config = log4rs::Config::builder()
.appender(Appender::builder().build("logfile", Box::new(logfile)))
- //.logger(log4rs::config::Logger::builder().build("sqlx::query", log::LevelFilter::Warn))
.logger(
log4rs::config::Logger::builder().build("handlebars::render", log::LevelFilter::Warn),
)
@@ -710,6 +770,7 @@ fn main() -> Result<()> {
)?;
log4rs::init_config(config)?;
+
log::debug!(
"{}",
env::args()
@@ -719,307 +780,9 @@ fn main() -> Result<()> {
.join(" ")
);
- process_arguments()?;
-
- Ok(())
-}
-
-#[cfg(test)]
-mod test {
- use crate::datastructures::{rand_str, Config, TestSuite};
- use crate::{cmd_add_user, cmd_authenticate_cookie, cmd_init, cmd_repo_user_control};
- use crate::{get_arg_matches, IOModule};
- use argon2::{
- password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
- Argon2,
+ if let Err(e) = process_arguments() {
+ log::error!("{:?}", e);
};
- use redis::AsyncCommands;
- use std::io::{Read, Write};
- use std::path::Path;
- use std::path::PathBuf;
- use std::thread::sleep;
- use std::time::Duration;
-
- #[test]
- fn test_argon2() {
- use rand_core::OsRng;
- let passwd = b"hunter2";
- let salt = SaltString::generate(&mut OsRng);
-
- let argon2 = Argon2::default();
-
- argon2.hash_password_simple(passwd, salt.as_ref()).unwrap();
- }
-
- #[test]
- fn test_argon2_verify() {
- let passwd = b"hunter2";
- let parsed_hash = PasswordHash::new("$argon2id$v=19$m=4096,t=3,p=1$szYDnoQSVPmXq+RD2LneBw$fRETH//iCQuIX+SgjYPdZ9iIbM8gEy9fBjTJ/KFFJNM").unwrap();
- let argon2 = Argon2::default();
- assert!(argon2.verify_password(passwd, &parsed_hash).is_ok())
- }
-
- async fn async_test_redis() -> anyhow::Result<()> {
- let redis_conn = redis::Client::open("redis://127.0.0.1/")?;
- let mut conn = redis_conn.get_async_connection().await?;
-
- let s = rand_str(crate::datastructures::COOKIE_LENGTH);
- conn.set_ex::<_, _, String>("auth_test", &s, 60).await?;
-
- assert!(conn.exists::<_, bool>("auth_test").await?);
-
- assert_eq!(conn.get::<_, String>("auth_test").await?, s);
-
- conn.del("auth_test").await?;
-
- assert_eq!(conn.exists::<_, bool>("auth_test").await?, false);
- Ok(())
- }
-
- #[test]
- fn test_redis() {
- tokio::runtime::Builder::new_current_thread()
- .enable_all()
- .build()
- .unwrap()
- .block_on(async_test_redis())
- .unwrap();
- }
-
- fn test_auth_post() -> String {
- let correct_input = br#"redirect=/&username=hunter2&password=hunter2"#;
- let matches = get_arg_matches(Some(vec![
- "a",
- "authenticate-post",
- "",
- "POST",
- "p=login",
- "https://git.example.com/?p=login",
- "/",
- "git.example.com",
- "",
- "",
- "login",
- "/?p=login",
- "/?p=login",
- ]));
- let mut output = Vec::new();
- let mut module = IOModule {
- reader: &correct_input[..],
- writer: &mut output,
- };
-
- let cfg = Config::generate_test_config();
-
- match matches.subcommand() {
- ("authenticate-post", Some(matches)) => tokio::runtime::Builder::new_current_thread()
- .enable_all()
- .build()
- .unwrap()
- .block_on(module.cmd_authenticate_post(matches, cfg))
- .unwrap(),
- _ => {}
- }
-
- String::from_utf8(output).unwrap()
- }
- #[test]
- fn test_01_auth_failure() {
- let out = test_auth_post();
- assert!(out.starts_with("Status: 403"));
- assert!(out.ends_with("\n\n"));
- }
-
- #[test]
- fn test_00_init_database() {
- let tmp_dir = Path::new("test");
-
- if tmp_dir.exists() {
- std::fs::remove_dir_all(tmp_dir).unwrap();
- }
- std::fs::create_dir(tmp_dir).unwrap();
- tokio::runtime::Builder::new_current_thread()
- .enable_all()
- .build()
- .unwrap()
- .block_on(cmd_init(Config::generate_test_config()))
- .unwrap();
- std::fs::File::create("test/DATABASE_INITED").unwrap();
- }
-
- fn lock(path: &std::path::PathBuf, sleep_length: usize) {
- for _ in 0..(sleep_length * 100) {
- sleep(Duration::from_millis(10));
- if path.exists() {
- break;
- }
- }
-
- if !path.exists() {
- panic!("Can't get lock from {}", path.to_str().unwrap())
- }
- }
-
- #[test]
- fn test_02_insert_user() {
- lock(&PathBuf::from("test/DATABASE_INITED"), 3);
- let matches = crate::get_arg_matches(Some(vec!["a", "adduser", "hunter2", "hunter2"]));
- match matches.subcommand() {
- ("adduser", Some(matches)) => {
- tokio::runtime::Builder::new_current_thread()
- .enable_all()
- .build()
- .unwrap()
- .block_on(cmd_add_user(matches, Config::generate_test_config()))
- .unwrap();
- std::fs::File::create("test/USER_WRITTEN").unwrap();
- }
- _ => unreachable!(),
- }
- assert!(Path::new("test/COMMIT").exists())
- }
-
- #[test]
- fn test_03_insert_repo() {
- lock(&PathBuf::from("test/USER_WRITTEN"), 5);
- let matches = crate::get_arg_matches(Some(vec!["a", "repoadd", "test", "hunter2"]));
- match matches.subcommand() {
- ("repoadd", Some(matches)) => {
- tokio::runtime::Builder::new_current_thread()
- .enable_all()
- .build()
- .unwrap()
- .block_on(cmd_repo_user_control(matches, Config::generate_test_config(), false))
- .unwrap();
- std::fs::File::create("test/REPO_USER_ADDED").unwrap();
- }
- _ => unreachable!()
- }
- }
-
- #[test]
- fn test_91_auth_pass() {
- lock(&PathBuf::from("test/REPO_USER_ADDED"), 7);
-
- let s = test_auth_post();
-
- let mut file = std::fs::OpenOptions::new()
- .create(true)
- .write(true)
- .open("test/RESPONSE")
- .unwrap();
- file.write_all(s.as_bytes()).unwrap();
-
- assert!(s.starts_with("Status: 302"));
- assert!(s.ends_with("\n\n"));
- assert!(!Path::new("test/COPIED").exists());
- }
-
- #[test]
- fn test_92_authenticate_cookie() {
- test_authenticate_cookie("test");
- }
-
- #[test]
- #[should_panic]
- fn test_93_authenticate_cookie() {
- test_authenticate_cookie("repo");
- }
-
- fn test_authenticate_cookie(repo: &str) {
- lock(&PathBuf::from("test/RESPONSE"), 15);
- let mut buffer = String::new();
-
- let mut file = std::fs::File::open("test/RESPONSE").unwrap();
- file.read_to_string(&mut buffer).unwrap();
-
- let buffer = buffer;
-
- let mut cookie = "";
-
- for line in buffer.lines().map(|x| x.trim()) {
- if !line.starts_with("Set-Cookie") {
- continue;
- }
- let (_, value) = line.split_once(":").unwrap();
- let (value, _) = value.split_once(";").unwrap();
- cookie = value.trim();
- break;
- }
-
- let matches = get_arg_matches(Some(vec![
- "a",
- "authenticate-cookie",
- cookie,
- "GET",
- "",
- "https://git.example.com/",
- "/",
- "git.example.com",
- "on",
- repo,
- "",
- "/",
- "/?p=login",
- ]));
- let result = match matches.subcommand() {
- ("authenticate-cookie", Some(matches)) => tokio::runtime::Builder::new_current_thread()
- .enable_all()
- .build()
- .unwrap()
- .block_on(cmd_authenticate_cookie(
- matches,
- Config::generate_test_config(),
- ))
- .unwrap(),
- _ => unreachable!(),
- };
- assert!(result);
- }
-
- fn write_to_specify_file(path: &PathBuf, data: &[u8]) -> Result<(), std::io::Error> {
- let mut file = std::fs::OpenOptions::new()
- .create(true)
- .write(true)
- .truncate(true)
- .open(path)?;
- file.write_all(data)?;
- file.sync_all()?;
- Ok(())
- }
-
- #[test]
- fn test_02_protected_repo_parser() {
- let tmpdir = tempdir::TempDir::new("test").unwrap();
-
- let another_file_path = format!(
- "include={}/REPO_SETTING # TEST\ncgit-simple-auth-protect=none",
- tmpdir.path().to_str().unwrap()
- );
- write_to_specify_file(&tmpdir.path().join("CFG"), another_file_path.as_bytes()).unwrap();
- write_to_specify_file(
- &tmpdir.path().join("REPO_SETTING"),
- b"repo.url=test\nrepo.protect=true",
- )
- .unwrap();
-
- let cfg = Config::load_from_path(tmpdir.path().join("CFG"));
-
- assert!(cfg.check_repo_protect("test"));
- assert!(!cfg.query_is_all_protected());
-
- write_to_specify_file(
- &tmpdir.path().join("REPO_SETTING"),
- b"repo.protect=true\nrepo.url=test",
- )
- .unwrap();
-
- let cfg = Config::load_from_path(tmpdir.path().join("CFG"));
-
- assert!(!cfg.check_repo_protect("test"));
- assert!(!cfg.query_is_all_protected());
-
- tmpdir.close().unwrap();
- }
+ Ok(())
}
diff --git a/src/test.rs b/src/test.rs
new file mode 100644
index 0000000..098a70d
--- /dev/null
+++ b/src/test.rs
@@ -0,0 +1,354 @@
+/*
+ ** Copyright (C) 2021 KunoiSayami
+ **
+ ** This file is part of cgit-simple-authentication and is released under
+ ** the AGPL v3 License: https://www.gnu.org/licenses/agpl-3.0.txt
+ **
+ ** This program is free software: you can redistribute it and/or modify
+ ** it under the terms of the GNU Affero General Public License as published by
+ ** the Free Software Foundation, either version 3 of the License, or
+ ** any later version.
+ **
+ ** This program is distributed in the hope that it will be useful,
+ ** but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ ** GNU Affero General Public License for more details.
+ **
+ ** You should have received a copy of the GNU Affero General Public License
+ ** along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#[cfg(test)]
+mod core_test {
+ use crate::datastructures::{rand_str, Config, TestSuite};
+ use crate::{cmd_add_user, cmd_authenticate_cookie, cmd_init, cmd_repo_user_control};
+ use crate::{get_arg_matches, IOModule};
+ use argon2::{
+ password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
+ Argon2,
+ };
+ use redis::AsyncCommands;
+ use std::io::{Read, Write};
+ use std::path::Path;
+ use std::path::PathBuf;
+ use std::thread::sleep;
+ use std::time::Duration;
+
+ #[test]
+ fn test_argon2() {
+ use rand_core::OsRng;
+ let passwd = b"hunter2";
+ let salt = SaltString::generate(&mut OsRng);
+
+ let argon2 = Argon2::default();
+
+ argon2.hash_password_simple(passwd, salt.as_ref()).unwrap();
+ }
+
+ #[test]
+ fn test_argon2_verify() {
+ let passwd = b"hunter2";
+ let parsed_hash = PasswordHash::new("$argon2id$v=19$m=4096,t=3,p=1$szYDnoQSVPmXq+RD2LneBw$fRETH//iCQuIX+SgjYPdZ9iIbM8gEy9fBjTJ/KFFJNM").unwrap();
+ let argon2 = Argon2::default();
+ assert!(argon2.verify_password(passwd, &parsed_hash).is_ok())
+ }
+
+ async fn async_test_redis() -> anyhow::Result<()> {
+ let redis_conn = redis::Client::open("redis://127.0.0.1/")?;
+ let mut conn = redis_conn.get_async_connection().await?;
+
+ let s = rand_str(crate::datastructures::COOKIE_LENGTH);
+ conn.set_ex::<_, _, String>("auth_test", &s, 60).await?;
+
+ assert!(conn.exists::<_, bool>("auth_test").await?);
+
+ assert_eq!(conn.get::<_, String>("auth_test").await?, s);
+
+ conn.del("auth_test").await?;
+
+ assert_eq!(conn.exists::<_, bool>("auth_test").await?, false);
+ Ok(())
+ }
+
+ #[test]
+ fn test_redis() {
+ tokio::runtime::Builder::new_current_thread()
+ .enable_all()
+ .build()
+ .unwrap()
+ .block_on(async_test_redis())
+ .unwrap();
+ }
+
+ fn test_auth_post() -> String {
+ let correct_input = br#"redirect=/&username=hunter2&password=hunter2"#;
+ let matches = get_arg_matches(Some(vec![
+ "a",
+ "authenticate-post",
+ "",
+ "POST",
+ "p=login",
+ "https://git.example.com/?p=login",
+ "/",
+ "git.example.com",
+ "",
+ "",
+ "login",
+ "/?p=login",
+ "/?p=login",
+ ]));
+ let mut output = Vec::new();
+ let mut module = IOModule {
+ reader: &correct_input[..],
+ writer: &mut output,
+ };
+
+ let cfg = Config::generate_test_config();
+
+ match matches.subcommand() {
+ ("authenticate-post", Some(matches)) => tokio::runtime::Builder::new_current_thread()
+ .enable_all()
+ .build()
+ .unwrap()
+ .block_on(module.cmd_authenticate_post(matches, cfg))
+ .unwrap(),
+ _ => {}
+ }
+
+ String::from_utf8(output).unwrap()
+ }
+
+ #[test]
+ fn test_01_auth_failure() {
+ let out = test_auth_post();
+ assert!(out.starts_with("Status: 403"));
+ assert!(out.ends_with("\n\n"));
+ }
+
+ #[test]
+ fn test_00_init_database() {
+ let tmp_dir = Path::new("test");
+
+ if tmp_dir.exists() {
+ std::fs::remove_dir_all(tmp_dir).unwrap();
+ }
+ std::fs::create_dir(tmp_dir).unwrap();
+ tokio::runtime::Builder::new_current_thread()
+ .enable_all()
+ .build()
+ .unwrap()
+ .block_on(cmd_init(Config::generate_test_config()))
+ .unwrap();
+ std::fs::File::create("test/DATABASE_INITED").unwrap();
+ }
+
+ fn lock(path: &std::path::PathBuf, sleep_length: usize) {
+ for _ in 0..(sleep_length * 100) {
+ sleep(Duration::from_millis(10));
+ if path.exists() {
+ break;
+ }
+ }
+
+ if !path.exists() {
+ panic!("Can't get lock from {}", path.to_str().unwrap())
+ }
+ }
+
+ #[test]
+ fn test_02_insert_user() {
+ lock(&PathBuf::from("test/DATABASE_INITED"), 3);
+ let matches = crate::get_arg_matches(Some(vec!["a", "adduser", "hunter2", "hunter2"]));
+ match matches.subcommand() {
+ ("adduser", Some(matches)) => {
+ tokio::runtime::Builder::new_current_thread()
+ .enable_all()
+ .build()
+ .unwrap()
+ .block_on(cmd_add_user(matches, Config::generate_test_config()))
+ .unwrap();
+ std::fs::File::create("test/USER_WRITTEN").unwrap();
+ }
+ _ => unreachable!(),
+ }
+ assert!(Path::new("test/COMMIT").exists())
+ }
+
+ #[test]
+ fn test_03_insert_repo() {
+ lock(&PathBuf::from("test/USER_WRITTEN"), 5);
+ let args = vec![
+ vec!["a", "repoadd", "test", "hunter2"],
+ vec!["a", "repoadd", "repo", "hunter"],
+ ];
+ for x in args {
+ let matches = crate::get_arg_matches(Some(x));
+ match matches.subcommand() {
+ ("repoadd", Some(matches)) => {
+ tokio::runtime::Builder::new_current_thread()
+ .enable_all()
+ .build()
+ .unwrap()
+ .block_on(cmd_repo_user_control(
+ matches,
+ Config::generate_test_config(),
+ false,
+ ))
+ .unwrap();
+ std::fs::File::create("test/REPO_USER_ADDED").unwrap();
+ }
+ _ => unreachable!(),
+ }
+ }
+ }
+
+ #[test]
+ fn test_91_auth_pass() {
+ lock(&PathBuf::from("test/REPO_USER_ADDED"), 10);
+
+ let s = test_auth_post();
+
+ let mut file = std::fs::OpenOptions::new()
+ .create(true)
+ .write(true)
+ .open("test/RESPONSE")
+ .unwrap();
+ file.write_all(s.as_bytes()).unwrap();
+
+ assert!(s.starts_with("Status: 302"));
+ assert!(s.ends_with("\n\n"));
+ assert!(!Path::new("test/COPIED").exists());
+ }
+
+ #[test]
+ fn test_92_authenticate_cookie() {
+ test_authenticate_cookie("test", Some("test/COOKIE_TEST_1"));
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_93_authenticate_cookie() {
+ test_authenticate_cookie("repo", Some("test/COOKIE_TEST_2"));
+ }
+
+ fn test_authenticate_cookie<P: AsRef<Path>>(repo: &str, path: Option<P>) {
+ lock(&PathBuf::from("test/RESPONSE"), 15);
+ let mut buffer = String::new();
+
+ let mut file = std::fs::File::open("test/RESPONSE").unwrap();
+ file.read_to_string(&mut buffer).unwrap();
+
+ let buffer = buffer;
+
+ let mut cookie = "";
+
+ for line in buffer.lines().map(|x| x.trim()) {
+ if !line.starts_with("Set-Cookie") {
+ continue;
+ }
+ let (_, value) = line.split_once(":").unwrap();
+ let (value, _) = value.split_once(";").unwrap();
+ cookie = value.trim();
+ break;
+ }
+
+ let matches = get_arg_matches(Some(vec![
+ "a",
+ "authenticate-cookie",
+ cookie,
+ "GET",
+ "",
+ "https://git.example.com/",
+ "/",
+ "git.example.com",
+ "on",
+ repo,
+ "",
+ "/",
+ "/?p=login",
+ ]));
+ let result = match matches.subcommand() {
+ ("authenticate-cookie", Some(matches)) => tokio::runtime::Builder::new_current_thread()
+ .enable_all()
+ .build()
+ .unwrap()
+ .block_on(cmd_authenticate_cookie(
+ matches,
+ Config::generate_test_config(),
+ ))
+ .unwrap(),
+ _ => unreachable!(),
+ };
+ if let Some(path) = path {
+ std::fs::File::create(path).unwrap();
+ }
+ assert!(result);
+ }
+
+ fn write_to_specify_file(path: &PathBuf, data: &[u8]) -> Result<(), std::io::Error> {
+ let mut file = std::fs::OpenOptions::new()
+ .create(true)
+ .write(true)
+ .truncate(true)
+ .open(path)?;
+ file.write_all(data)?;
+ file.sync_all()?;
+ Ok(())
+ }
+
+ #[test]
+ fn test_02_protected_repo_parser() {
+ let tmpdir = tempdir::TempDir::new("test").unwrap();
+
+ let another_file_path = format!(
+ "include={}/REPO_SETTING # TEST\ncgit-simple-auth-full-protect=false",
+ tmpdir.path().to_str().unwrap()
+ );
+ write_to_specify_file(&tmpdir.path().join("CFG"), another_file_path.as_bytes()).unwrap();
+ write_to_specify_file(
+ &tmpdir.path().join("REPO_SETTING"),
+ b"repo.url=test\nrepo.protect=true",
+ )
+ .unwrap();
+
+ let cfg = Config::load_from_path(tmpdir.path().join("CFG"));
+
+ assert!(cfg.check_repo_protect("test"));
+ assert!(!cfg.query_is_all_protected());
+
+ write_to_specify_file(
+ &tmpdir.path().join("REPO_SETTING"),
+ b"repo.protect=true\nrepo.url=test",
+ )
+ .unwrap();
+
+ let cfg = Config::load_from_path(tmpdir.path().join("CFG"));
+
+ assert!(!cfg.check_repo_protect("test"));
+ assert!(!cfg.query_is_all_protected());
+
+ tmpdir.close().unwrap();
+ }
+
+ async fn clear_redis_setting() -> anyhow::Result<()> {
+ let client = redis::Client::open("redis://127.0.0.1")?;
+ let mut conn = client.get_async_connection().await?;
+
+ for key in &["cgit_repo_test", "cgit_repo_repo"] {
+ conn.del::<_, i32>(*key).await?;
+ }
+ Ok(())
+ }
+
+ #[test]
+ fn test_99_clear() {
+ lock(&PathBuf::from("test/COOKIE_TEST_1"), 9);
+ lock(&PathBuf::from("test/COOKIE_TEST_2"), 6);
+ tokio::runtime::Builder::new_current_thread()
+ .enable_all()
+ .build()
+ .unwrap()
+ .block_on(clear_redis_setting())
+ .unwrap();
+ }
+}