1use crate::ssh_publishers::authorize_ssh_push;
2use crate::{PrayError, PrayResult};
3use std::path::Path;
4
5pub fn authorize_distribution_push(
6 root: &Path,
7 bind_host: &str,
8 allow_open_push: bool,
9 stdio_mode: bool,
10 authorization: Option<&str>,
11) -> PrayResult<()> {
12 if let Some(token) = publish_bearer_token(authorization) {
13 return authorize_publish_token(root, &token);
14 }
15
16 let stdio_mode = stdio_mode || std::env::var_os("PRAY_SERVE_STDIO").is_some();
17 if stdio_mode {
18 return authorize_ssh_push(root);
19 }
20
21 match authorize_ssh_push(root) {
22 Ok(()) => {
23 if publishers_configured(root)? {
24 return Ok(());
25 }
26 }
27 Err(error) => return Err(error),
28 }
29
30 if allow_open_push || is_loopback_bind_host(bind_host) {
31 return Ok(());
32 }
33
34 Err(PrayError::Resolution(
35 "HTTP push requires a publish token (Authorization: Bearer), configured ssh publishers, loopback bind, or --allow-open-push"
36 .to_string(),
37 ))
38}
39
40fn publish_bearer_token(authorization: Option<&str>) -> Option<String> {
41 #[cfg(feature = "auth")]
42 {
43 crate::auth_store::bearer_token_from_authorization(authorization)
44 }
45 #[cfg(not(feature = "auth"))]
46 {
47 let _ = authorization;
48 None
49 }
50}
51
52fn authorize_publish_token(root: &Path, token: &str) -> PrayResult<()> {
53 #[cfg(feature = "auth")]
54 {
55 let store = crate::auth_store::RegistryAuthStore::open(root)?;
56 match store.resolve_publish_token(token)? {
57 Some(_) => Ok(()),
58 None => Err(PrayError::Resolution(
59 "invalid or unknown publish token".to_string(),
60 )),
61 }
62 }
63 #[cfg(not(feature = "auth"))]
64 {
65 let _ = (root, token);
66 Err(PrayError::Unsupported(
67 "publish tokens require the auth feature".to_string(),
68 ))
69 }
70}
71
72fn publishers_configured(root: &Path) -> PrayResult<bool> {
73 match crate::ssh_publishers::read_ssh_publishers(root)? {
74 Some(config) => Ok(!config.publishers.is_empty()),
75 None => Ok(false),
76 }
77}
78
79pub fn is_loopback_bind_host(host: &str) -> bool {
80 matches!(host, "127.0.0.1" | "localhost" | "::1" | "0:0:0:0:0:0:0:1")
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use std::fs;
87
88 #[test]
89 fn loopback_allows_open_push_without_publishers() {
90 let root =
91 std::env::temp_dir().join(format!("pray-push-auth-loopback-{}", std::process::id()));
92 let _ = fs::remove_dir_all(&root);
93 fs::create_dir_all(&root).expect("temp root");
94 authorize_distribution_push(&root, "127.0.0.1", false, false, None)
95 .expect("loopback open push");
96 let _ = fs::remove_dir_all(&root);
97 }
98
99 #[test]
100 fn non_loopback_requires_flag_without_publishers() {
101 let root =
102 std::env::temp_dir().join(format!("pray-push-auth-public-{}", std::process::id()));
103 let _ = fs::remove_dir_all(&root);
104 fs::create_dir_all(&root).expect("temp root");
105 let error = authorize_distribution_push(&root, "0.0.0.0", false, false, None)
106 .expect_err("public bind");
107 assert!(error.to_string().contains("--allow-open-push"));
108 authorize_distribution_push(&root, "0.0.0.0", true, false, None).expect("flag allows");
109 let _ = fs::remove_dir_all(&root);
110 }
111}