Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(users): handle edge features for users in tenancy #6990

Merged
merged 9 commits into from
Jan 8, 2025
37 changes: 25 additions & 12 deletions crates/diesel_models/src/query/role.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,18 @@ impl Role {
role_id: &str,
merchant_id: &id_type::MerchantId,
org_id: &id_type::OrganizationId,
tenant_id: &id_type::TenantId,
) -> StorageResult<Self> {
generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
conn,
dsl::role_id.eq(role_id.to_owned()).and(
dsl::merchant_id.eq(merchant_id.to_owned()).or(dsl::org_id
.eq(org_id.to_owned())
.and(dsl::scope.eq(RoleScope::Organization))),
),
dsl::role_id
.eq(role_id.to_owned())
.and(dsl::tenant_id.eq(tenant_id.to_owned()))
.and(
dsl::merchant_id.eq(merchant_id.to_owned()).or(dsl::org_id
.eq(org_id.to_owned())
.and(dsl::scope.eq(RoleScope::Organization))),
),
)
.await
}
Expand All @@ -49,11 +53,13 @@ impl Role {
role_id: &str,
merchant_id: &id_type::MerchantId,
org_id: &id_type::OrganizationId,
tenant_id: &id_type::TenantId,
) -> StorageResult<Self> {
generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
conn,
dsl::role_id
.eq(role_id.to_owned())
.and(dsl::tenant_id.eq(tenant_id.to_owned()))
.and(dsl::org_id.eq(org_id.to_owned()))
.and(
dsl::scope.eq(RoleScope::Organization).or(dsl::merchant_id
Expand All @@ -64,15 +70,17 @@ impl Role {
.await
}

pub async fn find_by_role_id_and_org_id(
pub async fn find_by_role_id_org_id_tenant_id(
conn: &PgPooledConn,
role_id: &str,
org_id: &id_type::OrganizationId,
tenant_id: &id_type::TenantId,
) -> StorageResult<Self> {
generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
conn,
dsl::role_id
.eq(role_id.to_owned())
.and(dsl::tenant_id.eq(tenant_id.to_owned()))
.and(dsl::org_id.eq(org_id.to_owned())),
)
.await
Expand Down Expand Up @@ -108,12 +116,16 @@ impl Role {
conn: &PgPooledConn,
merchant_id: &id_type::MerchantId,
org_id: &id_type::OrganizationId,
tenant_id: &id_type::TenantId,
) -> StorageResult<Vec<Self>> {
let predicate = dsl::org_id.eq(org_id.to_owned()).and(
dsl::scope.eq(RoleScope::Organization).or(dsl::merchant_id
.eq(merchant_id.to_owned())
.and(dsl::scope.eq(RoleScope::Merchant))),
);
let predicate = dsl::tenant_id
.eq(tenant_id.to_owned())
.and(dsl::org_id.eq(org_id.to_owned()))
.and(
dsl::scope.eq(RoleScope::Organization).or(dsl::merchant_id
.eq(merchant_id.to_owned())
.and(dsl::scope.eq(RoleScope::Merchant))),
);

generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
conn,
Expand All @@ -127,13 +139,14 @@ impl Role {

pub async fn generic_roles_list_for_org(
conn: &PgPooledConn,
tenant_id: id_type::TenantId,
org_id: id_type::OrganizationId,
merchant_id: Option<id_type::MerchantId>,
entity_type: Option<common_enums::EntityType>,
limit: Option<u32>,
) -> StorageResult<Vec<Self>> {
let mut query = <Self as HasTable>::table()
.filter(dsl::org_id.eq(org_id))
.filter(dsl::tenant_id.eq(tenant_id).and(dsl::org_id.eq(org_id)))
.into_boxed();

if let Some(merchant_id) = merchant_id {
Expand Down
30 changes: 30 additions & 0 deletions crates/diesel_models/src/query/user_role.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,4 +294,34 @@ impl UserRole {
},
}
}

pub async fn list_user_roles_by_user_id_across_tenants(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this required ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will be using this query in future, to search for a user across tenancies.

conn: &PgPooledConn,
user_id: String,
limit: Option<u32>,
) -> StorageResult<Vec<Self>> {
let mut query = <Self as HasTable>::table()
.filter(dsl::user_id.eq(user_id))
.into_boxed();
if let Some(limit) = limit {
query = query.limit(limit.into());
}

router_env::logger::debug!(query = %debug_query::<Pg,_>(&query).to_string());

match generics::db_metrics::track_database_call::<Self, _, _>(
query.get_results_async(conn),
generics::db_metrics::DatabaseOperation::Filter,
)
.await
{
Ok(value) => Ok(value),
Err(err) => match err {
DieselError::NotFound => {
Err(report!(err)).change_context(errors::DatabaseError::NotFound)
}
_ => Err(report!(err)).change_context(errors::DatabaseError::Others),
},
}
}
}
2 changes: 2 additions & 0 deletions crates/diesel_models/src/role.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub struct Role {
pub last_modified_at: PrimitiveDateTime,
pub last_modified_by: String,
pub entity_type: enums::EntityType,
pub tenant_id: id_type::TenantId,
}

#[derive(router_derive::Setter, Clone, Debug, Insertable, router_derive::DebugAsDisplay)]
Expand All @@ -36,6 +37,7 @@ pub struct RoleNew {
pub last_modified_at: PrimitiveDateTime,
pub last_modified_by: String,
pub entity_type: enums::EntityType,
pub tenant_id: id_type::TenantId,
}

#[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay)]
Expand Down
2 changes: 2 additions & 0 deletions crates/diesel_models/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1308,6 +1308,8 @@ diesel::table! {
last_modified_by -> Varchar,
#[max_length = 64]
entity_type -> Varchar,
#[max_length = 64]
tenant_id -> Varchar,
}
}

Expand Down
2 changes: 2 additions & 0 deletions crates/diesel_models/src/schema_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1255,6 +1255,8 @@ diesel::table! {
last_modified_by -> Varchar,
#[max_length = 64]
entity_type -> Varchar,
#[max_length = 64]
tenant_id -> Varchar,
}
}

Expand Down
164 changes: 110 additions & 54 deletions crates/router/src/analytics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1925,32 +1925,57 @@ pub mod routes {
json_payload.into_inner(),
|state, auth: UserFromToken, req, _| async move {
let role_id = auth.role_id;
let role_info = RoleInfo::from_role_id_and_org_id(&state, &role_id, &auth.org_id)
.await
.change_context(UserErrors::InternalServerError)
.change_context(OpenSearchError::UnknownError)?;
let role_info = RoleInfo::from_role_id_org_id_tenant_id(
&state,
&role_id,
&auth.org_id,
auth.tenant_id.as_ref().unwrap_or(&state.tenant.tenant_id),
)
.await
.change_context(UserErrors::InternalServerError)
.change_context(OpenSearchError::UnknownError)?;
let permission_groups = role_info.get_permission_groups();
if !permission_groups.contains(&common_enums::PermissionGroup::OperationsView) {
return Err(OpenSearchError::AccessForbiddenError)?;
}
let user_roles: HashSet<UserRole> = state
.global_store
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
user_id: &auth.user_id,
tenant_id: auth.tenant_id.as_ref().unwrap_or(&state.tenant.tenant_id),
org_id: Some(&auth.org_id),
merchant_id: None,
profile_id: None,
entity_id: None,
version: None,
status: None,
limit: None,
})
.await
.change_context(UserErrors::InternalServerError)
.change_context(OpenSearchError::UnknownError)?
.into_iter()
.collect();
let user_roles: HashSet<UserRole> = match role_info.get_entity_type() {
EntityType::Tenant => state
.global_store
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
user_id: &auth.user_id,
tenant_id: auth.tenant_id.as_ref().unwrap_or(&state.tenant.tenant_id),
org_id: None,
merchant_id: None,
profile_id: None,
entity_id: None,
version: None,
status: None,
limit: None,
})
.await
.change_context(UserErrors::InternalServerError)
.change_context(OpenSearchError::UnknownError)?
.into_iter()
.collect(),
EntityType::Organization | EntityType::Merchant | EntityType::Profile => state
.global_store
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
user_id: &auth.user_id,
tenant_id: auth.tenant_id.as_ref().unwrap_or(&state.tenant.tenant_id),
org_id: Some(&auth.org_id),
merchant_id: None,
profile_id: None,
entity_id: None,
version: None,
status: None,
limit: None,
})
.await
.change_context(UserErrors::InternalServerError)
.change_context(OpenSearchError::UnknownError)?
.into_iter()
.collect(),
};

let state = Arc::new(state);
let role_info_map: HashMap<String, RoleInfo> = user_roles
Expand All @@ -1959,12 +1984,15 @@ pub mod routes {
let state = Arc::clone(&state);
let role_id = user_role.role_id.clone();
let org_id = user_role.org_id.clone().unwrap_or_default();
let tenant_id = &user_role.tenant_id;
async move {
RoleInfo::from_role_id_and_org_id(&state, &role_id, &org_id)
.await
.change_context(UserErrors::InternalServerError)
.change_context(OpenSearchError::UnknownError)
.map(|role_info| (role_id, role_info))
RoleInfo::from_role_id_org_id_tenant_id(
&state, &role_id, &org_id, tenant_id,
)
.await
.change_context(UserErrors::InternalServerError)
.change_context(OpenSearchError::UnknownError)
.map(|role_info| (role_id, role_info))
}
})
.collect::<FuturesUnordered<_>>()
Expand Down Expand Up @@ -2047,45 +2075,73 @@ pub mod routes {
indexed_req,
|state, auth: UserFromToken, req, _| async move {
let role_id = auth.role_id;
let role_info = RoleInfo::from_role_id_and_org_id(&state, &role_id, &auth.org_id)
.await
.change_context(UserErrors::InternalServerError)
.change_context(OpenSearchError::UnknownError)?;
let role_info = RoleInfo::from_role_id_org_id_tenant_id(
&state,
&role_id,
&auth.org_id,
auth.tenant_id.as_ref().unwrap_or(&state.tenant.tenant_id),
tsdk02 marked this conversation as resolved.
Show resolved Hide resolved
)
.await
.change_context(UserErrors::InternalServerError)
.change_context(OpenSearchError::UnknownError)?;
let permission_groups = role_info.get_permission_groups();
if !permission_groups.contains(&common_enums::PermissionGroup::OperationsView) {
return Err(OpenSearchError::AccessForbiddenError)?;
}
let user_roles: HashSet<UserRole> = state
.global_store
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
user_id: &auth.user_id,
tenant_id: auth.tenant_id.as_ref().unwrap_or(&state.tenant.tenant_id),
org_id: Some(&auth.org_id),
merchant_id: None,
profile_id: None,
entity_id: None,
version: None,
status: None,
limit: None,
})
.await
.change_context(UserErrors::InternalServerError)
.change_context(OpenSearchError::UnknownError)?
.into_iter()
.collect();
let user_roles: HashSet<UserRole> = match role_info.get_entity_type() {
EntityType::Tenant => state
.global_store
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
user_id: &auth.user_id,
tenant_id: auth.tenant_id.as_ref().unwrap_or(&state.tenant.tenant_id),
org_id: None,
merchant_id: None,
profile_id: None,
entity_id: None,
version: None,
status: None,
limit: None,
})
.await
.change_context(UserErrors::InternalServerError)
.change_context(OpenSearchError::UnknownError)?
.into_iter()
.collect(),
EntityType::Organization | EntityType::Merchant | EntityType::Profile => state
.global_store
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
user_id: &auth.user_id,
tenant_id: auth.tenant_id.as_ref().unwrap_or(&state.tenant.tenant_id),
org_id: Some(&auth.org_id),
merchant_id: None,
profile_id: None,
entity_id: None,
version: None,
status: None,
limit: None,
})
.await
.change_context(UserErrors::InternalServerError)
.change_context(OpenSearchError::UnknownError)?
.into_iter()
.collect(),
};
let state = Arc::new(state);
let role_info_map: HashMap<String, RoleInfo> = user_roles
.iter()
.map(|user_role| {
let state = Arc::clone(&state);
let role_id = user_role.role_id.clone();
let org_id = user_role.org_id.clone().unwrap_or_default();
let tenant_id = &user_role.tenant_id;
async move {
RoleInfo::from_role_id_and_org_id(&state, &role_id, &org_id)
.await
.change_context(UserErrors::InternalServerError)
.change_context(OpenSearchError::UnknownError)
.map(|role_info| (role_id, role_info))
RoleInfo::from_role_id_org_id_tenant_id(
&state, &role_id, &org_id, tenant_id,
)
.await
.change_context(UserErrors::InternalServerError)
.change_context(OpenSearchError::UnknownError)
.map(|role_info| (role_id, role_info))
}
})
.collect::<FuturesUnordered<_>>()
Expand Down
Loading
Loading