mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2025-05-15 16:13:55 +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,
|
invite_user,
|
||||||
logout,
|
logout,
|
||||||
delete_user,
|
delete_user,
|
||||||
|
delete_sso_user,
|
||||||
deauth_user,
|
deauth_user,
|
||||||
disable_user,
|
disable_user,
|
||||||
enable_user,
|
enable_user,
|
||||||
|
@ -239,6 +240,7 @@ struct AdminTemplateData {
|
||||||
page_data: Option<Value>,
|
page_data: Option<Value>,
|
||||||
logged_in: bool,
|
logged_in: bool,
|
||||||
urlpath: String,
|
urlpath: String,
|
||||||
|
sso_enabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AdminTemplateData {
|
impl AdminTemplateData {
|
||||||
|
@ -248,6 +250,7 @@ impl AdminTemplateData {
|
||||||
page_data: Some(page_data),
|
page_data: Some(page_data),
|
||||||
logged_in: true,
|
logged_in: true,
|
||||||
urlpath: CONFIG.domain_path(),
|
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> {
|
async fn get_users_json(_token: AdminToken, mut conn: DbConn) -> Json<Value> {
|
||||||
let users = User::get_all(&mut conn).await;
|
let users = User::get_all(&mut conn).await;
|
||||||
let mut users_json = Vec::with_capacity(users.len());
|
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;
|
let mut usr = u.to_json(&mut conn).await;
|
||||||
usr["userEnabled"] = json!(u.enabled);
|
usr["userEnabled"] = json!(u.enabled);
|
||||||
usr["createdAt"] = json!(format_naive_datetime_local(&u.created_at, DT_FMT));
|
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>> {
|
async fn users_overview(_token: AdminToken, mut conn: DbConn) -> ApiResult<Html<String>> {
|
||||||
let users = User::get_all(&mut conn).await;
|
let users = User::get_all(&mut conn).await;
|
||||||
let mut users_json = Vec::with_capacity(users.len());
|
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;
|
let mut usr = u.to_json(&mut conn).await;
|
||||||
usr["cipher_count"] = json!(Cipher::count_owned_by_user(&u.uuid, &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);
|
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)),
|
Some(dt) => json!(format_naive_datetime_local(&dt, DT_FMT)),
|
||||||
None => json!("Never"),
|
None => json!("Never"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
usr["sso_identifier"] = json!(sso_u.map(|u| u.identifier.to_string()).unwrap_or(String::new()));
|
||||||
|
|
||||||
users_json.push(usr);
|
users_json.push(usr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -417,6 +423,27 @@ async fn delete_user(user_id: UserId, token: AdminToken, mut conn: DbConn) -> Em
|
||||||
res
|
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")]
|
#[post("/users/<user_id>/deauth", format = "application/json")]
|
||||||
async fn deauth_user(user_id: UserId, _token: AdminToken, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
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?;
|
let mut user = get_user_or_404(&user_id, &mut conn).await?;
|
||||||
|
|
|
@ -90,7 +90,7 @@ pub enum EventType {
|
||||||
OrganizationUserUpdated = 1502,
|
OrganizationUserUpdated = 1502,
|
||||||
OrganizationUserRemoved = 1503,
|
OrganizationUserRemoved = 1503,
|
||||||
OrganizationUserUpdatedGroups = 1504,
|
OrganizationUserUpdatedGroups = 1504,
|
||||||
// OrganizationUserUnlinkedSso = 1505, // Not supported
|
OrganizationUserUnlinkedSso = 1505, // Not supported
|
||||||
OrganizationUserResetPasswordEnroll = 1506,
|
OrganizationUserResetPasswordEnroll = 1506,
|
||||||
OrganizationUserResetPasswordWithdraw = 1507,
|
OrganizationUserResetPasswordWithdraw = 1507,
|
||||||
OrganizationUserAdminResetPassword = 1508,
|
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: {
|
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()) })
|
.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;
|
max-width: 130px;
|
||||||
}
|
}
|
||||||
#users-table .vw-actions, #orgs-table .vw-actions {
|
#users-table .vw-actions, #orgs-table .vw-actions {
|
||||||
min-width: 135px;
|
min-width: 155px;
|
||||||
max-width: 140px;
|
max-width: 160px;
|
||||||
}
|
}
|
||||||
#users-table .vw-org-cell {
|
#users-table .vw-org-cell {
|
||||||
max-height: 120px;
|
max-height: 120px;
|
||||||
|
|
12
src/static/scripts/admin.js
vendored
12
src/static/scripts/admin.js
vendored
|
@ -28,11 +28,11 @@ function msg(text, reload_page = true) {
|
||||||
reload_page && reload();
|
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 respStatus;
|
||||||
let respStatusText;
|
let respStatusText;
|
||||||
fetch(url, {
|
fetch(url, {
|
||||||
method: "POST",
|
method: method,
|
||||||
body: body,
|
body: body,
|
||||||
mode: "same-origin",
|
mode: "same-origin",
|
||||||
credentials: "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
|
// Bootstrap Theme Selector
|
||||||
const getStoredTheme = () => localStorage.getItem("theme");
|
const getStoredTheme = () => localStorage.getItem("theme");
|
||||||
const setStoredTheme = theme => localStorage.setItem("theme", theme);
|
const setStoredTheme = theme => localStorage.setItem("theme", theme);
|
||||||
|
|
31
src/static/scripts/admin_users.js
vendored
31
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) {
|
function remove2fa(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
@ -246,6 +268,9 @@ function initUserTable() {
|
||||||
document.querySelectorAll("button[vw-delete-user]").forEach(btn => {
|
document.querySelectorAll("button[vw-delete-user]").forEach(btn => {
|
||||||
btn.addEventListener("click", deleteUser);
|
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 => {
|
document.querySelectorAll("button[vw-disable-user]").forEach(btn => {
|
||||||
btn.addEventListener("click", disableUser);
|
btn.addEventListener("click", disableUser);
|
||||||
});
|
});
|
||||||
|
@ -263,6 +288,8 @@ function initUserTable() {
|
||||||
|
|
||||||
// onLoad events
|
// onLoad events
|
||||||
document.addEventListener("DOMContentLoaded", (/*event*/) => {
|
document.addEventListener("DOMContentLoaded", (/*event*/) => {
|
||||||
|
const size = jQuery("#users-table > thead th").length;
|
||||||
|
const ssoOffset = size-7;
|
||||||
jQuery("#users-table").DataTable({
|
jQuery("#users-table").DataTable({
|
||||||
"drawCallback": function() {
|
"drawCallback": function() {
|
||||||
initUserTable();
|
initUserTable();
|
||||||
|
@ -275,10 +302,10 @@ document.addEventListener("DOMContentLoaded", (/*event*/) => {
|
||||||
],
|
],
|
||||||
"pageLength": -1, // Default show all
|
"pageLength": -1, // Default show all
|
||||||
"columnDefs": [{
|
"columnDefs": [{
|
||||||
"targets": [1, 2],
|
"targets": [1 + ssoOffset, 2 + ssoOffset],
|
||||||
"type": "date-iso"
|
"type": "date-iso"
|
||||||
}, {
|
}, {
|
||||||
"targets": 6,
|
"targets": size-1,
|
||||||
"searchable": false,
|
"searchable": false,
|
||||||
"orderable": false
|
"orderable": false
|
||||||
}]
|
}]
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="vw-account-details">User</th>
|
<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-created-at">Created at</th>
|
||||||
<th class="vw-last-active">Last Active</th>
|
<th class="vw-last-active">Last Active</th>
|
||||||
<th class="vw-entries">Entries</th>
|
<th class="vw-entries">Entries</th>
|
||||||
|
@ -38,6 +41,11 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
{{#if ../sso_enabled}}
|
||||||
|
<td>
|
||||||
|
<span class="d-block">{{sso_identifier}}</span>
|
||||||
|
</td>
|
||||||
|
{{/if}}
|
||||||
<td>
|
<td>
|
||||||
<span class="d-block">{{created_at}}</span>
|
<span class="d-block">{{created_at}}</span>
|
||||||
</td>
|
</td>
|
||||||
|
@ -67,6 +75,9 @@
|
||||||
{{/if}}
|
{{/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-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>
|
<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}}
|
{{#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>
|
<button type="button" class="btn btn-sm btn-link p-0 border-0 float-right" vw-disable-user>Disable User</button><br>
|
||||||
{{else}}
|
{{else}}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue