Bug: [refactor]: add level support for roles



Determine a given role exists at which level, i.e., determine a entity of each roles which it is associated to.
- This will help users to perform operation accordingly.
- A profile/merchant level user cannot perform org level opertaion.
- Similarly a hierarchy can be maintained where a user (which will be associated to a particular role) can not perform any operations which are for higher entities.
diff --git a/crates/api_models/src/user_role/role.rs b/crates/api_models/src/user_role/role.rs
index 6d0bb2154d9..73b25c84af5 100644
--- a/crates/api_models/src/user_role/role.rs
+++ b/crates/api_models/src/user_role/role.rs
@@ -1,4 +1,4 @@
-use common_enums::{PermissionGroup, RoleScope};
+use common_enums::{EntityType, PermissionGroup, RoleScope};
 
 use super::Permission;
 
@@ -7,6 +7,7 @@ pub struct CreateRoleRequest {
     pub role_name: String,
     pub groups: Vec<PermissionGroup>,
     pub role_scope: RoleScope,
+    pub entity_type: Option<EntityType>,
 }
 
 #[derive(Debug, serde::Deserialize, serde::Serialize)]
diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs
index 5c8a860c168..516e3d7030d 100644
--- a/crates/common_enums/src/enums.rs
+++ b/crates/common_enums/src/enums.rs
@@ -3026,6 +3026,27 @@ pub enum Owner {
     Internal,
 }
 
+#[derive(
+    Clone,
+    Copy,
+    Debug,
+    Eq,
+    PartialEq,
+    serde::Deserialize,
+    serde::Serialize,
+    strum::Display,
+    strum::EnumString,
+)]
+#[router_derive::diesel_enum(storage_type = "text")]
+#[strum(serialize_all = "snake_case")]
+#[serde(rename_all = "snake_case")]
+pub enum EntityType {
+    Internal,
+    Organization,
+    Merchant,
+    Profile,
+}
+
 #[derive(Clone, Debug, serde::Serialize)]
 #[serde(rename_all = "snake_case")]
 pub enum PayoutRetryType {
diff --git a/crates/diesel_models/src/role.rs b/crates/diesel_models/src/role.rs
index 713590adeda..3fb64e645d6 100644
--- a/crates/diesel_models/src/role.rs
+++ b/crates/diesel_models/src/role.rs
@@ -18,6 +18,7 @@ pub struct Role {
     pub created_by: String,
     pub last_modified_at: PrimitiveDateTime,
     pub last_modified_by: String,
+    pub entity_type: Option<enums::EntityType>,
 }
 
 #[derive(router_derive::Setter, Clone, Debug, Insertable, router_derive::DebugAsDisplay)]
@@ -34,6 +35,7 @@ pub struct RoleNew {
     pub created_by: String,
     pub last_modified_at: PrimitiveDateTime,
     pub last_modified_by: String,
+    pub entity_type: Option<enums::EntityType>,
 }
 
 #[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay)]
diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs
index 37ba11e4389..d2c66fc37fe 100644
--- a/crates/diesel_models/src/schema.rs
+++ b/crates/diesel_models/src/schema.rs
@@ -1183,6 +1183,8 @@ diesel::table! {
         last_modified_at -> Timestamp,
         #[max_length = 64]
         last_modified_by -> Varchar,
+        #[max_length = 64]
+        entity_type -> Nullable<Varchar>,
     }
 }
 
diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs
index 5854fa5f385..83a44486160 100644
--- a/crates/diesel_models/src/schema_v2.rs
+++ b/crates/diesel_models/src/schema_v2.rs
@@ -1189,6 +1189,8 @@ diesel::table! {
         last_modified_at -> Timestamp,
         #[max_length = 64]
         last_modified_by -> Varchar,
+        #[max_length = 64]
+        entity_type -> Nullable<Varchar>,
     }
 }
 
diff --git a/crates/diesel_models/src/user_role.rs b/crates/diesel_models/src/user_role.rs
index d484db2c6ba..87e6f332328 100644
--- a/crates/diesel_models/src/user_role.rs
+++ b/crates/diesel_models/src/user_role.rs
@@ -1,3 +1,4 @@
+use common_enums::EntityType;
 use common_utils::id_type;
 use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable};
 use time::PrimitiveDateTime;
@@ -19,7 +20,7 @@ pub struct UserRole {
     pub last_modified: PrimitiveDateTime,
     pub profile_id: Option<String>,
     pub entity_id: Option<String>,
-    pub entity_type: Option<String>,
+    pub entity_type: Option<EntityType>,
     pub version: enums::UserRoleVersion,
 }
 
@@ -37,7 +38,7 @@ pub struct UserRoleNew {
     pub last_modified: PrimitiveDateTime,
     pub profile_id: Option<String>,
     pub entity_id: Option<String>,
-    pub entity_type: Option<String>,
+    pub entity_type: Option<EntityType>,
     pub version: enums::UserRoleVersion,
 }
 
diff --git a/crates/router/src/core/user_role/role.rs b/crates/router/src/core/user_role/role.rs
index 386156f58e7..a286180e202 100644
--- a/crates/router/src/core/user_role/role.rs
+++ b/crates/router/src/core/user_role/role.rs
@@ -87,6 +87,7 @@ pub async fn create_role(
             org_id: user_from_token.org_id,
             groups: req.groups,
             scope: req.role_scope,
+            entity_type: req.entity_type,
             created_by: user_from_token.user_id.clone(),
             last_modified_by: user_from_token.user_id,
             created_at: now,
diff --git a/crates/router/src/db/role.rs b/crates/router/src/db/role.rs
index 286a85190b8..5802142b166 100644
--- a/crates/router/src/db/role.rs
+++ b/crates/router/src/db/role.rs
@@ -144,6 +144,7 @@ impl RoleInterface for MockDb {
             org_id: role.org_id,
             groups: role.groups,
             scope: role.scope,
+            entity_type: role.entity_type,
             created_by: role.created_by,
             created_at: role.created_at,
             last_modified_at: role.last_modified_at,
diff --git a/crates/router/src/services/authorization/roles.rs b/crates/router/src/services/authorization/roles.rs
index cda2371282a..9b506cc5ef7 100644
--- a/crates/router/src/services/authorization/roles.rs
+++ b/crates/router/src/services/authorization/roles.rs
@@ -1,6 +1,6 @@
 use std::collections::HashSet;
 
-use common_enums::{PermissionGroup, RoleScope};
+use common_enums::{EntityType, PermissionGroup, RoleScope};
 use common_utils::{errors::CustomResult, id_type};
 
 use super::{permission_groups::get_permissions_vec, permissions::Permission};
@@ -14,6 +14,7 @@ pub struct RoleInfo {
     role_name: String,
     groups: Vec<PermissionGroup>,
     scope: RoleScope,
+    entity_type: EntityType,
     is_invitable: bool,
     is_deletable: bool,
     is_updatable: bool,
@@ -37,6 +38,10 @@ impl RoleInfo {
         self.scope
     }
 
+    pub fn get_entity_type(&self) -> EntityType {
+        self.entity_type
+    }
+
     pub fn is_invitable(&self) -> bool {
         self.is_invitable
     }
@@ -91,6 +96,7 @@ impl From<diesel_models::role::Role> for RoleInfo {
             role_name: role.role_name,
             groups: role.groups.into_iter().map(Into::into).collect(),
             scope: role.scope,
+            entity_type: role.entity_type.unwrap_or(EntityType::Merchant),
             is_invitable: true,
             is_deletable: true,
             is_updatable: true,
diff --git a/crates/router/src/services/authorization/roles/predefined_roles.rs b/crates/router/src/services/authorization/roles/predefined_roles.rs
index 568af6e19fd..2f67af9f23a 100644
--- a/crates/router/src/services/authorization/roles/predefined_roles.rs
+++ b/crates/router/src/services/authorization/roles/predefined_roles.rs
@@ -1,6 +1,6 @@
 use std::collections::HashMap;
 
-use common_enums::{PermissionGroup, RoleScope};
+use common_enums::{EntityType, PermissionGroup, RoleScope};
 use once_cell::sync::Lazy;
 
 use super::RoleInfo;
@@ -28,6 +28,7 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
             role_id: consts::user_role::ROLE_ID_INTERNAL_ADMIN.to_string(),
             role_name: "internal_admin".to_string(),
             scope: RoleScope::Organization,
+            entity_type: EntityType::Internal,
             is_invitable: false,
             is_deletable: false,
             is_updatable: false,
@@ -48,6 +49,7 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
             role_id: consts::user_role::ROLE_ID_INTERNAL_VIEW_ONLY_USER.to_string(),
             role_name: "internal_view_only".to_string(),
             scope: RoleScope::Organization,
+            entity_type: EntityType::Internal,
             is_invitable: false,
             is_deletable: false,
             is_updatable: false,
@@ -75,6 +77,7 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
             role_id: consts::user_role::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
             role_name: "organization_admin".to_string(),
             scope: RoleScope::Organization,
+            entity_type: EntityType::Organization,
             is_invitable: false,
             is_deletable: false,
             is_updatable: false,
@@ -102,6 +105,7 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
             role_id: consts::user_role::ROLE_ID_MERCHANT_ADMIN.to_string(),
             role_name: "admin".to_string(),
             scope: RoleScope::Organization,
+            entity_type: EntityType::Merchant,
             is_invitable: true,
             is_deletable: true,
             is_updatable: true,
@@ -122,6 +126,7 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
             role_id: consts::user_role::ROLE_ID_MERCHANT_VIEW_ONLY.to_string(),
             role_name: "view_only".to_string(),
             scope: RoleScope::Organization,
+            entity_type: EntityType::Merchant,
             is_invitable: true,
             is_deletable: true,
             is_updatable: true,
@@ -141,6 +146,7 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
             role_id: consts::user_role::ROLE_ID_MERCHANT_IAM_ADMIN.to_string(),
             role_name: "iam".to_string(),
             scope: RoleScope::Organization,
+            entity_type: EntityType::Merchant,
             is_invitable: true,
             is_deletable: true,
             is_updatable: true,
@@ -161,6 +167,7 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
             role_id: consts::user_role::ROLE_ID_MERCHANT_DEVELOPER.to_string(),
             role_name: "developer".to_string(),
             scope: RoleScope::Organization,
+            entity_type: EntityType::Merchant,
             is_invitable: true,
             is_deletable: true,
             is_updatable: true,
@@ -182,6 +189,7 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
             role_id: consts::user_role::ROLE_ID_MERCHANT_OPERATOR.to_string(),
             role_name: "operator".to_string(),
             scope: RoleScope::Organization,
+            entity_type: EntityType::Merchant,
             is_invitable: true,
             is_deletable: true,
             is_updatable: true,
@@ -200,6 +208,7 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
             role_id: consts::user_role::ROLE_ID_MERCHANT_CUSTOMER_SUPPORT.to_string(),
             role_name: "customer_support".to_string(),
             scope: RoleScope::Organization,
+            entity_type: EntityType::Merchant,
             is_invitable: true,
             is_deletable: true,
             is_updatable: true,
diff --git a/migrations/2024-07-30-124102_add_entity_type_to_roles/down.sql b/migrations/2024-07-30-124102_add_entity_type_to_roles/down.sql
new file mode 100644
index 00000000000..af37d90893e
--- /dev/null
+++ b/migrations/2024-07-30-124102_add_entity_type_to_roles/down.sql
@@ -0,0 +1,2 @@
+-- This file should undo anything in `up.sql`
+ALTER TABLE roles DROP COLUMN entity_type;
\ No newline at end of file
diff --git a/migrations/2024-07-30-124102_add_entity_type_to_roles/up.sql b/migrations/2024-07-30-124102_add_entity_type_to_roles/up.sql
new file mode 100644
index 00000000000..14e0f945033
--- /dev/null
+++ b/migrations/2024-07-30-124102_add_entity_type_to_roles/up.sql
@@ -0,0 +1,2 @@
+-- Your SQL goes here
+ALTER TABLE roles ADD COLUMN entity_type VARCHAR(64);
\ No newline at end of file

## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix
- [ ] New feature
- [x] Enhancement
- [x] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
Add entity type to roles (predefined and customs)
This can be used to determine at which level a current entity exist, and can perform operations accordingly.

### Additional Changes

- [ ] This PR modifies the API contract
- [x] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables

<!--
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
2. `crates/router/src/configs`
3. `loadtest/config`
-->


## Motivation and Context
Closes [#5487](https://github.com/juspay/hyperswitch/issues/5487)


## How did you test it?
The change is at DB level and can be tested with the upcoming application changes PRs.


## Checklist
<!-- Put an `x` in the boxes that apply -->

- [x] I formatted the code `cargo +nightly fmt --all`
- [x] I addressed lints thrown by `cargo clippy`
- [x] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
