Bug: feat: change list roles, get role, info api to respond with groups




diff --git a/crates/api_models/src/events/user_role.rs b/crates/api_models/src/events/user_role.rs
index f19ff95f9ed..f46c5cf312b 100644
--- a/crates/api_models/src/events/user_role.rs
+++ b/crates/api_models/src/events/user_role.rs
@@ -2,15 +2,15 @@ use common_utils::events::{ApiEventMetric, ApiEventsType};
 
 use crate::user_role::{
     role::{
-        CreateRoleRequest, GetRoleRequest, ListRolesResponse, RoleInfoResponse, UpdateRoleRequest,
+        CreateRoleRequest, GetRoleRequest, ListRolesResponse, RoleInfoResponse,
+        RoleInfoWithPermissionsResponse, UpdateRoleRequest,
     },
     AcceptInvitationRequest, AuthorizationInfoResponse, DeleteUserRoleRequest,
     TransferOrgOwnershipRequest, UpdateUserRoleRequest,
 };
 
 common_utils::impl_misc_api_event_type!(
-    ListRolesResponse,
-    RoleInfoResponse,
+    RoleInfoWithPermissionsResponse,
     GetRoleRequest,
     AuthorizationInfoResponse,
     UpdateUserRoleRequest,
@@ -18,5 +18,7 @@ common_utils::impl_misc_api_event_type!(
     DeleteUserRoleRequest,
     TransferOrgOwnershipRequest,
     CreateRoleRequest,
-    UpdateRoleRequest
+    UpdateRoleRequest,
+    ListRolesResponse,
+    RoleInfoResponse
 );
diff --git a/crates/api_models/src/user_role.rs b/crates/api_models/src/user_role.rs
index ed0838adc24..80d3861969a 100644
--- a/crates/api_models/src/user_role.rs
+++ b/crates/api_models/src/user_role.rs
@@ -1,3 +1,4 @@
+use common_enums::PermissionGroup;
 use common_utils::pii;
 
 use crate::user::DashboardEntryResponse;
@@ -51,7 +52,14 @@ pub enum PermissionModule {
 }
 
 #[derive(Debug, serde::Serialize)]
-pub struct AuthorizationInfoResponse(pub Vec<ModuleInfo>);
+pub struct AuthorizationInfoResponse(pub Vec<AuthorizationInfo>);
+
+#[derive(Debug, serde::Serialize)]
+#[serde(untagged)]
+pub enum AuthorizationInfo {
+    Module(ModuleInfo),
+    Group(GroupInfo),
+}
 
 #[derive(Debug, serde::Serialize)]
 pub struct ModuleInfo {
@@ -60,6 +68,13 @@ pub struct ModuleInfo {
     pub permissions: Vec<PermissionInfo>,
 }
 
+#[derive(Debug, serde::Serialize)]
+pub struct GroupInfo {
+    pub group: PermissionGroup,
+    pub description: &'static str,
+    pub permissions: Vec<PermissionInfo>,
+}
+
 #[derive(Debug, serde::Serialize)]
 pub struct PermissionInfo {
     pub enum_name: Permission,
diff --git a/crates/api_models/src/user_role/role.rs b/crates/api_models/src/user_role/role.rs
index 4a767cfa723..fafb8fede0b 100644
--- a/crates/api_models/src/user_role/role.rs
+++ b/crates/api_models/src/user_role/role.rs
@@ -1,5 +1,7 @@
 use common_enums::{PermissionGroup, RoleScope};
 
+use super::Permission;
+
 #[derive(Debug, serde::Deserialize, serde::Serialize)]
 pub struct CreateRoleRequest {
     pub role_name: String,
@@ -16,10 +18,30 @@ pub struct UpdateRoleRequest {
 #[derive(Debug, serde::Serialize)]
 pub struct ListRolesResponse(pub Vec<RoleInfoResponse>);
 
+#[derive(Debug, serde::Deserialize)]
+pub struct GetGroupsQueryParam {
+    pub groups: Option<bool>,
+}
+
 #[derive(Debug, serde::Serialize)]
-pub struct RoleInfoResponse {
+#[serde(untagged)]
+pub enum RoleInfoResponse {
+    Permissions(RoleInfoWithPermissionsResponse),
+    Groups(RoleInfoWithGroupsResponse),
+}
+
+#[derive(Debug, serde::Serialize)]
+pub struct RoleInfoWithPermissionsResponse {
     pub role_id: String,
-    pub permissions: Vec<super::Permission>,
+    pub permissions: Vec<Permission>,
+    pub role_name: String,
+    pub role_scope: RoleScope,
+}
+
+#[derive(Debug, serde::Serialize)]
+pub struct RoleInfoWithGroupsResponse {
+    pub role_id: String,
+    pub groups: Vec<PermissionGroup>,
     pub role_name: String,
     pub role_scope: RoleScope,
 }
diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs
index b8168fc5329..4a87d0c256f 100644
--- a/crates/common_enums/src/enums.rs
+++ b/crates/common_enums/src/enums.rs
@@ -2197,6 +2197,7 @@ pub enum RoleScope {
     serde::Deserialize,
     strum::Display,
     strum::EnumString,
+    strum::EnumIter,
 )]
 #[router_derive::diesel_enum(storage_type = "text")]
 #[serde(rename_all = "snake_case")]
diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs
index b38faa34554..b8b8cbd24c7 100644
--- a/crates/router/src/core/user.rs
+++ b/crates/router/src/core/user.rs
@@ -442,7 +442,7 @@ pub async fn invite_user(
         .into());
     }
 
-    let role_info = roles::get_role_info_from_role_id(
+    let role_info = roles::RoleInfo::from_role_id(
         &state,
         &request.role_id,
         &user_from_token.merchant_id,
@@ -659,7 +659,7 @@ async fn handle_invitation(
         .into());
     }
 
-    let role_info = roles::get_role_info_from_role_id(
+    let role_info = roles::RoleInfo::from_role_id(
         state,
         &request.role_id,
         &user_from_token.merchant_id,
@@ -1054,7 +1054,7 @@ pub async fn switch_merchant_id(
 
     let user = user_from_token.get_user_from_db(&state).await?;
 
-    let role_info = roles::get_role_info_from_role_id(
+    let role_info = roles::RoleInfo::from_role_id(
         &state,
         &user_from_token.role_id,
         &user_from_token.merchant_id,
@@ -1207,7 +1207,7 @@ pub async fn get_users_for_merchant_account(
     let users_user_roles_and_roles =
         futures::future::try_join_all(users_and_user_roles.into_iter().map(
             |(user, user_role)| async {
-                roles::get_role_info_from_role_id(
+                roles::RoleInfo::from_role_id(
                     &state,
                     &user_role.role_id,
                     &user_role.merchant_id,
diff --git a/crates/router/src/core/user_role.rs b/crates/router/src/core/user_role.rs
index b954e0df6c6..9dca9b43d96 100644
--- a/crates/router/src/core/user_role.rs
+++ b/crates/router/src/core/user_role.rs
@@ -19,14 +19,28 @@ use crate::{
 
 pub mod role;
 
-pub async fn get_authorization_info(
+// TODO: To be deprecated once groups are stable
+pub async fn get_authorization_info_with_modules(
     _state: AppState,
 ) -> UserResponse<user_role_api::AuthorizationInfoResponse> {
     Ok(ApplicationResponse::Json(
         user_role_api::AuthorizationInfoResponse(
-            info::get_authorization_info()
+            info::get_module_authorization_info()
                 .into_iter()
-                .map(Into::into)
+                .map(|module_info| user_role_api::AuthorizationInfo::Module(module_info.into()))
+                .collect(),
+        ),
+    ))
+}
+
+pub async fn get_authorization_info_with_groups(
+    _state: AppState,
+) -> UserResponse<user_role_api::AuthorizationInfoResponse> {
+    Ok(ApplicationResponse::Json(
+        user_role_api::AuthorizationInfoResponse(
+            info::get_group_authorization_info()
+                .into_iter()
+                .map(user_role_api::AuthorizationInfo::Group)
                 .collect(),
         ),
     ))
@@ -37,7 +51,7 @@ pub async fn update_user_role(
     user_from_token: auth::UserFromToken,
     req: user_role_api::UpdateUserRoleRequest,
 ) -> UserResponse<()> {
-    let role_info = roles::get_role_info_from_role_id(
+    let role_info = roles::RoleInfo::from_role_id(
         &state,
         &req.role_id,
         &user_from_token.merchant_id,
@@ -67,7 +81,7 @@ pub async fn update_user_role(
         .await
         .to_not_found_response(UserErrors::InvalidRoleOperation)?;
 
-    let role_to_be_updated = roles::get_role_info_from_role_id(
+    let role_to_be_updated = roles::RoleInfo::from_role_id(
         &state,
         &user_role_to_be_updated.role_id,
         &user_from_token.merchant_id,
@@ -236,7 +250,7 @@ pub async fn delete_user_role(
         .find(|&role| role.merchant_id == user_from_token.merchant_id.as_str())
     {
         Some(user_role) => {
-            let role_info = roles::get_role_info_from_role_id(
+            let role_info = roles::RoleInfo::from_role_id(
                 &state,
                 &user_role.role_id,
                 &user_from_token.merchant_id,
diff --git a/crates/router/src/core/user_role/role.rs b/crates/router/src/core/user_role/role.rs
index 6edbda85bfd..cc31798bd8b 100644
--- a/crates/router/src/core/user_role/role.rs
+++ b/crates/router/src/core/user_role/role.rs
@@ -86,22 +86,25 @@ pub async fn create_role(
     Ok(ApplicationResponse::StatusOk)
 }
 
-pub async fn list_invitable_roles(
+// TODO: To be deprecated once groups are stable
+pub async fn list_invitable_roles_with_permissions(
     state: AppState,
     user_from_token: UserFromToken,
 ) -> UserResponse<role_api::ListRolesResponse> {
     let predefined_roles_map = PREDEFINED_ROLES
         .iter()
         .filter(|(_, role_info)| role_info.is_invitable())
-        .map(|(role_id, role_info)| role_api::RoleInfoResponse {
-            permissions: role_info
-                .get_permissions_set()
-                .into_iter()
-                .map(Into::into)
-                .collect(),
-            role_id: role_id.to_string(),
-            role_name: role_info.get_role_name().to_string(),
-            role_scope: role_info.get_scope(),
+        .map(|(role_id, role_info)| {
+            role_api::RoleInfoResponse::Permissions(role_api::RoleInfoWithPermissionsResponse {
+                permissions: role_info
+                    .get_permissions_set()
+                    .into_iter()
+                    .map(Into::into)
+                    .collect(),
+                role_id: role_id.to_string(),
+                role_name: role_info.get_role_name().to_string(),
+                role_scope: role_info.get_scope(),
+            })
         });
 
     let custom_roles_map = state
@@ -110,17 +113,22 @@ pub async fn list_invitable_roles(
         .await
         .change_context(UserErrors::InternalServerError)?
         .into_iter()
-        .map(roles::RoleInfo::from)
-        .filter(|role_info| role_info.is_invitable())
-        .map(|role_info| role_api::RoleInfoResponse {
-            permissions: role_info
-                .get_permissions_set()
-                .into_iter()
-                .map(Into::into)
-                .collect(),
-            role_id: role_info.get_role_id().to_string(),
-            role_name: role_info.get_role_name().to_string(),
-            role_scope: role_info.get_scope(),
+        .filter_map(|role| {
+            let role_info = roles::RoleInfo::from(role);
+            role_info
+                .is_invitable()
+                .then_some(role_api::RoleInfoResponse::Permissions(
+                    role_api::RoleInfoWithPermissionsResponse {
+                        permissions: role_info
+                            .get_permissions_set()
+                            .into_iter()
+                            .map(Into::into)
+                            .collect(),
+                        role_id: role_info.get_role_id().to_string(),
+                        role_name: role_info.get_role_name().to_string(),
+                        role_scope: role_info.get_scope(),
+                    },
+                ))
         });
 
     Ok(ApplicationResponse::Json(role_api::ListRolesResponse(
@@ -128,12 +136,54 @@ pub async fn list_invitable_roles(
     )))
 }
 
-pub async fn get_role(
+pub async fn list_invitable_roles_with_groups(
+    state: AppState,
+    user_from_token: UserFromToken,
+) -> UserResponse<role_api::ListRolesResponse> {
+    let predefined_roles_map = PREDEFINED_ROLES
+        .iter()
+        .filter(|(_, role_info)| role_info.is_invitable())
+        .map(|(role_id, role_info)| {
+            role_api::RoleInfoResponse::Groups(role_api::RoleInfoWithGroupsResponse {
+                groups: role_info.get_permission_groups().to_vec(),
+                role_id: role_id.to_string(),
+                role_name: role_info.get_role_name().to_string(),
+                role_scope: role_info.get_scope(),
+            })
+        });
+
+    let custom_roles_map = state
+        .store
+        .list_all_roles(&user_from_token.merchant_id, &user_from_token.org_id)
+        .await
+        .change_context(UserErrors::InternalServerError)?
+        .into_iter()
+        .filter_map(|role| {
+            let role_info = roles::RoleInfo::from(role);
+            role_info
+                .is_invitable()
+                .then_some(role_api::RoleInfoResponse::Groups(
+                    role_api::RoleInfoWithGroupsResponse {
+                        groups: role_info.get_permission_groups().to_vec(),
+                        role_id: role_info.get_role_id().to_string(),
+                        role_name: role_info.get_role_name().to_string(),
+                        role_scope: role_info.get_scope(),
+                    },
+                ))
+        });
+
+    Ok(ApplicationResponse::Json(role_api::ListRolesResponse(
+        predefined_roles_map.chain(custom_roles_map).collect(),
+    )))
+}
+
+// TODO: To be deprecated once groups are stable
+pub async fn get_role_with_permissions(
     state: AppState,
     user_from_token: UserFromToken,
     role: role_api::GetRoleRequest,
 ) -> UserResponse<role_api::RoleInfoResponse> {
-    let role_info = roles::get_role_info_from_role_id(
+    let role_info = roles::RoleInfo::from_role_id(
         &state,
         &role.role_id,
         &user_from_token.merchant_id,
@@ -152,12 +202,42 @@ pub async fn get_role(
         .map(Into::into)
         .collect();
 
-    Ok(ApplicationResponse::Json(role_api::RoleInfoResponse {
-        permissions,
-        role_id: role.role_id,
-        role_name: role_info.get_role_name().to_string(),
-        role_scope: role_info.get_scope(),
-    }))
+    Ok(ApplicationResponse::Json(
+        role_api::RoleInfoResponse::Permissions(role_api::RoleInfoWithPermissionsResponse {
+            permissions,
+            role_id: role.role_id,
+            role_name: role_info.get_role_name().to_string(),
+            role_scope: role_info.get_scope(),
+        }),
+    ))
+}
+
+pub async fn get_role_with_groups(
+    state: AppState,
+    user_from_token: UserFromToken,
+    role: role_api::GetRoleRequest,
+) -> UserResponse<role_api::RoleInfoResponse> {
+    let role_info = roles::RoleInfo::from_role_id(
+        &state,
+        &role.role_id,
+        &user_from_token.merchant_id,
+        &user_from_token.org_id,
+    )
+    .await
+    .to_not_found_response(UserErrors::InvalidRoleId)?;
+
+    if role_info.is_internal() {
+        return Err(UserErrors::InvalidRoleId.into());
+    }
+
+    Ok(ApplicationResponse::Json(
+        role_api::RoleInfoResponse::Groups(role_api::RoleInfoWithGroupsResponse {
+            groups: role_info.get_permission_groups().to_vec(),
+            role_id: role.role_id,
+            role_name: role_info.get_role_name().to_string(),
+            role_scope: role_info.get_scope(),
+        }),
+    ))
 }
 
 pub async fn update_role(
@@ -182,7 +262,7 @@ pub async fn update_role(
         .await?;
     }
 
-    let role_info = roles::get_role_info_from_role_id(
+    let role_info = roles::RoleInfo::from_role_id(
         &state,
         role_id,
         &user_from_token.merchant_id,
diff --git a/crates/router/src/routes/user_role.rs b/crates/router/src/routes/user_role.rs
index 52e739c36e1..75de9e7e6f7 100644
--- a/crates/router/src/routes/user_role.rs
+++ b/crates/router/src/routes/user_role.rs
@@ -1,10 +1,13 @@
 use actix_web::{web, HttpRequest, HttpResponse};
-use api_models::user_role as user_role_api;
+use api_models::user_role::{self as user_role_api, role as role_api};
 use router_env::Flow;
 
 use super::AppState;
 use crate::{
-    core::{api_locking, user_role as user_role_core},
+    core::{
+        api_locking,
+        user_role::{self as user_role_core, role as role_core},
+    },
     services::{
         api,
         authentication::{self as auth},
@@ -15,14 +18,23 @@ use crate::{
 pub async fn get_authorization_info(
     state: web::Data<AppState>,
     http_req: HttpRequest,
+    query: web::Query<role_api::GetGroupsQueryParam>,
 ) -> HttpResponse {
     let flow = Flow::GetAuthorizationInfo;
+    let respond_with_groups = query.into_inner().groups.unwrap_or(false);
     Box::pin(api::server_wrap(
         flow,
         state.clone(),
         &http_req,
         (),
-        |state, _: (), _| user_role_core::get_authorization_info(state),
+        |state, _: (), _| async move {
+            // TODO: Permissions to be deprecated once groups are stable
+            if respond_with_groups {
+                user_role_core::get_authorization_info_with_groups(state).await
+            } else {
+                user_role_core::get_authorization_info_with_modules(state).await
+            }
+        },
         &auth::JWTAuth(Permission::UsersRead),
         api_locking::LockAction::NotApplicable,
     ))
@@ -36,7 +48,7 @@ pub async fn get_role_from_token(state: web::Data<AppState>, req: HttpRequest) -
         state.clone(),
         &req,
         (),
-        |state, user, _| user_role_core::role::get_role_from_token(state, user),
+        |state, user, _| role_core::get_role_from_token(state, user),
         &auth::DashboardNoPermissionAuth,
         api_locking::LockAction::NotApplicable,
     ))
@@ -46,7 +58,7 @@ pub async fn get_role_from_token(state: web::Data<AppState>, req: HttpRequest) -
 pub async fn create_role(
     state: web::Data<AppState>,
     req: HttpRequest,
-    json_payload: web::Json<user_role_api::role::CreateRoleRequest>,
+    json_payload: web::Json<role_api::CreateRoleRequest>,
 ) -> HttpResponse {
     let flow = Flow::CreateRole;
     Box::pin(api::server_wrap(
@@ -54,21 +66,33 @@ pub async fn create_role(
         state.clone(),
         &req,
         json_payload.into_inner(),
-        user_role_core::role::create_role,
+        role_core::create_role,
         &auth::JWTAuth(Permission::UsersWrite),
         api_locking::LockAction::NotApplicable,
     ))
     .await
 }
 
-pub async fn list_all_roles(state: web::Data<AppState>, req: HttpRequest) -> HttpResponse {
+pub async fn list_all_roles(
+    state: web::Data<AppState>,
+    req: HttpRequest,
+    query: web::Query<role_api::GetGroupsQueryParam>,
+) -> HttpResponse {
     let flow = Flow::ListRoles;
+    let respond_with_groups = query.into_inner().groups.unwrap_or(false);
     Box::pin(api::server_wrap(
         flow,
         state.clone(),
         &req,
         (),
-        |state, user, _| user_role_core::role::list_invitable_roles(state, user),
+        |state, user, _| async move {
+            // TODO: Permissions to be deprecated once groups are stable
+            if respond_with_groups {
+                role_core::list_invitable_roles_with_groups(state, user).await
+            } else {
+                role_core::list_invitable_roles_with_permissions(state, user).await
+            }
+        },
         &auth::JWTAuth(Permission::UsersRead),
         api_locking::LockAction::NotApplicable,
     ))
@@ -79,17 +103,26 @@ pub async fn get_role(
     state: web::Data<AppState>,
     req: HttpRequest,
     path: web::Path<String>,
+    query: web::Query<role_api::GetGroupsQueryParam>,
 ) -> HttpResponse {
     let flow = Flow::GetRole;
     let request_payload = user_role_api::role::GetRoleRequest {
         role_id: path.into_inner(),
     };
+    let respond_with_groups = query.into_inner().groups.unwrap_or(false);
     Box::pin(api::server_wrap(
         flow,
         state.clone(),
         &req,
         request_payload,
-        user_role_core::role::get_role,
+        |state, user, payload| async move {
+            // TODO: Permissions to be deprecated once groups are stable
+            if respond_with_groups {
+                role_core::get_role_with_groups(state, user, payload).await
+            } else {
+                role_core::get_role_with_permissions(state, user, payload).await
+            }
+        },
         &auth::JWTAuth(Permission::UsersRead),
         api_locking::LockAction::NotApplicable,
     ))
@@ -99,7 +132,7 @@ pub async fn get_role(
 pub async fn update_role(
     state: web::Data<AppState>,
     req: HttpRequest,
-    json_payload: web::Json<user_role_api::role::UpdateRoleRequest>,
+    json_payload: web::Json<role_api::UpdateRoleRequest>,
     path: web::Path<String>,
 ) -> HttpResponse {
     let flow = Flow::UpdateRole;
@@ -110,7 +143,7 @@ pub async fn update_role(
         state.clone(),
         &req,
         json_payload.into_inner(),
-        |state, user, req| user_role_core::role::update_role(state, user, req, &role_id),
+        |state, user, req| role_core::update_role(state, user, req, &role_id),
         &auth::JWTAuth(Permission::UsersWrite),
         api_locking::LockAction::NotApplicable,
     ))
diff --git a/crates/router/src/services/authorization.rs b/crates/router/src/services/authorization.rs
index 034773a07ea..2a516cc82d5 100644
--- a/crates/router/src/services/authorization.rs
+++ b/crates/router/src/services/authorization.rs
@@ -6,6 +6,7 @@ use crate::{
     routes::app::AppStateInfo,
 };
 
+#[cfg(feature = "olap")]
 pub mod info;
 pub mod permission_groups;
 pub mod permissions;
diff --git a/crates/router/src/services/authorization/info.rs b/crates/router/src/services/authorization/info.rs
index d982317e023..d6a20b7f8ed 100644
--- a/crates/router/src/services/authorization/info.rs
+++ b/crates/router/src/services/authorization/info.rs
@@ -1,30 +1,32 @@
+use api_models::user_role::{GroupInfo, PermissionInfo};
+use common_enums::PermissionGroup;
 use strum::{EnumIter, IntoEnumIterator};
 
-use super::permissions::Permission;
+use super::{permission_groups::get_permissions_vec, permissions::Permission};
 
-pub fn get_authorization_info() -> Vec<ModuleInfo> {
+pub fn get_module_authorization_info() -> Vec<ModuleInfo> {
     PermissionModule::iter()
         .map(|module| ModuleInfo::new(&module))
         .collect()
 }
 
-pub struct PermissionInfo {
-    pub enum_name: Permission,
-    pub description: &'static str,
+pub fn get_group_authorization_info() -> Vec<GroupInfo> {
+    PermissionGroup::iter()
+        .map(get_group_info_from_permission_group)
+        .collect()
 }
 
-impl PermissionInfo {
-    pub fn new(permissions: &[Permission]) -> Vec<Self> {
-        permissions
-            .iter()
-            .map(|&per| Self {
-                description: Permission::get_permission_description(&per),
-                enum_name: per,
-            })
-            .collect()
-    }
+pub fn get_permission_info_from_permissions(permissions: &[Permission]) -> Vec<PermissionInfo> {
+    permissions
+        .iter()
+        .map(|&per| PermissionInfo {
+            description: Permission::get_permission_description(&per),
+            enum_name: per.into(),
+        })
+        .collect()
 }
 
+// TODO: Deprecate once groups are stable
 #[derive(PartialEq, EnumIter, Clone)]
 pub enum PermissionModule {
     Payments,
@@ -60,6 +62,7 @@ impl PermissionModule {
     }
 }
 
+// TODO: Deprecate once groups are stable
 pub struct ModuleInfo {
     pub module: PermissionModule,
     pub description: &'static str,
@@ -75,7 +78,7 @@ impl ModuleInfo {
             PermissionModule::Payments => Self {
                 module: module_name,
                 description,
-                permissions: PermissionInfo::new(&[
+                permissions: get_permission_info_from_permissions(&[
                     Permission::PaymentRead,
                     Permission::PaymentWrite,
                 ]),
@@ -83,7 +86,7 @@ impl ModuleInfo {
             PermissionModule::Refunds => Self {
                 module: module_name,
                 description,
-                permissions: PermissionInfo::new(&[
+                permissions: get_permission_info_from_permissions(&[
                     Permission::RefundRead,
                     Permission::RefundWrite,
                 ]),
@@ -91,7 +94,7 @@ impl ModuleInfo {
             PermissionModule::MerchantAccount => Self {
                 module: module_name,
                 description,
-                permissions: PermissionInfo::new(&[
+                permissions: get_permission_info_from_permissions(&[
                     Permission::MerchantAccountRead,
                     Permission::MerchantAccountWrite,
                 ]),
@@ -99,7 +102,7 @@ impl ModuleInfo {
             PermissionModule::Connectors => Self {
                 module: module_name,
                 description,
-                permissions: PermissionInfo::new(&[
+                permissions: get_permission_info_from_permissions(&[
                     Permission::MerchantConnectorAccountRead,
                     Permission::MerchantConnectorAccountWrite,
                 ]),
@@ -107,7 +110,7 @@ impl ModuleInfo {
             PermissionModule::Routing => Self {
                 module: module_name,
                 description,
-                permissions: PermissionInfo::new(&[
+                permissions: get_permission_info_from_permissions(&[
                     Permission::RoutingRead,
                     Permission::RoutingWrite,
                 ]),
@@ -115,12 +118,12 @@ impl ModuleInfo {
             PermissionModule::Analytics => Self {
                 module: module_name,
                 description,
-                permissions: PermissionInfo::new(&[Permission::Analytics]),
+                permissions: get_permission_info_from_permissions(&[Permission::Analytics]),
             },
             PermissionModule::Mandates => Self {
                 module: module_name,
                 description,
-                permissions: PermissionInfo::new(&[
+                permissions: get_permission_info_from_permissions(&[
                     Permission::MandateRead,
                     Permission::MandateWrite,
                 ]),
@@ -128,7 +131,7 @@ impl ModuleInfo {
             PermissionModule::Customer => Self {
                 module: module_name,
                 description,
-                permissions: PermissionInfo::new(&[
+                permissions: get_permission_info_from_permissions(&[
                     Permission::CustomerRead,
                     Permission::CustomerWrite,
                 ]),
@@ -136,7 +139,7 @@ impl ModuleInfo {
             PermissionModule::Disputes => Self {
                 module: module_name,
                 description,
-                permissions: PermissionInfo::new(&[
+                permissions: get_permission_info_from_permissions(&[
                     Permission::DisputeRead,
                     Permission::DisputeWrite,
                 ]),
@@ -144,7 +147,7 @@ impl ModuleInfo {
             PermissionModule::ThreeDsDecisionManager => Self {
                 module: module_name,
                 description,
-                permissions: PermissionInfo::new(&[
+                permissions: get_permission_info_from_permissions(&[
                     Permission::ThreeDsDecisionManagerRead,
                     Permission::ThreeDsDecisionManagerWrite,
                 ]),
@@ -153,7 +156,7 @@ impl ModuleInfo {
             PermissionModule::SurchargeDecisionManager => Self {
                 module: module_name,
                 description,
-                permissions: PermissionInfo::new(&[
+                permissions: get_permission_info_from_permissions(&[
                     Permission::SurchargeDecisionManagerRead,
                     Permission::SurchargeDecisionManagerWrite,
                 ]),
@@ -161,8 +164,46 @@ impl ModuleInfo {
             PermissionModule::AccountCreate => Self {
                 module: module_name,
                 description,
-                permissions: PermissionInfo::new(&[Permission::MerchantAccountCreate]),
+                permissions: get_permission_info_from_permissions(&[
+                    Permission::MerchantAccountCreate,
+                ]),
             },
         }
     }
 }
+
+fn get_group_info_from_permission_group(group: PermissionGroup) -> GroupInfo {
+    let description = get_group_description(group);
+    GroupInfo {
+        group,
+        description,
+        permissions: get_permission_info_from_permissions(get_permissions_vec(&group)),
+    }
+}
+
+fn get_group_description(group: PermissionGroup) -> &'static str {
+    match group {
+        PermissionGroup::OperationsView => {
+            "View Payments, Refunds, Mandates, Disputes and Customers"
+        }
+        PermissionGroup::OperationsManage => {
+            "Create,modify and delete Payments, Refunds, Mandates, Disputes and Customers"
+        }
+        PermissionGroup::ConnectorsView => {
+            "View connected Payment Processors, Payout Processors and Fraud & Risk Manager details"
+        }
+        PermissionGroup::ConnectorsManage => "Create, modify and delete connectors like Payment Processors, Payout Processors and Fraud & Risk Manager",
+        PermissionGroup::WorkflowsView => {
+            "View Routing, 3DS Decision Manager, Surcharge Decision Manager"
+        }
+        PermissionGroup::WorkflowsManage => {
+            "Create, modify and delete Routing, 3DS Decision Manager, Surcharge Decision Manager"
+        }
+        PermissionGroup::AnalyticsView => "View Analytics",
+        PermissionGroup::UsersView => "View Users",
+        PermissionGroup::UsersManage => "Manage and invite Users to the Team",
+        PermissionGroup::MerchantDetailsView => "View Merchant Details",
+        PermissionGroup::MerchantDetailsManage => "Create, modify and delete Merchant Details like api keys, webhooks, etc",
+        PermissionGroup::OrganizationManage => "Manage organization level tasks like create new Merchant accounts, Organization level roles, etc",
+    }
+}
diff --git a/crates/router/src/services/authorization/roles.rs b/crates/router/src/services/authorization/roles.rs
index 6372799f98c..f99892c565d 100644
--- a/crates/router/src/services/authorization/roles.rs
+++ b/crates/router/src/services/authorization/roles.rs
@@ -65,22 +65,22 @@ impl RoleInfo {
             .iter()
             .any(|group| get_permissions_vec(group).contains(required_permission))
     }
-}
 
-pub async fn get_role_info_from_role_id(
-    state: &AppState,
-    role_id: &str,
-    merchant_id: &str,
-    org_id: &str,
-) -> CustomResult<RoleInfo, errors::StorageError> {
-    if let Some(role) = predefined_roles::PREDEFINED_ROLES.get(role_id) {
-        Ok(role.clone())
-    } else {
-        state
-            .store
-            .find_role_by_role_id_in_merchant_scope(role_id, merchant_id, org_id)
-            .await
-            .map(RoleInfo::from)
+    pub async fn from_role_id(
+        state: &AppState,
+        role_id: &str,
+        merchant_id: &str,
+        org_id: &str,
+    ) -> CustomResult<Self, errors::StorageError> {
+        if let Some(role) = predefined_roles::PREDEFINED_ROLES.get(role_id) {
+            Ok(role.clone())
+        } else {
+            state
+                .store
+                .find_role_by_role_id_in_merchant_scope(role_id, merchant_id, org_id)
+                .await
+                .map(Self::from)
+        }
     }
 }
 
diff --git a/crates/router/src/types/domain/user.rs b/crates/router/src/types/domain/user.rs
index a759bf00806..d6e878456e8 100644
--- a/crates/router/src/types/domain/user.rs
+++ b/crates/router/src/types/domain/user.rs
@@ -811,15 +811,6 @@ impl From<info::PermissionModule> for user_role_api::PermissionModule {
     }
 }
 
-impl From<info::PermissionInfo> for user_role_api::PermissionInfo {
-    fn from(value: info::PermissionInfo) -> Self {
-        Self {
-            enum_name: value.enum_name.into(),
-            description: value.description,
-        }
-    }
-}
-
 pub enum SignInWithRoleStrategyType {
     SingleRole(SignInWithSingleRoleStrategy),
     MultipleRoles(SignInWithMultipleRolesStrategy),
diff --git a/crates/router/src/utils/user.rs b/crates/router/src/utils/user.rs
index e9b7143a26e..c3b795d1a57 100644
--- a/crates/router/src/utils/user.rs
+++ b/crates/router/src/utils/user.rs
@@ -64,7 +64,7 @@ impl UserFromToken {
     }
 
     pub async fn get_role_info_from_db(&self, state: &AppState) -> UserResult<RoleInfo> {
-        roles::get_role_info_from_role_id(state, &self.role_id, &self.merchant_id, &self.org_id)
+        roles::RoleInfo::from_role_id(state, &self.role_id, &self.merchant_id, &self.org_id)
             .await
             .change_context(UserErrors::InternalServerError)
     }

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

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

## Description
<!-- Describe your changes in detail -->
Change `list_roles`, `get_role` and `get_authorization_info` api to respond with groups if asked.

### Additional Changes

- [ ] This PR modifies the API contract
- [ ] 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
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless it is an obvious bug or documentation fix
that will have little conversation).
-->
closes #3836 

## How did you test it?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->
All the following APIs have a query param called groups, setting it to true will return the following response.

1. Get role list
	```
	curl --location 'http://localhost:8080/user/role/list?groups=true' \
	--header 'Authorization: Bearer JWT'
	```
	Response
	```
	[
	    {
	        "role_id": "merchant_iam_admin",
	        "groups": [
	            "operations_view",
	            "analytics_view",
	            "users_view",
	            "users_manage",
	            "merchant_details_view"
	        ],
	        "role_name": "IAM",
	        "role_scope": "organization"
	    },
	    {
	        "role_id": "merchant_customer_support",
	        "groups": [
	            "operations_view",
	            "analytics_view",
	            "users_view",
	            "merchant_details_view"
	        ],
	        "role_name": "Customer Support",
	        "role_scope": "organization"
	    },
	    {
	        "role_id": "merchant_admin",
	        "groups": [
	            "operations_view",
	            "operations_manage",
	            "connectors_view",
	            "connectors_manage",
	            "workflows_view",
	            "workflows_manage",
	            "analytics_view",
	            "users_view",
	            "users_manage",
	            "merchant_details_view",
	            "merchant_details_manage"
	        ],
	        "role_name": "Admin",
	        "role_scope": "organization"
	    },
	    {
	        "role_id": "merchant_developer",
	        "groups": [
	            "operations_view",
	            "connectors_view",
	            "analytics_view",
	            "users_view",
	            "merchant_details_view",
	            "merchant_details_manage"
	        ],
	        "role_name": "Developer",
	        "role_scope": "organization"
	    },
	    {
	        "role_id": "merchant_view_only",
	        "groups": [
	            "operations_view",
	            "connectors_view",
	            "workflows_view",
	            "analytics_view",
	            "users_view",
	            "merchant_details_view"
	        ],
	        "role_name": "View Only",
	        "role_scope": "organization"
	    },
	    {
	        "role_id": "merchant_operator",
	        "groups": [
	            "operations_view",
	            "operations_manage",
	            "connectors_view",
	            "workflows_view",
	            "analytics_view",
	            "users_view",
	            "merchant_details_view"
	        ],
	        "role_name": "Operator",
	        "role_scope": "organization"
	    },
	    {
	        "role_id": "role_01JlOAdLoUqtynjUfEV4",
	        "groups": [
	            "users_view"
	        ],
	        "role_name": "custom_role",
	        "role_scope": "merchant"
	    }
	]
	```
2. Get role info
	```
	curl --location 'http://localhost:8080/user/role/role_01JlOAdLoUqtynjUfEV4?groups=true' \
	--header 'Authorization: Bearer JWT'
	```
	Response
	```
	{
	    "role_id": "role_01JlOAdLoUqtynjUfEV4",
	    "groups": [
	        "users_view"
	    ],
	    "role_name": "custom_role",
	    "role_scope": "merchant"
	}
	```
3. Permission Info
	```
	curl --location 'http://localhost:8080/user/permission_info?groups=true' \
	--header 'Authorization: Bearer JWT'
	```
	Response
	```
	[
	    {
	        "group": "operations_view",
	        "description": "View Payments, Refunds, Mandates, Disputes and Customers",
	        "permissions": [
	            {
	                "enum_name": "PaymentRead",
	                "description": "View all payments"
	            },
	            {
	                "enum_name": "RefundRead",
	                "description": "View all refunds"
	            },
	            {
	                "enum_name": "MandateRead",
	                "description": "View mandates"
	            },
	            {
	                "enum_name": "DisputeRead",
	                "description": "View disputes"
	            },
	            {
	                "enum_name": "CustomerRead",
	                "description": "View customers"
	            },
	            {
	                "enum_name": "MerchantAccountRead",
	                "description": "View merchant account details"
	            }
	        ]
	    },
	    {
	        "group": "operations_manage",
	        "description": "Create,modify and delete Payments, Refunds, Mandates, Disputes and Customers",
	        "permissions": [
	            {
	                "enum_name": "PaymentWrite",
	                "description": "Create payment, download payments data"
	            },
	            {
	                "enum_name": "RefundWrite",
	                "description": "Create refund, download refunds data"
	            },
	            {
	                "enum_name": "MandateWrite",
	                "description": "Create and update mandates"
	            },
	            {
	                "enum_name": "DisputeWrite",
	                "description": "Create and update disputes"
	            },
	            {
	                "enum_name": "CustomerWrite",
	                "description": "Create, update and delete customers"
	            },
	            {
	                "enum_name": "MerchantAccountRead",
	                "description": "View merchant account details"
	            }
	        ]
	    },
	    {
	        "group": "connectors_view",
	        "description": "View connected Payment Processors, Payout Processors and Fraud & Risk Manager details",
	        "permissions": [
	            {
	                "enum_name": "MerchantConnectorAccountRead",
	                "description": "View connectors configured"
	            },
	            {
	                "enum_name": "MerchantAccountRead",
	                "description": "View merchant account details"
	            }
	        ]
	    },
	    {
	        "group": "connectors_manage",
	        "description": "Create, modify and delete connectors like Payment Processors, Payout Processors and Fraud & Risk Manager",
	        "permissions": [
	            {
	                "enum_name": "MerchantConnectorAccountWrite",
	                "description": "Create, update, verify and delete connector configurations"
	            },
	            {
	                "enum_name": "MerchantAccountRead",
	                "description": "View merchant account details"
	            }
	        ]
	    },
	    {
	        "group": "workflows_view",
	        "description": "View Routing, 3DS Decision Manager, Surcharge Decision Manager",
	        "permissions": [
	            {
	                "enum_name": "RoutingRead",
	                "description": "View routing configuration"
	            },
	            {
	                "enum_name": "ThreeDsDecisionManagerRead",
	                "description": "View all 3DS decision rules configured for a merchant"
	            },
	            {
	                "enum_name": "SurchargeDecisionManagerRead",
	                "description": "View all the surcharge decision rules"
	            },
	            {
	                "enum_name": "MerchantConnectorAccountRead",
	                "description": "View connectors configured"
	            },
	            {
	                "enum_name": "MerchantAccountRead",
	                "description": "View merchant account details"
	            }
	        ]
	    },
	    {
	        "group": "workflows_manage",
	        "description": "Create, modify and delete Routing, 3DS Decision Manager, Surcharge Decision Manager",
	        "permissions": [
	            {
	                "enum_name": "RoutingWrite",
	                "description": "Create and activate routing configurations"
	            },
	            {
	                "enum_name": "ThreeDsDecisionManagerWrite",
	                "description": "Create and update 3DS decision rules"
	            },
	            {
	                "enum_name": "SurchargeDecisionManagerWrite",
	                "description": "Create and update the surcharge decision rules"
	            },
	            {
	                "enum_name": "MerchantConnectorAccountRead",
	                "description": "View connectors configured"
	            },
	            {
	                "enum_name": "MerchantAccountRead",
	                "description": "View merchant account details"
	            }
	        ]
	    },
	    {
	        "group": "analytics_view",
	        "description": "View Analytics",
	        "permissions": [
	            {
	                "enum_name": "Analytics",
	                "description": "Access to analytics module"
	            },
	            {
	                "enum_name": "MerchantAccountRead",
	                "description": "View merchant account details"
	            }
	        ]
	    },
	    {
	        "group": "users_view",
	        "description": "View Users",
	        "permissions": [
	            {
	                "enum_name": "UsersRead",
	                "description": "View all the users for a merchant"
	            },
	            {
	                "enum_name": "MerchantAccountRead",
	                "description": "View merchant account details"
	            }
	        ]
	    },
	    {
	        "group": "users_manage",
	        "description": "Manage and invite Users to the Team",
	        "permissions": [
	            {
	                "enum_name": "UsersWrite",
	                "description": "Invite users, assign and update roles"
	            },
	            {
	                "enum_name": "MerchantAccountRead",
	                "description": "View merchant account details"
	            }
	        ]
	    },
	    {
	        "group": "merchant_details_view",
	        "description": "View Merchant Details",
	        "permissions": [
	            {
	                "enum_name": "MerchantAccountRead",
	                "description": "View merchant account details"
	            }
	        ]
	    },
	    {
	        "group": "merchant_details_manage",
	        "description": "Create, modify and delete Merchant Details like api keys, webhooks, etc",
	        "permissions": [
	            {
	                "enum_name": "MerchantAccountWrite",
	                "description": "Update merchant account details, configure webhooks, manage api keys"
	            },
	            {
	                "enum_name": "ApiKeyRead",
	                "description": "View API keys (masked generated for the system"
	            },
	            {
	                "enum_name": "ApiKeyWrite",
	                "description": "Create and update API keys"
	            },
	            {
	                "enum_name": "MerchantAccountRead",
	                "description": "View merchant account details"
	            }
	        ]
	    },
	    {
	        "group": "organization_manage",
	        "description": "Manage organization level tasks like create new Merchant accounts, Organization level roles, etc",
	        "permissions": [
	            {
	                "enum_name": "MerchantAccountCreate",
	                "description": "Create merchant account"
	            },
	            {
	                "enum_name": "MerchantAccountRead",
	                "description": "View merchant account details"
	            }
	        ]
	    }
	]
	```

## 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
- [ ] I added a [CHANGELOG](/CHANGELOG.md) entry if applicable
