aboutsummaryrefslogtreecommitdiffstats
path: root/git-lfs-authenticate
diff options
context:
space:
mode:
Diffstat (limited to 'git-lfs-authenticate')
-rw-r--r--git-lfs-authenticate/Cargo.toml10
-rw-r--r--git-lfs-authenticate/src/main.rs133
2 files changed, 0 insertions, 143 deletions
diff --git a/git-lfs-authenticate/Cargo.toml b/git-lfs-authenticate/Cargo.toml
deleted file mode 100644
index 15feba8..0000000
--- a/git-lfs-authenticate/Cargo.toml
+++ /dev/null
@@ -1,10 +0,0 @@
1[package]
2name = "git-lfs-authenticate"
3version = "0.1.0"
4edition = "2021"
5
6[dependencies]
7anyhow = "1.0"
8chrono = "0.4"
9common = { path = "../common" }
10serde_json = "1"
diff --git a/git-lfs-authenticate/src/main.rs b/git-lfs-authenticate/src/main.rs
deleted file mode 100644
index c9094a1..0000000
--- a/git-lfs-authenticate/src/main.rs
+++ /dev/null
@@ -1,133 +0,0 @@
1use anyhow::{anyhow, bail, Result};
2use chrono::Utc;
3use serde_json::json;
4use std::{process::ExitCode, time::Duration};
5
6fn main() -> ExitCode {
7 let config = match Config::load() {
8 Ok(config) => config,
9 Err(e) => {
10 eprintln!("Error: {e}");
11 return ExitCode::from(2);
12 }
13 };
14
15 let (repo_name, operation) = match parse_cmdline() {
16 Ok(args) => args,
17 Err(e) => {
18 eprintln!("Error: {e}\n");
19 eprintln!("Usage: git-lfs-authenticate <REPO> upload/download");
20 // Exit code 2 signifies bad usage of CLI.
21 return ExitCode::from(2);
22 }
23 };
24
25 if !repo_exists(&repo_name) {
26 eprintln!("Error: repository does not exist");
27 return ExitCode::FAILURE;
28 }
29
30 let expires_at = Utc::now() + Duration::from_secs(5 * 60);
31 let Some(tag) = common::generate_tag(
32 common::Claims {
33 specific_claims: common::SpecificClaims::BatchApi(operation),
34 repo_path: &repo_name,
35 expires_at,
36 },
37 config.key,
38 ) else {
39 eprintln!("Failed to generate validation tag");
40 return ExitCode::FAILURE;
41 };
42
43 let response = json!({
44 "header": {
45 "Authorization": format!(
46 "Gitolfs3-Hmac-Sha256 {tag} {}",
47 expires_at.timestamp()
48 ),
49 },
50 "expires_at": expires_at.to_rfc3339_opts(chrono::SecondsFormat::Secs, true),
51 "href": format!("{}{}/info/lfs", config.href_base, repo_name),
52 });
53 println!("{}", response);
54
55 ExitCode::SUCCESS
56}
57
58struct Config {
59 href_base: String,
60 key: common::Key,
61}
62
63impl Config {
64 fn load() -> Result<Self> {
65 let Ok(href_base) = std::env::var("GITOLFS3_HREF_BASE") else {
66 bail!("configured base URL not provided");
67 };
68 if !href_base.ends_with('/') {
69 bail!("configured base URL does not end with a slash");
70 }
71
72 let Ok(key_path) = std::env::var("GITOLFS3_KEY_PATH") else {
73 bail!("key path not provided");
74 };
75 let key = common::load_key(&key_path).map_err(|e| anyhow!("failed to load key: {e}"))?;
76
77 Ok(Self { href_base, key })
78 }
79}
80
81fn parse_cmdline() -> Result<(String, common::Operation)> {
82 let [repo_path, op_str] = get_cmdline_args::<2>()?;
83 let op: common::Operation = op_str
84 .parse()
85 .map_err(|e| anyhow!("unknown operation: {e}"))?;
86 validate_repo_path(&repo_path).map_err(|e| anyhow!("invalid repository name: {e}"))?;
87 Ok((repo_path.to_string(), op))
88}
89
90fn get_cmdline_args<const N: usize>() -> Result<[String; N]> {
91 let args = std::env::args();
92 if args.len() - 1 != N {
93 bail!("got {} argument(s), expected {}", args.len() - 1, N);
94 }
95
96 // Does not allocate.
97 const EMPTY_STRING: String = String::new();
98 let mut values = [EMPTY_STRING; N];
99
100 // Skip the first element; we do not care about the program name.
101 for (i, arg) in args.skip(1).enumerate() {
102 values[i] = arg
103 }
104 Ok(values)
105}
106
107fn validate_repo_path(path: &str) -> Result<()> {
108 if path.len() > 100 {
109 bail!("too long (more than 100 characters)");
110 }
111 if path.contains("//")
112 || path.contains("/./")
113 || path.contains("/../")
114 || path.starts_with("./")
115 || path.starts_with("../")
116 {
117 bail!("contains one or more path elements '.' and '..'");
118 }
119 if path.starts_with('/') {
120 bail!("starts with '/', which is not allowed");
121 }
122 if !path.ends_with(".git") {
123 bail!("missed '.git' suffix");
124 }
125 Ok(())
126}
127
128fn repo_exists(name: &str) -> bool {
129 match std::fs::metadata(name) {
130 Ok(metadata) => metadata.is_dir(),
131 _ => false,
132 }
133}