summaryrefslogtreecommitdiff
path: root/src/main.rs
diff options
context:
space:
mode:
authorJesper Jensen <jesper@jnsn.dev>2025-12-19 16:46:33 +0100
committerJesper Jensen <jesper@jnsn.dev>2025-12-19 16:46:33 +0100
commit7e1603c9add91857b72ef8d2b65c3fc0e3182338 (patch)
treec8d7c142c45827c8530a99a3c3e84d8f11f9749f /src/main.rs
parent9ff76e6a201e53f1377a0f7e5bcff6b728c1b8ab (diff)
Add the authentication stuff
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs142
1 files changed, 109 insertions, 33 deletions
diff --git a/src/main.rs b/src/main.rs
index 150c993..074b580 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -55,7 +55,7 @@ impl Auth {
#[derive(Debug)]
enum AuthMethod {
Basic,
- Bearer{realm: String},
+ Bearer{realm: String, service: String, scope: String},
}
#[derive(Debug)]
@@ -121,12 +121,14 @@ fn parse_quotedstring(str: &Vec<char>, start: usize) -> Option<(Range<usize>, us
}
fn parse_sp(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> {
+ if str.len() <= start {
+ return None;
+ }
+
let mut pos = start;
- loop {
- match str[pos] {
- ' ' => pos += 1,
- _ => break,
- }
+ match str[pos] {
+ ' ' => pos += 1,
+ _ => return None,
}
if pos == start {
@@ -137,6 +139,10 @@ fn parse_sp(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> {
}
fn parse_crlf(str: &Vec<char>, start: usize) -> Option<usize> {
+ if str.len() - 2 < start {
+ return None;
+ }
+
if str[start] == '\r' && str[start+1] == '\n' {
return Some(start + 2);
}
@@ -152,7 +158,10 @@ fn parse_lws(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> {
None => {},
}
- (_, pos) = parse_sp(str, pos).unwrap();
+ match parse_sp(str, pos) {
+ Some((_, npos)) => pos = npos,
+ None => return None,
+ }
loop {
match parse_sp(str, pos) {
Some((_, npos)) => pos = npos,
@@ -209,19 +218,48 @@ fn parse_challenge(str: &Vec<char>, start: usize) -> Option<(Challenge, usize)>
let mut params = vec!();
+ let mut pos = pos;
+ loop {
+ match parse_lws(str, pos) {
+ Some((_, npos)) => pos = npos,
+ None => break,
+ }
+ }
let (param, pos) = parse_param(str, pos).unwrap();
params.push(param);
let mut pos = pos;
loop {
+ loop {
+ match parse_lws(str, pos) {
+ Some((_, npos)) => pos = npos,
+ None => break,
+ }
+ }
+
+ if pos >= str.len() || str[pos] != ',' {
+ break;
+ }
+ pos += 1;
+
+ loop {
+ match parse_lws(str, pos) {
+ Some((_, npos)) => pos = npos,
+ None => break,
+ }
+ }
+
match parse_param(str, pos) {
Some((param, npos)) => {
pos = npos;
params.push(param);
},
- None => break,
+ None => return None,
}
}
+ if pos < str.len() {
+ return None;
+ }
return Some((Challenge {scheme: scheme_range, params}, pos));
}
@@ -236,13 +274,23 @@ impl AuthMethod {
if &header_str[parse.scheme.clone()] == "Bearer" {
let mut realm_param = None;
+ let mut scope_param = None;
+ let mut service_param = None;
for param in parse.params {
- if &header_str[param.key.clone()] == "realm" {
- realm_param = Some(param);
+ dbg!(&header_str[param.key.clone()]);
+ match &header_str[param.key.clone()] {
+ "realm" => realm_param = Some(param),
+ "scope" => scope_param = Some(param),
+ "service" => service_param = Some(param),
+ _ => {},
}
}
- return Some(AuthMethod::Bearer{realm: header_str[realm_param.unwrap().value].to_string()});
+ return Some(AuthMethod::Bearer{
+ realm: header_str[realm_param.unwrap().value].to_string(),
+ scope: header_str[scope_param.unwrap().value].to_string(),
+ service: header_str[service_param.unwrap().value].to_string(),
+ });
}
return None;
@@ -271,16 +319,41 @@ fn perform_registry_request(registry: &str, url: String, auth: &mut Auth) -> Res
if let Some(ref mut state) = state {
if let AuthStage::Idle = state.stage {
if let Some(auth_header) = response.headers().get("www-authenticate") {
- dbg!(AuthMethod::from_header(auth_header));
- let auth_header = auth_header.to_str().unwrap();
- if auth_header.starts_with("Basic") {
- let basic_auth = format!("Basic {}", BASE64_STANDARD.encode(format!("{}:{}", state.info.username, state.info.password)));
- state.stage = AuthStage::Authorized(basic_auth);
-
- continue; // try again
- } else if auth_header.starts_with("Bearer") {
- } else {
- todo!("Implement other authentication scheme");
+ let auth_header = AuthMethod::from_header(auth_header);
+ match auth_header {
+ Some(AuthMethod::Basic) => {
+ let basic_auth = format!("Basic {}", BASE64_STANDARD.encode(format!("{}:{}", state.info.username, state.info.password)));
+ state.stage = AuthStage::Authorized(basic_auth);
+
+ continue; // try again
+ },
+ Some(AuthMethod::Bearer{realm, scope, service}) => {
+ let basic_auth = format!("Basic {}", BASE64_STANDARD.encode(format!("{}:{}", state.info.username, state.info.password)));
+
+ let url = format!("{}?service={}&scope={}", realm, service, scope);
+ dbg!(&url);
+
+ let body = ureq::get(url)
+ .config().http_status_as_error(false).build()
+ .header("Authorization", basic_auth)
+ .call();
+
+ dbg!(&body);
+
+ let body = body.unwrap()
+ .body_mut().read_to_string().unwrap();
+
+ dbg!(&body);
+
+ let body: tinyjson::JsonValue = body
+ .parse().unwrap();
+
+ let token: &String = body["token"].get().unwrap();
+
+ state.stage = AuthStage::Authorized(format!("Bearer {}", token));
+ continue;
+ },
+ None => todo!(),
}
} else {
todo!("Server didn't ask us to authenticate");
@@ -454,27 +527,30 @@ fn fetch_new_image(file: &str, auth: &mut Auth, img: DockerRef, edits: &mut Vec<
let registry = img.registry.map(|x| &file[x]).unwrap_or("registry.jnsn.dev/");
let tag = img.tag.map(|x| &file[x]).unwrap_or("latest");
- // Find the digest for the newest image
+ let digest = None;
+
+ // Find the digest for the tag
if let Some(digest) = img.digest {
let url = format!("/v2/{}/manifests/{}", &file[img.image], tag);
let mut response = perform_registry_request(registry, url, auth).unwrap();
let body: tinyjson::JsonValue = response.body_mut().read_to_string().unwrap().parse().unwrap();
+ dbg!(&body);
let media_type : &String = body["mediaType"].get().unwrap();
- assert!(media_type == "application/vnd.docker.distribution.manifest.v2+json");
+ // assert!(media_type == "application/vnd.docker.distribution.manifest.v2+json");
+ }
- let image_ref = {
- let prefix = &file[img.full_range.start..digest.start];
- let digest = &response.headers()["docker-content-digest"].to_str().unwrap();
- format!("{}{}", prefix, digest)
- };
+ let image_ref = {
+ let prefix = &file[img.full_range.start..digest.start];
+ let digest = &response.headers()["docker-content-digest"].to_str().unwrap();
+ format!("{}{}", prefix, digest)
+ };
- edits.push(Update{
- position: img.full_range,
- content: image_ref,
- });
- }
+ edits.push(Update{
+ position: img.full_range,
+ content: image_ref,
+ });
}
fn main() {