Skip to content

Commit

Permalink
ref(common): Make the DSN public key Copy (#795)
Browse files Browse the repository at this point in the history
Creates a type for DSN public keys that implements copy. Every public
key is a 32-character hexadecimal string. Storage requirements could be
halved by parsing the hexadecimal contents of the key, however this is
currently not enforced anywhere in Sentry. For this reason, we're taking
the conservative approach using 32 bytes.

The type is called `ProjectKey` to distinguish it from Relay's own
public key.
  • Loading branch information
jan-auer authored Oct 5, 2020
1 parent 0415a69 commit 79ebd1e
Show file tree
Hide file tree
Showing 11 changed files with 203 additions and 66 deletions.
4 changes: 3 additions & 1 deletion relay-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod cell;
mod constants;
mod glob;
mod log;
mod project;
mod retry;
mod time;
mod utils;
Expand All @@ -19,11 +20,12 @@ pub use crate::cell::*;
pub use crate::constants::*;
pub use crate::glob::*;
pub use crate::log::*;
pub use crate::project::*;
pub use crate::retry::*;
pub use crate::time::*;
pub use crate::utils::*;

pub use sentry_types::protocol::LATEST as PROTOCOL_VERSION;
pub use sentry_types::{
Auth, Dsn, ParseAuthError, ParseDsnError, ParseProjectIdError, ProjectId, Scheme, Uuid,
Auth, Dsn, ParseAuthError, ParseDsnError, ParseProjectIdError, Scheme, Uuid,
};
77 changes: 77 additions & 0 deletions relay-common/src/project.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use std::borrow::Cow;
use std::fmt;

use serde::{Deserialize, Serialize};

#[doc(inline)]
pub use sentry_types::ProjectId;

/// An error parsing [`ProjectKey`](struct.ProjectKey.html).
#[derive(Clone, Copy, Debug)]
pub struct ParseProjectKeyError;

impl fmt::Display for ParseProjectKeyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "invalid project key")
}
}

impl std::error::Error for ParseProjectKeyError {}

/// The public key used in a DSN to identify and authenticate for a project at Sentry.
///
/// Project keys are always 32-character hexadecimal strings.
#[derive(Clone, Copy, Eq, Hash, Ord, PartialOrd, PartialEq)]
pub struct ProjectKey([u8; 32]);

impl Serialize for ProjectKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.as_str())
}
}

impl<'de> Deserialize<'de> for ProjectKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let cow = Cow::<str>::deserialize(deserializer)?;
Self::parse(&cow).map_err(serde::de::Error::custom)
}
}

impl ProjectKey {
/// Parses a `ProjectKey` from a string.
pub fn parse(key: &str) -> Result<Self, ParseProjectKeyError> {
if key.len() != 32 || !key.is_ascii() {
return Err(ParseProjectKeyError);
}

let mut project_key = Self(Default::default());
project_key.0.copy_from_slice(key.as_bytes());
Ok(project_key)
}

/// Returns the string representation of the project key.
#[inline]
pub fn as_str(&self) -> &str {
// Safety: The string is already validated to be of length 32 and valid ASCII when
// constructing `ProjectKey`.
unsafe { std::str::from_utf8_unchecked(&self.0) }
}
}

impl fmt::Debug for ProjectKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ProjectKey(\"{}\")", self.as_str())
}
}

impl fmt::Display for ProjectKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_str().fmt(f)
}
}
30 changes: 15 additions & 15 deletions relay-quotas/src/quota.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ use std::str::FromStr;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;

use relay_common::ProjectId;
use relay_common::{ProjectId, ProjectKey};

/// Data scoping information.
///
/// This structure holds information of all scopes required for attributing an item to quotas.
#[derive(Clone, Debug)]
#[derive(Clone, Copy, Debug)]
pub struct Scoping {
/// The organization id.
pub organization_id: u64,
Expand All @@ -18,7 +18,7 @@ pub struct Scoping {
pub project_id: ProjectId,

/// The DSN public key.
pub public_key: String,
pub public_key: ProjectKey,

/// The public key's internal id.
pub key_id: Option<u64>,
Expand Down Expand Up @@ -567,7 +567,7 @@ mod tests {
scoping: &Scoping {
organization_id: 42,
project_id: ProjectId::new(21),
public_key: "a94ae32be2584e0bbd7a4cbb95971fee".to_owned(),
public_key: ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fee").unwrap(),
key_id: Some(17),
}
}));
Expand All @@ -590,7 +590,7 @@ mod tests {
scoping: &Scoping {
organization_id: 42,
project_id: ProjectId::new(21),
public_key: "a94ae32be2584e0bbd7a4cbb95971fee".to_owned(),
public_key: ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fee").unwrap(),
key_id: Some(17),
}
}));
Expand All @@ -613,7 +613,7 @@ mod tests {
scoping: &Scoping {
organization_id: 42,
project_id: ProjectId::new(21),
public_key: "a94ae32be2584e0bbd7a4cbb95971fee".to_owned(),
public_key: ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fee").unwrap(),
key_id: Some(17),
}
}));
Expand All @@ -623,7 +623,7 @@ mod tests {
scoping: &Scoping {
organization_id: 42,
project_id: ProjectId::new(21),
public_key: "a94ae32be2584e0bbd7a4cbb95971fee".to_owned(),
public_key: ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fee").unwrap(),
key_id: Some(17),
}
}));
Expand All @@ -646,7 +646,7 @@ mod tests {
scoping: &Scoping {
organization_id: 42,
project_id: ProjectId::new(21),
public_key: "a94ae32be2584e0bbd7a4cbb95971fee".to_owned(),
public_key: ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fee").unwrap(),
key_id: Some(17),
}
}));
Expand All @@ -669,7 +669,7 @@ mod tests {
scoping: &Scoping {
organization_id: 42,
project_id: ProjectId::new(21),
public_key: "a94ae32be2584e0bbd7a4cbb95971fee".to_owned(),
public_key: ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fee").unwrap(),
key_id: Some(17),
}
}));
Expand All @@ -679,7 +679,7 @@ mod tests {
scoping: &Scoping {
organization_id: 0,
project_id: ProjectId::new(21),
public_key: "a94ae32be2584e0bbd7a4cbb95971fee".to_owned(),
public_key: ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fee").unwrap(),
key_id: Some(17),
}
}));
Expand All @@ -702,7 +702,7 @@ mod tests {
scoping: &Scoping {
organization_id: 42,
project_id: ProjectId::new(21),
public_key: "a94ae32be2584e0bbd7a4cbb95971fee".to_owned(),
public_key: ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fee").unwrap(),
key_id: Some(17),
}
}));
Expand All @@ -712,7 +712,7 @@ mod tests {
scoping: &Scoping {
organization_id: 42,
project_id: ProjectId::new(0),
public_key: "a94ae32be2584e0bbd7a4cbb95971fee".to_owned(),
public_key: ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fee").unwrap(),
key_id: Some(17),
}
}));
Expand All @@ -735,7 +735,7 @@ mod tests {
scoping: &Scoping {
organization_id: 42,
project_id: ProjectId::new(21),
public_key: "a94ae32be2584e0bbd7a4cbb95971fee".to_owned(),
public_key: ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fee").unwrap(),
key_id: Some(17),
}
}));
Expand All @@ -745,7 +745,7 @@ mod tests {
scoping: &Scoping {
organization_id: 42,
project_id: ProjectId::new(21),
public_key: "a94ae32be2584e0bbd7a4cbb95971fee".to_owned(),
public_key: ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fee").unwrap(),
key_id: Some(0),
}
}));
Expand All @@ -755,7 +755,7 @@ mod tests {
scoping: &Scoping {
organization_id: 42,
project_id: ProjectId::new(21),
public_key: "a94ae32be2584e0bbd7a4cbb95971fee".to_owned(),
public_key: ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fee").unwrap(),
key_id: None,
}
}));
Expand Down
32 changes: 17 additions & 15 deletions relay-quotas/src/rate_limit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::fmt;
use std::str::FromStr;
use std::time::{Duration, Instant};

use relay_common::ProjectId;
use relay_common::{ProjectId, ProjectKey};

use crate::quota::{DataCategories, ItemScoping, Quota, QuotaScope, ReasonCode, Scoping};
use crate::REJECT_ALL_SECS;
Expand Down Expand Up @@ -107,7 +107,7 @@ pub enum RateLimitScope {
/// A project with identifier.
Project(ProjectId),
/// A DSN public key.
Key(String),
Key(ProjectKey),
}

impl RateLimitScope {
Expand All @@ -116,9 +116,9 @@ impl RateLimitScope {
match scope {
QuotaScope::Organization => RateLimitScope::Organization(scoping.organization_id),
QuotaScope::Project => RateLimitScope::Project(scoping.project_id),
QuotaScope::Key => RateLimitScope::Key(scoping.public_key.clone()),
QuotaScope::Key => RateLimitScope::Key(scoping.public_key),
// For unknown scopes, assume the most specific scope:
QuotaScope::Unknown => RateLimitScope::Key(scoping.public_key.clone()),
QuotaScope::Unknown => RateLimitScope::Key(scoping.public_key),
}
}

Expand Down Expand Up @@ -379,7 +379,7 @@ mod tests {
scoping: &Scoping {
organization_id: 42,
project_id: ProjectId::new(21),
public_key: "a94ae32be2584e0bbd7a4cbb95971fee".to_owned(),
public_key: ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fee").unwrap(),
key_id: None,
}
}));
Expand All @@ -389,7 +389,7 @@ mod tests {
scoping: &Scoping {
organization_id: 42,
project_id: ProjectId::new(21),
public_key: "a94ae32be2584e0bbd7a4cbb95971fee".to_owned(),
public_key: ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fee").unwrap(),
key_id: None,
}
}));
Expand All @@ -409,7 +409,7 @@ mod tests {
scoping: &Scoping {
organization_id: 42,
project_id: ProjectId::new(21),
public_key: "a94ae32be2584e0bbd7a4cbb95971fee".to_owned(),
public_key: ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fee").unwrap(),
key_id: None,
}
}));
Expand All @@ -419,7 +419,7 @@ mod tests {
scoping: &Scoping {
organization_id: 0,
project_id: ProjectId::new(21),
public_key: "a94ae32be2584e0bbd7a4cbb95971fee".to_owned(),
public_key: ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fee").unwrap(),
key_id: None,
}
}));
Expand All @@ -439,7 +439,7 @@ mod tests {
scoping: &Scoping {
organization_id: 42,
project_id: ProjectId::new(21),
public_key: "a94ae32be2584e0bbd7a4cbb95971fee".to_owned(),
public_key: ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fee").unwrap(),
key_id: None,
}
}));
Expand All @@ -449,7 +449,7 @@ mod tests {
scoping: &Scoping {
organization_id: 42,
project_id: ProjectId::new(0),
public_key: "a94ae32be2584e0bbd7a4cbb95971fee".to_owned(),
public_key: ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fee").unwrap(),
key_id: None,
}
}));
Expand All @@ -459,7 +459,9 @@ mod tests {
fn test_rate_limit_matches_key() {
let rate_limit = RateLimit {
categories: DataCategories::new(),
scope: RateLimitScope::Key("a94ae32be2584e0bbd7a4cbb95971fee".to_owned()),
scope: RateLimitScope::Key(
ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fee").unwrap(),
),
reason_code: None,
retry_after: RetryAfter::from_secs(1),
};
Expand All @@ -469,7 +471,7 @@ mod tests {
scoping: &Scoping {
organization_id: 42,
project_id: ProjectId::new(21),
public_key: "a94ae32be2584e0bbd7a4cbb95971fee".to_owned(),
public_key: ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fee").unwrap(),
key_id: None,
}
}));
Expand All @@ -479,7 +481,7 @@ mod tests {
scoping: &Scoping {
organization_id: 0,
project_id: ProjectId::new(21),
public_key: "deadbeefdeadbeefdeadbeefdeadbeef".to_owned(),
public_key: ProjectKey::parse("deadbeefdeadbeefdeadbeefdeadbeef").unwrap(),
key_id: None,
}
}));
Expand Down Expand Up @@ -715,7 +717,7 @@ mod tests {
scoping: &Scoping {
organization_id: 42,
project_id: ProjectId::new(21),
public_key: "a94ae32be2584e0bbd7a4cbb95971fee".to_owned(),
public_key: ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fee").unwrap(),
key_id: None,
},
});
Expand Down Expand Up @@ -762,7 +764,7 @@ mod tests {
scoping: &Scoping {
organization_id: 42,
project_id: ProjectId::new(21),
public_key: "a94ae32be2584e0bbd7a4cbb95971fee".to_owned(),
public_key: ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fee").unwrap(),
key_id: None,
},
};
Expand Down
Loading

0 comments on commit 79ebd1e

Please sign in to comment.