Rust implementation of OCI Distribution Spec with granular access control
at main 105 lines 3.3 kB view raw
1// | ID | Method | API Endpoint | Success | Failure | 2// | ------ | -------------- | ------------------------------------------------------------ | ----------- | ----------------- | 3// | end-8a | `GET` | `/v2/<name>/tags/list` | `200` | `404` | 4// | end-8b | `GET` | `/v2/<name>/tags/list?n=<integer>&last=<integer>` | `200` | `404` | 5 6use axum::body::Body; 7use axum::http::{HeaderMap, StatusCode}; 8use axum::response::Response; 9use serde::Deserialize; 10use std::sync::Arc; 11 12use crate::{auth, permissions, response, state, storage}; 13use axum::extract::{Path, Query, State}; 14 15// end-8a GET /v2/:name/tags/list 16// end-8b GET /v2/:name/tags/list?n=<integer>&last=<integer> 17#[derive(Deserialize)] 18pub(crate) struct TagsQuery { 19 pub n: Option<usize>, 20 pub last: Option<String>, 21} 22 23fn paginate_tags(tags: Vec<String>, n: Option<usize>, last: Option<String>) -> Vec<String> { 24 let mut result = tags; 25 26 // Filter tags after 'last' cursor 27 if let Some(last_tag) = last { 28 result = result 29 .into_iter() 30 .skip_while(|tag| tag <= &last_tag) 31 .collect(); 32 } 33 34 // Limit to 'n' results 35 if let Some(limit) = n { 36 result.truncate(limit); 37 } 38 39 result 40} 41 42pub(crate) async fn get_tags_list( 43 State(state): State<Arc<state::App>>, 44 Path((org, repo)): Path<(String, String)>, 45 Query(params): Query<TagsQuery>, 46 headers: HeaderMap, 47) -> Response<Body> { 48 let host = &state.args.host; 49 let repository = format!("{}/{}", org, repo); 50 51 // Check permission (Pull for tag listing) 52 match auth::check_permission( 53 &state, 54 &headers, 55 &repository, 56 None, 57 permissions::Action::Pull, 58 ) 59 .await 60 { 61 Ok(_) => {} 62 Err(_) => { 63 return if auth::authenticate_user(&state, &headers).await.is_ok() { 64 response::forbidden() 65 } else { 66 response::unauthorized(host) 67 }; 68 } 69 } 70 71 // Get all tags from storage 72 match storage::list_tags(&org, &repo) { 73 Ok(all_tags) => { 74 // Apply pagination 75 let paginated_tags = paginate_tags(all_tags, params.n, params.last); 76 77 // Build response JSON 78 let response_body = serde_json::json!({ 79 "name": format!("{}/{}", org, repo), 80 "tags": paginated_tags 81 }); 82 83 Response::builder() 84 .status(StatusCode::OK) 85 .header("Content-Type", "application/json") 86 .body(Body::from(response_body.to_string())) 87 .unwrap() 88 } 89 Err(e) => { 90 log::error!("Failed to list tags for {}/{}: {}", org, repo, e); 91 92 // Return empty list if directory doesn't exist (valid case) 93 let response_body = serde_json::json!({ 94 "name": format!("{}/{}", org, repo), 95 "tags": [] 96 }); 97 98 Response::builder() 99 .status(StatusCode::OK) 100 .header("Content-Type", "application/json") 101 .body(Body::from(response_body.to_string())) 102 .unwrap() 103 } 104 } 105}