1use crate::api;
3use crate::api::OpenError;
4use crate::attr;
5use crate::handle::Handle;
6use crate::misc::time_ext;
7use crate::servers::yivi;
8
9use actix_web::web;
10
11use std::collections::{BTreeSet, VecDeque};
12use std::rc::Rc;
13
14use super::server::*;
15
16#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
18#[serde(deny_unknown_fields)]
19pub struct CardConfig {
20 #[serde(rename = "type")]
22 #[serde(default)]
23 pub card_type: CardType,
24
25 #[serde(default = "default_card_attr_type")]
27 pub card_attr_type: Handle,
28
29 #[serde(default)]
31 pub valid_for: CardValidFor,
32
33 #[serde(default)]
37 registration_source: Option<String>,
38}
39
40#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, PartialEq, Eq)]
42#[serde(untagged)]
43pub enum CardValidFor {
44 Historic(BTreeSet<HistoricCardValidFor>),
45
46 Simple(#[serde(with = "time_ext::human_duration")] core::time::Duration),
48}
49
50impl Default for CardValidFor {
51 fn default() -> Self {
52 CardValidFor::Historic(Default::default()) }
54}
55
56impl CardValidFor {
57 pub fn now(&self) -> core::time::Duration {
59 self.at(api::NumericDate::now())
60 }
61
62 pub fn at(&self, moment: api::NumericDate) -> core::time::Duration {
64 match self {
65 CardValidFor::Simple(duration) => *duration,
66 CardValidFor::Historic(historic) => {
67 historic
68 .range(
70 ..=HistoricCardValidFor {
72 starting_epoch: yivi::Epoch::from(moment),
73 value: core::time::Duration::MAX,
74 },
75 )
76 .next_back()
78 .copied()
79 .unwrap_or_default()
81 .value
82 }
83 }
84 }
85
86 pub fn to_welcome_ep_format(&self) -> Option<Vec<api::auths::HistoricCardValidity>> {
87 let CardValidFor::Historic(historic) = self else {
88 return None;
89 };
90
91 let mut list: VecDeque<api::auths::HistoricCardValidity> = historic
92 .iter()
93 .map(|hcvf| api::auths::HistoricCardValidity {
94 starting_at_timestamp: hcvf.starting_epoch.starts(),
95 card_valid_for_secs: hcvf.value.as_secs(),
96 })
97 .collect();
98
99 let unix_epoch = api::NumericDate::new(0);
100
101 'epoch_present: {
102 if let Some(hcv) = list.front()
103 && hcv.starting_at_timestamp == unix_epoch
104 {
105 break 'epoch_present;
106 }
107
108 list.push_front(api::auths::HistoricCardValidity {
109 starting_at_timestamp: unix_epoch,
110 card_valid_for_secs: self.at(unix_epoch).as_secs(),
111 });
112 }
113
114 Some(list.into())
115 }
116}
117
118#[derive(
120 serde::Deserialize, serde::Serialize, Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq,
121)]
122#[serde(deny_unknown_fields)]
123pub struct HistoricCardValidFor {
124 pub starting_epoch: yivi::Epoch,
127
128 #[serde(with = "time_ext::human_duration")]
129 pub value: core::time::Duration,
130}
131
132impl Default for HistoricCardValidFor {
133 fn default() -> Self {
134 Self {
135 starting_epoch: yivi::Epoch::with_seqnr(0),
136 value: core::time::Duration::from_secs(2 * 7 * 24 * 3600), }
138 }
139}
140
141fn default_card_attr_type() -> Handle {
142 "ph_card".parse().unwrap()
143}
144
145impl Default for CardConfig {
146 fn default() -> Self {
147 serde_json::from_value(serde_json::json!({}))
148 .expect("expected all fields of CardConfig to have default values")
149 }
150}
151
152#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, Copy, Default)]
154#[serde(rename_all = "snake_case")]
155pub enum CardType {
156 Demo,
157 #[default]
158 Real,
159}
160
161impl CardType {
162 pub fn credential(self) -> &'static str {
163 match self {
164 Self::Real => "pbdf.PubHubs.account",
165 Self::Demo => "irma-demo.PubHubs.account",
166 }
167 }
168
169 pub fn id(&self) -> &'static str {
170 "id"
171 }
172
173 pub fn date(self) -> &'static str {
174 match self {
175 Self::Real => "registrationDate",
176 Self::Demo => "registration_date",
177 }
178 }
179
180 pub fn source(self) -> &'static str {
181 match self {
182 Self::Real => "registrationSource",
183 Self::Demo => "registration_source",
184 }
185 }
186}
187
188impl App {
189 pub(crate) fn registration_source<'a>(&'a self, yivi: &'a YiviCtx) -> &'a str {
191 if let Some(rs) = yivi.card_config.registration_source.as_ref() {
192 return rs.as_str();
193 }
194
195 self.phc_url.as_str()
196 }
197
198 pub(crate) fn issue_card(
200 &self,
201 card_pseud_package: api::phc::user::CardPseudPackage,
202 comment: Option<String>,
203 ) -> api::Result<(yivi::ExtendedSessionRequest, attr::Attr)> {
204 let yivi = self.get_yivi()?;
205
206 let mut registration_date: String = card_pseud_package
207 .registration_date
208 .as_ref()
209 .map(api::NumericDate::date)
210 .unwrap_or_else(|| "?".to_string());
211
212 if let Some(comment) = comment {
213 registration_date = format!("{registration_date}, {comment}");
214 }
215
216 let card_pseud = card_pseud_package.card_pseud.to_string();
217
218 let credential = yivi::CredentialToBeIssued::new(
219 yivi.card_config
220 .card_type
221 .credential()
222 .parse()
223 .map_err(|err| {
224 log::error!("failed to parse pubhubs card yivi credential: {err}");
225 api::ErrorCode::InternalError
226 })?,
227 )
228 .valid_for(yivi.card_config.valid_for.now())
229 .attribute(
230 yivi.card_config.card_type.id().to_string(),
231 card_pseud.clone(),
232 )
233 .attribute(
234 yivi.card_config.card_type.source().to_string(),
235 self.registration_source(yivi).to_string(),
236 )
237 .attribute(
238 yivi.card_config.card_type.date().to_string(),
239 registration_date,
240 );
241
242 let esr = yivi::ExtendedSessionRequest::issuance(vec![credential]);
243
244 let Some(attr_type) = self.attr_type_from_handle(&yivi.card_config.card_attr_type) else {
245 log::error!(
246 "pubhubs card attribute type {} not found",
247 yivi.card_config.card_attr_type
248 );
249 return Err(api::ErrorCode::InternalError);
250 };
251
252 let attr = attr::Attr {
253 attr_type: attr_type.id,
254 value: card_pseud,
255 bannable: attr_type.bannable,
256 not_identifying: !attr_type.identifying,
257 not_addable: false,
258 };
259
260 Ok((esr, attr))
261 }
262
263 pub async fn handle_card(
265 app: Rc<Self>,
266 req: web::Json<api::auths::CardReq>,
267 ) -> api::Result<api::auths::CardResp> {
268 let yivi = app.get_yivi()?;
269 let running_state = app.running_state_or_please_retry()?;
270
271 let api::auths::CardReq {
272 card_pseud_package: cpp_signed,
273 comment,
274 } = req.into_inner();
275
276 let card_pseudonym_package =
277 match cpp_signed.open(&*running_state.constellation.phc_jwt_key, None) {
278 Ok(cpp) => cpp,
279 Err(OpenError::OtherConstellation(..)) | Err(OpenError::InternalError) => {
280 return Err(api::ErrorCode::InternalError);
281 }
282 Err(OpenError::OtherwiseInvalid) => {
283 return Err(api::ErrorCode::BadRequest);
284 }
285 Err(OpenError::Expired) | Err(OpenError::InvalidSignature) => {
286 return Ok(api::auths::CardResp::PleaseRetryWithNewCardPseud);
287 }
288 };
289
290 let (esr, attr) = app.issue_card(card_pseudonym_package, comment)?;
291
292 let issuance_request = esr.sign(&yivi.requestor_creds).map_err(|err| {
293 log::error!("failed to sign extended session request for issuance of card: {err:?}");
294 api::ErrorCode::InternalError
295 })?;
296
297 let attr = api::Signed::<attr::Attr>::new(
298 &running_state.attr_signing_key,
299 &attr,
300 app.auth_window,
301 )?;
302
303 Ok(api::auths::CardResp::Success {
304 attr,
305 issuance_request,
306 yivi_requestor_url: yivi.requestor_url.clone(),
307 })
308 }
309}
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314
315 #[test]
316 fn test_card_valid_for() {
317 let cvf: CardValidFor = serde_json::from_value(serde_json::json!("3w")).unwrap();
318 assert_eq!(
319 cvf,
320 CardValidFor::Simple(core::time::Duration::from_hours(3 * 24 * 7))
321 );
322
323 assert_eq!(cvf.now(), core::time::Duration::from_hours(3 * 24 * 7));
324
325 let cvf: CardValidFor = serde_json::from_value(serde_json::json!([
326 { "starting_epoch": 521, "value": "4w" }, { "starting_epoch": 1565, "value": "5w" } ]))
329 .unwrap();
330
331 assert_eq!(cvf.now(), core::time::Duration::from_hours(5 * 24 * 7));
332 assert_eq!(
333 cvf.at(api::NumericDate::from(
334 humantime::parse_rfc3339_weak("1980-01-01 00:00:00").unwrap()
335 )),
336 core::time::Duration::from_hours(4 * 24 * 7)
337 );
338 assert_eq!(
339 cvf.at(api::NumericDate::from(
340 humantime::parse_rfc3339_weak("1979-12-27T00:00:00").unwrap()
342 )),
343 core::time::Duration::from_hours(4 * 24 * 7)
344 );
345 assert_eq!(
346 cvf.at(api::NumericDate::from(
347 humantime::parse_rfc3339_weak("1979-12-26T23:59:59").unwrap()
349 )),
350 core::time::Duration::from_hours(2 * 24 * 7)
351 );
352 }
353}