Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 95 additions & 17 deletions pallets/identity/src/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -891,23 +891,33 @@ impl<T: Config> Pallet<T> {
Ok(())
}

/// Ensures that `origin`'s key is the primary key of a DID.
/// Ensures that `origin`'s key is the primary key of a DID and has a valid CDD claim.
/// Returns the caller's account and DID.
pub fn ensure_primary_key(
origin: T::RuntimeOrigin,
) -> Result<(T::AccountId, IdentityId), DispatchError> {
let sender = ensure_signed(origin)?;
let key_rec = KeyRecords::<T>::get(&sender)
.ok_or(pallet_permissions::Error::<T>::UnauthorizedCaller)?;
let did = key_rec.is_primary_key().ok_or(Error::<T>::KeyNotAllowed)?;
ensure!(
Self::has_valid_cdd(did),
Error::<T>::UnauthorizedCallerDidMissingCdd
);
Ok((sender, did))
}

/// Ensures that `origin`'s key is linked to a DID and returns both.
/// Ensures that `origin`'s key is linked to a DID and has a valid CDD claim.
/// Returns the caller's account and DID.
pub fn ensure_did(
origin: T::RuntimeOrigin,
) -> Result<(T::AccountId, IdentityId), DispatchError> {
let sender = ensure_signed(origin)?;
let did = Self::get_identity(&sender).ok_or(Error::<T>::MissingIdentity)?;
ensure!(
Self::has_valid_cdd(did),
Error::<T>::UnauthorizedCallerDidMissingCdd
);
Ok((sender, did))
}

Expand Down Expand Up @@ -966,35 +976,103 @@ impl<T: Config> Pallet<T> {
}
Ok(())
}

/// Checks if the caller is permissioned to call the current extrinsic skipping CDD checks.
/// If `must_be_primary_key` ensures that the caller is a primary key.
pub fn ensure_valid_origin(
origin: T::RuntimeOrigin,
must_be_primary_key: bool,
) -> Result<(T::AccountId, IdentityId), DispatchError> {
let caller_acc = ensure_signed(origin)?;
let caller_did =
pallet_permissions::Pallet::<T>::ensure_valid_origin(&caller_acc, must_be_primary_key)?;
Ok((caller_acc, caller_did))
}
}

impl<T: Config> CheckAccountCallPermissions<T::AccountId> for Pallet<T> {
// For weighting purposes, the function reads 4 storage values.
fn check_account_call_permissions(
who: &T::AccountId,
pallet_name: impl FnOnce() -> PalletName,
function_name: impl FnOnce() -> ExtrinsicName,
) -> Option<AccountCallPermissionsData<T::AccountId>> {
) -> Result<AccountCallPermissionsData<T::AccountId>, DispatchError> {
let data = |did, secondary_key| AccountCallPermissionsData {
primary_did: did,
secondary_key,
};

match KeyRecords::<T>::get(who)? {
// Primary keys do not have / require further permission checks.
KeyRecord::PrimaryKey(did) => Some(data(did, None)),
// Secondary Key. Ensure DID isn't frozen + key has sufficient permissions.
KeyRecord::SecondaryKey(did) if !IsDidFrozen::<T>::get(&did) => {
let key_record = KeyRecords::<T>::get(who).ok_or(Error::<T>::MissingIdentity)?;

match key_record {
// Primary keys only require a valid CDD claim.
KeyRecord::PrimaryKey(did) => {
ensure!(
Self::has_valid_cdd(did),
Error::<T>::UnauthorizedCallerDidMissingCdd
);
Ok(data(did, None))
}
// Secondary Key. Ensure DID isn't frozen + has a valid CDD claim + key has sufficient permissions.
KeyRecord::SecondaryKey(did) => {
ensure!(
!IsDidFrozen::<T>::get(&did),
Error::<T>::UnauthorizedCallerFrozenDid
);

ensure!(
Self::has_valid_cdd(did),
Error::<T>::UnauthorizedCallerDidMissingCdd
);

let permissions = Self::get_key_permissions(who);
let sk = SecondaryKey {
key: who.clone(),
permissions,
};
sk.has_extrinsic_permission(&pallet_name(), &function_name())
.then(|| data(did, Some(sk)))
let sk = SecondaryKey::new(who.clone(), permissions);
ensure!(
sk.has_extrinsic_permission(&pallet_name(), &function_name()),
Error::<T>::UnauthorizedCallerMissingPermissions
);

Ok(data(did, Some(sk)))
}
// DIDs with frozen secondary keys, AKA frozen DIDs, are not permitted to call extrinsics.
_ => None,
KeyRecord::MultiSigSignerKey(_) => Err(Error::<T>::MissingIdentity.into()),
}
}

fn ensure_valid_origin(
caller_acc: &T::AccountId,
must_be_primary_key: bool,
pallet_name: impl FnOnce() -> PalletName,
function_name: impl FnOnce() -> ExtrinsicName,
) -> Result<IdentityId, DispatchError> {
let key_record = KeyRecords::<T>::get(&caller_acc).ok_or(Error::<T>::MissingIdentity)?;

if must_be_primary_key {
let did = key_record
.is_primary_key()
.ok_or(Error::<T>::KeyNotAllowed)?;
return Ok(did);
}

if let KeyRecord::PrimaryKey(did) = key_record {
return Ok(did);
}

let did = key_record
.is_secondary_key()
.ok_or(Error::<T>::KeyNotAllowed)?;

ensure!(
!IsDidFrozen::<T>::get(&did),
Error::<T>::UnauthorizedCallerFrozenDid
);

let permissions = Self::get_key_permissions(&caller_acc);
ensure!(
permissions
.extrinsic
.sufficient_for(&pallet_name(), &function_name()),
Error::<T>::UnauthorizedCallerMissingPermissions
);

Ok(did)
}
}
8 changes: 8 additions & 0 deletions pallets/identity/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1088,6 +1088,14 @@ pub mod pallet {
/// Auth identified by an `auth_id` for a given `target` does not exist.
/// The `target` might be wrong or the `auth_id` was never created at all.
InvalidAuthorization,
/// Frozen secondary key.
UnauthorizedCallerFrozenDid,
/// The DID is missing a CDD claim.
UnauthorizedCallerDidMissingCdd,
/// The key does not have permissions to execute the extrinsic.
UnauthorizedCallerMissingPermissions,
/// Multisig keys are not allowed for some extrinsics.
UnauthorizedCallerMultisigKey,
}
}

Expand Down
30 changes: 10 additions & 20 deletions pallets/multisig/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,7 @@ use sp_runtime::traits::{Dispatchable, Hash};
use sp_std::convert::TryFrom;
use sp_std::prelude::*;

use pallet_identity::{
CddAuthForPrimaryKeyRotation, Config as IdentityConfig, PermissionedCallOriginData,
};
use pallet_identity::{CddAuthForPrimaryKeyRotation, Config as IdentityConfig};
use pallet_permissions::with_call_metadata;
use polymesh_primitives::multisig::{ProposalState, ProposalVoteCount};
use polymesh_primitives::{
Expand Down Expand Up @@ -236,25 +234,17 @@ pub mod pallet {
sigs_required: u64,
permissions: Option<Permissions>,
) -> DispatchResultWithPostInfo {
let (caller, caller_did, permissions) = match permissions {
Some(permissions) => {
// Only the primary key can add a secondary key with custom permissions.
let (caller, did) = IdentityPallet::<T>::ensure_primary_key(origin)?;
(caller, did, permissions)
}
None => {
// Default to empty permissions for the new secondary key.
let PermissionedCallOriginData {
sender: caller,
primary_did,
..
} = IdentityPallet::<T>::ensure_origin_call_permissions(origin)?;
(caller, primary_did, Permissions::empty())
}
};
let (caller, caller_did) =
IdentityPallet::<T>::ensure_valid_origin(origin, permissions.is_some())?;
let signers_len: u64 = u64::try_from(signers.len()).unwrap_or_default();
Self::ensure_sigs_in_bounds(signers_len, sigs_required)?;
Self::base_create_multisig(caller, caller_did, signers, sigs_required, permissions)?;
Self::base_create_multisig(
caller,
caller_did,
signers,
sigs_required,
permissions.unwrap_or(Permissions::empty()),
)?;
Ok(().into())
}

Expand Down
27 changes: 25 additions & 2 deletions pallets/permissions/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,17 @@ pub mod pallet {
who: &AccountId,
pallet_name: impl FnOnce() -> PalletName,
function_name: impl FnOnce() -> ExtrinsicName,
) -> Option<AccountCallPermissionsData<AccountId>>;
) -> Result<AccountCallPermissionsData<AccountId>, DispatchError>;

/// Checks whether `who` can call the current extrinsic represented by `pallet_name` and
/// `function_name` skipping CDD checks. If `must_be_primary_key` is true, ensures that
/// the caller is a primary key.
fn ensure_valid_origin(
who: &AccountId,
must_be_primary_key: bool,
pallet_name: impl FnOnce() -> PalletName,
function_name: impl FnOnce() -> ExtrinsicName,
) -> Result<IdentityId, DispatchError>;
}

#[pallet::pallet]
Expand Down Expand Up @@ -110,7 +120,20 @@ pub mod pallet {
|| CurrentPalletName::<T>::get(),
|| CurrentDispatchableName::<T>::get(),
)
.ok_or_else(|| Error::<T>::UnauthorizedCaller.into())
}

/// Checks if the caller is permissioned to call the current extrinsic skipping CDD checks.
/// If `must_be_primary_key` ensures that the caller is a primary key.
pub fn ensure_valid_origin(
who: &T::AccountId,
must_be_primary_key: bool,
) -> Result<IdentityId, DispatchError> {
T::Checker::ensure_valid_origin(
who,
must_be_primary_key,
|| CurrentPalletName::<T>::get(),
|| CurrentDispatchableName::<T>::get(),
)
}
}
}
Expand Down
6 changes: 1 addition & 5 deletions pallets/relayer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,11 +314,7 @@ impl<T: Config> Pallet<T> {
user_key: T::AccountId,
polyx_limit: Balance,
) -> DispatchResult {
let PermissionedCallOriginData {
sender: paying_key,
primary_did: paying_did,
..
} = <Identity<T>>::ensure_origin_call_permissions(origin)?;
let (paying_key, paying_did) = Identity::<T>::ensure_valid_origin(origin, false)?;

// Create authorization for `paying_key` to subsidise the `user_key`, with `polyx_limit` POLYX.
Self::unverified_add_auth_for_paying_key(paying_did, user_key, paying_key, polyx_limit)?;
Expand Down
43 changes: 5 additions & 38 deletions pallets/runtime/common/src/fee_details.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,42 +34,15 @@ where
/// However, this does not set the payer context since that is meant to remain constant
/// throughout the transaction. This function can also be used to simply check CDD and update identity context.
fn get_valid_payer(call: &C, caller: &AccountId) -> ValidPayerResult {
// Check if the `did` has a valid CDD claim.
let check_did_cdd = |did: &IdentityId| {
if Identity::<A>::has_valid_cdd(*did) {
Ok(None)
} else {
CDD_REQUIRED
}
};

// Check if the `did` has a valid CDD claim
// and return the primary key as the payer.
let did_primary_pays = |did: &IdentityId| {
check_did_cdd(did)?;
Ok(Identity::<A>::get_primary_key(*did))
};

// Check if the `caller` key has a DID and a valid CDD claim.
// The caller is also the payer.
let caller_pays = |caller: &AccountId| {
match Identity::<A>::get_identity(caller) {
Some(did) => {
check_did_cdd(&did)?;
Ok(Some(caller.clone()))
}
// Return if there's no DID.
None => MISSING_ID,
}
};
// Return the primary key as the payer.
let did_primary_pays = |did: &IdentityId| Ok(Identity::<A>::get_primary_key(*did));

let handle_multisig = |multisig: &AccountId, caller: &AccountId| {
if pallet_multisig::MultiSigSigners::<A>::contains_key(multisig, caller) {
let ms_pays = caller_pays(multisig)?;
// If the `multisig` has a paying DID, then it's primary key pays.
match pallet_multisig::Pallet::<A>::get_paying_did(multisig) {
Some(did) => Ok(Identity::<A>::get_primary_key(did)),
None => Ok(ms_pays),
None => Ok(Some(multisig.clone())),
}
} else {
MISSING_ID
Expand Down Expand Up @@ -164,10 +137,8 @@ where
| pallet_multisig::Call::approve { multisig, .. }
| pallet_multisig::Call::reject { multisig, .. },
)) => handle_multisig(multisig, caller),
// All other calls.
//
// The external account must directly be linked to an identity with valid CDD.
_ => caller_pays(caller),
// All other calls
_ => Ok(Some(caller.clone())),
}
}

Expand Down Expand Up @@ -201,10 +172,6 @@ enum CallType {

type ValidPayerResult = Result<Option<AccountId>, InvalidTransaction>;

const CDD_REQUIRED: ValidPayerResult = Err(InvalidTransaction::Custom(
TransactionError::CddRequired as u8,
));

const MISSING_ID: ValidPayerResult = Err(InvalidTransaction::Custom(
TransactionError::MissingIdentity as u8,
));
Expand Down
2 changes: 1 addition & 1 deletion pallets/runtime/tests/src/contracts_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ fn chain_extension_calls() {
None,
register_ticker_input.clone()
),
pallet_permissions::Error::<TestStorage>::UnauthorizedCaller,
pallet_identity::Error::<TestStorage>::UnauthorizedCallerMissingPermissions,
));
// Successfull call
assert_ok!(Identity::set_secondary_key_permissions(
Expand Down
Loading