#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SubscriptionOAuthPins {
pub client_id: &'static str,
pub auth_origin: &'static str,
pub oidc_discovery_url: Option<&'static str>,
pub scopes: &'static str,
pub authorize_path: &'static str,
pub token_path: &'static str,
pub revoke_path: &'static str,
pub jwks_path: &'static str,
pub userinfo_path: &'static str,
pub device_authorization_path: &'static str,
pub device_token_path: Option<&'static str>,
pub device_verification_path: Option<&'static str>,
pub device_redirect_path: Option<&'static str>,
pub browser_callback_path: &'static str,
pub loopback_redirect_uri: &'static str,
pub loopback_authority: &'static str,
pub loopback_ports: &'static [u16],
}
impl SubscriptionOAuthPins {
#[must_use]
pub const fn primary_loopback_port(self) -> u16 {
self.loopback_ports[0]
}
}
#[cfg(test)]
pub(crate) const fn browser_test_pins() -> SubscriptionOAuthPins {
SubscriptionOAuthPins {
client_id: "browser-test-client",
auth_origin: "https://browser.example/",
oidc_discovery_url: None,
scopes: "openid offline_access",
authorize_path: "/oauth/authorize",
token_path: "/oauth/token",
revoke_path: "/oauth/revoke",
jwks_path: "/.well-known/jwks.json",
userinfo_path: "/oauth/userinfo",
device_authorization_path: "/oauth/device/code",
device_token_path: Some("/oauth/device/token"),
device_verification_path: Some("/device"),
device_redirect_path: Some("/device/callback"),
browser_callback_path: "/callback",
loopback_redirect_uri: "http://localhost:1455/callback",
loopback_authority: "localhost:1455",
loopback_ports: &[1455, 1457],
}
}
#[cfg(test)]
pub(crate) const fn oidc_test_pins() -> SubscriptionOAuthPins {
SubscriptionOAuthPins {
client_id: "oidc-test-client",
auth_origin: "https://oidc.example",
oidc_discovery_url: Some("https://oidc.example/.well-known/openid-configuration"),
scopes: "openid profile offline_access",
authorize_path: "/oauth2/authorize",
token_path: "/oauth2/token",
revoke_path: "/oauth2/revoke",
jwks_path: "/.well-known/jwks.json",
userinfo_path: "/oauth2/userinfo",
device_authorization_path: "/oauth2/device/code",
device_token_path: None,
device_verification_path: None,
device_redirect_path: None,
browser_callback_path: "/callback",
loopback_redirect_uri: "http://127.0.0.1:56121/callback",
loopback_authority: "127.0.0.1:56121",
loopback_ports: &[56121],
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct CompatibilityProfilePins {
pub profile_id: &'static str,
pub source_revision: &'static str,
pub source_locator: Option<&'static str>,
pub notice_revision: &'static str,
pub kill_switch_revision: &'static str,
pub reviewed_at_unix: u64,
pub expires_at_unix: u64,
pub killed: bool,
}
impl CompatibilityProfilePins {
#[must_use]
pub const fn is_active(self, now_unix: u64) -> bool {
!self.killed && now_unix < self.expires_at_unix
}
#[must_use]
pub const fn is_acceptance_current(self, accepted_at_unix: u64, now_unix: u64) -> bool {
self.is_acceptance_current_at(accepted_at_unix, now_unix, self.killed)
}
#[must_use]
pub const fn is_acceptance_current_at(
self,
accepted_at_unix: u64,
now_unix: u64,
killed: bool,
) -> bool {
!killed
&& now_unix < self.expires_at_unix
&& accepted_at_unix >= self.reviewed_at_unix
&& accepted_at_unix <= now_unix
}
#[must_use]
pub const fn is_acceptance_current_until(
self,
accepted_at_unix: u64,
now_unix: u64,
expires_at_unix: u64,
) -> bool {
!self.killed
&& now_unix < expires_at_unix
&& accepted_at_unix >= self.reviewed_at_unix
&& accepted_at_unix <= now_unix
}
}
#[cfg(test)]
#[allow(
clippy::expect_used,
clippy::unwrap_used,
reason = "unit tests may assert with expect/unwrap"
)]
mod tests {
use super::CompatibilityProfilePins;
const fn profile_pins() -> CompatibilityProfilePins {
CompatibilityProfilePins {
profile_id: "test.compat.r1",
source_revision: "test-source-r1",
source_locator: None,
notice_revision: "test-notice-r1",
kill_switch_revision: "test-kill-r1",
reviewed_at_unix: 100,
expires_at_unix: 200,
killed: false,
}
}
#[test]
fn profile_acceptance_current_checks_kill_expiry_reviewed_and_future() {
let pins = profile_pins();
let now = pins.reviewed_at_unix + 1;
assert!(pins.is_active(now));
assert!(pins.is_acceptance_current(pins.reviewed_at_unix, now));
assert!(!pins.is_acceptance_current(pins.reviewed_at_unix.saturating_sub(1), now));
assert!(!pins.is_acceptance_current(now + 1, now));
assert!(!pins.is_acceptance_current(pins.reviewed_at_unix, pins.expires_at_unix));
assert!(!pins.is_acceptance_current_at(pins.reviewed_at_unix, now, true));
assert!(!pins.is_acceptance_current_until(
pins.reviewed_at_unix,
now,
now.saturating_sub(1).max(1)
));
assert!(pins.is_acceptance_current_until(pins.reviewed_at_unix, now, pins.expires_at_unix));
}
}