ic-auth 0.35.2

Portable authentication, delegation proof verification and local session machinery
Documentation
#![cfg(feature = "sessions")]
use ic_auth::{
    canonical::CanonicalAuthError,
    session::{
        MemorySessionStore, RequiredScopes, SessionError, SessionLimits, SessionOccupancy,
        SessionStore,
    },
};

#[test]
fn required_scope_admission_preserves_operation_semantics_and_enforces_bounds() {
    assert!(RequiredScopes::NONE.as_slice().is_empty());
    assert_eq!(
        RequiredScopes::new(vec![]).unwrap().as_slice(),
        &[] as &[ic_auth_protocol_types::ScopeLabel]
    );
    let scopes = vec!["write".into(), "read".into(), "write".into()];
    assert_eq!(
        RequiredScopes::new(scopes.clone())
            .unwrap()
            .as_slice()
            .iter()
            .map(|scope| scope.as_str())
            .collect::<Vec<_>>(),
        scopes
    );
    assert!(RequiredScopes::new(vec!["a".repeat(64); 16]).is_ok());
    assert_eq!(
        RequiredScopes::new(vec!["read".into(); 17]),
        Err(SessionError::InvalidPolicyOrRecord)
    );
    assert_eq!(
        RequiredScopes::new(vec![String::new()]),
        Err(SessionError::Canonical(CanonicalAuthError::Scope(
            ic_auth_protocol_types::ScopeLabelError::Empty
        )))
    );
    for scope in ["a::b".into(), "a".repeat(65), "read🚫".into()] {
        assert!(matches!(
            RequiredScopes::new(vec![scope]),
            Err(SessionError::Canonical(CanonicalAuthError::Scope(_)))
        ));
    }
}

#[test]
fn limits_refuse_each_invalid_dimension_before_store_construction() {
    let selected = [100_000, 200_000, 1024, 512 * 1024 * 1024];
    for index in 0..4 {
        let mut values = selected;
        values[index] = 0;
        assert_eq!(
            SessionLimits::new(values[0], values[1], values[2], values[3]),
            Err(SessionError::InvalidPolicyOrRecord)
        );
    }
    for values in [[1; 4], selected, [usize::MAX; 4]] {
        let limits = SessionLimits::new(values[0], values[1], values[2], values[3]).unwrap();
        assert_eq!(
            [
                limits.max_sessions(),
                limits.max_replays(),
                limits.max_replays_per_caller(),
                limits.max_encoded_bytes()
            ],
            values
        );
        let mut store = MemorySessionStore::new(u64::MAX, limits);
        assert_eq!(store.read(|reader| Ok(reader.limits())).unwrap(), limits);
        assert_eq!(store.generation(), u64::MAX);
        assert_eq!(store.encoded_bytes(), 0);
    }
}

#[test]
fn host_selected_capacity_refuses_each_exhausted_dimension_and_preserves_replacement() {
    let limits = SessionLimits::new(100_000, 200_000, 1024, 512 * 1024 * 1024).unwrap();
    let available = SessionOccupancy {
        sessions: 99_999,
        replays: 199_999,
        replays_for_caller: 1023,
    };
    assert_eq!(limits.check_capacity(available, false), Ok(()));
    let sessions_full = SessionOccupancy {
        sessions: 100_000,
        ..available
    };
    assert_eq!(
        limits.check_capacity(sessions_full, false),
        Err(SessionError::Capacity)
    );
    assert_eq!(limits.check_capacity(sessions_full, true), Ok(()));
    for full in [
        SessionOccupancy {
            replays: 200_000,
            ..available
        },
        SessionOccupancy {
            replays_for_caller: 1024,
            ..available
        },
    ] {
        for replacing in [false, true] {
            assert_eq!(
                limits.check_capacity(full, replacing),
                Err(SessionError::Capacity)
            );
        }
    }
    let maximum = SessionLimits::new(usize::MAX, usize::MAX, usize::MAX, usize::MAX).unwrap();
    assert_eq!(
        maximum.check_capacity(
            SessionOccupancy {
                sessions: usize::MAX - 1,
                replays: usize::MAX - 1,
                replays_for_caller: usize::MAX - 1
            },
            false
        ),
        Ok(())
    );
    assert_eq!(
        maximum.check_capacity(
            SessionOccupancy {
                sessions: usize::MAX,
                replays: usize::MAX,
                replays_for_caller: usize::MAX
            },
            true
        ),
        Err(SessionError::Capacity)
    );
}