1use std::borrow::Cow;
2use std::fmt;
3use std::str::FromStr;
4
5use chrono::{DateTime, Utc};
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<DateTime<Utc>>,
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
64 .parse()
65 .ok()
66 .and_then(|ts| timestamp_to_datetime(ts).single())
67 .or_else(|| value.parse().ok());
68
69 rv.timestamp = timestamp;
70 }
71 "sentry_client" => {
72 rv.client = Some(value.into());
73 }
74 "sentry_version" => {
75 rv.version = value
76 .splitn(2, '.')
77 .next()
78 .and_then(|v| v.parse().ok())
79 .ok_or(ParseAuthError::InvalidVersion)?;
80 }
81 "sentry_key" => {
82 rv.key = value.into();
83 }
84 "sentry_secret" => {
85 rv.secret = Some(value.into());
86 }
87 _ => {}
88 }
89 }
90
91 if rv.key.is_empty() {
92 return Err(ParseAuthError::MissingPublicKey);
93 }
94
95 Ok(rv)
96 }
97
98 pub fn from_querystring(qs: &[u8]) -> Result<Auth, ParseAuthError> {
100 Auth::from_pairs(form_urlencoded::parse(qs))
101 }
102
103 pub fn timestamp(&self) -> Option<DateTime<Utc>> {
105 self.timestamp
106 }
107
108 pub fn version(&self) -> u16 {
110 self.version
111 }
112
113 pub fn public_key(&self) -> &str {
115 &self.key
116 }
117
118 pub fn secret_key(&self) -> Option<&str> {
120 self.secret.as_deref()
121 }
122
123 pub fn is_public(&self) -> bool {
125 self.secret.is_none()
126 }
127
128 pub fn client_agent(&self) -> Option<&str> {
130 self.client.as_deref()
131 }
132}
133
134impl fmt::Display for Auth {
135 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
136 write!(
137 f,
138 "Sentry sentry_key={}, sentry_version={}",
139 self.key, self.version
140 )?;
141 if let Some(ts) = self.timestamp {
142 write!(f, ", sentry_timestamp={}", datetime_to_timestamp(&ts))?;
143 }
144 if let Some(ref client) = self.client {
145 write!(f, ", sentry_client={}", client)?;
146 }
147 if let Some(ref secret) = self.secret {
148 write!(f, ", sentry_secret={}", secret)?;
149 }
150 Ok(())
151 }
152}
153
154impl FromStr for Auth {
155 type Err = ParseAuthError;
156
157 fn from_str(s: &str) -> Result<Auth, ParseAuthError> {
158 let mut base_iter = s.splitn(2, ' ');
159
160 let prefix = base_iter.next().unwrap_or("");
161 let items = base_iter.next().unwrap_or("");
162
163 if !prefix.eq_ignore_ascii_case("sentry") {
164 return Err(ParseAuthError::NonSentryAuth);
165 }
166
167 let auth = Self::from_pairs(items.split(',').filter_map(|item| {
168 let mut kviter = item.split('=');
169 Some((kviter.next()?.trim(), kviter.next()?.trim()))
170 }))?;
171
172 if auth.key.is_empty() {
173 return Err(ParseAuthError::MissingPublicKey);
174 }
175
176 Ok(auth)
177 }
178}
179
180pub(crate) fn auth_from_dsn_and_client(dsn: &Dsn, client: Option<&str>) -> Auth {
181 Auth {
182 timestamp: Some(Utc::now()),
183 client: client.map(|x| x.to_string()),
184 version: protocol::LATEST,
185 key: dsn.public_key().to_string(),
186 secret: dsn.secret_key().map(|x| x.to_string()),
187 }
188}