Skip to main content

vcon_server/
access.rs

1//! Turns the bearer token on a request into a caller, or refuses it.
2
3use std::fmt;
4
5use aws_lc_rs::constant_time;
6use aws_lc_rs::digest::{SHA256, digest};
7use base64::Engine;
8use base64::engine::general_purpose::URL_SAFE_NO_PAD;
9
10use crate::ids::{KeyId, TenantId};
11use crate::tenants::Tenants;
12
13/// The fixed start of every token, so secret scanners can spot a leaked
14/// one.
15const PREFIX: &str = "vcs_";
16
17/// A secret is 256 random bits.
18const SECRET_LEN: usize = 32;
19
20/// What a key may do.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum Scope {
23    /// Stores vCons: `POST /vcons` and `PUT /vcons/{uuid}`.
24    Write,
25    /// Lists and reads vCons: `GET /vcons` and `GET /vcons/{uuid}`.
26    Read,
27    /// Deletes vCons: `DELETE /vcons/{uuid}`.
28    Delete,
29    /// The `/legal/` routes: deleted vCons, old versions, restore, holds.
30    Legal,
31    /// Reads the tenant's own audit log.
32    Audit,
33    /// Manages tenants and keys; held only by an operator key.
34    Admin,
35}
36
37impl Scope {
38    /// Every scope, in the order a key's scopes are written.
39    const ALL: [Scope; 6] = [
40        Scope::Write,
41        Scope::Read,
42        Scope::Delete,
43        Scope::Legal,
44        Scope::Audit,
45        Scope::Admin,
46    ];
47
48    /// The scope with this exact name.
49    #[must_use]
50    pub fn named(name: &str) -> Option<Scope> {
51        Scope::ALL.into_iter().find(|s| s.name() == name)
52    }
53
54    fn bit(self) -> u8 {
55        1 << self as u8
56    }
57
58    fn name(self) -> &'static str {
59        match self {
60            Scope::Write => "write",
61            Scope::Read => "read",
62            Scope::Delete => "delete",
63            Scope::Legal => "legal",
64            Scope::Audit => "audit",
65            Scope::Admin => "admin",
66        }
67    }
68}
69
70/// A set of scopes.
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
72pub struct Scopes(u8);
73
74impl Scopes {
75    /// The set of `scopes`; a scope given twice is held once.
76    #[must_use]
77    pub fn of(scopes: &[Scope]) -> Scopes {
78        Scopes(scopes.iter().fold(0, |bits, scope| bits | scope.bit()))
79    }
80
81    /// Whether the set holds `scope`.
82    #[must_use]
83    pub fn contains(self, scope: Scope) -> bool {
84        self.0 & scope.bit() != 0
85    }
86
87    /// Whether the set holds no scope.
88    #[must_use]
89    pub fn is_empty(self) -> bool {
90        self.0 == 0
91    }
92
93    /// The scopes a list names, or None when one is unknown or named twice,
94    /// so a list means one thing. Which scopes a key may hold is Tenants'
95    /// rule.
96    #[must_use]
97    pub fn named(names: &[String]) -> Option<Scopes> {
98        let mut held = Vec::new();
99        for name in names {
100            let scope = Scope::named(name)?;
101            if held.contains(&scope) {
102                return None;
103            }
104            held.push(scope);
105        }
106        Some(Scopes::of(&held))
107    }
108
109    /// Reads scopes written by `Display`: names in the fixed order, comma
110    /// separated, each once.
111    pub fn parse(text: &str) -> Option<Scopes> {
112        let scopes: Vec<Scope> = text.split(',').map(Scope::named).collect::<Option<_>>()?;
113        let read = Scopes::of(&scopes);
114        (read.to_string() == text).then_some(read)
115    }
116}
117
118impl Scopes {
119    /// The names of the scopes held, in the fixed order of `Scope::ALL`,
120    /// as the API and the command line list them.
121    pub fn names(self) -> Vec<&'static str> {
122        Scope::ALL
123            .into_iter()
124            .filter(|s| self.contains(*s))
125            .map(Scope::name)
126            .collect()
127    }
128}
129
130impl std::fmt::Display for Scopes {
131    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132        f.write_str(&self.names().join(","))
133    }
134}
135
136/// A new token for a new key: `vcs_<key id>_<secret>`, with a random id
137/// and a random 256-bit secret.
138pub(crate) fn new_token() -> (KeyId, String) {
139    let id = KeyId::generate();
140    let mut secret = [0; SECRET_LEN];
141    // AWS-LC's RAND_bytes aborts the process when it cannot get enough
142    // randomness, so `fill` never returns its error.
143    aws_lc_rs::rand::fill(&mut secret).expect("AWS-LC aborts instead of failing");
144    let token = format!("{PREFIX}{id}_{}", URL_SAFE_NO_PAD.encode(secret));
145    (id, token)
146}
147
148/// The SHA-256 of a whole token, as the key table stores it.
149pub(crate) fn token_hash(token: &str) -> [u8; 32] {
150    digest(&SHA256, token.as_bytes())
151        .as_ref()
152        .try_into()
153        .expect("SHA-256 is 32 bytes")
154}
155
156/// Who a request comes from: a tenant's key, or with no tenant the
157/// operator's.
158#[derive(Debug, Clone, Copy, PartialEq, Eq)]
159pub struct Caller {
160    /// The id of the key the request's token names.
161    pub key: KeyId,
162    /// The key's tenant, or None for an operator key.
163    pub tenant: Option<TenantId>,
164    /// The scopes the key holds.
165    pub scopes: Scopes,
166}
167
168/// Why a request's token was refused.
169#[derive(Debug, Clone, Copy, PartialEq, Eq)]
170pub enum AuthError {
171    /// No Authorization header.
172    Missing,
173    /// The key does not hold the scope the request needs.
174    Forbidden(Scope),
175    /// Not `Bearer` and a token as `new_token` writes one.
176    Malformed,
177    /// No key has this id, or the secret is not its own.
178    Unknown,
179    /// The key was revoked.
180    Revoked,
181    /// The key table could not be read, so nothing is let through.
182    Io,
183}
184
185impl fmt::Display for AuthError {
186    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187        match self {
188            AuthError::Missing => f.write_str("no Authorization header"),
189            AuthError::Forbidden(scope) => {
190                write!(f, "the key does not hold the {} scope", scope.name())
191            }
192            AuthError::Malformed => {
193                f.write_str("the Authorization header is not Bearer and a token")
194            }
195            AuthError::Unknown => f.write_str("no key has this id and secret"),
196            AuthError::Revoked => f.write_str("the key was revoked"),
197            AuthError::Io => f.write_str("the key table could not be read"),
198        }
199    }
200}
201
202impl std::error::Error for AuthError {}
203
204/// The caller a request's Authorization header names. The key is looked up
205/// on every call, with no cache, so a revoked key fails on its next
206/// request.
207///
208/// A failed check is counted in the audit (`Tenants::count_auth_failure`),
209/// except when the key table could not be read.
210///
211/// # Errors
212///
213/// [`AuthError::Missing`] with no header, [`AuthError::Malformed`] when it
214/// is not `Bearer` and a token, [`AuthError::Unknown`] for a key id no key
215/// has or a wrong secret, [`AuthError::Revoked`] for a revoked key, and
216/// [`AuthError::Io`] when the key table cannot be read.
217pub fn authenticate(keys: &Tenants, authorization: Option<&str>) -> Result<Caller, AuthError> {
218    match check(keys, authorization) {
219        Ok(caller) => Ok(caller),
220        Err((error, known)) => {
221            if error != AuthError::Io {
222                keys.count_auth_failure(known);
223            }
224            Err(error)
225        }
226    }
227}
228
229/// A failed check, with the key it named if that key is known.
230type Failure = (AuthError, Option<(KeyId, Option<TenantId>)>);
231
232fn check(keys: &Tenants, authorization: Option<&str>) -> Result<Caller, Failure> {
233    let unknown = |error| (error, None);
234    let header = authorization.ok_or(unknown(AuthError::Missing))?;
235    let token = bearer(header).ok_or(unknown(AuthError::Malformed))?;
236    let id = parse(token).map_err(unknown)?;
237    let key = keys
238        .stored_key(id)
239        .map_err(|_| unknown(AuthError::Io))?
240        .ok_or(unknown(AuthError::Unknown))?;
241    let known = |error| (error, Some((id, key.tenant)));
242    // The secret is checked before revocation, so a wrong secret learns
243    // nothing about the key.
244    constant_time::verify_slices_are_equal(&token_hash(token), &key.hash)
245        .map_err(|_| known(AuthError::Unknown))?;
246    if key.revoked {
247        return Err(known(AuthError::Revoked));
248    }
249    Ok(Caller {
250        key: id,
251        tenant: key.tenant,
252        scopes: key.scopes,
253    })
254}
255
256/// Refuses a caller whose key does not hold `scope`. Admin belongs only to
257/// an operator key, and the vCon, legal and audit scopes only to a tenant
258/// key, so a key holding a scope of the other kind is still refused it.
259///
260/// # Errors
261///
262/// [`AuthError::Forbidden`] naming `scope`.
263pub fn require(caller: &Caller, scope: Scope) -> Result<(), AuthError> {
264    let of_its_kind = (scope == Scope::Admin) == caller.tenant.is_none();
265    if of_its_kind && caller.scopes.contains(scope) {
266        Ok(())
267    } else {
268        Err(AuthError::Forbidden(scope))
269    }
270}
271
272/// The tenant a vCon or legal request acts on: the caller's own, from its
273/// key, never from the request.
274///
275/// # Errors
276///
277/// [`AuthError::Forbidden`] naming `scope` when [`require`] refuses it or
278/// the key has no tenant.
279pub fn tenant_for(caller: &Caller, scope: Scope) -> Result<TenantId, AuthError> {
280    require(caller, scope)?;
281    caller.tenant.ok_or(AuthError::Forbidden(scope))
282}
283
284/// The token in `Bearer <token>`. The scheme's case does not matter, and
285/// one or more spaces may follow it (RFC 7235).
286fn bearer(header: &str) -> Option<&str> {
287    let (scheme, rest) = header.split_once(' ')?;
288    scheme
289        .eq_ignore_ascii_case("Bearer")
290        .then(|| rest.trim_start_matches(' '))
291}
292
293/// Reads a token written as `vcs_<key id>_<secret>`: the key id is 16
294/// lowercase hex digits, and the secret is 256 bits in base64url without
295/// padding. The secret may hold `_`, so the parts are found by position.
296///
297/// # Errors
298///
299/// [`AuthError::Malformed`] for any other text.
300pub fn parse(token: &str) -> Result<KeyId, AuthError> {
301    let malformed = AuthError::Malformed;
302    let rest = token.strip_prefix(PREFIX).ok_or(malformed)?;
303    let (id, rest) = rest.split_at_checked(16).ok_or(malformed)?;
304    let secret = rest.strip_prefix('_').ok_or(malformed)?;
305    let id = KeyId::parse(id).ok_or(malformed)?;
306    // The decoding refuses padding, other alphabets and stray trailing
307    // bits, so each secret has one spelling.
308    let secret = URL_SAFE_NO_PAD.decode(secret).map_err(|_| malformed)?;
309    if secret.len() != SECRET_LEN {
310        return Err(malformed);
311    }
312    Ok(id)
313}
314
315#[cfg(test)]
316mod tests {
317    use super::*;
318
319    const ID: &str = "0123456789abcdef";
320
321    fn secret(byte: u8) -> String {
322        URL_SAFE_NO_PAD.encode([byte; 32])
323    }
324
325    #[test]
326    fn a_well_formed_token_gives_its_key_id() {
327        let id = KeyId([0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]);
328        let secrets = [
329            secret(0x00),
330            secret(0xff),
331            // 0xfb gives '-' and 0xff gives '_' in base64url.
332            URL_SAFE_NO_PAD.encode([0xfb; 32]),
333            URL_SAFE_NO_PAD.encode([[0xff; 16], [0x00; 16]].concat()),
334        ];
335        for secret in secrets {
336            assert_eq!(
337                secret.len(),
338                43,
339                "32 bytes are 43 base64url characters without padding"
340            );
341            let token = format!("vcs_{ID}_{secret}");
342            assert_eq!(parse(&token), Ok(id), "{token}");
343        }
344        assert!(
345            secret(0xff).contains('_'),
346            "a secret may hold the separator"
347        );
348    }
349
350    #[test]
351    fn a_malformed_token_is_refused() {
352        let good = secret(0x42);
353        let malformed = [
354            String::new(),
355            "vcs_".to_owned(),
356            format!("{ID}_{good}"),
357            format!("vck_{ID}_{good}"),
358            format!("VCS_{ID}_{good}"),
359            format!("vcs_{}_{good}", &ID[..15]),
360            format!("vcs_{ID}0_{good}"),
361            format!("vcs_{}_{good}", ID.to_uppercase()),
362            format!("vcs_0123456789abcdeg_{good}"),
363            format!("vcs_{ID}{good}"),
364            format!("vcs_{ID}-{good}"),
365            format!("vcs_{ID}_{}", &good[..42]),
366            format!("vcs_{ID}_{good}A"),
367            format!("vcs_{ID}_{good}="),
368            format!("vcs_{ID}_{}", good.replace('Q', "+")),
369            format!("vcs_{ID}_{}+", &good[..42]),
370            format!("vcs_{ID}_{}B", &good[..42]),
371            format!(" vcs_{ID}_{good}"),
372            format!("vcs_{ID}_{good} "),
373            format!("vcs_{ID}__{good}"),
374            format!("vcs_\u{0661}123456789abcdef_{good}"),
375        ];
376        for token in malformed {
377            assert_eq!(parse(&token), Err(AuthError::Malformed), "{token:?}");
378        }
379    }
380
381    #[test]
382    fn scopes_are_a_set_written_in_a_fixed_order() {
383        let none = Scopes::of(&[]);
384        assert!(none.is_empty(), "no scopes given, none held");
385        let scopes = Scopes::of(&[Scope::Delete, Scope::Write, Scope::Delete]);
386        assert!(
387            !scopes.is_empty(),
388            "two scopes given, so the set is not empty"
389        );
390        for scope in Scope::ALL {
391            assert_eq!(
392                scopes.contains(scope),
393                matches!(scope, Scope::Write | Scope::Delete),
394                "{scope:?}"
395            );
396        }
397        assert_eq!(
398            scopes.to_string(),
399            "write,delete",
400            "a repeated scope is held once, and written in the fixed order"
401        );
402        assert_eq!(
403            Scopes::of(&Scope::ALL).to_string(),
404            "write,read,delete,legal,audit,admin",
405            "every scope, in the fixed order"
406        );
407        assert_eq!(
408            Scopes::of(&[Scope::Legal, Scope::Write, Scope::Read]).names(),
409            ["write", "read", "legal"],
410            "names come in Scope::ALL's order, not the order given"
411        );
412        for scope in Scope::ALL {
413            let one = Scopes::of(&[scope]);
414            assert_eq!(Scopes::parse(&one.to_string()), Some(one), "{scope:?}");
415        }
416        let all = Scopes::of(&Scope::ALL);
417        assert_eq!(
418            Scopes::parse(&all.to_string()),
419            Some(all),
420            "parse reads every scope back from its written form"
421        );
422    }
423
424    #[test]
425    fn only_the_written_spelling_of_scopes_is_read() {
426        for text in [
427            "",
428            "delete,write",
429            "write,write",
430            "write,",
431            ",write",
432            "write, read",
433            "Write",
434            "owner",
435            "write,read,",
436        ] {
437            assert_eq!(Scopes::parse(text), None, "{text:?}");
438        }
439    }
440
441    /// audit is a tenant key's scope: it reads the tenant's own log.
442    #[test]
443    fn audit_is_required_of_a_tenant_key_only() {
444        let tenant = Caller {
445            key: KeyId::generate(),
446            tenant: Some(TenantId::generate()),
447            scopes: Scopes::of(&[Scope::Audit]),
448        };
449        assert_eq!(
450            Scope::named("audit"),
451            Some(Scope::Audit),
452            "the scope is named audit"
453        );
454        assert_eq!(
455            require(&tenant, Scope::Audit),
456            Ok(()),
457            "a tenant key with the audit scope may read its log"
458        );
459        assert_eq!(
460            tenant_for(&tenant, Scope::Audit),
461            Ok(tenant.tenant.expect("one")),
462            "and the log it reads is its own tenant's"
463        );
464        assert_eq!(
465            require(&tenant, Scope::Read),
466            Err(AuthError::Forbidden(Scope::Read)),
467            "audit grants no other scope"
468        );
469        let operator = Caller {
470            tenant: None,
471            scopes: Scopes::of(&[Scope::Admin, Scope::Audit]),
472            ..tenant
473        };
474        assert_eq!(
475            require(&operator, Scope::Audit),
476            Err(AuthError::Forbidden(Scope::Audit)),
477            "an operator key has no tenant log to read, even with the audit scope"
478        );
479    }
480
481    #[test]
482    fn a_scope_is_found_by_its_exact_name() {
483        for scope in Scope::ALL {
484            assert_eq!(Scope::named(scope.name()), Some(scope), "{scope:?}");
485        }
486        for name in ["", "Read", "READ", " read", "read,write", "superuser"] {
487            assert_eq!(Scope::named(name), None, "{name:?}");
488        }
489    }
490
491    #[test]
492    fn a_new_token_is_well_formed_and_new_each_time() {
493        let (id, token) = new_token();
494        assert_eq!(
495            parse(&token),
496            Ok(id),
497            "a new token parses to the id it was made with"
498        );
499        let (other_id, other) = new_token();
500        assert_ne!(other_id, id, "each new token has a new random key id");
501        assert_ne!(other[21..], token[21..], "a new secret");
502    }
503
504    /// RFC 6234's SHA-256 test vector for "abc".
505    #[test]
506    fn a_token_is_stored_as_its_sha_256() {
507        assert_eq!(
508            token_hash("abc")
509                .iter()
510                .map(|b| format!("{b:02x}"))
511                .collect::<String>(),
512            "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
513            "the hash is plain SHA-256, as the RFC's vector shows"
514        );
515    }
516
517    mod authenticate {
518        use super::*;
519        use crate::audit::Actor;
520        use crate::tenants::{Fixture, NewKey, fixture};
521
522        fn vcon_scopes() -> Scopes {
523            Scopes::of(&[Scope::Write, Scope::Read])
524        }
525
526        fn key(f: &Fixture) -> (TenantId, NewKey) {
527            let t = f.tenants.create(Actor::Cli, "acme", None).expect("made");
528            let key = f
529                .tenants
530                .create_key(Actor::Cli, Some(t), vcon_scopes(), "x")
531                .expect("made");
532            (t, key)
533        }
534
535        fn auth(f: &Fixture, header: &str) -> Result<Caller, AuthError> {
536            authenticate(&f.tenants, Some(header))
537        }
538
539        #[test]
540        fn a_valid_token_names_its_caller() {
541            let f = fixture();
542            let (t, key) = key(&f);
543            let caller = Caller {
544                key: key.id,
545                tenant: Some(t),
546                scopes: vcon_scopes(),
547            };
548            for header in [
549                format!("Bearer {}", key.token),
550                format!("bearer {}", key.token),
551                format!("BEARER   {}", key.token),
552            ] {
553                assert_eq!(auth(&f, &header), Ok(caller), "{header}");
554            }
555            let operator = f
556                .tenants
557                .create_key(Actor::Cli, None, Scopes::of(&[Scope::Admin]), "op")
558                .expect("made");
559            assert_eq!(
560                auth(&f, &format!("Bearer {}", operator.token)),
561                Ok(Caller {
562                    key: operator.id,
563                    tenant: None,
564                    scopes: Scopes::of(&[Scope::Admin]),
565                }),
566                "an operator key's caller has no tenant"
567            );
568        }
569
570        #[test]
571        fn a_missing_or_malformed_header_is_refused() {
572            let f = fixture();
573            let (_, key) = key(&f);
574            assert_eq!(
575                authenticate(&f.tenants, None),
576                Err(AuthError::Missing),
577                "no Authorization header is told apart from a bad one"
578            );
579            for header in [
580                String::new(),
581                "Bearer".to_owned(),
582                "Bearer ".to_owned(),
583                format!("Bearer{}", key.token),
584                format!("Basic {}", key.token),
585                format!("Bearers {}", key.token),
586                format!("Bearer {} ", key.token),
587                format!("Bearer\t{}", key.token),
588                "Bearer vcs_0123456789abcdef_short".to_owned(),
589            ] {
590                assert_eq!(auth(&f, &header), Err(AuthError::Malformed), "{header:?}");
591            }
592        }
593
594        #[test]
595        fn an_unknown_key_or_a_wrong_secret_is_unknown() {
596            let f = fixture();
597            let (_, key) = key(&f);
598            let (_, stranger) = new_token();
599            assert_eq!(
600                auth(&f, &format!("Bearer {stranger}")),
601                Err(AuthError::Unknown),
602                "a well-formed token whose key id no catalog holds"
603            );
604            let (_, other) = new_token();
605            let forged = format!("{}{}", &key.token[..21], &other[21..]);
606            assert_eq!(
607                auth(&f, &format!("Bearer {forged}")),
608                Err(AuthError::Unknown),
609                "a real key id with another secret is answered as an unknown key"
610            );
611            f.tenants.revoke_key(Actor::Cli, key.id).expect("revoked");
612            assert_eq!(
613                auth(&f, &format!("Bearer {forged}")),
614                Err(AuthError::Unknown),
615                "a wrong secret learns nothing about revocation"
616            );
617        }
618
619        #[test]
620        fn a_revoked_key_is_refused_on_its_next_call() {
621            let f = fixture();
622            let (_, key) = key(&f);
623            let header = format!("Bearer {}", key.token);
624            assert!(
625                auth(&f, &header).is_ok(),
626                "the key works before it is revoked"
627            );
628            f.tenants.revoke_key(Actor::Cli, key.id).expect("revoked");
629            assert_eq!(
630                auth(&f, &header),
631                Err(AuthError::Revoked),
632                "the next call after revocation is refused: no cache keeps it alive"
633            );
634        }
635
636        fn admin_failures(f: &Fixture) -> Vec<serde_json::Value> {
637            f.admin_records("auth_failed")
638        }
639
640        fn tenant_failures(f: &Fixture, t: TenantId) -> Vec<serde_json::Value> {
641            crate::audit::testing::exported(&f.tenant_catalog(t), None)
642                .into_iter()
643                .filter(|r| r["action"] == "auth_failed")
644                .collect()
645        }
646
647        fn forged(key: &NewKey) -> String {
648            let (_, other) = new_token();
649            format!("Bearer {}{}", &key.token[..21], &other[21..])
650        }
651
652        #[test]
653        fn a_burst_of_bad_tokens_leaves_one_counted_record() {
654            let f = fixture();
655            let (t, _) = key(&f);
656            for _ in 0..20 {
657                assert!(auth(&f, "Bearer not-a-token").is_err(), "a malformed token");
658            }
659            for _ in 0..5 {
660                assert!(authenticate(&f.tenants, None).is_err(), "no header");
661                let (_, made_up) = new_token();
662                assert!(
663                    auth(&f, &format!("Bearer {made_up}")).is_err(),
664                    "a key id no catalog holds"
665                );
666            }
667            let records = admin_failures(&f);
668            assert_eq!(records.len(), 1, "{records:?}");
669            assert_eq!(
670                records[0]["actor"], "",
671                "no key is known, so no actor is named"
672            );
673            assert_eq!(
674                records[0]["status"], 401,
675                "each was refused as unauthenticated"
676            );
677            assert_eq!(
678                records[0]["count"], 30,
679                "20 malformed, 5 missing, 5 unknown"
680            );
681            assert_eq!(
682                records[0]["at"], "2026-10-23T12:00:00Z",
683                "the record is stamped with the fixture clock's minute"
684            );
685            assert_eq!(
686                tenant_failures(&f, t),
687                Vec::<serde_json::Value>::new(),
688                "no failure named this tenant's key"
689            );
690
691            f.clock
692                .set(time::macros::datetime!(2026-10-23 12:01:30 UTC));
693            assert!(auth(&f, "Bearer not-a-token").is_err(), "a malformed token");
694            let records = admin_failures(&f);
695            assert_eq!(records.len(), 2, "a new minute, a new record");
696            assert_eq!(
697                records[1]["count"], 1,
698                "the new record counts only the new failure"
699            );
700        }
701
702        #[test]
703        fn a_known_key_that_fails_is_counted_in_its_tenants_log() {
704            let f = fixture();
705            let (t, key) = key(&f);
706            for _ in 0..3 {
707                assert_eq!(
708                    auth(&f, &forged(&key)),
709                    Err(AuthError::Unknown),
710                    "a wrong secret for a real key"
711                );
712            }
713            f.tenants.revoke_key(Actor::Cli, key.id).expect("revoked");
714            for _ in 0..2 {
715                assert_eq!(
716                    auth(&f, &format!("Bearer {}", key.token)),
717                    Err(AuthError::Revoked),
718                    "the right secret for a revoked key"
719                );
720            }
721            let records = tenant_failures(&f, t);
722            assert_eq!(records.len(), 1, "{records:?}");
723            assert_eq!(
724                records[0]["actor"],
725                key.id.to_string(),
726                "the failures name the key they tried"
727            );
728            assert_eq!(
729                records[0]["status"], 401,
730                "each was refused as unauthenticated"
731            );
732            assert_eq!(
733                records[0]["count"], 5,
734                "3 wrong secrets and 2 calls after revocation"
735            );
736            assert_eq!(
737                admin_failures(&f),
738                Vec::<serde_json::Value>::new(),
739                "a tenant key's failures go to its tenant's log only"
740            );
741        }
742
743        #[test]
744        fn an_operator_key_that_fails_is_counted_in_the_admin_log() {
745            let f = fixture();
746            let operator = f
747                .tenants
748                .create_key(Actor::Cli, None, Scopes::of(&[Scope::Admin]), "op")
749                .expect("made");
750            assert!(auth(&f, &forged(&operator)).is_err(), "a wrong secret");
751            let records = admin_failures(&f);
752            assert_eq!(
753                records.len(),
754                1,
755                "an operator key has no tenant log: {records:?}"
756            );
757            assert_eq!(
758                records[0]["actor"],
759                operator.id.to_string(),
760                "the record names the operator key tried"
761            );
762        }
763
764        #[test]
765        fn a_known_key_whose_tenant_log_is_gone_is_counted_in_the_admin_log() {
766            let f = fixture();
767            let (t, key) = key(&f);
768            f.tenants
769                .remove(Actor::Cli, t, crate::tenants::Removal::Force)
770                .expect("removed");
771            assert_eq!(
772                auth(&f, &format!("Bearer {}", key.token)),
773                Err(AuthError::Revoked),
774                "removing a tenant revokes its keys"
775            );
776            let records = admin_failures(&f);
777            assert_eq!(records.len(), 1, "with the tenant's log gone: {records:?}");
778            assert_eq!(
779                records[0]["actor"],
780                key.id.to_string(),
781                "the record still names the key tried"
782            );
783        }
784
785        #[test]
786        fn a_failure_that_cannot_be_counted_is_refused_all_the_same() {
787            let f = fixture();
788            f.conn()
789                .execute_batch(
790                    "CREATE TEMP TRIGGER no_audit BEFORE INSERT ON audit
791                     BEGIN SELECT RAISE(ABORT, 'no audit'); END;",
792                )
793                .expect("trigger");
794            assert_eq!(
795                auth(&f, "Bearer not-a-token"),
796                Err(AuthError::Malformed),
797                "a failed audit write does not turn a refusal into a pass"
798            );
799        }
800
801        #[test]
802        fn a_successful_check_counts_nothing() {
803            let f = fixture();
804            let (t, key) = key(&f);
805            assert!(
806                auth(&f, &format!("Bearer {}", key.token)).is_ok(),
807                "the right token"
808            );
809            assert_eq!(
810                admin_failures(&f),
811                Vec::<serde_json::Value>::new(),
812                "a success is not a failure to count"
813            );
814            assert_eq!(
815                tenant_failures(&f, t),
816                Vec::<serde_json::Value>::new(),
817                "a success is not a failure to count"
818            );
819        }
820
821        #[test]
822        fn a_key_table_that_cannot_be_read_lets_nothing_through() {
823            let f = fixture();
824            let (_, key) = key(&f);
825            let header = format!("Bearer {}", key.token);
826            f.conn()
827                .execute(
828                    "UPDATE api_key SET scopes = 'owner' WHERE id = ?1",
829                    [key.id.bytes()],
830                )
831                .expect("damage");
832            assert_eq!(auth(&f, &header), Err(AuthError::Io), "damaged scopes");
833            f.conn()
834                .execute_batch("ALTER TABLE api_key RENAME TO gone")
835                .expect("lose the table");
836            assert_eq!(auth(&f, &header), Err(AuthError::Io), "no table");
837            assert_eq!(
838                admin_failures(&f),
839                Vec::<serde_json::Value>::new(),
840                "an unreadable table is not a failed check"
841            );
842        }
843    }
844
845    mod scopes {
846        use super::*;
847
848        const TENANT: TenantId = TenantId([1; 16]);
849
850        fn caller(tenant: Option<TenantId>, scopes: &[Scope]) -> Caller {
851            Caller {
852                key: KeyId([2; 8]),
853                tenant,
854                scopes: Scopes::of(scopes),
855            }
856        }
857
858        /// Which scopes each key may use: (key, scopes it allows).
859        #[test]
860        fn each_key_may_use_only_the_scopes_it_holds_of_its_kind() {
861            let vcon = [Scope::Write, Scope::Read, Scope::Delete, Scope::Legal];
862            let matrix = [
863                (caller(Some(TENANT), &[Scope::Write]), vec![Scope::Write]),
864                (
865                    caller(Some(TENANT), &[Scope::Read, Scope::Delete]),
866                    vec![Scope::Read, Scope::Delete],
867                ),
868                (caller(Some(TENANT), &[Scope::Legal]), vec![Scope::Legal]),
869                (caller(Some(TENANT), &vcon), vcon.to_vec()),
870                (caller(None, &[Scope::Admin]), vec![Scope::Admin]),
871                // A key of one kind holding a scope of the other, as only a
872                // damaged table could, is refused it.
873                (
874                    caller(Some(TENANT), &[Scope::Admin, Scope::Read]),
875                    vec![Scope::Read],
876                ),
877                (
878                    caller(None, &[Scope::Admin, Scope::Write]),
879                    vec![Scope::Admin],
880                ),
881            ];
882            for (who, allowed) in matrix {
883                for scope in Scope::ALL {
884                    let expected = if allowed.contains(&scope) {
885                        Ok(())
886                    } else {
887                        Err(AuthError::Forbidden(scope))
888                    };
889                    assert_eq!(require(&who, scope), expected, "{who:?} {scope:?}");
890                }
891            }
892        }
893
894        #[test]
895        fn a_request_acts_on_the_tenant_of_its_key() {
896            let tenant_key = caller(Some(TENANT), &[Scope::Write]);
897            assert_eq!(
898                tenant_for(&tenant_key, Scope::Write),
899                Ok(TENANT),
900                "a tenant key acts on its own tenant"
901            );
902            assert_eq!(
903                tenant_for(&tenant_key, Scope::Read),
904                Err(AuthError::Forbidden(Scope::Read)),
905                "only with a scope it holds"
906            );
907        }
908
909        #[test]
910        fn an_operator_key_acts_on_no_tenant() {
911            let operator = caller(None, &[Scope::Admin, Scope::Write]);
912            for scope in Scope::ALL {
913                assert_eq!(
914                    tenant_for(&operator, scope),
915                    Err(AuthError::Forbidden(scope)),
916                    "{scope:?}"
917                );
918            }
919        }
920    }
921}