Skip to content

Commit

Permalink
Adding register OIDC client call to identity management pallet (#2931)
Browse files Browse the repository at this point in the history
* adding register_oidc_client to identity-management pallet

* updating mock

* adding tests

* adding unregister_oidc_client call

* adding test assertion for register_oidc_client

* adding tests for unregister_oidc_client

* adding temporary weights

* refactoring names

* adding docs comment

* adding TODO comment

* updating runtimes

* fixing clippy issues

* increasing runtime version in tests

* adding getter function
  • Loading branch information
silva-fj authored Jul 26, 2024
1 parent cf7e86a commit 705e892
Show file tree
Hide file tree
Showing 7 changed files with 225 additions and 6 deletions.
99 changes: 98 additions & 1 deletion pallets/identity-management/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,23 @@ use pallet_teebag::ShardIdentifier;
use sp_core::H256;
use sp_std::vec::Vec;

const MAX_REDIRECT_URL_LEN: u32 = 256;

#[frame_support::pallet]
pub mod pallet {
use super::{ShardIdentifier, Vec, WeightInfo, H256};
use super::{ShardIdentifier, Vec, WeightInfo, H256, MAX_REDIRECT_URL_LEN};
use core_primitives::{ErrorDetail, IMPError, Identity};
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;

#[derive(
Clone, Eq, PartialEq, Encode, Decode, Default, RuntimeDebug, TypeInfo, MaxEncodedLen,
)]
#[scale_info(skip_type_params(MaxOIDCClientUris, MaxRedirectUriLen))]
pub struct OIDCClient<MaxOIDCClientUris: Get<u32>, MaxRedirectUriLen: Get<u32>> {
redirect_uris: BoundedVec<BoundedVec<u8, MaxRedirectUriLen>, MaxOIDCClientUris>,
}

#[pallet::pallet]
pub struct Pallet<T>(_);

Expand All @@ -67,6 +77,19 @@ pub mod pallet {
type DelegateeAdminOrigin: EnsureOrigin<Self::RuntimeOrigin>;
// origin that is allowed to call extrinsics
type ExtrinsicWhitelistOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Self::AccountId>;
// maximum number of OIDC client URIs
#[pallet::constant]
type MaxOIDCClientRedirectUris: Get<u32>;
}

#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn integrity_test() {
assert!(
<T as Config>::MaxOIDCClientRedirectUris::get() > 0,
"MaxOIDCClientUris must be greater than zero."
);
}
}

#[pallet::event]
Expand Down Expand Up @@ -146,19 +169,46 @@ pub mod pallet {
detail: ErrorDetail,
req_ext_hash: H256,
},
OIDCClientRegistered {
client_id: T::AccountId,
},
OIDCClientUnregistered {
client_id: T::AccountId,
},
}

// delegatees who can send extrinsics(currently only `link_identity`) on users' behalf
#[pallet::storage]
#[pallet::getter(fn delegatee)]
pub type Delegatee<T: Config> = StorageMap<_, Blake2_128Concat, T::AccountId, (), OptionQuery>;

// OIDC clients who can use the OIDC flow
#[pallet::storage]
#[pallet::getter(fn oidc_client)]
pub type OIDCClients<T: Config> = StorageMap<
_,
Blake2_128Concat,
T::AccountId,
OIDCClient<T::MaxOIDCClientRedirectUris, ConstU32<MAX_REDIRECT_URL_LEN>>,
OptionQuery,
>;

#[pallet::error]
pub enum Error<T> {
/// a delegatee doesn't exist
DelegateeNotExist,
/// a `link_identity` request from unauthorized user
UnauthorizedUser,
/// redirect_uris exceed the maximum length
TooManyRedirectUris,
/// redirect_uris is empty
EmptyRedirectUris,
/// redirect_uri exceeds the maximum length
RedirectUriTooLong,
/// OIDC client already exists
OIDCClientAlreadyRegistered,
/// OIDC client does not exists
OIDCClientDoesNotExist,
}

#[pallet::call]
Expand Down Expand Up @@ -261,6 +311,53 @@ pub mod pallet {
Ok(().into())
}

/// Register an OIDC client
// TODO: take a deposit to cover the storage cost and prevent spamming
#[pallet::call_index(6)]
#[pallet::weight((195_000_000, DispatchClass::Normal))]
pub fn register_oidc_client(
origin: OriginFor<T>,
redirect_uris: Vec<Vec<u8>>,
) -> DispatchResult {
let client_id = ensure_signed(origin)?;
ensure!(!redirect_uris.is_empty(), Error::<T>::EmptyRedirectUris);
ensure!(
!OIDCClients::<T>::contains_key(&client_id),
Error::<T>::OIDCClientAlreadyRegistered
);

let client_redirect_uris = redirect_uris
.into_iter()
.map(|uri| {
BoundedVec::<u8, _>::try_from(uri).map_err(|_| Error::<T>::RedirectUriTooLong)
})
.collect::<Result<Vec<_>, _>>()?;

OIDCClients::<T>::insert(
&client_id,
OIDCClient {
redirect_uris: BoundedVec::try_from(client_redirect_uris)
.map_err(|_| Error::<T>::TooManyRedirectUris)?,
},
);

Self::deposit_event(Event::OIDCClientRegistered { client_id });

Ok(())
}

/// Unregister an OIDC client
#[pallet::call_index(7)]
#[pallet::weight((195_000_000, DispatchClass::Normal))]
pub fn unregister_oidc_client(origin: OriginFor<T>) -> DispatchResult {
let client_id = ensure_signed(origin)?;
ensure!(OIDCClients::<T>::contains_key(&client_id), Error::<T>::OIDCClientDoesNotExist);
OIDCClients::<T>::remove(&client_id);
Self::deposit_event(Event::OIDCClientUnregistered { client_id });

Ok(())
}

/// ---------------------------------------------------
/// The following extrinsics are supposed to be called by TEE only
/// ---------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions pallets/identity-management/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ impl pallet_identity_management::Config for Test {
type TEECallOrigin = EnsureEnclaveSigner<Self>;
type DelegateeAdminOrigin = EnsureRoot<Self::AccountId>;
type ExtrinsicWhitelistOrigin = IMPExtrinsicWhitelist;
type MaxOIDCClientRedirectUris = ConstU32<3>;
}

impl pallet_group::Config for Test {
Expand Down
122 changes: 121 additions & 1 deletion pallets/identity-management/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Litentry. If not, see <https://www.gnu.org/licenses/>.
#[allow(unused)]
use crate::{mock::*, Error, ShardIdentifier};
use crate::{mock::*, Error, OIDCClients, ShardIdentifier};
use core_primitives::{ErrorDetail, IMPError};
use frame_support::{assert_noop, assert_ok};
use sp_core::H256;
Expand Down Expand Up @@ -192,3 +192,123 @@ fn extrinsic_whitelist_origin_works() {
));
});
}

#[test]
fn register_oidc_client_works() {
new_test_ext().execute_with(|| {
let alice: SystemAccountId = get_signer(ALICE_PUBKEY);
let redirect_uris = vec!["https://example.com".as_bytes().to_vec()];
assert_ok!(IdentityManagement::register_oidc_client(
RuntimeOrigin::signed(alice.clone()),
redirect_uris
));
System::assert_last_event(RuntimeEvent::IdentityManagement(
crate::Event::OIDCClientRegistered { client_id: alice.clone() },
));
assert!(OIDCClients::<Test>::contains_key(&alice));
});
}

#[test]
fn register_oidc_client_empty_redirect_uris_check_works() {
new_test_ext().execute_with(|| {
let alice: SystemAccountId = get_signer(ALICE_PUBKEY);
assert_noop!(
IdentityManagement::register_oidc_client(RuntimeOrigin::signed(alice), vec![]),
Error::<Test>::EmptyRedirectUris
);
});
}

#[test]
fn register_oidc_client_already_registered_check_works() {
new_test_ext().execute_with(|| {
let alice: SystemAccountId = get_signer(ALICE_PUBKEY);
let redirect_uris = vec!["https://example.com".as_bytes().to_vec()];
assert_ok!(IdentityManagement::register_oidc_client(
RuntimeOrigin::signed(alice.clone()),
redirect_uris.clone()
));
assert_noop!(
IdentityManagement::register_oidc_client(RuntimeOrigin::signed(alice), redirect_uris),
Error::<Test>::OIDCClientAlreadyRegistered
);
});
}

#[test]
fn register_oidc_client_redirect_uri_too_long_check_works() {
new_test_ext().execute_with(|| {
let alice: SystemAccountId = get_signer(ALICE_PUBKEY);
let redirect_uris = vec!["https://example.com".as_bytes().to_vec(), "https://very-long-uriiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii.com".as_bytes().to_vec()];
assert_noop!(
IdentityManagement::register_oidc_client(RuntimeOrigin::signed(alice), redirect_uris),
Error::<Test>::RedirectUriTooLong
);
});
}

#[test]
fn register_oidc_client_too_many_redirect_uris_check_works() {
new_test_ext().execute_with(|| {
let alice: SystemAccountId = get_signer(ALICE_PUBKEY);
let redirect_uris = vec![
"https://example1.com".as_bytes().to_vec(),
"https://example2.com".as_bytes().to_vec(),
"https://example3.com".as_bytes().to_vec(),
"https://example4.com".as_bytes().to_vec(),
];
assert_noop!(
IdentityManagement::register_oidc_client(RuntimeOrigin::signed(alice), redirect_uris),
Error::<Test>::TooManyRedirectUris
);
});
}

#[test]
fn unregister_oidc_client_works() {
new_test_ext().execute_with(|| {
let alice: SystemAccountId = get_signer(ALICE_PUBKEY);
let redirect_uris = vec!["https://example.com".as_bytes().to_vec()];
assert_ok!(IdentityManagement::register_oidc_client(
RuntimeOrigin::signed(alice.clone()),
redirect_uris
));
assert!(OIDCClients::<Test>::contains_key(&alice));
assert_ok!(IdentityManagement::unregister_oidc_client(RuntimeOrigin::signed(
alice.clone()
)));
System::assert_last_event(RuntimeEvent::IdentityManagement(
crate::Event::OIDCClientUnregistered { client_id: alice.clone() },
));
assert!(!OIDCClients::<Test>::contains_key(&alice));
});
}

#[test]
fn unregister_oidc_client_does_not_exists_works() {
new_test_ext().execute_with(|| {
let bob: SystemAccountId = get_signer(BOB_PUBKEY);
assert_noop!(
IdentityManagement::unregister_oidc_client(RuntimeOrigin::signed(bob)),
Error::<Test>::OIDCClientDoesNotExist
);
});
}

#[test]
fn unregister_oidc_client_unauthorized_sender() {
new_test_ext().execute_with(|| {
let alice: SystemAccountId = get_signer(ALICE_PUBKEY);
let redirect_uris = vec!["https://example.com".as_bytes().to_vec()];
assert_ok!(IdentityManagement::register_oidc_client(
RuntimeOrigin::signed(alice),
redirect_uris
));
let bob: SystemAccountId = get_signer(BOB_PUBKEY);
assert_noop!(
IdentityManagement::unregister_oidc_client(RuntimeOrigin::signed(bob)),
Error::<Test>::OIDCClientDoesNotExist
);
});
}
2 changes: 1 addition & 1 deletion runtime/litentry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
impl_name: create_runtime_str!("litentry-parachain"),
authoring_version: 1,
// same versioning-mechanism as polkadot: use last digit for minor updates
spec_version: 9185,
spec_version: 9186,
impl_version: 0,
apis: RUNTIME_API_VERSIONS,
transaction_version: 1,
Expand Down
2 changes: 1 addition & 1 deletion runtime/litmus/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
impl_name: create_runtime_str!("litmus-parachain"),
authoring_version: 1,
// same versioning-mechanism as polkadot: use last digit for minor updates
spec_version: 9185,
spec_version: 9186,
impl_version: 0,
apis: RUNTIME_API_VERSIONS,
transaction_version: 1,
Expand Down
3 changes: 2 additions & 1 deletion runtime/rococo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
impl_name: create_runtime_str!("rococo-parachain"),
authoring_version: 1,
// same versioning-mechanism as polkadot: use last digit for minor updates
spec_version: 9185,
spec_version: 9186,
impl_version: 0,
apis: RUNTIME_API_VERSIONS,
transaction_version: 1,
Expand Down Expand Up @@ -1023,6 +1023,7 @@ impl pallet_identity_management::Config for Runtime {
type TEECallOrigin = EnsureEnclaveSigner<Runtime>;
type DelegateeAdminOrigin = EnsureRootOrAllCouncil;
type ExtrinsicWhitelistOrigin = IMPExtrinsicWhitelist;
type MaxOIDCClientRedirectUris = ConstU32<10>;
}

impl pallet_bitacross::Config for Runtime {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ export async function assertVc(context: IntegrationTestContext, subject: CorePri
// check runtime version is present
assert.deepEqual(
vcPayloadJson.issuer.runtimeVersion,
{ parachain: 9185, sidechain: 109 },
{ parachain: 9186, sidechain: 109 },
'Check VC runtime version: it should equal the current defined versions'
);

Expand Down

0 comments on commit 705e892

Please sign in to comment.