aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKunoiSayami <[email protected]>2021-05-15 00:58:56 +0800
committerKunoiSayami <[email protected]>2021-05-15 01:02:20 +0800
commit722d7da76380d8f3814f5f2a59e3ef14928b6203 (patch)
treef103015d9bf8190d85f20c81f3c1eed035599d45
parentdaa61c3e0c96800c58931f1e933462b462fc7b66 (diff)
feat(core): Add per-repository protect optionv0.4.0
* feat(auth): Support per-repository user authenticate * feat(test): Support new configure structure test * refactor(database): Upgrade database structure to v3
-rw-r--r--.gitignore2
-rw-r--r--Cargo.lock2
-rw-r--r--Cargo.toml2
-rw-r--r--src/database.rs43
-rw-r--r--src/datastructures.rs148
-rw-r--r--src/main.rs308
6 files changed, 376 insertions, 129 deletions
diff --git a/.gitignore b/.gitignore
index 9dd6a1d..3b09037 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,4 +10,4 @@ target/
.vscode/
# generate by cargo test
-test/ \ No newline at end of file
+test*/ \ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index ba4a0c8..cb44441 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -377,7 +377,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cgit-simple-authentication"
-version = "0.3.8"
+version = "0.4.0"
dependencies = [
"anyhow",
"argon2",
diff --git a/Cargo.toml b/Cargo.toml
index c5a730a..6f038c4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "cgit-simple-authentication"
-version = "0.3.8"
+version = "0.4.0"
authors = ["KunoiSayami <[email protected]>"]
edition = "2018"
diff --git a/src/database.rs b/src/database.rs
index e620241..5854e81 100644
--- a/src/database.rs
+++ b/src/database.rs
@@ -17,39 +17,47 @@
** 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/>.
*/
-#[deprecated(since = "0.3.0", note = "Please use v2 instead")]
+#[deprecated(since = "0.4.0", note = "Please use v3 instead")]
#[allow(dead_code)]
-pub mod v1 {
+pub mod v2 {
pub const CREATE_TABLES: &str = r#"
CREATE TABLE "accounts" (
"user" TEXT NOT NULL,
"password" TEXT NOT NULL,
+ "uid" TEXT NOT NULL,
PRIMARY KEY("user")
);
-
CREATE TABLE "auth_meta" (
"key" TEXT NOT NULL,
"value" TEXT NOT NULL,
PRIMARY KEY("key")
);
- INSERT INTO "auth_meta" VALUES ('version', '1');
+ CREATE TABLE "repo" (
+ "uid" TEXT NOT NULL,
+ "repos" TEXT NOT NULL,
+ "expire" INTEGER,
+ PRIMARY KEY("uid")
+ );
+
+ INSERT INTO "auth_meta" VALUES ('version', '2');
"#;
pub const DROP_TABLES: &str = r#"
DROP TABLE "accounts";
- DROP TABLE "auth_meta";
+ DROP TABLE "repo";
+ DROP TABLE "auth_meta";
"#;
- pub const VERSION: &str = "1";
+ pub const VERSION: &str = "2";
}
#[allow(dead_code)]
-pub mod v2 {
+pub mod v3 {
pub const CREATE_TABLES: &str = r#"
CREATE TABLE "accounts" (
"user" TEXT NOT NULL,
@@ -64,29 +72,28 @@ pub mod v2 {
PRIMARY KEY("key")
);
- CREATE TABLE "repo" (
- "uid" TEXT NOT NULL,
- "repos" TEXT NOT NULL,
- "expire" INTEGER,
- PRIMARY KEY("uid")
+ CREATE TABLE "repos" (
+ "repo" TEXT NOT NULL,
+ "users" TEXT NOT NULL,
+ PRIMARY KEY("repo")
);
- INSERT INTO "auth_meta" VALUES ('version', '2');
+ INSERT INTO "auth_meta" VALUES ('version', '3');
"#;
pub const DROP_TABLES: &str = r#"
DROP TABLE "accounts";
- DROP TABLE "repo";
+ DROP TABLE "repos";
DROP TABLE "auth_meta";
"#;
- pub const VERSION: &str = "2";
+ pub const VERSION: &str = "3";
}
#[allow(deprecated)]
-pub use v1 as previous;
-pub use v2 as current;
-pub use v2::VERSION;
+pub use v2 as previous;
+pub use v3 as current;
+pub use v3::VERSION;
diff --git a/src/datastructures.rs b/src/datastructures.rs
index 4fa72ab..81e04aa 100644
--- a/src/datastructures.rs
+++ b/src/datastructures.rs
@@ -70,6 +70,10 @@ pub fn rand_str(len: usize) -> String {
password
}
+pub(crate) trait TestSuite {
+ fn generate_test_config() -> Self;
+}
+
#[derive(Debug, Clone)]
pub struct Config {
pub cookie_ttl: u64,
@@ -78,6 +82,7 @@ pub struct Config {
pub bypass_root: bool,
//secret: String,
pub(crate) test: bool,
+ protected_repos: ProtectedRepo,
}
impl Default for Config {
@@ -88,6 +93,7 @@ impl Default for Config {
bypass_root: false,
//secret: Default::default(),
test: false,
+ protected_repos: Default::default(),
}
}
}
@@ -98,7 +104,7 @@ impl Config {
}
pub fn load_from_path<P: AsRef<Path>>(path: P) -> Self {
- let file = read_to_string(path).unwrap_or_default();
+ let file = read_to_string(&path).unwrap_or_default();
let mut cookie_ttl: u64 = DEFAULT_COOKIE_TTL;
let mut database: &str = "/etc/cgit/auth.db";
let mut bypass_root: bool = false;
@@ -129,6 +135,7 @@ impl Config {
bypass_root,
test: false,
//secret: secret.to_string(),
+ protected_repos: ProtectedRepo::load_from_file(path),
}
}
@@ -148,15 +155,6 @@ impl Config {
)
}
- pub(crate) fn generate_test_config() -> Self {
- Self {
- database: "test/tmp.db".to_string(),
- bypass_root: false,
- cookie_ttl: DEFAULT_COOKIE_TTL,
- test: true,
- }
- }
-
async fn read_timestamp_from_file<P: AsRef<Path>>(path: P) -> Result<u64> {
let mut file = tokio::fs::File::open(path).await?;
let mut buffer = String::new();
@@ -206,6 +204,22 @@ impl Config {
))
.await
}
+
+ pub fn check_repo_protect(&self, repo: &str) -> bool {
+ self.protected_repos.query_is_protected(repo)
+ }
+}
+
+impl TestSuite for Config {
+ fn generate_test_config() -> Self {
+ Self {
+ database: "test/tmp.db".to_string(),
+ bypass_root: false,
+ cookie_ttl: DEFAULT_COOKIE_TTL,
+ test: true,
+ protected_repos: Default::default(),
+ }
+ }
}
#[derive(Debug, Clone, Default)]
@@ -365,6 +379,7 @@ impl Cookie {
format!("{}_{}", self.timestamp, self.randint)
}
+ #[allow(dead_code)]
pub fn get_user(&self) -> &str {
self.user.as_str()
}
@@ -388,48 +403,99 @@ impl std::fmt::Display for Cookie {
}
}
-/*
-pub struct CookieOrigin {
- timestamp: u64,
- randint: RandIntType,
- user: String,
- rand_str: String,
+
+/// To set specify repository protect, You should setup repo's protect attribute
+///
+/// # Examples
+///
+/// ```conf
+/// repo.url=test
+/// repo.protected=true
+/// ```
+///
+/// OR:
+///
+/// Set all repository under protected, by set cgit-simple-auth-protect=full
+
+#[derive(Debug, Clone)]
+pub struct ProtectedRepo {
+ protect_all: bool,
+ protect_repos: Vec<String>,
}
-impl CookieOrigin {
- pub fn new(randint: RandIntType, user: &String, rand_str: &String) -> Self {
- Self {
- timestamp: get_current_timestamp(),
- randint,
- user: user.clone(),
- rand_str: rand_str.clone(),
- }
- }
+impl ProtectedRepo {
- pub async fn to_cookie(&self, cfg: &Config) -> Result<Cookie> {
- let mut file = File::open(Path::new(CACHE_DIR).join(DEFAULT_IV_FILE_NAME)).await?;
- let mut context = String::new();
- file.read_to_string(&mut context).await?;
- let iv_file: IvFile = serde_json::from_str(&context)?;
- let key = cfg.secret.to_hex();
- let iv = iv_file.iv.to_hex();
+ pub fn load_from_file<P: AsRef<Path>>(path: P) -> Self {
+ let context = read_to_string(path).unwrap();
- let mut cipher = Blowfish::new(key);
+ let mut protect_repos = Vec::new();
- cipher.write_all().awa
+ let mut protect_all = false;
+ let mut current_repo: &str = "";
- let mut buffer = [0u8, 128];
+ for line in context.lines() {
+ let line = line.trim();
+ if line.starts_with('#') || !line.contains('=') {
+ continue
+ }
- let plain_text = format!("{}, {}", self.user, self.rand_str);
+ let (key, value) = if line.contains('#') {
+ line.split_once('#').unwrap().0.trim().split_once('=').unwrap()
+ } else {
+ line.split_once('=').unwrap()
+ };
+
+ if key.eq("repo.url") {
+ current_repo = value.trim();
+ continue
+ }
- let pos = plain_text.len();
+ 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("include") {
+ let r = Self::load_from_file(value);
+ if r.protect_all {
+ protect_all = true;
+ break
+ }
+ protect_repos.extend(r.protect_repos)
+ }
- buffer[..pos].copy_from_slice(plain_text.as_bytes());
+ if key.eq("cgit-simple-auth-protect") && value.trim().to_lowercase().eq("full") {
+ protect_all = true;
+ break
+ }
+ }
+ if protect_all {
+ protect_repos.clear();
+ }
+ Self {
+ protect_all,
+ protect_repos,
+ }
+ }
- let b = Block::from_mut_slice(&mut buffer);
+ pub fn query_is_protected(&self, repo: &str) -> bool {
+ if self.protect_all {
+ return true
+ }
+ self.protect_repos.iter().any(|x| x.eq(repo))
+ }
+ pub fn query_is_all_protected(&self) -> bool {
+ self.protect_all
+ }
+}
- Ok(())
+impl Default for ProtectedRepo {
+ fn default() -> Self {
+ Self {
+ protect_all: true,
+ protect_repos: Default::default()
+ }
}
}
-*/
diff --git a/src/main.rs b/src/main.rs
index 932a29f..7edd019 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -21,7 +21,7 @@
mod database;
mod datastructures;
-use crate::datastructures::{Config, Cookie, FormData};
+use crate::datastructures::{Config, Cookie, FormData, TestSuite};
use anyhow::Result;
use argon2::password_hash::PasswordHash;
use clap::{App, Arg, ArgMatches, SubCommand};
@@ -51,12 +51,12 @@ impl<R: BufRead, W: Write> IOModule<R, W> {
// 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 redis_conn = redis::Client::open("redis://127.0.0.1/")?;
- let ret = verify_login(&cfg, &data, redis_conn.clone()).await;
+ let ret = verify_login(&cfg, &data).await;
if let Err(ref e) = ret {
eprintln!("{:?}", e);
@@ -64,6 +64,7 @@ impl<R: BufRead, W: Write> IOModule<R, W> {
}
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_async_connection().await?;
@@ -103,6 +104,7 @@ impl<R: BufRead, W: Write> IOModule<R, W> {
// Processing the `authenticate-cookie` called by cgit.
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 mut bypass = false;
@@ -110,7 +112,7 @@ async fn cmd_authenticate_cookie(matches: &ArgMatches<'_>, cfg: Config) -> Resul
bypass = true;
}
- if bypass {
+ if bypass || !cfg.check_repo_protect(repo){
return Ok(true);
}
@@ -121,6 +123,26 @@ async fn cmd_authenticate_cookie(matches: &ArgMatches<'_>, cfg: Config) -> Resul
let redis_conn = redis::Client::open("redis://127.0.0.1/")?;
let mut conn = redis_conn.get_async_connection().await?;
+ if !repo.is_empty() {
+ let key = format!("cgit_repo_{}", repo);
+ if !conn.exists(&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 iter = users.split_whitespace().collect::<Vec<&str>>();
+ conn.sadd(&key, iter).await?;
+ }
+ }
+ // TODO: redis repository ACL check should goes here
+ }
+
if let Ok(Some(cookie)) = Cookie::load_from_request(cookies) {
if let Ok(r) = conn
.get::<_, String>(format!("cgit_auth_{}", cookie.get_key()))
@@ -167,7 +189,7 @@ async fn cmd_init(cfg: Config) -> Result<()> {
Ok(())
}
-async fn verify_login(cfg: &Config, data: &FormData, redis_conn: redis::Client) -> Result<bool> {
+async fn verify_login(cfg: &Config, data: &FormData) -> Result<bool> {
if !cfg.test {
let last_copied = cfg.get_last_copy_timestamp().await.unwrap_or(0);
if last_copied == 0 || cfg.get_last_commit_timestamp().await.unwrap_or(0) != last_copied {
@@ -179,8 +201,6 @@ async fn verify_login(cfg: &Config, data: &FormData, redis_conn: redis::Client)
}
}
- let mut rd = redis_conn.get_async_connection().await?;
-
let mut conn = sqlx::sqlite::SqliteConnectOptions::from_str(
cfg.get_copied_database_location().to_str().unwrap(),
)?
@@ -189,26 +209,13 @@ async fn verify_login(cfg: &Config, data: &FormData, redis_conn: redis::Client)
.connect()
.await?;
- let (passwd_hash, uid) = sqlx::query_as::<_, (String, String)>(
- r#"SELECT "password", "uid" FROM "accounts" WHERE "user" = ?"#,
+ let (passwd_hash,) = sqlx::query_as::<_, (String,)>(
+ r#"SELECT "password" FROM "accounts" WHERE "user" = ?"#,
)
.bind(data.get_user())
.fetch_one(&mut conn)
.await?;
- let key = format!("cgit_repo_{}", data.get_user());
- if !rd.exists(&key).await? {
- if let Some((repos,)) =
- sqlx::query_as::<_, (String,)>(r#"SELECT "repos" FROM "repo" WHERE "uid" = ? "#)
- .bind(uid)
- .fetch_optional(&mut conn)
- .await?
- {
- let iter = repos.split_whitespace().collect::<Vec<&str>>();
- rd.sadd(&key, iter).await?;
- }
- }
-
let parsed_hash = PasswordHash::new(passwd_hash.as_str()).unwrap();
Ok(data.verify_password(&parsed_hash))
}
@@ -360,15 +367,15 @@ async fn cmd_reset_database(matches: &ArgMatches<'_>, cfg: Config) -> Result<()>
async fn cmd_upgrade_database(cfg: Config) -> Result<()> {
let tmp_dir = TempDir::new("rolling")?;
- let v1_path = tmp_dir.path().join("v1.db");
let v2_path = tmp_dir.path().join("v2.db");
+ let v3_path = tmp_dir.path().join("v3.db");
- drop(std::fs::File::create(&v2_path).expect("Create v2 database failure"));
+ drop(std::fs::File::create(&v3_path).expect("Create v3 database failure"));
- std::fs::copy(cfg.get_database_location(), &v1_path)
- .expect("Copy v1 database to tempdir failure");
+ std::fs::copy(cfg.get_database_location(), &v2_path)
+ .expect("Copy v2 database to tempdir failure");
- let mut origin_conn = SqliteConnectOptions::from_str(v1_path.as_path().to_str().unwrap())?
+ let mut origin_conn = SqliteConnectOptions::from_str(v2_path.as_path().to_str().unwrap())?
.read_only(true)
.connect()
.await?;
@@ -382,17 +389,16 @@ async fn cmd_upgrade_database(cfg: Config) -> Result<()> {
#[allow(deprecated)]
if v.eq(database::previous::VERSION) {
- let mut conn = SqliteConnection::connect(v2_path.as_path().to_str().unwrap()).await?;
+ 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)>(r#"SELECT * FROM "accounts""#)
+ let mut iter = sqlx::query_as::<_, (String, String, String)>(r#"SELECT * FROM "accounts""#)
.fetch(&mut origin_conn);
- while let Some(Ok((user, passwd))) = iter.next().await {
- let uid = uuid::Uuid::new_v4().to_hyphenated().to_string();
+ while let Some(Ok((user, passwd, uid))) = iter.next().await {
sqlx::query(r#"INSERT INTO "accounts" VALUES (?, ?, ?)"#)
.bind(user.as_str())
.bind(passwd)
@@ -403,7 +409,7 @@ async fn cmd_upgrade_database(cfg: Config) -> Result<()> {
}
drop(conn);
- std::fs::copy(&v2_path, cfg.get_database_location())
+ std::fs::copy(&v3_path, cfg.get_database_location())
.expect("Copy back to database location failure");
println!("Upgrade database successful");
} else {
@@ -420,6 +426,96 @@ async fn cmd_upgrade_database(cfg: Config) -> Result<()> {
Ok(())
}
+async fn cmd_repo_user_control(matches: &ArgMatches<'_>, cfg: Config, is_delete: bool) -> Result<()> {
+ let repo = matches.value_of("repo").unwrap_or("");
+ let user = matches.value_of("user").unwrap_or("");
+
+ let clear_all = matches.is_present("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_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 {} {} repository {} ACL successful",
+ if is_delete { "Delete" } else { "Add" },
+ user,
+ if is_delete { "from" } else { "to" },
+ repo,
+ );
+ } else {
+ println!("Clear all users from repository {} ACL", 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()
@@ -466,6 +562,13 @@ async fn async_main(arg_matches: ArgMatches<'_>) -> Result<i32> {
("upgrade", Some(_matches)) => {
cmd_upgrade_database(cfg).await?;
}
+ ("repoadd", Some(matches)) => {
+ cmd_repo_user_control(matches, cfg, false).await?
+ }
+ ("repodel", Some(matches)) => {
+ cmd_repo_user_control(matches, cfg, true).await?;
+ }
+ // TODO: other repository rated command
_ => {}
}
Ok(0)
@@ -524,7 +627,29 @@ fn get_arg_matches(arguments: Option<Vec<&str>>) -> ArgMatches {
)
.subcommand(
SubCommand::with_name("upgrade")
- .about("Upgrade database from v1(v0.1.x - v0.2.x) to v2(^v0.3.x)"),
+ .about("Upgrade database from v2(v0.3.x) to v3(^v0.4.x)"),
+ )
+ .subcommand(
+ SubCommand::with_name("repoadd")
+ .about("Add user to repository")
+ .arg(Arg::with_name("repo").required(true))
+ .arg(Arg::with_name("user").required(true)),
+ )
+ .subcommand(
+ SubCommand::with_name("repodel")
+ .about("Del user from repository")
+ .arg(Arg::with_name("repo").required(true))
+ .arg(Arg::with_name("user").takes_value(true))
+ .arg(
+ Arg::with_name("clear-all")
+ .long("--clear-all")
+ .conflicts_with("user"),
+ ),
+ )
+ .subcommand(
+ SubCommand::with_name("repos")
+ .about("Show all repositories or only show specify repository detail")
+ .arg(Arg::with_name("repo").takes_value(true)),
);
let matches = if let Some(args) = arguments {
@@ -588,8 +713,8 @@ fn main() -> Result<()> {
#[cfg(test)]
mod test {
- use crate::datastructures::{rand_str, Config};
- use crate::{cmd_add_user, cmd_init, cmd_authenticate_cookie};
+ use crate::datastructures::{rand_str, Config, TestSuite, ProtectedRepo};
+ use crate::{cmd_add_user, cmd_authenticate_cookie, cmd_init};
use crate::{get_arg_matches, IOModule};
use argon2::{
password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
@@ -687,14 +812,14 @@ mod test {
}
#[test]
- fn test_1_auth_failure() {
+ 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_0_init_database() {
+ fn test_00_init_database() {
let tmp_dir = Path::new("test");
if tmp_dir.exists() {
@@ -724,7 +849,7 @@ mod test {
}
#[test]
- fn test_2_insert_user() {
+ 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() {
@@ -743,7 +868,13 @@ mod test {
}
#[test]
- fn test_3_auth_pass() {
+ fn test_03_insert_repo() {
+ lock(&PathBuf::from("test/USER_WRITTEN"), 5);
+ let matches = crate::get_arg_matches(Some(vec!["a", "repoadd", "hunter2", "hunter2"]));
+ }
+
+ #[test]
+ fn test_91_auth_pass() {
lock(&PathBuf::from("test/USER_WRITTEN"), 7);
let s = test_auth_post();
@@ -761,7 +892,7 @@ mod test {
}
#[test]
- fn test_4_authenticate_cookie() {
+ fn test_92_authenticate_cookie() {
lock(&PathBuf::from("test/RESPONSE"), 15);
let mut buffer = String::new();
@@ -774,42 +905,85 @@ mod test {
for line in buffer.lines().map(|x| x.trim()) {
if !line.starts_with("Set-Cookie") {
- continue
+ continue;
}
let (_, value) = line.split_once(":").unwrap();
let (value, _) = value.split_once(";").unwrap();
cookie = value.trim();
- break
+ break;
}
- let matches = get_arg_matches(
- Some(
- vec![
- "a",
- "authenticate-cookie",
- cookie,
- "GET",
- "",
- "https://git.example.com/",
- "/",
- "git.example.com",
- "on",
- "",
- "",
- "/",
- "/?p=login",
+ let matches = get_arg_matches(Some(vec![
+ "a",
+ "authenticate-cookie",
+ cookie,
+ "GET",
+ "",
+ "https://git.example.com/",
+ "/",
+ "git.example.com",
+ "on",
+ "",
+ "",
+ "/",
+ "/?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!()
+ ("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", 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 result = ProtectedRepo::load_from_file(tmpdir.path().join("CFG"));
+
+ assert!(result.query_is_protected("test"));
+ assert!(!result.query_is_all_protected());
+
+ write_to_specify_file(&tmpdir.path().join("REPO_SETTING"), b"repo.protect=true\nrepo.url=test").unwrap();
+
+ let result = ProtectedRepo::load_from_file(tmpdir.path().join("CFG"));
+
+ assert!(!result.query_is_protected("test"));
+ assert!(!result.query_is_all_protected());
+
+ write_to_specify_file(&tmpdir.path().join("REPO_SETTING"), b"repo.protect=true\nrepo.url=test\n\ncgit-simple-auth-protect=full").unwrap();
+
+ let result = ProtectedRepo::load_from_file(tmpdir.path().join("CFG"));
+
+ assert!(result.query_is_all_protected());
+ assert!(result.query_is_protected("test"));
+
+ tmpdir.close().unwrap();
+ }
}