Skip to content

Commit

Permalink
handle inactive users
Browse files Browse the repository at this point in the history
  • Loading branch information
fiji-flo committed Sep 24, 2020
1 parent d9433a5 commit 8c8e88f
Show file tree
Hide file tree
Showing 8 changed files with 207 additions and 7 deletions.
13 changes: 12 additions & 1 deletion migrations/2020-02-14-142048_groupslist/up.sql
Original file line number Diff line number Diff line change
@@ -1 +1,12 @@
CREATE VIEW groups_list AS SELECT groups.name, groups.typ, groups.trust, count(memberships.user_uuid) as members_count FROM groups JOIN memberships ON groups.group_id = memberships.group_id GROUP BY groups.group_id;
CREATE VIEW groups_list AS
SELECT
groups.name,
groups.typ,
groups.trust,
count(memberships.user_uuid) AS members_count
FROM
GROUPS
JOIN memberships ON groups.group_id = memberships.group_id
GROUP BY
groups.group_id;

1 change: 0 additions & 1 deletion migrations/2020-08-25-110359_nonnda/up.sql
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,3 @@ FROM
users_staff u
WHERE
p.user_uuid = u.user_uuid;

11 changes: 11 additions & 0 deletions src/api/sudo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,16 @@ async fn delete_inactive_group(
.map_err(ApiError::GenericBadRequest)
}

#[guard(Staff, Admin, Medium)]
async fn delete_inactive_users(
pool: web::Data<Pool>,
scope_and_user: ScopeAndUser,
) -> Result<HttpResponse, ApiError> {
operations::users::delete_inactive_users(&pool, &scope_and_user)
.map(|_| HttpResponse::Ok().json(""))
.map_err(ApiError::GenericBadRequest)
}

#[guard(Staff, Admin, Medium)]
async fn subscribe_nda_mailing_list(
pool: web::Data<Pool>,
Expand Down Expand Up @@ -272,6 +282,7 @@ pub fn sudo_app<T: AsyncCisClientTrait + 'static>() -> impl HttpServiceFactory {
web::resource("/member/{group_name}/{user_uuid}")
.route(web::delete().to(remove_member::<T>)),
)
.service(web::resource("/user/inactive").route(web::delete().to(delete_inactive_users)))
.service(web::resource("/user/{uuid}").route(web::delete().to(delete_user)))
.service(web::resource("/user/uuids/staff").route(web::get().to(all_staff_uuids)))
.service(web::resource("/user/uuids/members").route(web::get().to(all_member_uuids)))
Expand Down
37 changes: 37 additions & 0 deletions src/db/internal/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ pub fn user_profile_by_user_id(
}

pub fn delete_user(connection: &PgConnection, user: &User) -> Result<(), Error> {
diesel::delete(schema::requests::table)
.filter(schema::requests::user_uuid.eq(user.user_uuid))
.execute(connection)?;
diesel::delete(schema::invitations::table)
.filter(schema::invitations::user_uuid.eq(user.user_uuid))
.execute(connection)?;
Expand Down Expand Up @@ -420,3 +423,37 @@ pub fn all_members(connection: &PgConnection) -> Result<Vec<Uuid>, Error> {
.get_results::<Uuid>(connection)
.map_err(Into::into)
}

use diesel::pg::expression::dsl::array;
use diesel::pg::types::sql_types::Array;
use diesel::pg::types::sql_types::Jsonb;
use diesel::pg::Pg;
use diesel::sql_types::Text;

sql_function! {
fn jsonb_extract_path(from_json: Jsonb, path_elems: Array<Text>) -> Jsonb
}

diesel_infix_operator!(ExtrPath, " #> ", Jsonb, backend: Pg);

fn extr_path<T, U>(left: T, right: U) -> ExtrPath<T, U>
where
T: Expression,
U: Expression,
{
ExtrPath::new(left, right)
}

pub fn all_inactive(connection: &PgConnection) -> Result<Vec<Uuid>, Error> {
schema::profiles::table
.filter(
extr_path(
schema::profiles::profile,
array::<Text, _>(("active".to_string(), "value".to_string())),
)
.eq(serde_json::Value::from(false)),
)
.select(schema::profiles::user_uuid)
.get_results::<Uuid>(connection)
.map_err(Into::into)
}
41 changes: 36 additions & 5 deletions src/db/operations/users.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::cis::operations::send_groups_to_cis;
use crate::db::internal;
use crate::db::logs::log_comment_body;
use crate::db::operations::members::revoke_memberships_by_trust;
Expand All @@ -17,6 +18,7 @@ use cis_client::AsyncCisClientTrait;
use cis_profile::schema::Profile;
use dino_park_gate::scope::ScopeAndUser;
use failure::Error;
use log::info;
use std::sync::Arc;
use uuid::Uuid;

Expand Down Expand Up @@ -116,17 +118,20 @@ pub async fn update_user_cache(
profile: &Profile,
cis_client: Arc<impl AsyncCisClientTrait>,
) -> Result<(), Error> {
let user_uuid = Uuid::parse_str(&profile.uuid.value.clone().ok_or(PacksError::NoUuid)?)?;
if profile.active.value == Some(false) {
return delete_user(pool, &User { user_uuid });
}
let connection = pool.get()?;
let new_trust = trust_for_profile(&profile);
let uuid = Uuid::parse_str(&profile.uuid.value.clone().ok_or(PacksError::NoUuid)?)?;
let old_profile = internal::user::user_profile_by_uuid_maybe(&connection, &uuid)?;
let old_profile = internal::user::user_profile_by_uuid_maybe(&connection, &user_uuid)?;
internal::user::update_user_cache(&connection, profile)?;

if let Some(old_profile) = old_profile {
let old_trust = trust_for_profile(&old_profile.profile);
drop(connection);
let remove_groups = RemoveGroups {
user: User { user_uuid: uuid },
user: User { user_uuid },
group_names: &[],
force: true,
notify: true,
Expand All @@ -142,7 +147,12 @@ pub async fn update_user_cache(
)
.await?;
}
} else if let Some(ref groups) = profile.access_information.mozilliansorg.values {
if !groups.0.is_empty() {
send_groups_to_cis(pool, cis_client, &user_uuid).await?;
}
}

Ok(())
}

Expand All @@ -156,13 +166,32 @@ pub fn user_profile_by_uuid(pool: &Pool, user_uuid: &Uuid) -> Result<UserProfile
internal::user::user_profile_by_uuid(&connection, user_uuid)
}

pub fn delete_inactive_users(pool: &Pool, scope_and_user: &ScopeAndUser) -> Result<(), Error> {
let connection = pool.get()?;
let host = internal::user::user_by_id(&connection, &scope_and_user.user_id)?;
ONLY_ADMINS.run(&RuleContext::minimal(
pool,
&scope_and_user,
"",
&host.user_uuid,
))?;
let inactive_uuids = internal::user::all_inactive(&connection)?;
drop(connection);
info!("deleting {} users", inactive_uuids.len());
for user_uuid in inactive_uuids {
delete_user(pool, &User { user_uuid })?;
info!("delete user {}", user_uuid);
}
Ok(())
}

pub fn get_all_member_uuids(
pool: &Pool,
scope_and_user: &ScopeAndUser,
) -> Result<Vec<Uuid>, Error> {
let connection = pool.get()?;
let host = internal::user::user_by_id(&connection, &scope_and_user.user_id)?;
SEARCH_USERS.run(&RuleContext::minimal(
ONLY_ADMINS.run(&RuleContext::minimal(
pool,
&scope_and_user,
"",
Expand All @@ -174,11 +203,13 @@ pub fn get_all_member_uuids(
pub fn get_all_staff_uuids(pool: &Pool, scope_and_user: &ScopeAndUser) -> Result<Vec<Uuid>, Error> {
let connection = pool.get()?;
let host = internal::user::user_by_id(&connection, &scope_and_user.user_id)?;
SEARCH_USERS.run(&RuleContext::minimal(
ONLY_ADMINS.run(&RuleContext::minimal(
pool,
scope_and_user,
"",
&host.user_uuid,
))?;
internal::user::all_staff(&connection)
}

pub use internal::user::update_user_cache as _update_user_cache;
106 changes: 106 additions & 0 deletions tests/api/inactive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use crate::helpers::api::*;
use crate::helpers::db::get_pool;
use crate::helpers::db::reset;
use crate::helpers::misc::read_json;
use crate::helpers::misc::test_app_and_cis;
use crate::helpers::misc::Soa;
use crate::helpers::sudo::add_to_group;
use crate::helpers::users::basic_user;
use crate::helpers::users::user_id;
use actix_web::test;
use actix_web::App;
use cis_client::getby::GetBy;
use cis_client::AsyncCisClientTrait;
use dino_park_packs::db::operations::users::_update_user_cache;
use dino_park_packs::db::operations::users::update_user_cache;
use failure::Error;
use serde_json::json;
use std::sync::Arc;

#[actix_rt::test]
async fn update_inactive() -> Result<(), Error> {
reset()?;
let (service, cis_client) = test_app_and_cis().await;
let cis_client = Arc::new(cis_client);
let app = App::new().service(service);
let mut app = test::init_service(app).await;

let host_user = basic_user(1, true);
let mut staff_user_1 = basic_user(2, true);
let mut staff_user_2 = basic_user(3, true);
let host = Soa::from(&host_user).aal_medium();

let res = post(
&mut app,
"/groups/api/v1/groups",
json!({ "name": "inactive-test", "description": "a group", "trust": "Staff" }),
&host.clone().creator(),
)
.await;
assert!(res.status().is_success());

let res = get(&mut app, "/groups/api/v1/groups", &host).await;
assert!(res.status().is_success());
assert_eq!(read_json(res).await["groups"][0]["typ"], "Closed");

add_to_group(&mut app, &host, &staff_user_1, "inactive-test").await;
add_to_group(&mut app, &host, &staff_user_2, "inactive-test").await;

let res = get(&mut app, "/groups/api/v1/members/inactive-test", &host).await;
assert!(res.status().is_success());
let members = read_json(res).await;
assert_eq!(members["members"].as_array().map(|a| a.len()), Some(3));

let pool = get_pool();
staff_user_2.active.value = Some(false);
update_user_cache(&pool, &staff_user_2, Arc::clone(&cis_client)).await?;

let res = get(&mut app, "/groups/api/v1/members/inactive-test", &host).await;
assert!(res.status().is_success());
let members = read_json(res).await;
assert_eq!(members["members"].as_array().map(|a| a.len()), Some(2));

// updating an inactive profile must not fail
update_user_cache(&pool, &staff_user_2, Arc::clone(&cis_client)).await?;

let mut staff_user_2_reactivated = cis_client
.get_user_by(&user_id(&staff_user_2), &GetBy::Uuid, None)
.await?;
// enabling a user again with groups resets groups to db state
staff_user_2_reactivated.active.value = Some(true);
update_user_cache(&pool, &staff_user_2_reactivated, Arc::clone(&cis_client)).await?;
assert_eq!(
cis_client
.get_user_by(&user_id(&staff_user_2), &GetBy::Uuid, None)
.await?
.access_information
.mozilliansorg
.values
.map(|kv| kv.0.is_empty()),
Some(true)
);

staff_user_1.active.value = Some(false);
let connection = pool.get()?;
_update_user_cache(&connection, &staff_user_1)?;

let res = get(&mut app, "/groups/api/v1/members/inactive-test", &host).await;
assert!(res.status().is_success());
let members = read_json(res).await;
assert_eq!(members["members"].as_array().map(|a| a.len()), Some(2));

let res = delete(
&mut app,
"/groups/api/v1/sudo/user/inactive",
&host.clone().admin(),
)
.await;
assert!(res.status().is_success());

let res = get(&mut app, "/groups/api/v1/members/inactive-test", &host).await;
assert!(res.status().is_success());
let members = read_json(res).await;
assert_eq!(members["members"].as_array().map(|a| a.len()), Some(1));

Ok(())
}
1 change: 1 addition & 0 deletions tests/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod errors;
mod expiration;
mod groups;
mod import;
mod inactive;
mod invitations;
mod join;
mod requests;
Expand Down
4 changes: 4 additions & 0 deletions tests/helpers/users.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ pub fn user_uuid(p: &Profile) -> String {
p.uuid.value.clone().unwrap()
}

pub fn user_id(p: &Profile) -> String {
p.user_id.value.clone().unwrap()
}

pub fn user_email(p: &Profile) -> String {
p.primary_email.value.clone().unwrap()
}

0 comments on commit 8c8e88f

Please sign in to comment.