Skip to content

Commit 3401069

Browse files
committed
Repository code splitting
1 parent 6628ecf commit 3401069

File tree

3 files changed

+250
-98
lines changed

3 files changed

+250
-98
lines changed

src/repo/manifest.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use crate::chunks::{Chunk, HashKind};
2+
3+
#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
4+
pub struct RepoManifest {
5+
pub metadata: Metadata,
6+
pub packages: Vec<PackageManifest>,
7+
pub updates_url: Option<String>,
8+
pub public_key: String,
9+
pub mirrors: Vec<String>,
10+
pub edition: String,
11+
pub hash_kind: HashKind,
12+
}
13+
14+
#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
15+
pub struct PackageManifest {
16+
pub metadata: Metadata,
17+
pub id: String,
18+
pub aliases: Vec<String>,
19+
pub chunks: Vec<Chunk>,
20+
pub commands: Vec<String>,
21+
}
22+
23+
/// All of these are user visible, and should carry no actual weight.
24+
#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
25+
pub struct Metadata {
26+
pub title: Option<String>,
27+
pub description: Option<String>,
28+
pub homepage_url: Option<String>,
29+
/// User visible, not actually used to compare versions
30+
pub version: Option<String>,
31+
/// SPDX Identifier
32+
pub license: Option<String>,
33+
}

src/repo/manifest_io.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
use std::{fs, path::Path};
2+
3+
use anyhow::Result;
4+
5+
use crate::{
6+
crypto::{key::deserialize_verifying_key, signing::verify_signature},
7+
repo::RepoManifest,
8+
};
9+
10+
/// Reads a manifest without verifying. This is best for AFTER it has been downloaded.
11+
///
12+
/// # Errors
13+
///
14+
/// - Filesystem errors (Permissions or doesn't exist)
15+
pub fn read_manifest_unsigned(repo_path: &Path) -> Result<RepoManifest> {
16+
let manifest_serialized = fs::read_to_string(repo_path.join("manifest.yml"))?;
17+
let manifest = serde_yaml::from_str(&manifest_serialized)?;
18+
19+
Ok(manifest)
20+
}
21+
22+
/// Reads a manifest and verifys it from the EXISTING key. This is best for GENERAL reading.
23+
///
24+
/// # Warning
25+
/// Do NOT run on downloaded manifests before `read_manifest_signed`, or else potentially malicious inputs will be parsed.
26+
///
27+
/// # Errors
28+
///
29+
/// - Filesystem errors (Permissions or doesn't exist)
30+
/// - Invalid signature
31+
pub fn read_manifest(repo_path: &Path) -> Result<RepoManifest> {
32+
let manifest_serialized = fs::read_to_string(repo_path.join("manifest.yml"))?;
33+
let manifest_signature_serialized = fs::read(repo_path.join("manifest.yml.sig"))?;
34+
35+
let manifest: RepoManifest = serde_yaml::from_str(&manifest_serialized)?;
36+
37+
verify_signature(
38+
&manifest_serialized,
39+
&manifest_signature_serialized,
40+
deserialize_verifying_key(&manifest.public_key)?,
41+
)?;
42+
Ok(manifest)
43+
}
44+
45+
/// Reads a manifest and verifys it. This is best for WHEN it has been downloaded.
46+
///
47+
/// # Errors
48+
///
49+
/// - Filesystem errors (Permissions or doesn't exist)
50+
/// - Invalid signature
51+
pub fn read_manifest_signed(repo_path: &Path, public_key_serialized: &str) -> Result<RepoManifest> {
52+
let manifest_serialized = fs::read_to_string(repo_path.join("manifest.yml"))?;
53+
let manifest_signature_serialized = fs::read(repo_path.join("manifest.yml.sig"))?;
54+
55+
verify_signature(
56+
&manifest_serialized,
57+
&manifest_signature_serialized,
58+
deserialize_verifying_key(public_key_serialized)?,
59+
)?;
60+
61+
let manifest = serde_yaml::from_str(&manifest_serialized)?;
62+
Ok(manifest)
63+
}
64+
65+
/// Replaces the existing manifest with another one
66+
/// Verifies that it is correct
67+
///
68+
/// # Errors
69+
///
70+
/// - Invalid Signature
71+
/// - Filesystem error when updating (Out of space, Permissions)
72+
/// - New manifest is invalid
73+
pub fn update_manifest(
74+
repo_path: &Path,
75+
new_manifest_serialized: &str,
76+
signature: &[u8],
77+
) -> Result<()> {
78+
let old_manifest = read_manifest_unsigned(repo_path)?;
79+
80+
// VERIFY. IMPORTANT.
81+
verify_signature(
82+
new_manifest_serialized,
83+
signature,
84+
deserialize_verifying_key(&old_manifest.public_key)?,
85+
)?;
86+
87+
// Make sure it actually deserializes
88+
let _: RepoManifest = serde_yaml::from_str(new_manifest_serialized)?;
89+
90+
// Write to a .new, and then rename atomically
91+
atomic_replace(
92+
repo_path,
93+
"manifest.yml",
94+
new_manifest_serialized.as_bytes(),
95+
)?;
96+
atomic_replace(repo_path, "manifest.yml.sig", signature)?;
97+
98+
Ok(())
99+
}
100+
101+
fn atomic_replace(repo_path: &Path, filename: &str, contents: &[u8]) -> Result<()> {
102+
let new_path = &repo_path.join(filename.to_owned() + ".new");
103+
104+
fs::write(new_path, contents)?;
105+
fs::rename(new_path, repo_path.join(filename))?;
106+
107+
Ok(())
108+
}

src/repo/mod.rs

Lines changed: 109 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,15 @@
1+
use anyhow::{Result, bail};
12
use std::fs::create_dir_all;
23
use std::{fs, path::Path};
34

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;
298

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::*;
4113

4214
/// Creates a repository at `repo_path`
4315
///
@@ -75,88 +47,127 @@ pub fn create(repo_path: &Path) -> Result<()> {
7547
Ok(())
7648
}
7749

78-
/// Reads a manifest without verifying. This is best for AFTER it has been downloaded.
50+
/// Inserts a package into a local repository.
7951
///
8052
/// # 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())?;
8688

87-
Ok(manifest)
89+
Ok(())
8890
}
8991

90-
/// Reads a manifest and verifys it. This is best for WHEN it has been downloaded.
92+
/// Removes a package from a local repository.
9193
///
9294
/// # 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(())
108110
}
109111

110-
/// Replaces the existing manifest with another one
111-
/// Verifies that it is correct
112+
/// Gets a package manifest from a repository.
112113
///
113114
/// # Errors
114115
///
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+
}
150128

151-
Ok(())
129+
bail!("No package found in Repository.");
152130
}
153131

154132
#[cfg(test)]
155133
mod tests {
156134
use temp_dir::TempDir;
157135

158136
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+
}
160171

161172
#[test]
162173
fn test_create_and_read_unsigned() {

0 commit comments

Comments
 (0)