mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2025-05-09 21:23:58 +00:00
Add sso identifier in admin user panel
This commit is contained in:
parent
2c8027b0d2
commit
8105ed9e23
7 changed files with 101 additions and 13 deletions
|
@ -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?;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
vendored
4
src/static/scripts/admin.css
vendored
|
@ -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
vendored
14
src/static/scripts/admin.js
vendored
|
@ -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
vendored
33
src/static/scripts/admin_users.js
vendored
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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…
Add table
Add a link
Reference in a new issue