|
| 1 | +use anyhow::{Result, bail}; |
1 | 2 | use std::fs::create_dir_all; |
2 | 3 | use std::{fs, path::Path}; |
3 | 4 |
|
4 | | -use anyhow::{Result, bail}; |
5 | | - |
6 | | -use crate::chunks::{Chunk, HashKind}; |
7 | | -use crate::crypto::key::{deserialize_verifying_key, get_public_key, serialize_verifying_key}; |
8 | | -use crate::crypto::signing::{sign, verify_signature}; |
9 | | - |
10 | | -#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] |
11 | | -pub struct RepoManifest { |
12 | | - pub metadata: Metadata, |
13 | | - pub packages: Vec<PackageManifest>, |
14 | | - pub updates_url: Option<String>, |
15 | | - pub public_key: String, |
16 | | - pub mirrors: Vec<String>, |
17 | | - edition: String, |
18 | | - pub hash_kind: HashKind, |
19 | | -} |
20 | | - |
21 | | -#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] |
22 | | -pub struct PackageManifest { |
23 | | - pub metadata: Metadata, |
24 | | - pub id: String, |
25 | | - pub aliases: Vec<String>, |
26 | | - pub chunks: Vec<Chunk>, |
27 | | - pub commands: Vec<String>, |
28 | | -} |
| 5 | +use crate::chunks::HashKind; |
| 6 | +use crate::crypto::key::{get_public_key, serialize_verifying_key}; |
| 7 | +use crate::crypto::signing::sign; |
29 | 8 |
|
30 | | -/// All of these are user visible, and should carry no actual weight. |
31 | | -#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] |
32 | | -pub struct Metadata { |
33 | | - pub title: Option<String>, |
34 | | - pub description: Option<String>, |
35 | | - pub homepage_url: Option<String>, |
36 | | - /// User visible, not actually used to compare versions |
37 | | - pub version: Option<String>, |
38 | | - /// SPDX Identifier |
39 | | - pub license: Option<String>, |
40 | | -} |
| 9 | +mod manifest; |
| 10 | +mod manifest_io; |
| 11 | +pub use manifest::*; |
| 12 | +pub use manifest_io::*; |
41 | 13 |
|
42 | 14 | /// Creates a repository at `repo_path` |
43 | 15 | /// |
@@ -75,88 +47,127 @@ pub fn create(repo_path: &Path) -> Result<()> { |
75 | 47 | Ok(()) |
76 | 48 | } |
77 | 49 |
|
78 | | -/// Reads a manifest without verifying. This is best for AFTER it has been downloaded. |
| 50 | +/// Inserts a package into a local repository. |
79 | 51 | /// |
80 | 52 | /// # Errors |
81 | | -/// |
82 | | -/// - Filesystem errors (Permissions or doesn't exist) |
83 | | -pub fn read_manifest_unsigned(repo_path: &Path) -> Result<RepoManifest> { |
84 | | - let manifest_serialized = fs::read_to_string(repo_path.join("manifest.yml"))?; |
85 | | - let manifest = serde_yaml::from_str(&manifest_serialized)?; |
| 53 | +/// - Repo not signed with local signature |
| 54 | +pub fn insert_package(package_manifest: &PackageManifest, repo_path: &Path) -> Result<()> { |
| 55 | + let mut repo_manifest = read_manifest(repo_path)?; |
| 56 | + |
| 57 | + let mut packages: Vec<PackageManifest> = repo_manifest |
| 58 | + .packages |
| 59 | + .iter() |
| 60 | + .filter(|package| package.id == package_manifest.id) |
| 61 | + .cloned() |
| 62 | + .collect(); |
| 63 | + |
| 64 | + for package in &packages { |
| 65 | + if package.aliases.contains(&package_manifest.id) { |
| 66 | + bail!( |
| 67 | + "A package in this repo has an alias with that package id already: {}", |
| 68 | + package.id |
| 69 | + ) |
| 70 | + } |
| 71 | + for alias in &package_manifest.aliases { |
| 72 | + if &package.id == alias || package.aliases.contains(alias) { |
| 73 | + bail!( |
| 74 | + "A package in this repo has an alias with that package id already: {}", |
| 75 | + package.id |
| 76 | + ) |
| 77 | + } |
| 78 | + } |
| 79 | + } |
| 80 | + |
| 81 | + packages.push(package_manifest.clone()); |
| 82 | + repo_manifest.packages = packages; |
| 83 | + |
| 84 | + let repo_manifest_serialized = serde_yaml::to_string(&repo_manifest)?; |
| 85 | + |
| 86 | + let signature = sign(repo_path, &repo_manifest_serialized)?; |
| 87 | + update_manifest(repo_path, &repo_manifest_serialized, &signature.to_bytes())?; |
86 | 88 |
|
87 | | - Ok(manifest) |
| 89 | + Ok(()) |
88 | 90 | } |
89 | 91 |
|
90 | | -/// Reads a manifest and verifys it. This is best for WHEN it has been downloaded. |
| 92 | +/// Removes a package from a local repository. |
91 | 93 | /// |
92 | 94 | /// # Errors |
93 | | -/// |
94 | | -/// - Filesystem errors (Permissions or doesn't exist) |
95 | | -/// - Invalid signature |
96 | | -pub fn read_manifest_signed(repo_path: &Path, public_key_serialized: &str) -> Result<RepoManifest> { |
97 | | - let manifest_serialized = fs::read_to_string(repo_path.join("manifest.yml"))?; |
98 | | - let manifest_signature_serialized = fs::read(repo_path.join("manifest.yml.sig"))?; |
99 | | - |
100 | | - verify_signature( |
101 | | - &manifest_serialized, |
102 | | - &manifest_signature_serialized, |
103 | | - deserialize_verifying_key(public_key_serialized)?, |
104 | | - )?; |
105 | | - |
106 | | - let manifest = serde_yaml::from_str(&manifest_serialized)?; |
107 | | - Ok(manifest) |
| 95 | +/// - Repo not signed with local signature |
| 96 | +/// - Filesystem errors |
| 97 | +pub fn remove_package(package_id: &str, repo_path: &Path) -> Result<()> { |
| 98 | + let mut repo_manifest = read_manifest(repo_path)?; |
| 99 | + |
| 100 | + repo_manifest |
| 101 | + .packages |
| 102 | + .retain(|package| package.id != package_id); |
| 103 | + |
| 104 | + let repo_manifest_serialized = serde_yaml::to_string(&repo_manifest)?; |
| 105 | + |
| 106 | + let signature = sign(repo_path, &repo_manifest_serialized)?; |
| 107 | + update_manifest(repo_path, &repo_manifest_serialized, &signature.to_bytes())?; |
| 108 | + |
| 109 | + Ok(()) |
108 | 110 | } |
109 | 111 |
|
110 | | -/// Replaces the existing manifest with another one |
111 | | -/// Verifies that it is correct |
| 112 | +/// Gets a package manifest from a repository. |
112 | 113 | /// |
113 | 114 | /// # Errors |
114 | 115 | /// |
115 | | -/// - Invalid Signature |
116 | | -/// - Filesystem error when updating (Out of space, Permissions) |
117 | | -/// - New manifest is invalid |
118 | | -pub fn update_manifest( |
119 | | - repo_path: &Path, |
120 | | - new_manifest_serialized: &str, |
121 | | - signature: &[u8], |
122 | | -) -> Result<()> { |
123 | | - let old_manifest = read_manifest_unsigned(repo_path)?; |
124 | | - |
125 | | - // VERIFY. IMPORTANT. |
126 | | - verify_signature( |
127 | | - new_manifest_serialized, |
128 | | - signature, |
129 | | - deserialize_verifying_key(&old_manifest.public_key)?, |
130 | | - )?; |
131 | | - |
132 | | - // Make sure it actually deserializes |
133 | | - let _: RepoManifest = serde_yaml::from_str(new_manifest_serialized)?; |
134 | | - |
135 | | - // Write to a .new, and then rename atomically |
136 | | - fs::write(repo_path.join("manifest.yml.new"), new_manifest_serialized)?; |
137 | | - fs::write( |
138 | | - repo_path.join("manifest.yml.sig.new"), |
139 | | - new_manifest_serialized, |
140 | | - )?; |
141 | | - |
142 | | - fs::rename( |
143 | | - repo_path.join("manifest.yml.new"), |
144 | | - repo_path.join("manifest.yml"), |
145 | | - )?; |
146 | | - fs::rename( |
147 | | - repo_path.join("manifest.yml.sig.new"), |
148 | | - repo_path.join("manifest.yml.sig"), |
149 | | - )?; |
| 116 | +/// - Filesystem errors (Permissions most likely) |
| 117 | +/// - Repository doesn't exist |
| 118 | +/// - ID doesn't exist inside the Repository |
| 119 | +pub fn get_package(repo_path: &Path, id: &str) -> Result<PackageManifest> { |
| 120 | + let repo_manifest = read_manifest(repo_path)?; |
| 121 | + |
| 122 | + // Check ID's and aliases |
| 123 | + for package in repo_manifest.packages { |
| 124 | + if package.id == id || package.aliases.contains(&id.to_string()) { |
| 125 | + return Ok(package); |
| 126 | + } |
| 127 | + } |
150 | 128 |
|
151 | | - Ok(()) |
| 129 | + bail!("No package found in Repository."); |
152 | 130 | } |
153 | 131 |
|
154 | 132 | #[cfg(test)] |
155 | 133 | mod tests { |
156 | 134 | use temp_dir::TempDir; |
157 | 135 |
|
158 | 136 | use super::*; |
159 | | - use std::fs; |
| 137 | + |
| 138 | + #[test] |
| 139 | + fn insert_and_get_and_remove_package() -> Result<()> { |
| 140 | + // Create repo |
| 141 | + let repo = TempDir::new()?; |
| 142 | + let repo_path = repo.path(); |
| 143 | + create(repo_path)?; |
| 144 | + |
| 145 | + // Make sure errors on no package |
| 146 | + assert!(get_package(repo_path, "test").is_err()); |
| 147 | + |
| 148 | + let package_manifest = PackageManifest { |
| 149 | + aliases: vec!["example_alias".into()], |
| 150 | + id: "test".into(), |
| 151 | + chunks: vec![], |
| 152 | + commands: vec![], |
| 153 | + metadata: Metadata { |
| 154 | + title: None, |
| 155 | + description: None, |
| 156 | + homepage_url: None, |
| 157 | + version: None, |
| 158 | + license: None, |
| 159 | + }, |
| 160 | + }; |
| 161 | + |
| 162 | + insert_package(&package_manifest, repo_path)?; |
| 163 | + assert!(get_package(repo_path, "test").is_ok()); |
| 164 | + assert!(insert_package(&package_manifest, repo_path).is_err()); |
| 165 | + |
| 166 | + remove_package(&package_manifest.id, repo_path)?; |
| 167 | + assert!(get_package(repo_path, "test").is_err()); |
| 168 | + |
| 169 | + Ok(()) |
| 170 | + } |
160 | 171 |
|
161 | 172 | #[test] |
162 | 173 | fn test_create_and_read_unsigned() { |
|
0 commit comments