Skip to content

Commit 1fdf3a8

Browse files
committed
Merge #980: Do not allow invalid tracker keys
e81914b refactor: [#976] concrete errors for parsing keys (Jose Celano) 8d41d18 fix: [#976] do not allow invalid tracker keys (Jose Celano) Pull request description: Do not allow invalid tracker keys. Keys cannot be constructed if they: - Are longer than 32 chars. - Contain non-alphanumeric ASCII chars. ACKs for top commit: josecelano: ACK e81914b Tree-SHA512: 4916e50c0b853173bd8b28e9cbe7f1b9b707e413ae5f164339427710a1d5482575cb2ba8fee430341503d24a8b8db48ba25fd8de4e0333a8d14f83681783a483
2 parents 1f5de8c + e81914b commit 1fdf3a8

3 files changed

Lines changed: 67 additions & 29 deletions

File tree

src/core/auth.rs

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -131,13 +131,37 @@ impl ExpiringKey {
131131
}
132132
}
133133

134-
/// A randomly generated token used for authentication.
134+
/// A token used for authentication.
135135
///
136-
/// It contains lower and uppercase letters and numbers.
137-
/// It's a 32-char string.
136+
/// - It contains only ascii alphanumeric chars: lower and uppercase letters and
137+
/// numbers.
138+
/// - It's a 32-char string.
138139
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Display, Hash)]
139140
pub struct Key(String);
140141

142+
impl Key {
143+
/// # Errors
144+
///
145+
/// Will return an error is the string represents an invalid key.
146+
/// Valid keys can only contain 32 chars including 0-9, a-z and A-Z.
147+
pub fn new(value: &str) -> Result<Self, ParseKeyError> {
148+
if value.len() != AUTH_KEY_LENGTH {
149+
return Err(ParseKeyError::InvalidKeyLength);
150+
}
151+
152+
if !value.chars().all(|c| c.is_ascii_alphanumeric()) {
153+
return Err(ParseKeyError::InvalidChars);
154+
}
155+
156+
Ok(Self(value.to_owned()))
157+
}
158+
159+
#[must_use]
160+
pub fn value(&self) -> &str {
161+
&self.0
162+
}
163+
}
164+
141165
/// Error returned when a key cannot be parsed from a string.
142166
///
143167
/// ```rust,no_run
@@ -151,24 +175,27 @@ pub struct Key(String);
151175
/// assert_eq!(key.unwrap().to_string(), key_string);
152176
/// ```
153177
///
154-
/// If the string does not contains a valid key, the parser function will return this error.
155-
#[derive(Debug, PartialEq, Eq, Display)]
156-
pub struct ParseKeyError;
178+
/// If the string does not contains a valid key, the parser function will return
179+
/// this error.
180+
#[derive(Debug, Error)]
181+
pub enum ParseKeyError {
182+
#[error("Invalid key length. Key must be have 32 chars")]
183+
InvalidKeyLength,
184+
#[error("Invalid chars for key. Key can only alphanumeric chars (0-9, a-z, A-Z)")]
185+
InvalidChars,
186+
}
157187

158188
impl FromStr for Key {
159189
type Err = ParseKeyError;
160190

161191
fn from_str(s: &str) -> Result<Self, Self::Err> {
162-
if s.len() != AUTH_KEY_LENGTH {
163-
return Err(ParseKeyError);
164-
}
165-
192+
Key::new(s)?;
166193
Ok(Self(s.to_string()))
167194
}
168195
}
169196

170-
/// Verification error. Error returned when an [`ExpiringKey`] cannot be verified with the [`verify(...)`](crate::core::auth::verify) function.
171-
///
197+
/// Verification error. Error returned when an [`ExpiringKey`] cannot be
198+
/// verified with the [`verify(...)`](crate::core::auth::verify) function.
172199
#[derive(Debug, Error)]
173200
#[allow(dead_code)]
174201
pub enum Error {
@@ -209,6 +236,22 @@ mod tests {
209236
assert!(key.is_ok());
210237
assert_eq!(key.unwrap().to_string(), key_string);
211238
}
239+
240+
#[test]
241+
fn length_should_be_32() {
242+
let key = Key::new("");
243+
assert!(key.is_err());
244+
245+
let string_longer_than_32 = "012345678901234567890123456789012"; // DevSkim: ignore DS173237
246+
let key = Key::new(string_longer_than_32);
247+
assert!(key.is_err());
248+
}
249+
250+
#[test]
251+
fn should_only_include_alphanumeric_chars() {
252+
let key = Key::new("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%");
253+
assert!(key.is_err());
254+
}
212255
}
213256

214257
mod expiring_auth_key {

tests/servers/api/v1/asserts.rs

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ pub async fn assert_bad_request(response: Response, body: &str) {
6161
assert_eq!(response.text().await.unwrap(), body);
6262
}
6363

64+
pub async fn assert_bad_request_with_text(response: Response, text: &str) {
65+
assert_eq!(response.status(), 400);
66+
assert_eq!(response.headers().get("content-type").unwrap(), "text/plain; charset=utf-8");
67+
assert!(response.text().await.unwrap().contains(text));
68+
}
69+
6470
pub async fn assert_unprocessable_content(response: Response, text: &str) {
6571
assert_eq!(response.status(), 422);
6672
assert_eq!(response.headers().get("content-type").unwrap(), "text/plain; charset=utf-8");
@@ -93,20 +99,9 @@ pub async fn assert_invalid_auth_key_get_param(response: Response, invalid_auth_
9399
}
94100

95101
pub async fn assert_invalid_auth_key_post_param(response: Response, invalid_auth_key: &str) {
96-
assert_bad_request(
102+
assert_bad_request_with_text(
97103
response,
98-
&format!(
99-
"Invalid URL: invalid auth key: string \"{}\", ParseKeyError",
100-
&invalid_auth_key
101-
),
102-
)
103-
.await;
104-
}
105-
106-
pub async fn _assert_unprocessable_auth_key_param(response: Response, _invalid_value: &str) {
107-
assert_unprocessable_content(
108-
response,
109-
"Failed to deserialize the JSON body into the target type: seconds_valid: invalid type",
104+
&format!("Invalid URL: invalid auth key: string \"{}\"", &invalid_auth_key),
110105
)
111106
.await;
112107
}

tests/servers/api/v1/contract/context/auth_key.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,10 @@ async fn should_fail_generating_a_new_auth_key_when_the_provided_key_is_invalid(
136136
let invalid_keys = [
137137
// "", it returns 404
138138
// " ", it returns 404
139-
"-1", // Not a string
140-
"invalid", // Invalid string
141-
"GQEs2ZNcCm9cwEV9dBpcPB5OwNFWFiR", // Not a 32-char string
142-
// "%QEs2ZNcCm9cwEV9dBpcPB5OwNFWFiRd", // Invalid char. todo: this doesn't fail
139+
"-1", // Not a string
140+
"invalid", // Invalid string
141+
"GQEs2ZNcCm9cwEV9dBpcPB5OwNFWFiR", // Not a 32-char string
142+
"%QEs2ZNcCm9cwEV9dBpcPB5OwNFWFiRd", // Invalid char.
143143
];
144144

145145
for invalid_key in invalid_keys {

0 commit comments

Comments
 (0)