sentry_types/
auth.rs
1use std::borrow::Cow;
2use std::fmt;
3use std::str::FromStr;
4use std::time::SystemTime;
5
6use serde::{Deserialize, Serialize};
7use thiserror::Error;
8use url::form_urlencoded;
9
10use crate::dsn::Dsn;
11use crate::protocol;
12use crate::utils::{datetime_to_timestamp, timestamp_to_datetime};
13
14#[derive(Debug, Error, Copy, Clone, Eq, PartialEq)]
16pub enum ParseAuthError {
17 #[error("non sentry auth")]
19 NonSentryAuth,
20 #[error("invalid value for version")]
22 InvalidVersion,
23 #[error("missing public key in auth header")]
25 MissingPublicKey,
26}
27
28#[derive(Debug, Serialize, Deserialize, Clone)]
30pub struct Auth {
31 #[serde(skip)]
32 timestamp: Option<SystemTime>,
33 #[serde(rename = "sentry_client")]
34 client: Option<String>,
35 #[serde(rename = "sentry_version")]
36 version: u16,
37 #[serde(rename = "sentry_key")]
38 key: String,
39 #[serde(rename = "sentry_secret")]
40 secret: Option<String>,
41}
42
43impl Auth {
44 pub fn from_pairs<'a, I, K, V>(pairs: I) -> Result<Auth, ParseAuthError>
46 where
47 I: IntoIterator<Item = (K, V)>,
48 K: AsRef<str>,
49 V: Into<Cow<'a, str>>,
50 {
51 let mut rv = Auth {
52 timestamp: None,
53 client: None,
54 version: protocol::LATEST,
55 key: "".into(),
56 secret: None,
57 };
58
59 for (key, value) in pairs {
60 let value = value.into();
61 match key.as_ref() {
62 "sentry_timestamp" => {
63 let timestamp = value.parse().ok().and_then(timestamp_to_datetime);
64
65 rv.timestamp = timestamp;
66 }
67 "sentry_client" => {
68 rv.client = Some(value.into());
69 }
70 "sentry_version" => {
71 rv.version = value
72 .split('.')
73 .next()
74 .and_then(|v| v.parse().ok())
75 .ok_or(ParseAuthError::InvalidVersion)?;
76 }
77 "sentry_key" => {
78 rv.key = value.into();
79 }
80 "sentry_secret" => {
81 rv.secret = Some(value.into());
82 }
83 _ => {}
84 }
85 }
86
87 if rv.key.is_empty() {
88 return Err(ParseAuthError::MissingPublicKey);
89 }
90
91 Ok(rv)
92 }
93
94 pub fn from_querystring(qs: &[u8]) -> Result<Auth, ParseAuthError> {
96 Auth::from_pairs(form_urlencoded::parse(qs))
97 }
98
99 pub fn timestamp(&self) -> Option<SystemTime> {
101 self.timestamp
102 }
103
104 pub fn version(&self) -> u16 {
106 self.version
107 }
108
109 pub fn public_key(&self) -> &str {
111 &self.key
112 }
113
114 pub fn secret_key(&self) -> Option<&str> {
116 self.secret.as_deref()
117 }
118
119 pub fn is_public(&self) -> bool {
121 self.secret.is_none()
122 }
123
124 pub fn client_agent(&self) -> Option<&str> {
126 self.client.as_deref()
127 }
128}
129
130impl fmt::Display for Auth {
131 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
132 write!(
133 f,
134 "Sentry sentry_key={}, sentry_version={}",
135 self.key, self.version
136 )?;
137 if let Some(ts) = self.timestamp {
138 write!(f, ", sentry_timestamp={}", datetime_to_timestamp(&ts))?;
139 }
140 if let Some(ref client) = self.client {
141 write!(f, ", sentry_client={client}")?;
142 }
143 if let Some(ref secret) = self.secret {
144 write!(f, ", sentry_secret={secret}")?;
145 }
146 Ok(())
147 }
148}
149
150impl FromStr for Auth {
151 type Err = ParseAuthError;
152
153 fn from_str(s: &str) -> Result<Auth, ParseAuthError> {
154 let mut base_iter = s.splitn(2, ' ');
155
156 let prefix = base_iter.next().unwrap_or("");
157 let items = base_iter.next().unwrap_or("");
158
159 if !prefix.eq_ignore_ascii_case("sentry") {
160 return Err(ParseAuthError::NonSentryAuth);
161 }
162
163 let auth = Self::from_pairs(items.split(',').filter_map(|item| {
164 let mut kviter = item.split('=');
165 Some((kviter.next()?.trim(), kviter.next()?.trim()))
166 }))?;
167
168 if auth.key.is_empty() {
169 return Err(ParseAuthError::MissingPublicKey);
170 }
171
172 Ok(auth)
173 }
174}
175
176pub(crate) fn auth_from_dsn_and_client(dsn: &Dsn, client: Option<&str>) -> Auth {
177 Auth {
178 timestamp: Some(SystemTime::now()),
179 client: client.map(|x| x.to_string()),
180 version: protocol::LATEST,
181 key: dsn.public_key().to_string(),
182 secret: dsn.secret_key().map(|x| x.to_string()),
183 }
184}