Skip to content

Commit 39f0050

Browse files
committed
#556 constrain URLs
1 parent 70469ce commit 39f0050

File tree

11 files changed

+65
-24
lines changed

11 files changed

+65
-24
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ See [STATUS.md](server/STATUS.md) to learn more about which features will remain
2323
- Update JS assets & playwright
2424
- Fix initial indexing bug #560
2525
- Improve check_append error #558
26+
- Most Collection routes now live under `/collections`, e.g. `/collections/agents` instead of `/agents`. #556
27+
- Constrain new URLs. Commits for new Resources are now only valid if their parent is part of the current URL. So it's no longer possible to create `/some-path/new-resource` if `new-resource` is its parent is not in its URL. #556
2628

2729
## [v0.34.0] - 2022-10-31
2830

cli/src/new.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ fn prompt_field(
173173
}
174174
DataType::Date => {
175175
let msg = format!("date YYYY-MM-DD{}", msg_appendix);
176-
let date: Option<String> = prompt_opt(&msg).unwrap();
176+
let date: Option<String> = prompt_opt(msg).unwrap();
177177
let re = Regex::new(atomic_lib::values::DATE_REGEX).unwrap();
178178
match date {
179179
Some(date_val) => {
@@ -259,7 +259,7 @@ fn prompt_field(
259259
},
260260
DataType::Timestamp => {
261261
let msg = format!("timestamp{}", msg_appendix);
262-
let number: Option<u64> = prompt_opt(&msg)?;
262+
let number: Option<u64> = prompt_opt(msg)?;
263263
match number {
264264
Some(nr) => {
265265
input = Some(nr.to_string());
@@ -272,7 +272,7 @@ fn prompt_field(
272272
"unsupported datatype {}, defaulting to string{}",
273273
unsup, msg_appendix
274274
);
275-
let string: Option<String> = prompt_opt(&msg)?;
275+
let string: Option<String> = prompt_opt(msg)?;
276276
match string {
277277
Some(nr) => {
278278
input = Some(nr);

lib/src/atomic_url.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ impl AtomicUrl {
3636
pub fn set_route(&self, route: Routes) -> Self {
3737
let path = match route {
3838
Routes::AllVersions => "/all-versions".to_string(),
39-
Routes::Agents => "/agents".to_string(),
39+
Routes::Agents => "/collections/agents".to_string(),
4040
Routes::Collections => "/collections".to_string(),
41-
Routes::Commits => "/commits".to_string(),
41+
Routes::Commits => "/collections/commits".to_string(),
4242
Routes::CommitsUnsigned => "/commits-unsigned".to_string(),
4343
Routes::Endpoints => "/endpoints".to_string(),
4444
Routes::Import => "/import".to_string(),

lib/src/collections.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -396,14 +396,24 @@ pub fn create_collection_resource_for_class(
396396
) -> AtomicResult<Resource> {
397397
let class = store.get_class(class_subject)?;
398398

399+
// We use the `Collections` collection as the parent for all collections.
400+
// This also influences their URLs.
401+
let is_collections_collection = class.subject == urls::COLLECTION;
402+
399403
// Pluralize the shortname
400404
let pluralized = match class.shortname.as_ref() {
401405
"class" => "classes".to_string(),
402406
"property" => "properties".to_string(),
403407
other => format!("{}s", other),
404408
};
405409

406-
let mut collection = CollectionBuilder::class_collection(&class.subject, &pluralized, store);
410+
let path = if is_collections_collection {
411+
"/collections".to_string()
412+
} else {
413+
format!("/collections/{}", pluralized)
414+
};
415+
416+
let mut collection = CollectionBuilder::class_collection(&class.subject, &path, store);
407417

408418
collection.sort_by = match class_subject {
409419
urls::COMMIT => Some(urls::CREATED_AT.to_string()),
@@ -424,7 +434,7 @@ pub fn create_collection_resource_for_class(
424434
.ok_or("No self_url present in store, can't populate collections")?;
425435

426436
// Let the Collections collection be the top level item
427-
let parent = if class.subject == urls::COLLECTION {
437+
let parent = if is_collections_collection {
428438
drive.to_string()
429439
} else {
430440
drive

lib/src/commit.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ pub struct CommitOpts {
3939
pub update_index: bool,
4040
/// For who the right checks will be perormed. If empty, the signer of the Commit will be used.
4141
pub validate_for_agent: Option<String>,
42+
/// Checks if the URL of the parent is present in its Parent URL.
43+
pub validate_subject_url_parent: bool,
4244
}
4345

4446
/// A Commit is a set of changes to a Resource.
@@ -160,6 +162,20 @@ impl Commit {
160162
.apply_changes(resource_old.clone(), store, false)
161163
.map_err(|e| format!("Error applying changes to Resource {}. {}", self.subject, e))?;
162164

165+
// For new subjects, make sure that the parent of the resource is part of the URL of the new subject.
166+
if is_new && opts.validate_subject_url_parent {
167+
if let Ok(parent) = resource_new.get(urls::PARENT) {
168+
let parent_str = parent.to_string();
169+
if !self.subject.starts_with(&parent_str) {
170+
return Err(format!(
171+
"The parent '{}' is not part of the URL of the new subject '{}'.",
172+
parent_str, self.subject
173+
)
174+
.into());
175+
}
176+
}
177+
}
178+
163179
if opts.validate_rights {
164180
let validate_for = opts.validate_for_agent.as_ref().unwrap_or(&self.signer);
165181
if is_new {
@@ -374,6 +390,7 @@ impl Commit {
374390
validate_signature: false,
375391
validate_timestamp: false,
376392
validate_rights: false,
393+
validate_subject_url_parent: false,
377394
validate_previous_commit: false,
378395
validate_for_agent: None,
379396
update_index: false,
@@ -694,6 +711,7 @@ mod test {
694711
validate_previous_commit: true,
695712
validate_rights: false,
696713
validate_for_agent: None,
714+
validate_subject_url_parent: true,
697715
update_index: true,
698716
};
699717
}

lib/src/db/test.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,12 @@ fn destroy_resource_and_check_collection_and_commits() {
187187
#[test]
188188
fn get_extended_resource_pagination() {
189189
let store = Db::init_temp("get_extended_resource_pagination").unwrap();
190-
let subject = format!("{}commits?current_page=2", store.get_server_url());
190+
let num = 3;
191+
let subject = format!(
192+
"{}collections/commits?current_page={}",
193+
store.get_server_url(),
194+
num
195+
);
191196
// Should throw, because page 2 is out of bounds for default page size
192197
let _wrong_resource = store
193198
.get_resource_extended(&subject, false, None)
@@ -202,7 +207,7 @@ fn get_extended_resource_pagination() {
202207
.unwrap()
203208
.to_int()
204209
.unwrap();
205-
assert_eq!(cur_page, 2);
210+
assert_eq!(cur_page, num);
206211
assert_eq!(resource.get_subject(), &subject_with_page_size);
207212
}
208213

lib/src/parse.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ fn parse_json_ad_map_to_resource(
338338
validate_rights: parse_opts.for_agent.is_some(),
339339
validate_previous_commit: false,
340340
validate_for_agent: parse_opts.for_agent.clone(),
341+
validate_subject_url_parent: true,
341342
update_index: true,
342343
};
343344

@@ -553,8 +554,8 @@ mod test {
553554
.import(include_str!("../test_files/local_id.json"), &parse_opts)
554555
.unwrap();
555556

556-
let reference_subject = generate_id_from_local_id(&importer, "reference");
557-
let my_subject = generate_id_from_local_id(&importer, "my-local-id");
557+
let reference_subject = generate_id_from_local_id(&importer, "parent");
558+
let my_subject = generate_id_from_local_id(&importer, "parent/my-local-id");
558559
let found = store.get_resource(&my_subject).unwrap();
559560
let found_ref = store.get_resource(&reference_subject).unwrap();
560561

lib/src/resources.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,7 @@ impl Resource {
331331
validate_for_agent: Some(agent.subject),
332332
// TODO: auto-merge should work before we enable this https://github.com/atomicdata-dev/atomic-data-rust/issues/412
333333
validate_previous_commit: false,
334+
validate_subject_url_parent: true,
334335
update_index: true,
335336
};
336337
let commit_response = commit.apply_opts(store, &opts)?;
@@ -359,6 +360,8 @@ impl Resource {
359360
validate_for_agent: Some(agent.subject),
360361
// https://github.com/atomicdata-dev/atomic-data-rust/issues/412
361362
validate_previous_commit: false,
363+
// This is contentious: https://github.com/atomicdata-dev/atomic-data-rust/issues/556
364+
validate_subject_url_parent: false,
362365
update_index: true,
363366
};
364367
let commit_response = commit.apply_opts(store, &opts)?;
@@ -652,6 +655,7 @@ mod test {
652655
validate_signature: true,
653656
validate_timestamp: true,
654657
validate_rights: false,
658+
validate_subject_url_parent: true,
655659
validate_previous_commit: true,
656660
validate_for_agent: None,
657661
update_index: true,

lib/test_files/local_id.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
[
22
{
3-
"https://atomicdata.dev/properties/localId": "reference",
3+
"https://atomicdata.dev/properties/localId": "parent",
44
"https://atomicdata.dev/properties/write": [
5-
"my-local-id"
5+
"parent/my-local-id"
66
],
77
"https://atomicdata.dev/properties/name": "My referenced resource"
88
},
99
{
10-
"https://atomicdata.dev/properties/localId": "my-local-id",
10+
"https://atomicdata.dev/properties/localId": "parent/my-local-id",
1111
"https://atomicdata.dev/properties/name": "My resource that refers",
12-
"https://atomicdata.dev/properties/parent": "reference",
12+
"https://atomicdata.dev/properties/parent": "parent",
1313
"https://atomicdata.dev/properties/write": [
14-
"reference"
14+
"parent"
1515
]
1616
}
1717
]

server/src/handlers/commit.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ pub async fn post_commit(
1313
let mut builder = HttpResponse::Ok();
1414
let incoming_commit_resource = parse_json_ad_commit_resource(&body, store)?;
1515
let incoming_commit = Commit::from_resource(incoming_commit_resource)?;
16-
if store.is_external_subject(&incoming_commit.subject)? {
16+
if store.is_external_subject(&incoming_commit.subject)? {
1717
return Err("Subject of commit is external, and should be sent to its origin domain. This store can not own this resource. See https://github.com/atomicdata-dev/atomic-data-rust/issues/509".into());
1818
}
1919
let opts = CommitOpts {
@@ -24,6 +24,7 @@ if store.is_external_subject(&incoming_commit.subject)? {
2424
// https://github.com/atomicdata-dev/atomic-data-rust/issues/412
2525
validate_previous_commit: false,
2626
validate_for_agent: Some(incoming_commit.signer.to_string()),
27+
validate_subject_url_parent: true,
2728
update_index: true,
2829
};
2930
let commit_response = incoming_commit.apply_opts(store, &opts)?;

0 commit comments

Comments
 (0)