diff options
Diffstat (limited to 'src/login.rs')
-rw-r--r-- | src/login.rs | 115 |
1 files changed, 115 insertions, 0 deletions
diff --git a/src/login.rs b/src/login.rs new file mode 100644 index 0000000..ca12c50 --- /dev/null +++ b/src/login.rs @@ -0,0 +1,115 @@ +use std::path::{Path, PathBuf}; + +use crate::Login; + +use clap::Args; +use clap::{Parser, Subcommand}; + +#[derive(Parser)] +#[command()] +struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + #[command(name = "git-upload-pack")] + GitUploadPack(GitUploadPack), + #[command(name = "git-receive-pack")] + GitReceivePack(GitUploadPack), // TODO: own args +} +#[derive(Args)] +struct GitUploadPack { + repo_path: String, +} +pub(crate) fn do_login(login: &Login) { + let command = std::env::var("SSH_ORIGINAL_COMMAND").unwrap_or("<no command>".to_owned()); + log::info!( + "Performed token login for {} {} with command: {}", + login.keytype, + login.keydata, + command + ); + let user_token = get_user_token(login.keytype.to_owned(), login.keydata.to_owned()); + let mut command = shlex::split(&command).unwrap(); + command.insert(0, "fagit-dummy".to_owned()); + let args = Cli::parse_from(command.iter()); + match args.command { + Commands::GitUploadPack(upload_pack) => { + let repo_path = canonicalize_repo(&upload_pack.repo_path, &user_token); + if can_read(&repo_path, &user_token) { + let mut child = std::process::Command::new("git-upload-pack") + .args([repo_path.repo_path().as_os_str()]) + .spawn() + .unwrap(); + child.wait().unwrap(); + } + } + Commands::GitReceivePack(upload_pack) => { + let repo_path = canonicalize_repo(&upload_pack.repo_path, &user_token); + if can_write(&repo_path, &user_token) { + let mut child = std::process::Command::new("git-receive-pack") + .args([repo_path.repo_path().as_os_str()]) + .spawn() + .unwrap(); + child.wait().unwrap(); + } + } + } +} +struct UserToken { + keytype: String, + keydata: String, + + is_operator: bool, +} +struct RepoId(String); +impl RepoId { + fn is_meta_repo(&self) -> bool { + return self.0.starts_with("meta/") || self.0 == "meta.git"; + } + fn repo_path(&self) -> PathBuf { + return Path::join(crate::config::repo_store(), self.0.clone()); + } +} + +fn canonicalize_repo(path: &str, user: &UserToken) -> RepoId { + let mut path = path.to_owned(); + path.make_ascii_lowercase(); + if path.starts_with("/") { + path.remove(0); + } + if path.ends_with("/") { + path.remove(path.len() - 1); + } + if !path.ends_with(".git") { + path += ".git"; + } + // TODO: properly check directory traversal or smth here. just a regex should do the trick + return RepoId(path); +} + +fn can_write(repo_path: &RepoId, user_token: &UserToken) -> bool { + if repo_path.is_meta_repo() { + return user_token.is_operator; + } + return true; +} +fn can_read(repo_path: &RepoId, user_token: &UserToken) -> bool { + if repo_path.is_meta_repo() { + return user_token.is_operator; + } + return true; +} + +fn get_user_token(keytype: String, keydata: String) -> UserToken { + // TODO: settings + let is_operator = "ssh-ed25519" == keytype + && "AAAAC3NzaC1lZDI1NTE5AAAAINg2WYMRKINwbH5UCqqK2qq/qW0gG1NnaALHqEyU4NzM" == keydata; + return UserToken { + keytype, + keydata, + is_operator, + }; +} |