aboutsummaryrefslogtreecommitdiff
path: root/src/login.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/login.rs')
-rw-r--r--src/login.rs115
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,
+ };
+}