Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/gunnar-ui

crates/gunnar-ui-server/src/passkeys_at_gunnar/fixtures/mod.rs

//! **The fake gunnar's IdentityService**: edda's real `IdentityGrpc` over
//! the fake's host, every call heard as `Identity.<Method>`, and the faults a
//! real gunnar answers injected where a test asks for them. A test fixture:
//! the adapter's method names are the identity core's own.

use super::*;


/// The fake's IdentityService: edda's `IdentityGrpc` over [`FakeHost`],
/// every call heard as `Identity.<Method>`, and the faults a real gunnar
/// answers injected where the tests ask for them.
#[derive(Clone)]
pub(crate) struct Heard {
    pub(super) fake: Fake,
    pub(super) inner: nornir_identity::grpc::IdentityGrpc<FakeHost>,
}

type Reply<T> = Result<Response<T>, Status>;

#[tonic::async_trait]
impl ipb::identity_service_server::IdentityService for Heard {
    async fn sign_up(&self, r: Request<ipb::SignUpRequest>) -> Reply<ipb::SignUpReply> {
        self.fake.said("Identity.SignUp".to_owned());
        self.inner.sign_up(r).await
    }
    async fn create_invite(&self, r: Request<ipb::CreateInviteRequest>) -> Reply<ipb::CreateInviteReply> {
        let by = self.fake.core().session(&r.get_ref().session).map(|v| v.user_id).unwrap_or_default();
        self.fake.said(format!("Identity.CreateInvite by {by}"));
        if self.fake.stale_at_core.load(Ordering::SeqCst) {
            return Err(nornir_identity::grpc::status(nornir_identity::Refused::new(
                nornir_identity::Code::NeedsStepUp,
                "this needs a fresh touch of your own passkey",
            )));
        }
        let person = r.get_ref().person.clone();
        let answer = self.inner.create_invite(r).await?;
        lock(&self.fake.invite_requests).push(person);
        Ok(answer)
    }
    async fn list_invites(&self, r: Request<ipb::ListInvitesRequest>) -> Reply<ipb::ListInvitesReply> {
        let by = self.fake.core().session(&r.get_ref().session).map(|v| v.user_id).unwrap_or_default();
        self.fake.said(format!("Identity.ListInvites by {by}"));
        self.inner.list_invites(r).await
    }
    async fn revoke_invite(&self, r: Request<ipb::RevokeInviteRequest>) -> Reply<ipb::RevokeInviteReply> {
        let by = self.fake.core().session(&r.get_ref().session).map(|v| v.user_id).unwrap_or_default();
        self.fake.said(format!("Identity.RevokeInvite by {by}"));
        self.inner.revoke_invite(r).await
    }
    async fn redeem_start(&self, r: Request<ipb::RedeemStartRequest>) -> Reply<ipb::CeremonyReply> {
        self.fake.said("Identity.RedeemStart".to_owned());
        if let Some(fault) = self.fake.fault() {
            return Err(fault);
        }
        self.fake.delay().await;
        lock(&self.fake.starts).push(r.get_ref().client.clone());
        self.fake.issued.fetch_add(1, Ordering::SeqCst);
        self.inner.redeem_start(r).await
    }
    async fn redeem_finish(&self, r: Request<ipb::RedeemFinishRequest>) -> Reply<ipb::SessionReply> {
        self.fake.said("Identity.RedeemFinish".to_owned());
        let delay = *lock(&self.fake.register_delay);
        if let Some(d) = delay {
            tokio::time::sleep(d).await;
        }
        attestation_carries(&r.get_ref().attestation_object, &r.get_ref().public_key)?;
        let asked = r.get_ref().clone();
        let answer = self.inner.redeem_finish(r).await?;
        lock(&self.fake.registrations).push(Registered {
            principal_id: answer.get_ref().session.as_ref().map(|s| s.user_id.clone()).unwrap_or_default(),
            credential_id: asked.credential_id,
            public_key: asked.public_key,
            attestation_object: asked.attestation_object,
            rp_id: asked.rp_id,
            origin: asked.origin,
        });
        Ok(answer)
    }
    async fn login_start(&self, r: Request<ipb::LoginStartRequest>) -> Reply<ipb::CeremonyReply> {
        self.fake.said("Identity.LoginStart".to_owned());
        if let Some(fault) = self.fake.fault() {
            return Err(fault);
        }
        self.fake.delay().await;
        lock(&self.fake.starts).push(r.get_ref().client.clone());
        self.fake.issued.fetch_add(1, Ordering::SeqCst);
        self.inner.login_start(r).await
    }
    async fn login_finish(&self, r: Request<ipb::LoginFinishRequest>) -> Reply<ipb::SessionReply> {
        self.fake.said("Identity.LoginFinish".to_owned());
        lock(&self.fake.logins).push(r.get_ref().clone());
        if self.fake.refuse_logins.load(Ordering::SeqCst) {
            return Err(nornir_identity::grpc::status(nornir_identity::Refused::new(nornir_identity::Code::Ceremony, "the signature does not verify")));
        }
        if let Some((code, message)) = lock(&self.fake.login_status).clone() {
            return Err(Status::new(code, message));
        }
        if self.fake.growing.load(Ordering::SeqCst) {
            return Err(nornir_identity::grpc::status(nornir_identity::Refused::new(
                nornir_identity::Code::Store,
                "the control store is off the stripe while the data set grows (the transaction log is released for the remount); writes are refused until /data is mounted again — retry in a few minutes",
            )));
        }
        self.inner.login_finish(r).await
    }
    async fn whoami(&self, r: Request<ipb::WhoamiRequest>) -> Reply<ipb::SessionInfo> {
        if let Some(fault) = self.fake.fault() {
            return Err(fault);
        }
        self.inner.whoami(r).await
    }
    async fn step_up_start(&self, r: Request<ipb::StepUpStartRequest>) -> Reply<ipb::ChallengeReply> {
        self.fake.said("Identity.StepUpStart".to_owned());
        self.inner.step_up_start(r).await
    }
    async fn step_up_finish(&self, r: Request<ipb::StepUpFinishRequest>) -> Reply<ipb::SessionInfo> {
        self.fake.said("Identity.StepUpFinish".to_owned());
        let answer = self.inner.step_up_finish(r).await?;
        self.fake.stale_at_core.store(false, Ordering::SeqCst);
        Ok(answer)
    }
    async fn logout(&self, r: Request<ipb::LogoutRequest>) -> Reply<ipb::LogoutReply> {
        let by = self.fake.core().session(&r.get_ref().session).map(|v| v.user_id).unwrap_or_default();
        self.fake.said(format!("Identity.Logout by {by} everywhere={}", r.get_ref().everywhere));
        self.inner.logout(r).await
    }
    async fn deactivate(&self, r: Request<ipb::DeactivateRequest>) -> Reply<ipb::DeactivateReply> {
        self.inner.deactivate(r).await
    }
    async fn end_all_sessions(&self, r: Request<ipb::EndAllSessionsRequest>) -> Reply<ipb::EndAllSessionsReply> {
        self.fake.said("Identity.EndAllSessions".to_owned());
        self.inner.end_all_sessions(r).await
    }
}