Browse Source

Add sso identifier in admin user panel

pull/3899/head
Timshel 1 month ago
parent
commit
8105ed9e23
  1. 31
      src/api/admin.rs
  2. 2
      src/db/models/event.rs
  3. 19
      src/db/models/user.rs
  4. 4
      src/static/scripts/admin.css
  5. 14
      src/static/scripts/admin.js
  6. 33
      src/static/scripts/admin_users.js
  7. 11
      src/static/templates/admin/users.hbs

31
src/api/admin.rs

@ -46,6 +46,7 @@ pub fn routes() -> Vec<Route> {
invite_user,
logout,
delete_user,
delete_sso_user,
deauth_user,
disable_user,
enable_user,
@ -239,6 +240,7 @@ struct AdminTemplateData {
page_data: Option<Value>,
logged_in: bool,
urlpath: String,
sso_enabled: bool,
}
impl AdminTemplateData {
@ -248,6 +250,7 @@ impl AdminTemplateData {
page_data: Some(page_data),
logged_in: true,
urlpath: CONFIG.domain_path(),
sso_enabled: CONFIG.sso_enabled(),
}
}
@ -336,7 +339,7 @@ fn logout(cookies: &CookieJar<'_>) -> Redirect {
async fn get_users_json(_token: AdminToken, mut conn: DbConn) -> Json<Value> {
let users = User::get_all(&mut conn).await;
let mut users_json = Vec::with_capacity(users.len());
for u in users {
for (u, _) in users {
let mut usr = u.to_json(&mut conn).await;
usr["userEnabled"] = json!(u.enabled);
usr["createdAt"] = json!(format_naive_datetime_local(&u.created_at, DT_FMT));
@ -354,7 +357,7 @@ async fn get_users_json(_token: AdminToken, mut conn: DbConn) -> Json<Value> {
async fn users_overview(_token: AdminToken, mut conn: DbConn) -> ApiResult<Html<String>> {
let users = User::get_all(&mut conn).await;
let mut users_json = Vec::with_capacity(users.len());
for u in users {
for (u, sso_u) in users {
let mut usr = u.to_json(&mut conn).await;
usr["cipher_count"] = json!(Cipher::count_owned_by_user(&u.uuid, &mut conn).await);
usr["attachment_count"] = json!(Attachment::count_by_user(&u.uuid, &mut conn).await);
@ -365,6 +368,9 @@ async fn users_overview(_token: AdminToken, mut conn: DbConn) -> ApiResult<Html<
Some(dt) => json!(format_naive_datetime_local(&dt, DT_FMT)),
None => json!("Never"),
};
usr["sso_identifier"] = json!(sso_u.map(|u| u.identifier.to_string()).unwrap_or(String::new()));
users_json.push(usr);
}
@ -417,6 +423,27 @@ async fn delete_user(user_id: UserId, token: AdminToken, mut conn: DbConn) -> Em
res
}
#[delete("/users/<user_id>/sso", format = "application/json")]
async fn delete_sso_user(user_id: UserId, token: AdminToken, mut conn: DbConn) -> EmptyResult {
let memberships = Membership::find_any_state_by_user(&user_id, &mut conn).await;
let res = SsoUser::delete(&user_id, &mut conn).await;
for membership in memberships {
log_event(
EventType::OrganizationUserUnlinkedSso as i32,
&membership.uuid,
&membership.org_uuid,
&ACTING_ADMIN_USER.into(),
14, // Use UnknownBrowser type
&token.ip.ip,
&mut conn,
)
.await;
}
res
}
#[post("/users/<user_id>/deauth", format = "application/json")]
async fn deauth_user(user_id: UserId, _token: AdminToken, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
let mut user = get_user_or_404(&user_id, &mut conn).await?;

2
src/db/models/event.rs

@ -90,7 +90,7 @@ pub enum EventType {
OrganizationUserUpdated = 1502,
OrganizationUserRemoved = 1503,
OrganizationUserUpdatedGroups = 1504,
// OrganizationUserUnlinkedSso = 1505, // Not supported
OrganizationUserUnlinkedSso = 1505, // Not supported
OrganizationUserResetPasswordEnroll = 1506,
OrganizationUserResetPasswordWithdraw = 1507,
OrganizationUserAdminResetPassword = 1508,

19
src/db/models/user.rs

@ -394,9 +394,16 @@ impl User {
}}
}
pub async fn get_all(conn: &mut DbConn) -> Vec<Self> {
pub async fn get_all(conn: &mut DbConn) -> Vec<(User, Option<SsoUser>)> {
db_run! {conn: {
users::table.load::<UserDb>(conn).expect("Error loading users").from_db()
users::table
.left_join(sso_users::table)
.select(<(UserDb, Option<SsoUserDb>)>::as_select())
.load(conn)
.expect("Error loading groups for user")
.into_iter()
.map(|(user, sso_user)| { (user.from_db(), sso_user.from_db()) })
.collect()
}}
}
@ -532,4 +539,12 @@ impl SsoUser {
.map(|(user, sso_user)| { (user.from_db(), sso_user.from_db()) })
}}
}
pub async fn delete(user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult {
db_run! {conn: {
diesel::delete(sso_users::table.filter(sso_users::user_uuid.eq(user_uuid)))
.execute(conn)
.map_res("Error deleting sso user")
}}
}
}

4
src/static/scripts/admin.css

@ -38,8 +38,8 @@ img {
max-width: 130px;
}
#users-table .vw-actions, #orgs-table .vw-actions {
min-width: 135px;
max-width: 140px;
min-width: 155px;
max-width: 160px;
}
#users-table .vw-org-cell {
max-height: 120px;

14
src/static/scripts/admin.js

@ -28,11 +28,11 @@ function msg(text, reload_page = true) {
reload_page && reload();
}
function _post(url, successMsg, errMsg, body, reload_page = true) {
function _fetch(method, url, successMsg, errMsg, body, reload_page = true) {
let respStatus;
let respStatusText;
fetch(url, {
method: "POST",
method: method,
body: body,
mode: "same-origin",
credentials: "same-origin",
@ -65,6 +65,14 @@ function _post(url, successMsg, errMsg, body, reload_page = true) {
});
}
function _post(url, successMsg, errMsg, body, reload_page = true) {
return _fetch("POST", url, successMsg, errMsg, body, reload_page);
}
function _delete(url, successMsg, errMsg, body, reload_page = true) {
return _fetch("DELETE", url, successMsg, errMsg, body, reload_page);
}
// Bootstrap Theme Selector
const getStoredTheme = () => localStorage.getItem("theme");
const setStoredTheme = theme => localStorage.setItem("theme", theme);
@ -146,4 +154,4 @@ document.addEventListener("DOMContentLoaded", (/*event*/) => {
navItem[0].className = navItem[0].className + " active";
navItem[0].setAttribute("aria-current", "page");
}
});
});

33
src/static/scripts/admin_users.js

@ -24,6 +24,28 @@ function deleteUser(event) {
}
}
function deleteSSOUser(event) {
event.preventDefault();
event.stopPropagation();
const id = event.target.parentNode.dataset.vwUserUuid;
const email = event.target.parentNode.dataset.vwUserEmail;
if (!id || !email) {
alert("Required parameters not found!");
return false;
}
const input_email = prompt(`To delete user "${email}", please type the email below`);
if (input_email != null) {
if (input_email == email) {
_delete(`${BASE_URL}/admin/users/${id}/sso`,
"User SSO Associtation deleted correctly",
"Error deleting user SSO association"
);
} else {
alert("Wrong email, please try again");
}
}
}
function remove2fa(event) {
event.preventDefault();
event.stopPropagation();
@ -246,6 +268,9 @@ function initUserTable() {
document.querySelectorAll("button[vw-delete-user]").forEach(btn => {
btn.addEventListener("click", deleteUser);
});
document.querySelectorAll("button[vw-delete-sso-user]").forEach(btn => {
btn.addEventListener("click", deleteSSOUser);
});
document.querySelectorAll("button[vw-disable-user]").forEach(btn => {
btn.addEventListener("click", disableUser);
});
@ -263,6 +288,8 @@ function initUserTable() {
// onLoad events
document.addEventListener("DOMContentLoaded", (/*event*/) => {
const size = jQuery("#users-table > thead th").length;
const ssoOffset = size-7;
jQuery("#users-table").DataTable({
"drawCallback": function() {
initUserTable();
@ -275,10 +302,10 @@ document.addEventListener("DOMContentLoaded", (/*event*/) => {
],
"pageLength": -1, // Default show all
"columnDefs": [{
"targets": [1, 2],
"targets": [1 + ssoOffset, 2 + ssoOffset],
"type": "date-iso"
}, {
"targets": 6,
"targets": size-1,
"searchable": false,
"orderable": false
}]
@ -303,4 +330,4 @@ document.addEventListener("DOMContentLoaded", (/*event*/) => {
if (btnInviteUserForm) {
btnInviteUserForm.addEventListener("submit", inviteUser);
}
});
});

11
src/static/templates/admin/users.hbs

@ -6,6 +6,9 @@
<thead>
<tr>
<th class="vw-account-details">User</th>
{{#if sso_enabled}}
<th class="vw-sso-identifier">SSO Identifier</th>
{{/if}}
<th class="vw-created-at">Created at</th>
<th class="vw-last-active">Last Active</th>
<th class="vw-entries">Entries</th>
@ -38,6 +41,11 @@
</span>
</div>
</td>
{{#if ../sso_enabled}}
<td>
<span class="d-block">{{sso_identifier}}</span>
</td>
{{/if}}
<td>
<span class="d-block">{{created_at}}</span>
</td>
@ -67,6 +75,9 @@
{{/if}}
<button type="button" class="btn btn-sm btn-link p-0 border-0 float-right" vw-deauth-user>Deauthorize sessions</button><br>
<button type="button" class="btn btn-sm btn-link p-0 border-0 float-right" vw-delete-user>Delete User</button><br>
{{#if ../sso_enabled}}
<button type="button" class="btn btn-sm btn-link p-0 border-0 float-right" vw-delete-sso-user>Delete SSO Association</button><br>
{{/if}}
{{#if user_enabled}}
<button type="button" class="btn btn-sm btn-link p-0 border-0 float-right" vw-disable-user>Disable User</button><br>
{{else}}

Loading…
Cancel
Save