diff --git a/Cargo.lock b/Cargo.lock index c893608..113d73d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -324,9 +324,9 @@ checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "cc" -version = "1.2.9" +version = "1.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8293772165d9345bdaaa39b45b2109591e63fe5e6fbc23c6ff930a048aa310b" +checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" dependencies = [ "jobserver", "libc", @@ -811,7 +811,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.7.0", + "indexmap 2.7.1", "slab", "tokio", "tokio-util", @@ -1190,9 +1190,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", "hashbrown 0.15.2", @@ -1209,9 +1209,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.10.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "itertools" @@ -1515,7 +1515,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12779523996a67c13c84906a876ac6fe4d07a6e1adb54978378e13f199251a62" dependencies = [ "base64 0.22.1", - "indexmap 2.7.0", + "indexmap 2.7.1", "metrics", "metrics-util", "quanta", @@ -1710,9 +1710,9 @@ dependencies = [ [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-src" @@ -2223,9 +2223,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" -version = "0.38.43" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags", "errno", @@ -2457,7 +2457,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.7.0", + "indexmap 2.7.1", "itoa", "ryu", "serde", @@ -2537,9 +2537,9 @@ dependencies = [ [[package]] name = "similar" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "sketches-ddsketch" @@ -3066,9 +3066,9 @@ checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "11cd88e12b17c6494200a9c1b683a04fcac9573ed74cd1b62aeb2727c5592243" [[package]] name = "unsafe-libyaml" @@ -3107,18 +3107,18 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744018581f9a3454a9e15beb8a33b017183f1e7c0cd170232a2d1453b23a51c4" +checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" dependencies = [ "getrandom", ] [[package]] name = "valuable" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "vcpkg" diff --git a/src/registry/registry.rs b/src/registry/registry.rs index 85c28b0..45fec91 100644 --- a/src/registry/registry.rs +++ b/src/registry/registry.rs @@ -17,14 +17,14 @@ struct TokenResponse { } #[derive(Debug)] -struct AuthChallenge { - realm: String, - service: String, - scope: String, +pub struct AuthChallenge { + pub realm: String, + pub service: String, + pub scope: String, } impl AuthChallenge { - fn from_header(header: &str) -> Option { + pub fn from_header(header: &str) -> Option { let mut realm = None; let mut service = None; let mut scope = None; @@ -57,11 +57,11 @@ impl AuthChallenge { #[derive(Debug)] pub struct RegistryChecker { - client: Client, - registry_url: String, - auth_token: Option, - username: Option, - password: Option, + pub client: Client, + pub registry_url: String, + pub auth_token: Option, + pub username: Option, + pub password: Option, } impl RegistryChecker { @@ -107,7 +107,7 @@ impl RegistryChecker { }) } - async fn get_bearer_token(&self, challenge: &AuthChallenge) -> Result { + pub async fn get_bearer_token(&self, challenge: &AuthChallenge) -> Result { let mut request = self .client .get(&challenge.realm) diff --git a/tests/registry_tests.rs b/tests/registry_tests.rs index 6bfb545..9b5d17f 100644 --- a/tests/registry_tests.rs +++ b/tests/registry_tests.rs @@ -1,10 +1,11 @@ #[cfg(test)] mod tests { use gitops_operator::registry::*; + use serde_json::json; use tracing_subscriber::{fmt, EnvFilter}; use wiremock::{ - matchers::{header, method, path}, + matchers::{header, method, path, query_param}, Mock, MockServer, ResponseTemplate, }; @@ -148,4 +149,117 @@ mod tests { assert!(!result.unwrap()); } + + #[test] + fn test_auth_challenge_from_header() { + // Test successful parsing + let header = r#"Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:test/image:pull""#; + let challenge = AuthChallenge::from_header(header); + assert!(challenge.is_some()); + + let challenge = challenge.unwrap(); + assert_eq!(challenge.realm, "https://auth.docker.io/token"); + assert_eq!(challenge.service, "registry.docker.io"); + assert_eq!(challenge.scope, "repository:test/image:pull"); + + // Test missing Bearer prefix + let header = r#"realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:test/image:pull""#; + let challenge = AuthChallenge::from_header(header); + assert!(challenge.is_none()); + + // Test missing required field + let header = + r#"Bearer realm="https://auth.docker.io/token",scope="repository:test/image:pull""#; + let challenge = AuthChallenge::from_header(header); + assert!(challenge.is_none()); + + // Test malformed header + let header = r#"Bearer malformed_content"#; + let challenge = AuthChallenge::from_header(header); + assert!(challenge.is_none()); + } + + #[tokio::test] + async fn test_get_bearer_token_no_auth() { + init_logging(); + let mock_server = MockServer::start().await; + + let challenge = AuthChallenge { + realm: mock_server.uri() + "/token", + service: "registry.test.com".to_string(), + scope: "repository:test/image:pull".to_string(), + }; + + Mock::given(method("GET")) + .and(path("/token")) + .and(query_param("service", "registry.test.com")) + .and(query_param("scope", "repository:test/image:pull")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "token": "new-token", + "expires_in": 300 + }))) + .mount(&mock_server) + .await; + + let checker = RegistryChecker::new(mock_server.uri(), None).await.unwrap(); + let token = checker.get_bearer_token(&challenge).await; + assert!(token.is_ok()); + assert_eq!(token.unwrap(), "new-token"); + } + + #[tokio::test] + async fn test_get_bearer_token_with_basic_auth() { + init_logging(); + let mock_server = MockServer::start().await; + + let challenge = AuthChallenge { + realm: mock_server.uri() + "/token", + service: "registry.test.com".to_string(), + scope: "repository:test/image:pull".to_string(), + }; + + Mock::given(method("GET")) + .and(path("/token")) + .and(query_param("service", "registry.test.com")) + .and(query_param("scope", "repository:test/image:pull")) + .and(header("authorization", "Basic dXNlcjpwYXNz")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "token": "new-token-with-auth", + "expires_in": 300 + }))) + .mount(&mock_server) + .await; + + let checker = + RegistryChecker::new(mock_server.uri(), Some("Basic dXNlcjpwYXNz".to_string())) + .await + .unwrap(); + let token = checker.get_bearer_token(&challenge).await; + assert!(token.is_ok()); + assert_eq!(token.unwrap(), "new-token-with-auth"); + } + + #[tokio::test] + async fn test_get_bearer_token_failed_auth() { + init_logging(); + let mock_server = MockServer::start().await; + + let challenge = AuthChallenge { + realm: mock_server.uri() + "/token", + service: "registry.test.com".to_string(), + scope: "repository:test/image:pull".to_string(), + }; + + Mock::given(method("GET")) + .and(path("/token")) + .and(query_param("service", "registry.test.com")) + .and(query_param("scope", "repository:test/image:pull")) + .respond_with(ResponseTemplate::new(401)) + .mount(&mock_server) + .await; + + let checker = RegistryChecker::new(mock_server.uri(), None).await.unwrap(); + let token = checker.get_bearer_token(&challenge).await; + assert!(token.is_err()); + } }