Skip to content
This repository was archived by the owner on Feb 28, 2021. It is now read-only.

Commit 9e7fe9a

Browse files
authored
Merge pull request #246 from radicle-dev/xla/239-adjust-org-id-validation
Adjust org id validations
2 parents 885544b + c1b1677 commit 9e7fe9a

File tree

9 files changed

+256
-20
lines changed

9 files changed

+256
-20
lines changed

client/examples/project_registration.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Register a project on the ledger
22
use futures::compat::{Compat, Future01CompatExt};
33
use futures::future::FutureExt;
4+
use std::convert::TryFrom;
45

56
use radicle_registry_client::*;
67

@@ -19,7 +20,7 @@ async fn go() -> Result<(), Error> {
1920
let client = Client::create(node_host).await?;
2021

2122
let project_name = ProjectName::from_string("radicle-registry".to_string()).unwrap();
22-
let org_id = OrgId::from_string("Monadic".to_string()).unwrap();
23+
let org_id = OrgId::try_from("monadic").unwrap();
2324

2425
// Choose some random project hash and create a checkpoint
2526
let project_hash = H256::random();

client/tests/end_to_end.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ async fn register_project() {
2828
.result
2929
.unwrap();
3030

31-
let org_id = random_string32();
31+
let org_id = random_org_id();
3232
let register_org_message = message::RegisterOrg {
3333
org_id: org_id.clone(),
3434
};

core/src/lib.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ pub use bytes128::Bytes128;
3636
pub mod string32;
3737
pub use string32::String32;
3838

39+
mod org_id;
40+
pub use org_id::OrgId;
41+
3942
mod error;
4043
pub use error::RegistryError;
4144

@@ -58,16 +61,14 @@ pub type ProjectId = (ProjectName, OrgId);
5861
/// The name a project is registered with.
5962
pub type ProjectName = String32;
6063

61-
pub type OrgId = String32;
62-
6364
/// Org
6465
///
6566
/// Different from [state::Org] in which this type gathers
66-
/// both the id and the other Org fields, respectively stored
67+
/// both the [`OrgId`] and the other [`Org`] fields, respectively stored
6768
/// as an Org's storage key and data, into one complete type.
6869
#[derive(Clone, Debug, Eq, PartialEq)]
6970
pub struct Org {
70-
// Id of an Org.
71+
// Unique id of an Org.
7172
pub id: OrgId,
7273

7374
/// See [state::Org::account_id]

core/src/org_id.rs

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
// Radicle Registry
2+
// Copyright (C) 2019 Monadic GmbH <[email protected]>
3+
//
4+
// This program is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License version 3 as
6+
// published by the Free Software Foundation.
7+
//
8+
// This program is distributed in the hope that it will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
// GNU General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU General Public License
14+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
15+
16+
/// `OrgId` is the unique identifier for organisations.
17+
///
18+
/// https://github.com/radicle-dev/registry-spec/blob/master/body.tex#L110
19+
use alloc::prelude::v1::*;
20+
use core::convert::{From, Into, TryFrom};
21+
use parity_scale_codec as codec;
22+
23+
use crate::string32;
24+
25+
#[derive(codec::Encode, Clone, Debug, Eq, PartialEq)]
26+
pub struct OrgId(String);
27+
28+
impl OrgId {
29+
fn from_string(input: String) -> Result<Self, InvalidOrgIdError> {
30+
// Must be at least 1 character.
31+
if input.is_empty() {
32+
return Err(InvalidOrgIdError("must be at least 1 character"));
33+
}
34+
// Must be no longer than 32.
35+
if input.len() > 32 {
36+
return Err(InvalidOrgIdError("must not exceed 32 characters"));
37+
}
38+
// Must only contain a-z, 0-9 and '-' characters.
39+
if !input
40+
.chars()
41+
.all(|c| c.is_ascii_digit() || c.is_ascii_lowercase() || c == '-')
42+
{
43+
return Err(InvalidOrgIdError("must only include a-z, 0-9 and '-'"));
44+
}
45+
46+
// Must not start with a '-'.
47+
if input.starts_with('-') {
48+
return Err(InvalidOrgIdError("must not start with a '-'"));
49+
}
50+
// Must not end with a '-'.
51+
if input.ends_with('-') {
52+
return Err(InvalidOrgIdError("must not end with a '-'"));
53+
}
54+
// Must not contain sequences of more than one '-'.
55+
if input.contains("--") {
56+
return Err(InvalidOrgIdError(
57+
"must not have more than one consecutive '-'",
58+
));
59+
}
60+
61+
let id = Self(input);
62+
63+
Ok(id)
64+
}
65+
}
66+
67+
impl codec::Decode for OrgId {
68+
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
69+
let decoded: String = String::decode(input)?;
70+
71+
match OrgId::try_from(decoded) {
72+
Ok(id) => Ok(id),
73+
Err(err) => Err(codec::Error::from(err.what())),
74+
}
75+
}
76+
}
77+
78+
impl Into<String> for OrgId {
79+
fn into(self) -> String {
80+
self.0
81+
}
82+
}
83+
84+
impl TryFrom<String> for OrgId {
85+
type Error = InvalidOrgIdError;
86+
87+
fn try_from(input: String) -> Result<Self, Self::Error> {
88+
Self::from_string(input)
89+
}
90+
}
91+
92+
impl TryFrom<&str> for OrgId {
93+
type Error = InvalidOrgIdError;
94+
95+
fn try_from(input: &str) -> Result<Self, Self::Error> {
96+
Self::from_string(input.to_string())
97+
}
98+
}
99+
100+
impl TryFrom<string32::String32> for OrgId {
101+
type Error = InvalidOrgIdError;
102+
103+
fn try_from(input: string32::String32) -> Result<Self, Self::Error> {
104+
Self::from_string(input.into())
105+
}
106+
}
107+
108+
impl core::str::FromStr for OrgId {
109+
type Err = InvalidOrgIdError;
110+
111+
fn from_str(s: &str) -> Result<Self, Self::Err> {
112+
Self::from_string(s.to_string())
113+
}
114+
}
115+
116+
#[cfg(feature = "std")]
117+
impl core::fmt::Display for OrgId {
118+
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
119+
write!(f, "OrgId({})", self.0)
120+
}
121+
}
122+
123+
/// Error type when conversion from an inordinate input failed.
124+
#[derive(codec::Encode, Clone, Debug, Eq, PartialEq)]
125+
pub struct InvalidOrgIdError(&'static str);
126+
127+
impl InvalidOrgIdError {
128+
/// Error description
129+
///
130+
/// This function returns an actual error str.
131+
pub fn what(&self) -> &'static str {
132+
self.0
133+
}
134+
}
135+
136+
#[cfg(feature = "std")]
137+
impl std::fmt::Display for InvalidOrgIdError {
138+
fn fmt(&self, f: &mut core::fmt::Formatter) -> std::fmt::Result {
139+
write!(f, "InvalidOrgIdError({})", self.0)
140+
}
141+
}
142+
143+
#[cfg(feature = "std")]
144+
impl std::error::Error for InvalidOrgIdError {
145+
fn description(&self) -> &str {
146+
self.0
147+
}
148+
}
149+
150+
impl From<&'static str> for InvalidOrgIdError {
151+
#[cfg(feature = "std")]
152+
fn from(s: &'static str) -> Self {
153+
Self(s)
154+
}
155+
156+
#[cfg(not(feature = "std"))]
157+
fn from(s: &'static str) -> Self {
158+
InvalidOrgIdError(s)
159+
}
160+
}
161+
162+
#[cfg(test)]
163+
mod test {
164+
use super::OrgId;
165+
use parity_scale_codec::{Decode, Encode};
166+
167+
#[test]
168+
fn id_too_short() {
169+
assert!(OrgId::from_string("".into()).is_err());
170+
}
171+
172+
#[test]
173+
fn id_too_long() {
174+
let input = std::iter::repeat("X").take(33).collect::<String>();
175+
let too_long = OrgId::from_string(input);
176+
assert!(too_long.is_err());
177+
}
178+
179+
#[test]
180+
fn id_invalid_characters() {
181+
let invalid_characters = OrgId::from_string("AZ+*".into());
182+
assert!(invalid_characters.is_err());
183+
}
184+
185+
#[test]
186+
fn id_invalid_prefix() {
187+
let invalid_prefix = OrgId::from_string("-radicle".into());
188+
assert!(invalid_prefix.is_err());
189+
}
190+
191+
#[test]
192+
fn id_invalid_suffix() {
193+
let invalid_suffix = OrgId::from_string("radicle-".into());
194+
assert!(invalid_suffix.is_err());
195+
}
196+
197+
#[test]
198+
fn id_double_dash() {
199+
let double_dash = OrgId::from_string("radicle--registry".into());
200+
assert!(double_dash.is_err());
201+
}
202+
203+
#[test]
204+
fn id_valid() {
205+
let valid = OrgId::from_string("radicle-registry001".into());
206+
assert!(valid.is_ok());
207+
}
208+
209+
#[test]
210+
fn encode_then_decode() {
211+
let id = OrgId::from_string("monadic".into()).unwrap();
212+
let encoded = id.encode();
213+
let decoded = OrgId::decode(&mut &encoded[..]).unwrap();
214+
215+
assert_eq!(id, decoded)
216+
}
217+
218+
#[test]
219+
fn encoded_then_decode_invalid() {
220+
let invalid = Encode::encode("-Invalid-");
221+
let decoded = OrgId::decode(&mut &invalid[..]);
222+
223+
assert!(decoded.is_err());
224+
}
225+
}

runtime/src/registry.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -341,14 +341,16 @@ pub fn decode_blake_two128_concat_key<K: parity_scale_codec::Decode>(
341341

342342
#[cfg(test)]
343343
mod test {
344-
use super::*;
344+
use core::convert::TryFrom;
345345
use frame_support::storage::generator::StorageMap;
346346

347+
use super::*;
348+
347349
#[test]
348350
/// Test that store::Orgs::decode_key after store::Orgs::storage_map_final_key
349351
/// is identify as to the original input id.
350352
fn orgs_decode_key_identity() {
351-
let org_id = OrgId::from_string("Monadic".into()).unwrap();
353+
let org_id = OrgId::try_from("monadic").unwrap();
352354
let hashed_key = store::Orgs::storage_map_final_key(org_id.clone());
353355
let decoded_key = store::Orgs::decode_key(&hashed_key).unwrap();
354356
assert_eq!(decoded_key, org_id);
@@ -358,9 +360,9 @@ mod test {
358360
/// Test that store::Projects::decode_key after store::Projects::storage_map_final_key
359361
/// is identify as to the original input id.
360362
fn projects_decode_key_identity() {
361-
let org_id = OrgId::from_string("Monadic".into()).unwrap();
363+
let org_id = OrgId::try_from("monadic").unwrap();
362364
let project_name = ProjectName::from_string("Radicle".into()).unwrap();
363-
let project_id: ProjectId = (org_id, project_name);
365+
let project_id: ProjectId = (project_name, org_id);
364366
let hashed_key = store::Projects::storage_map_final_key(project_id.clone());
365367
let decoded_key = store::Projects::decode_key(&hashed_key).unwrap();
366368
assert_eq!(decoded_key, project_id);

runtime/tests/checkpoing_setting.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ async fn set_checkpoint() {
1212
let client = Client::new_emulator();
1313
let charles = key_pair_from_string("Charles");
1414

15-
let org_id = random_string32();
15+
let org_id = random_org_id();
1616
let project = create_project_with_checkpoint(org_id.clone(), &client, &charles).await;
1717
let project_name = project.clone().name;
1818

@@ -53,7 +53,7 @@ async fn set_checkpoint_without_permission() {
5353
let client = Client::new_emulator();
5454
let eve = key_pair_from_string("Eve");
5555

56-
let org_id = random_string32();
56+
let org_id = random_org_id();
5757
let project = create_project_with_checkpoint(org_id.clone(), &client, &eve).await;
5858
let project_name = project.name.clone();
5959

@@ -100,7 +100,7 @@ async fn fail_to_set_nonexistent_checkpoint() {
100100
let client = Client::new_emulator();
101101
let david = key_pair_from_string("David");
102102

103-
let org_id = random_string32();
103+
let org_id = random_org_id();
104104
let project = create_project_with_checkpoint(org_id.clone(), &client, &david).await;
105105
let project_name = project.name.clone();
106106
let garbage = CheckpointId::random();
@@ -134,7 +134,7 @@ async fn set_fork_checkpoint() {
134134
let client = Client::new_emulator();
135135
let grace = key_pair_from_string("Grace");
136136

137-
let org_id = random_string32();
137+
let org_id = random_org_id();
138138
let project = create_project_with_checkpoint(org_id.clone(), &client, &grace).await;
139139
let project_name = project.name.clone();
140140
let mut current_cp = project.current_cp;

runtime/tests/org_registration.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ async fn unregister_org_with_projects() {
124124
let client = Client::new_emulator();
125125
let alice = key_pair_from_string("Alice");
126126

127-
let org_id = random_string32();
127+
let org_id = random_org_id();
128128
let random_project = create_project_with_checkpoint(org_id.clone(), &client, &alice).await;
129129

130130
assert!(

runtime/tests/project_registration.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ async fn register_project_with_inexistent_org() {
9191
.result
9292
.unwrap();
9393

94-
let inexistent_org_id = random_string32();
94+
let inexistent_org_id = random_org_id();
9595
let message = random_register_project_message(inexistent_org_id, checkpoint_id);
9696
let tx_applied = submit_ok(&client, &alice, message.clone()).await;
9797

@@ -115,7 +115,7 @@ async fn register_project_with_duplicate_id() {
115115
.result
116116
.unwrap();
117117

118-
let org_id = random_string32();
118+
let org_id = random_org_id();
119119
let register_org = message::RegisterOrg {
120120
org_id: org_id.clone(),
121121
};
@@ -165,7 +165,7 @@ async fn register_project_with_bad_checkpoint() {
165165

166166
let checkpoint_id = H256::random();
167167

168-
let org_id = random_string32();
168+
let org_id = random_org_id();
169169
let register_project = random_register_project_message(org_id.clone(), checkpoint_id);
170170
let register_org = message::RegisterOrg { org_id };
171171
submit_ok(&client, &alice, register_org.clone()).await;
@@ -189,7 +189,7 @@ async fn register_project_with_bad_actor() {
189189
let god_actor = key_pair_from_string("Alice");
190190
let bad_actor = key_pair_from_string("BadActor");
191191

192-
let org_id = random_string32();
192+
let org_id = random_org_id();
193193
let register_project = random_register_project_message(org_id.clone(), H256::random());
194194
let register_org = message::RegisterOrg { org_id };
195195
submit_ok(&client, &god_actor, register_org.clone()).await;

0 commit comments

Comments
 (0)