diff --git a/pallets/pass/src/mock.rs b/pallets/pass/src/mock.rs index 851d663..a1c666d 100644 --- a/pallets/pass/src/mock.rs +++ b/pallets/pass/src/mock.rs @@ -1,11 +1,12 @@ //! Test environment for pallet pass. use crate::{self as pallet_pass, Config}; +pub use authenticators::*; use codec::{Decode, Encode, MaxEncodedLen}; -use fc_traits_authn::Challenger; +use fc_traits_authn::{composite_authenticators, util::AuthorityFromPalletId, Challenger}; use frame_support::{ derive_impl, parameter_types, - traits::{ConstU32, ConstU64, EqualPrivilegeOnly, OnInitialize, Randomness}, + traits::{ConstU32, ConstU64, EqualPrivilegeOnly, OnInitialize}, weights::Weight, DebugNoBound, EqNoBound, PalletId, }; @@ -14,11 +15,12 @@ use scale_info::TypeInfo; use sp_core::{blake2_256, H256}; use sp_io::TestExternalities; use sp_runtime::{ - str_array as s, traits::{IdentifyAccount, IdentityLookup, Verify}, MultiSignature, }; +mod authenticators; + pub type Block = frame_system::mocking::MockBlock; pub type AccountPublic = ::Signer; @@ -78,144 +80,21 @@ impl pallet_scheduler::Config for Test { type WeightInfo = (); } -pub struct RandomnessFromBlockNumber; -impl frame_support::traits::Randomness for RandomnessFromBlockNumber { - fn random(subject: &[u8]) -> (H256, u64) { - let block_number = System::block_number(); - let block_number_as_bytes = block_number.to_le_bytes(); - ( - H256(blake2_256( - &vec![block_number_as_bytes.to_vec(), subject.to_vec()].concat()[..], - )), - block_number, - ) - } -} - -#[derive(TypeInfo, MaxEncodedLen, DebugNoBound, EqNoBound, PartialEq, Clone, Encode, Decode)] -pub struct MockDeviceAttestation { - pub(crate) context: (), - pub(crate) user_id: fc_traits_authn::HashedUserId, - pub(crate) challenge: fc_traits_authn::Challenge, - pub(crate) device_id: fc_traits_authn::DeviceId, -} - -impl fc_traits_authn::ChallengeResponse<()> for MockDeviceAttestation { - fn is_valid(&self) -> bool { - self.challenge == MockChallenger::generate(&self.context) - } - - fn used_challenge(&self) -> ((), fc_traits_authn::Challenge) { - ((), MockChallenger::generate(&self.context)) - } - - fn authority(&self) -> fc_traits_authn::AuthorityId { - s("DummyAuthenticator") - } -} - -impl fc_traits_authn::UserAuthenticator for MockDeviceAttestation { - const AUTHORITY: fc_traits_authn::AuthorityId = s("MockDevice"); - type Challenger = MockChallenger; - type Credential = Self; - - fn device_id(&self) -> fc_traits_authn::DeviceId { - todo!() - } -} - -impl fc_traits_authn::DeviceChallengeResponse<()> for MockDeviceAttestation { - fn device_id(&self) -> fc_traits_authn::DeviceId { - self.device_id - } -} - -impl fc_traits_authn::UserChallengeResponse<()> for MockDeviceAttestation { - fn user_id(&self) -> fc_traits_authn::HashedUserId { - self.user_id - } -} - -pub struct MockChallenger; -impl fc_traits_authn::Challenger for MockChallenger { - type Context = (); - - fn generate(_: &Self::Context) -> fc_traits_authn::Challenge { - let (hash, _) = RandomnessFromBlockNumber::random_seed(); - hash.0 - } -} - -pub struct InvalidAuthenticator; -impl fc_traits_authn::Authenticator for InvalidAuthenticator { - const AUTHORITY: fc_traits_authn::AuthorityId = s("InvalidAuthenticator"); - type Challenger = MockChallenger; - type DeviceAttestation = MockDeviceAttestation; - type Device = MockDevice; - - fn verify_device(&self, _: &Self::DeviceAttestation) -> Option { - None - } - - fn unpack_device(&self, _: &Self::DeviceAttestation) -> Self::Device { - () - } -} - -pub struct DummyAuthenticator; -impl fc_traits_authn::Authenticator for DummyAuthenticator { - const AUTHORITY: fc_traits_authn::AuthorityId = s("DummyAuthenticator"); - type Challenger = MockChallenger; - type DeviceAttestation = MockDeviceAttestation; - type Device = MockDeviceAttestation; - - fn unpack_device(&self, verification: &Self::DeviceAttestation) -> Self::Device { - todo!() - } - // fn get_device_id(&self, device: Vec) -> Option { - // let len = device.len(); - // Some([(len as u8) + 1; 32]) - // } - - // fn authenticate( - // &self, - // _device: Vec, - // challenge: &[u8], - // payload: &[u8], - // ) -> Result<(), fc_traits_authn::AuthenticateError> { - // ensure!( - // challenge == payload, - // fc_traits_authn::AuthenticateError::ChallengeFailed - // ); - // Ok(()) - // } -} - -#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, Debug, Clone, Eq, PartialEq)] -pub enum MockAuthenticationMethods { - InvalidAuthenticationMethod, - DummyAuthenticationMethod, -} - -impl Into> for MockAuthenticationMethods { - fn into(self) -> Box { - match self { - MockAuthenticationMethods::InvalidAuthenticationMethod => { - Box::new(InvalidAuthenticator) - } - MockAuthenticationMethods::DummyAuthenticationMethod => Box::new(DummyAuthenticator), - } - } -} - parameter_types! { pub PassPalletId: PalletId = PalletId(*b"py/pass_"); } +composite_authenticators! { + pub Pass = { + AuthenticatorA, + AuthenticatorB, + }; +} + impl Config for Test { type WeightInfo = (); type RuntimeEvent = RuntimeEvent; - type Authenticator = MockAuthenticationMethods; + type Authenticator = PassAuthenticator>; type RegisterOrigin = EnsureSigned; type RuntimeCall = RuntimeCall; type PalletId = PassPalletId; diff --git a/pallets/pass/src/mock/authenticators.rs b/pallets/pass/src/mock/authenticators.rs new file mode 100644 index 0000000..17c4fdb --- /dev/null +++ b/pallets/pass/src/mock/authenticators.rs @@ -0,0 +1,210 @@ +use super::*; +use fc_traits_authn::{util::AuthorityFromPalletId, *}; +use frame_support::traits::Randomness; +use sp_core::Get; + +pub use authenticator_a::AuthenticatorA; +pub use authenticator_b::AuthenticatorB; + +pub struct RandomnessFromBlockNumber; +impl Randomness for RandomnessFromBlockNumber { + fn random(subject: &[u8]) -> (H256, u64) { + let block_number = System::block_number(); + let block_number_as_bytes = block_number.to_le_bytes(); + ( + H256(blake2_256( + &vec![block_number_as_bytes.to_vec(), subject.to_vec()].concat()[..], + )), + block_number, + ) + } +} + +pub(self) type PassAuthorityId = AuthorityFromPalletId; + +pub mod authenticator_a { + use super::*; + + pub struct AuthenticatorA; + + #[derive(TypeInfo, DebugNoBound, EqNoBound, PartialEq, Clone, Encode, Decode)] + pub struct DeviceAttestation { + pub(crate) device_id: DeviceId, + pub(crate) challenge: Challenge, + } + + #[derive(TypeInfo, Encode, Decode, MaxEncodedLen)] + pub struct Device { + pub(crate) device_id: DeviceId, + } + + #[derive( + TypeInfo, DebugNoBound, EqNoBound, PartialEq, Clone, Encode, Decode, MaxEncodedLen, + )] + pub struct Credential { + pub(crate) user_id: HashedUserId, + pub(crate) challenge: Challenge, + } + + impl Authenticator for AuthenticatorA { + type Authority = PassAuthorityId; + type Challenger = Self; + type DeviceAttestation = DeviceAttestation; + type Device = Device; + + fn unpack_device(attestation: Self::DeviceAttestation) -> Self::Device { + Device { + device_id: attestation.device_id, + } + } + } + + impl Challenger for AuthenticatorA { + type Context = (); + + fn generate(_: &Self::Context) -> Challenge { + let (hash, _) = RandomnessFromBlockNumber::random_seed(); + hash.0 + } + } + + impl UserAuthenticator for Device { + type Authority = PassAuthorityId; + type Challenger = AuthenticatorA; + type Credential = Credential; + + fn device_id(&self) -> &DeviceId { + &self.device_id + } + } + + impl DeviceChallengeResponse<()> for DeviceAttestation { + fn is_valid(&self) -> bool { + true + } + + fn used_challenge(&self) -> ((), Challenge) { + ((), self.challenge) + } + + fn authority(&self) -> AuthorityId { + PassAuthorityId::get() + } + + fn device_id(&self) -> &DeviceId { + &self.device_id + } + } + + impl UserChallengeResponse<()> for Credential { + fn is_valid(&self) -> bool { + true + } + + fn used_challenge(&self) -> ((), Challenge) { + ((), self.challenge) + } + + fn authority(&self) -> AuthorityId { + PassAuthorityId::get() + } + + fn user_id(&self) -> HashedUserId { + self.user_id + } + } +} + +pub mod authenticator_b { + use super::*; + + pub struct AuthenticatorB; + + #[derive(TypeInfo, DebugNoBound, EqNoBound, PartialEq, Clone, Encode, Decode)] + pub struct DeviceAttestation { + pub(crate) device_id: DeviceId, + pub(crate) challenge: Challenge, + } + + #[derive(TypeInfo, Encode, Decode, MaxEncodedLen)] + pub struct Device { + pub(crate) device_id: DeviceId, + } + + #[derive( + TypeInfo, DebugNoBound, EqNoBound, PartialEq, Clone, Encode, Decode, MaxEncodedLen, + )] + pub struct Credential { + pub(crate) device_id: DeviceId, + pub(crate) user_id: HashedUserId, + pub(crate) challenge: Challenge, + } + + impl Authenticator for AuthenticatorB { + type Authority = PassAuthorityId; + type Challenger = Self; + type DeviceAttestation = DeviceAttestation; + type Device = Device; + + fn unpack_device(attestation: Self::DeviceAttestation) -> Self::Device { + Device { + device_id: attestation.device_id, + } + } + } + + impl Challenger for AuthenticatorB { + type Context = DeviceId; + + fn generate(context: &Self::Context) -> Challenge { + let (hash, _) = RandomnessFromBlockNumber::random(context); + hash.0 + } + } + + impl UserAuthenticator for Device { + type Authority = PassAuthorityId; + type Challenger = AuthenticatorB; + type Credential = Credential; + + fn device_id(&self) -> &DeviceId { + &self.device_id + } + } + + impl DeviceChallengeResponse for DeviceAttestation { + fn is_valid(&self) -> bool { + true + } + + fn used_challenge(&self) -> (DeviceId, Challenge) { + (self.device_id, self.challenge) + } + + fn authority(&self) -> AuthorityId { + PassAuthorityId::get() + } + + fn device_id(&self) -> &DeviceId { + &self.device_id + } + } + + impl UserChallengeResponse for Credential { + fn is_valid(&self) -> bool { + true + } + + fn used_challenge(&self) -> (DeviceId, Challenge) { + (self.device_id, self.challenge) + } + + fn authority(&self) -> AuthorityId { + PassAuthorityId::get() + } + + fn user_id(&self) -> HashedUserId { + self.user_id + } + } +} diff --git a/pallets/pass/src/tests.rs b/pallets/pass/src/tests.rs index c651f59..443738e 100644 --- a/pallets/pass/src/tests.rs +++ b/pallets/pass/src/tests.rs @@ -1,80 +1,64 @@ //! Tests for pass pallet. -use super::{Account, AccountStatus, Accounts, Error, Event}; +use super::{Error, Event}; use crate::mock::*; -use codec::Encode; -use frame_support::{assert_noop, assert_ok, parameter_types, traits::Randomness, BoundedVec}; -use sp_core::ConstU32; + +use fc_traits_authn::{Challenger, HashedUserId}; +use frame_support::{assert_noop, assert_ok, parameter_types}; +use sp_core::Hasher; const SIGNER: AccountId = AccountId::new([0u8; 32]); const OTHER: AccountId = AccountId::new([1u8; 32]); -const THE_DEVICE: fc_traits_authn::DeviceId = [1u8; 32]; +const THE_DEVICE: fc_traits_authn::DeviceId = [0u8; 32]; +const OTHER_DEVICE: fc_traits_authn::DeviceId = [1u8; 32]; parameter_types! { - pub AccountName: BoundedVec> = - BoundedVec::truncate_from((*b"@account:example.org").to_vec()); + pub AccountNameA: HashedUserId = ::Hashing::hash( + &*b"@account.a:example.org" + ).0; + pub AccountNameB: HashedUserId = ::Hashing::hash( + &*b"@account.b:example.org" + ).0; } mod register { + use super::*; #[test] - fn fails_if_already_registered() { + fn fail_if_already_registered() { new_test_ext().execute_with(|| { - Accounts::::insert( - AccountName::get(), - Account::new(AccountId::new([0u8; 32]), crate::AccountStatus::Active), - ); - - assert_noop!( - Pass::register( - RuntimeOrigin::signed(SIGNER), - AccountName::get(), - MockAuthenticationMethods::DummyAuthenticationMethod, - BoundedVec::new(), - (*b"challeng").to_vec() - ), - Error::::AlreadyRegistered - ); - }); - } + let account_id = + Pass::account_id_for(AccountNameA::get()).expect("account exists; qed"); + assert_ok!(Pass::create_account(&account_id)); - #[test] - fn fails_if_cannot_resolve_device() { - new_test_ext().execute_with(|| { assert_noop!( Pass::register( RuntimeOrigin::signed(SIGNER), - AccountName::get(), - MockAuthenticationMethods::InvalidAuthenticationMethod, - BoundedVec::new(), - (*b"challeng").to_vec() + AccountNameA::get(), + PassDeviceAttestation::AuthenticatorA(authenticator_a::DeviceAttestation { + device_id: THE_DEVICE, + challenge: AuthenticatorA::generate(&()), + }), ), - Error::::InvalidDeviceForAuthenticationMethod + Error::::AccountAlreadyRegistered ); }); } #[test] - fn fails_if_cannot_fulfill_challenge() { + fn fail_if_attestation_is_invalid() { new_test_ext().execute_with(|| { - let challenge_response = - RandomnessFromBlockNumber::random(&Encode::encode(&PassPalletId::get())) - .0 - .as_bytes() - .to_vec(); - - System::set_block_number(2); - assert_noop!( Pass::register( RuntimeOrigin::signed(SIGNER), - AccountName::get(), - MockAuthenticationMethods::DummyAuthenticationMethod, - BoundedVec::new(), - challenge_response, + AccountNameB::get(), + PassDeviceAttestation::AuthenticatorB(authenticator_b::DeviceAttestation { + device_id: THE_DEVICE, + challenge: AuthenticatorB::generate(&OTHER_DEVICE), + }), ), - Error::::ChallengeFailed + Error::::DeviceAttestationInvalid ); }); } @@ -82,180 +66,131 @@ mod register { #[test] fn it_works() { new_test_ext().execute_with(|| { - let account_id = Pass::account_id_for(&AccountName::get()); + let account_id = + Pass::account_id_for(AccountNameA::get()).expect("account exists; qed"); assert_ok!(Pass::register( RuntimeOrigin::signed(SIGNER), - AccountName::get(), - MockAuthenticationMethods::DummyAuthenticationMethod, - BoundedVec::new(), - RandomnessFromBlockNumber::random(&Encode::encode(&PassPalletId::get())) - .0 - .as_bytes() - .to_vec() + AccountNameA::get(), + PassDeviceAttestation::AuthenticatorA(authenticator_a::DeviceAttestation { + device_id: THE_DEVICE, + challenge: AuthenticatorA::generate(&()), + }), )); System::assert_has_event( Event::::Registered { - account_name: AccountName::get(), - account_id, + who: account_id.clone(), } .into(), ); System::assert_has_event( Event::::AddedDevice { - account_name: AccountName::get(), - device_id: [1u8; 32], + who: account_id, + device_id: THE_DEVICE, } .into(), ); }); } - - #[test] - fn unreserving_if_uninitialized_works() { - // Test for uninitialized account that unreserves if not activated after timeout - new_test_ext().execute_with(|| { - assert_ok!(Pass::register( - RuntimeOrigin::signed(SIGNER), - AccountName::get(), - MockAuthenticationMethods::DummyAuthenticationMethod, - BoundedVec::new(), - RandomnessFromBlockNumber::random(&Encode::encode(&PassPalletId::get())) - .0 - .as_bytes() - .to_vec() - )); - - assert_eq!( - Accounts::::get(AccountName::get()), - Some(Account::new( - Pass::account_id_for(&AccountName::get()), - AccountStatus::Uninitialized - )) - ); - - run_to(12); - assert_eq!(Accounts::::get(AccountName::get()), None); - }); - - // Test for uninitialized account that is initialized before timeout - new_test_ext().execute_with(|| { - assert_ok!(Pass::register( - RuntimeOrigin::signed(SIGNER), - AccountName::get(), - MockAuthenticationMethods::DummyAuthenticationMethod, - BoundedVec::new(), - RandomnessFromBlockNumber::random(&Encode::encode(&PassPalletId::get())) - .0 - .as_bytes() - .to_vec() - )); - - let account_id = Pass::account_id_for(&AccountName::get()); - - assert_eq!( - Accounts::::get(AccountName::get()), - Some(Account::new( - account_id.clone(), - AccountStatus::Uninitialized - )) - ); - - run_to(11); - System::inc_providers(&account_id); - - run_to(12); - assert_eq!( - Accounts::::get(AccountName::get()), - Some(Account::new(account_id.clone(), AccountStatus::Active)) - ); - }); - } -} - -const DURATION: u64 = 10; -parameter_types! { - pub ChallengeResponse: Vec = - RandomnessFromBlockNumber::random(&Encode::encode(&PassPalletId::get())) - .0 - .as_bytes() - .to_vec(); } -fn prepare() -> sp_io::TestExternalities { +fn prepare(user_id: HashedUserId) -> sp_io::TestExternalities { let mut t = new_test_ext(); t.execute_with(|| { assert_ok!(Pass::register( RuntimeOrigin::signed(SIGNER), - AccountName::get(), - MockAuthenticationMethods::DummyAuthenticationMethod, - BoundedVec::new(), - ChallengeResponse::get(), + user_id, + PassDeviceAttestation::AuthenticatorA(authenticator_a::DeviceAttestation { + device_id: THE_DEVICE, + challenge: AuthenticatorA::generate(&()), + }), )); }); t } +const DURATION: u64 = 10; + mod authenticate { use super::*; - fn prepare() -> sp_io::TestExternalities { - let mut t = super::prepare(); - t.execute_with(|| { - // Simulate device initialization - System::inc_providers(&Pass::account_id_for(&AccountName::get())); + #[test] + fn fail_if_cannot_find_account() { + prepare(AccountNameA::get()).execute_with(|| { + assert_noop!( + Pass::authenticate( + RuntimeOrigin::signed(OTHER), + THE_DEVICE, + PassCredential::AuthenticatorA(authenticator_a::Credential { + user_id: AccountNameB::get(), + challenge: AuthenticatorA::generate(&()), + }), + Some(DURATION), + ), + Error::::AccountNotFound + ); }); - t } #[test] - fn fails_if_account_is_uninitialized() { - super::prepare().execute_with(|| { + fn fail_if_cannot_find_device() { + prepare(AccountNameA::get()).execute_with(|| { assert_noop!( Pass::authenticate( RuntimeOrigin::signed(OTHER), - AccountName::get(), - MockAuthenticationMethods::DummyAuthenticationMethod, - THE_DEVICE, - ChallengeResponse::get(), + OTHER_DEVICE, + PassCredential::AuthenticatorA(authenticator_a::Credential { + user_id: AccountNameA::get(), + challenge: AuthenticatorA::generate(&()), + }), Some(DURATION), ), - Error::::Uninitialized + Error::::DeviceNotFound ); }); } #[test] - fn fails_if_cannot_resolve_device() { - prepare().execute_with(|| { - let device = [2u8; 32]; + fn fail_if_attestation_is_invalid() { + new_test_ext().execute_with(|| { + assert_ok!(Pass::register( + RuntimeOrigin::signed(SIGNER), + AccountNameA::get(), + PassDeviceAttestation::AuthenticatorB(authenticator_b::DeviceAttestation { + device_id: THE_DEVICE, + challenge: AuthenticatorB::generate(&THE_DEVICE), + }), + )); assert_noop!( Pass::authenticate( RuntimeOrigin::signed(OTHER), - AccountName::get(), - MockAuthenticationMethods::DummyAuthenticationMethod, - device, - ChallengeResponse::get(), + THE_DEVICE, + PassCredential::AuthenticatorB(authenticator_b::Credential { + user_id: AccountNameA::get(), + device_id: THE_DEVICE, + challenge: AuthenticatorB::generate(&OTHER_DEVICE), + }), Some(DURATION), ), - Error::::DeviceNotFound + Error::::CredentialInvalid ); }); } #[test] fn it_works() { - prepare().execute_with(|| { + prepare(AccountNameA::get()).execute_with(|| { let block_number = System::block_number(); assert_ok!(Pass::authenticate( RuntimeOrigin::signed(OTHER), - AccountName::get(), - MockAuthenticationMethods::DummyAuthenticationMethod, THE_DEVICE, - ChallengeResponse::get(), + PassCredential::AuthenticatorA(authenticator_a::Credential { + user_id: AccountNameA::get(), + challenge: AuthenticatorA::generate(&()), + }), Some(DURATION), )); @@ -272,26 +207,33 @@ mod authenticate { mod add_device { use super::*; - const NEW_DEVICE_ID: fc_traits_authn::DeviceId = [2u8; 32]; fn prepare() -> sp_io::TestExternalities { - let mut t = super::prepare(); + let mut t = super::prepare(AccountNameA::get()); t.execute_with(|| { - // Simulate device initialization - System::inc_providers(&Pass::account_id_for(&AccountName::get())); + assert_ok!(Pass::authenticate( + RuntimeOrigin::signed(SIGNER), + THE_DEVICE, + PassCredential::AuthenticatorA(authenticator_a::Credential { + user_id: AccountNameA::get(), + challenge: AuthenticatorA::generate(&()), + }), + Some(DURATION), + )); }); t } #[test] - fn fails_if_not_signed_by_session_key() { + fn fail_if_not_signed_by_session_key() { prepare().execute_with(|| { assert_noop!( Pass::add_device( RuntimeOrigin::signed(OTHER), - MockAuthenticationMethods::DummyAuthenticationMethod, - BoundedVec::truncate_from(vec![1u8]), - ChallengeResponse::get() + PassDeviceAttestation::AuthenticatorA(authenticator_a::DeviceAttestation { + device_id: OTHER_DEVICE, + challenge: AuthenticatorA::generate(&()), + }), ), Error::::SessionNotFound ); @@ -301,26 +243,20 @@ mod add_device { #[test] fn it_works() { prepare().execute_with(|| { - assert_ok!(Pass::authenticate( - RuntimeOrigin::signed(OTHER), - AccountName::get(), - MockAuthenticationMethods::DummyAuthenticationMethod, - THE_DEVICE, - ChallengeResponse::get(), - Some(DURATION), - )); + let who = Pass::account_id_for(AccountNameA::get()).expect("account exists; qed"); assert_ok!(Pass::add_device( - RuntimeOrigin::signed(OTHER), - MockAuthenticationMethods::DummyAuthenticationMethod, - BoundedVec::truncate_from(vec![1u8]), - ChallengeResponse::get() - )); + RuntimeOrigin::signed(SIGNER), + PassDeviceAttestation::AuthenticatorA(authenticator_a::DeviceAttestation { + device_id: OTHER_DEVICE, + challenge: AuthenticatorA::generate(&()), + }), + ),); System::assert_has_event( Event::::AddedDevice { - account_name: AccountName::get(), - device_id: NEW_DEVICE_ID, + who, + device_id: OTHER_DEVICE, } .into(), ); @@ -330,60 +266,55 @@ mod add_device { mod dispatch { use super::*; - use sp_runtime::traits::Hash; fn prepare() -> sp_io::TestExternalities { - let mut t = super::prepare(); + let mut t = super::prepare(AccountNameA::get()); t.execute_with(|| { - // Emulate provisioning of account - - System::inc_providers(&Pass::account_id_for(&AccountName::get())); + assert_ok!(Pass::authenticate( + RuntimeOrigin::signed(SIGNER), + THE_DEVICE, + PassCredential::AuthenticatorA(authenticator_a::Credential { + user_id: AccountNameA::get(), + challenge: AuthenticatorA::generate(&()), + }), + Some(DURATION), + )); }); t } #[test] - fn fails_if_account_is_uninitialized() { - super::prepare().execute_with(|| { + fn fail_without_credentials_if_not_signed_by_session_key() { + prepare().execute_with(|| { assert_noop!( Pass::dispatch( RuntimeOrigin::signed(OTHER), Box::new(RuntimeCall::System(frame_system::Call::remark_with_event { remark: b"Hello, world".to_vec() })), - Some(( - AccountName::get(), - MockAuthenticationMethods::DummyAuthenticationMethod, - THE_DEVICE, - ChallengeResponse::get(), - )), + None, None ), - Error::::Uninitialized + Error::::SessionNotFound ); }); } #[test] - fn dispatching_with_explicit_authentication_works() { + fn without_credentials_it_works() { prepare().execute_with(|| { assert_ok!(Pass::dispatch( - RuntimeOrigin::signed(OTHER), + RuntimeOrigin::signed(SIGNER), Box::new(RuntimeCall::System(frame_system::Call::remark_with_event { remark: b"Hello, world".to_vec() })), - Some(( - AccountName::get(), - MockAuthenticationMethods::DummyAuthenticationMethod, - THE_DEVICE, - ChallengeResponse::get(), - )), + None, None )); System::assert_has_event( frame_system::Event::Remarked { - sender: Pass::account_id_for(&AccountName::get()), + sender: Pass::account_id_for(AccountNameA::get()).expect("account exists; qed"), hash: ::Hashing::hash(&*b"Hello, world"), } .into(), @@ -392,29 +323,72 @@ mod dispatch { } #[test] - fn dispatching_signed_with_a_session_key_works() { + fn fail_with_credentials_if_account_not_found() { prepare().execute_with(|| { - assert_ok!(Pass::authenticate( - RuntimeOrigin::signed(OTHER), - AccountName::get(), - MockAuthenticationMethods::DummyAuthenticationMethod, - THE_DEVICE, - ChallengeResponse::get(), - None, - )); + assert_noop!( + Pass::dispatch( + RuntimeOrigin::signed(OTHER), + Box::new(RuntimeCall::System(frame_system::Call::remark_with_event { + remark: b"Hello, world".to_vec() + })), + Some(( + OTHER_DEVICE, + PassCredential::AuthenticatorA(authenticator_a::Credential { + user_id: AccountNameB::get(), + challenge: AuthenticatorA::generate(&()) + }) + )), + None + ), + Error::::AccountNotFound + ); + }); + } + + #[test] + fn fail_with_credentials_if_device_not_found() { + prepare().execute_with(|| { + assert_noop!( + Pass::dispatch( + RuntimeOrigin::signed(OTHER), + Box::new(RuntimeCall::System(frame_system::Call::remark_with_event { + remark: b"Hello, world".to_vec() + })), + Some(( + OTHER_DEVICE, + PassCredential::AuthenticatorA(authenticator_a::Credential { + user_id: AccountNameA::get(), + challenge: AuthenticatorA::generate(&()) + }) + )), + None + ), + Error::::DeviceNotFound + ); + }); + } + #[test] + fn with_credentials_it_works() { + prepare().execute_with(|| { assert_ok!(Pass::dispatch( RuntimeOrigin::signed(OTHER), Box::new(RuntimeCall::System(frame_system::Call::remark_with_event { remark: b"Hello, world".to_vec() })), - None, + Some(( + THE_DEVICE, + PassCredential::AuthenticatorA(authenticator_a::Credential { + user_id: AccountNameA::get(), + challenge: AuthenticatorA::generate(&()) + }) + )), None )); System::assert_has_event( frame_system::Event::Remarked { - sender: Pass::account_id_for(&AccountName::get()), + sender: Pass::account_id_for(AccountNameA::get()).expect("account exists; qed"), hash: ::Hashing::hash(&*b"Hello, world"), } .into(), @@ -423,36 +397,38 @@ mod dispatch { } #[test] - fn dispatching_creates_new_session_key() { + fn with_new_session_key_it_creates_a_session() { prepare().execute_with(|| { - assert_ok!(Pass::authenticate( - RuntimeOrigin::signed(OTHER), - AccountName::get(), - MockAuthenticationMethods::DummyAuthenticationMethod, - THE_DEVICE, - ChallengeResponse::get(), - None, - )); + let block_number = System::block_number(); assert_ok!(Pass::dispatch( RuntimeOrigin::signed(OTHER), Box::new(RuntimeCall::System(frame_system::Call::remark_with_event { remark: b"Hello, world".to_vec() })), - None, - Some(SIGNER) + Some(( + THE_DEVICE, + PassCredential::AuthenticatorA(authenticator_a::Credential { + user_id: AccountNameA::get(), + challenge: AuthenticatorA::generate(&()) + }) + )), + Some(OTHER) )); System::assert_has_event( - Event::::SessionCreated { - session_key: SIGNER, - until: 11, + Event::SessionCreated { + session_key: OTHER, + until: block_number + DURATION, } .into(), ); + }); + } - next_block(); - + #[test] + fn session_duration_is_met() { + prepare().execute_with(|| { assert_ok!(Pass::dispatch( RuntimeOrigin::signed(SIGNER), Box::new(RuntimeCall::System(frame_system::Call::remark_with_event { @@ -461,22 +437,42 @@ mod dispatch { None, None, )); - }); - } - #[test] - fn dispatching_signed_with_a_session_key_fails_on_expired_session() { - prepare().execute_with(|| { - assert_ok!(Pass::authenticate( + run_to(9); + + assert_ok!(Pass::dispatch( + RuntimeOrigin::signed(SIGNER), + Box::new(RuntimeCall::System(frame_system::Call::remark_with_event { + remark: b"Hello, world".to_vec() + })), + None, + Some(OTHER), + )); + + run_to(12); + + assert_noop!( + Pass::dispatch( + RuntimeOrigin::signed(SIGNER), + Box::new(RuntimeCall::System(frame_system::Call::remark_with_event { + remark: b"Hello, world".to_vec() + })), + None, + None, + ), + Error::::SessionExpired + ); + + assert_ok!(Pass::dispatch( RuntimeOrigin::signed(OTHER), - AccountName::get(), - MockAuthenticationMethods::DummyAuthenticationMethod, - THE_DEVICE, - ChallengeResponse::get(), - Some(2), + Box::new(RuntimeCall::System(frame_system::Call::remark_with_event { + remark: b"Hello, world".to_vec() + })), + None, + None, )); - run_to(4); + run_to(20); assert_noop!( Pass::dispatch( @@ -485,9 +481,9 @@ mod dispatch { remark: b"Hello, world".to_vec() })), None, - None + None, ), - Error::::ExpiredSession + Error::::SessionExpired ); }); }