forked from github-starred/komodo
support manage user permissions on groups
This commit is contained in:
@@ -11,17 +11,17 @@ use crate::{auth::RequestUser, state::State};
|
||||
impl State {
|
||||
pub async fn get_group_check_permissions(
|
||||
&self,
|
||||
deployment_id: &str,
|
||||
group_id: &str,
|
||||
user: &RequestUser,
|
||||
permission_level: PermissionLevel,
|
||||
) -> anyhow::Result<Group> {
|
||||
let group = self.db.get_group(deployment_id).await?;
|
||||
let group = self.db.get_group(group_id).await?;
|
||||
let permissions = group.get_user_permissions(&user.id);
|
||||
if user.is_admin || permissions >= permission_level {
|
||||
Ok(group)
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"user does not have required permissions on this deployment"
|
||||
"user does not have required permissions on this group"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ use axum::{routing::post, Extension, Json, Router};
|
||||
use helpers::handle_anyhow_error;
|
||||
use mungos::{doc, Deserialize, Document, Serialize};
|
||||
use types::{
|
||||
monitor_timestamp, Build, Deployment, Log, Operation, PermissionLevel, PermissionsTarget,
|
||||
Procedure, Server, Update, UpdateStatus, UpdateTarget,
|
||||
monitor_timestamp, Build, Deployment, Group, Log, Operation, PermissionLevel,
|
||||
PermissionsTarget, Procedure, Server, Update, UpdateStatus, UpdateTarget,
|
||||
};
|
||||
use typeshare::typeshare;
|
||||
|
||||
@@ -82,10 +82,10 @@ pub fn router() -> Router {
|
||||
|
||||
async fn update_permissions(
|
||||
Extension(state): StateExtension,
|
||||
Extension(user): RequestUserExtension,
|
||||
Extension(req_user): RequestUserExtension,
|
||||
Json(permission_update): Json<PermissionsUpdateBody>,
|
||||
) -> anyhow::Result<Update> {
|
||||
if !user.is_admin {
|
||||
if !req_user.is_admin {
|
||||
return Err(anyhow!(
|
||||
"user not authorized for this action (is not admin)"
|
||||
));
|
||||
@@ -107,7 +107,7 @@ async fn update_permissions(
|
||||
operation: Operation::ModifyUserPermissions,
|
||||
start_ts: monitor_timestamp(),
|
||||
success: true,
|
||||
operator: user.id.clone(),
|
||||
operator: req_user.id.clone(),
|
||||
status: UpdateStatus::Complete,
|
||||
..Default::default()
|
||||
};
|
||||
@@ -199,9 +199,9 @@ async fn update_permissions(
|
||||
.procedures
|
||||
.find_one_by_id(&permission_update.target_id)
|
||||
.await
|
||||
.context("failed at find build query")?
|
||||
.context("failed at find procedure query")?
|
||||
.ok_or(anyhow!(
|
||||
"failed to find a build with id {}",
|
||||
"failed to find a procedure with id {}",
|
||||
permission_update.target_id
|
||||
))?;
|
||||
state
|
||||
@@ -220,6 +220,33 @@ async fn update_permissions(
|
||||
target_user.username, permission_update.permission, procedure.name
|
||||
)
|
||||
}
|
||||
PermissionsTarget::Group => {
|
||||
let group = state
|
||||
.db
|
||||
.groups
|
||||
.find_one_by_id(&permission_update.target_id)
|
||||
.await
|
||||
.context("failed at find group query")?
|
||||
.ok_or(anyhow!(
|
||||
"failed to find a group with id {}",
|
||||
permission_update.target_id
|
||||
))?;
|
||||
state
|
||||
.db
|
||||
.groups
|
||||
.update_one::<Group>(
|
||||
&permission_update.target_id,
|
||||
mungos::Update::Set(doc! {
|
||||
format!("permissions.{}", permission_update.user_id): permission_update.permission.to_string()
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
update.target = UpdateTarget::Group(group.id);
|
||||
format!(
|
||||
"user {} given {} permissions on group {}",
|
||||
target_user.username, permission_update.permission, group.name
|
||||
)
|
||||
}
|
||||
};
|
||||
update
|
||||
.logs
|
||||
@@ -231,10 +258,10 @@ async fn update_permissions(
|
||||
|
||||
async fn modify_user_enabled(
|
||||
Extension(state): StateExtension,
|
||||
Extension(user): RequestUserExtension,
|
||||
Extension(req_user): RequestUserExtension,
|
||||
Json(ModifyUserEnabledBody { user_id, enabled }): Json<ModifyUserEnabledBody>,
|
||||
) -> anyhow::Result<Update> {
|
||||
if !user.is_admin {
|
||||
if !req_user.is_admin {
|
||||
return Err(anyhow!(
|
||||
"user does not have permissions for this action (not admin)"
|
||||
));
|
||||
@@ -264,7 +291,7 @@ async fn modify_user_enabled(
|
||||
end_ts: Some(ts),
|
||||
status: UpdateStatus::Complete,
|
||||
success: true,
|
||||
operator: user.id.clone(),
|
||||
operator: req_user.id.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
update.id = state.add_update(update.clone()).await?;
|
||||
@@ -273,13 +300,13 @@ async fn modify_user_enabled(
|
||||
|
||||
async fn modify_user_create_server_permissions(
|
||||
Extension(state): StateExtension,
|
||||
Extension(user): RequestUserExtension,
|
||||
Extension(req_user): RequestUserExtension,
|
||||
Json(ModifyUserCreateServerBody {
|
||||
user_id,
|
||||
create_server_permissions,
|
||||
}): Json<ModifyUserCreateServerBody>,
|
||||
) -> anyhow::Result<Update> {
|
||||
if !user.is_admin {
|
||||
if !req_user.is_admin {
|
||||
return Err(anyhow!(
|
||||
"user does not have permissions for this action (not admin)"
|
||||
));
|
||||
@@ -319,7 +346,7 @@ async fn modify_user_create_server_permissions(
|
||||
end_ts: Some(ts),
|
||||
status: UpdateStatus::Complete,
|
||||
success: true,
|
||||
operator: user.id.clone(),
|
||||
operator: req_user.id.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
update.id = state.add_update(update.clone()).await?;
|
||||
@@ -328,13 +355,13 @@ async fn modify_user_create_server_permissions(
|
||||
|
||||
async fn modify_user_create_build_permissions(
|
||||
Extension(state): StateExtension,
|
||||
Extension(user): RequestUserExtension,
|
||||
Extension(req_user): RequestUserExtension,
|
||||
Json(ModifyUserCreateBuildBody {
|
||||
user_id,
|
||||
create_build_permissions,
|
||||
}): Json<ModifyUserCreateBuildBody>,
|
||||
) -> anyhow::Result<Update> {
|
||||
if !user.is_admin {
|
||||
if !req_user.is_admin {
|
||||
return Err(anyhow!(
|
||||
"user does not have permissions for this action (not admin)"
|
||||
));
|
||||
@@ -374,7 +401,7 @@ async fn modify_user_create_build_permissions(
|
||||
end_ts: Some(ts),
|
||||
status: UpdateStatus::Complete,
|
||||
success: true,
|
||||
operator: user.id.clone(),
|
||||
operator: req_user.id.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
update.id = state.add_update(update.clone()).await?;
|
||||
|
||||
@@ -5,13 +5,13 @@ import { useAppState } from "../../state/StateProvider";
|
||||
import { useUser } from "../../state/UserProvider";
|
||||
import { PermissionLevel } from "../../types";
|
||||
import { getId } from "../../util/helpers";
|
||||
import Flex from "../shared/layout/Flex";
|
||||
import Grid from "../shared/layout/Grid";
|
||||
import Flex from "../shared/layout/Flex";
|
||||
|
||||
const Resources: Component<{}> = (p) => {
|
||||
const { user, user_id } = useUser();
|
||||
const { user_id } = useUser();
|
||||
const { isMobile } = useAppDimensions();
|
||||
const { builds, deployments, servers } = useAppState();
|
||||
const { builds, deployments, servers, groups } = useAppState();
|
||||
const [search, setSearch] = createSignal("");
|
||||
const _servers = createMemo(() => {
|
||||
return servers.filterArray((s) => {
|
||||
@@ -34,6 +34,13 @@ const Resources: Component<{}> = (p) => {
|
||||
return p ? p !== PermissionLevel.None : false;
|
||||
});
|
||||
});
|
||||
const _groups = createMemo(() => {
|
||||
return groups.filterArray((b) => {
|
||||
if (!b.name.includes(search())) return false;
|
||||
const p = b.permissions?.[user_id()];
|
||||
return p ? p !== PermissionLevel.None : false;
|
||||
});
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<Grid
|
||||
@@ -55,7 +62,9 @@ const Resources: Component<{}> = (p) => {
|
||||
>
|
||||
<Grid gap="0.25rem">
|
||||
<h2>{item.server.name}</h2>
|
||||
<div class="dimmed">{item.server.region || "unknown region"}</div>
|
||||
<div class="dimmed">
|
||||
{item.server.region || "unknown region"}
|
||||
</div>
|
||||
</Grid>
|
||||
<div>{item.server.permissions?.[user_id()] || "none"}</div>
|
||||
</A>
|
||||
@@ -117,6 +126,27 @@ const Resources: Component<{}> = (p) => {
|
||||
</For>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid
|
||||
class="card shadow"
|
||||
style={{ width: "100%", "box-sizing": "border-box" }}
|
||||
>
|
||||
<h1>groups</h1>
|
||||
<Grid gridTemplateColumns={isMobile() ? undefined : "1fr 1fr"}>
|
||||
<For each={_groups()}>
|
||||
{(item) => (
|
||||
<Flex
|
||||
class="card light shadow hover full-width"
|
||||
style={{
|
||||
"justify-content": "space-between",
|
||||
}}
|
||||
>
|
||||
<h2>{item.name}</h2>
|
||||
<div>{item.permissions?.[user_id()] || "none"}</div>
|
||||
</Flex>
|
||||
)}
|
||||
</For>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -33,7 +33,7 @@ import Selector from "../shared/menu/Selector";
|
||||
|
||||
const User: Component = () => {
|
||||
const { isMobile } = useAppDimensions();
|
||||
const { builds, deployments, servers, ws } = useAppState();
|
||||
const { builds, deployments, servers, groups, ws } = useAppState();
|
||||
const params = useParams<{ id: string }>();
|
||||
const [user, { refetch }] = createResource(() =>
|
||||
client.get_user_by_id(params.id)
|
||||
@@ -86,6 +86,17 @@ const User: Component = () => {
|
||||
});
|
||||
}
|
||||
});
|
||||
const _groups = createMemo(() => {
|
||||
if (showAll()) {
|
||||
return groups.filterArray((b) => b.name.includes(search()));
|
||||
} else {
|
||||
return groups.filterArray((b) => {
|
||||
if (!b.name.includes(search())) return false;
|
||||
const p = b.permissions?.[params.id];
|
||||
return p ? p !== PermissionLevel.None : false;
|
||||
});
|
||||
}
|
||||
});
|
||||
return (
|
||||
<Grid
|
||||
class="card shadow"
|
||||
@@ -138,7 +149,7 @@ const User: Component = () => {
|
||||
<Flex alignItems="center">
|
||||
<h1>servers</h1>
|
||||
<Show when={_servers()?.length === 0}>
|
||||
<div>empty</div>
|
||||
<div>none</div>
|
||||
</Show>
|
||||
</Flex>
|
||||
<Grid gridTemplateColumns={isMobile() ? undefined : "1fr 1fr"}>
|
||||
@@ -182,7 +193,7 @@ const User: Component = () => {
|
||||
<Flex alignItems="center">
|
||||
<h1>deployments</h1>
|
||||
<Show when={_deployments()?.length === 0}>
|
||||
<div>empty</div>
|
||||
<div>none</div>
|
||||
</Show>
|
||||
</Flex>
|
||||
<Grid gridTemplateColumns={isMobile() ? undefined : "1fr 1fr"}>
|
||||
@@ -229,7 +240,7 @@ const User: Component = () => {
|
||||
<Flex alignItems="center">
|
||||
<h1>builds</h1>
|
||||
<Show when={_builds()?.length === 0}>
|
||||
<div>empty</div>
|
||||
<div>none</div>
|
||||
</Show>
|
||||
</Flex>
|
||||
<Grid gridTemplateColumns={isMobile() ? undefined : "1fr 1fr"}>
|
||||
@@ -263,6 +274,44 @@ const User: Component = () => {
|
||||
</For>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid class="card light shadow">
|
||||
<Flex alignItems="center">
|
||||
<h1>groups</h1>
|
||||
<Show when={_builds()?.length === 0}>
|
||||
<div>none</div>
|
||||
</Show>
|
||||
</Flex>
|
||||
<Grid gridTemplateColumns={isMobile() ? undefined : "1fr 1fr"}>
|
||||
<For each={_groups()}>
|
||||
{(item) => (
|
||||
<Flex
|
||||
class="card shadow"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<h2>{item.name}</h2>
|
||||
<Selector
|
||||
targetClass={
|
||||
(item.permissions?.[params.id] || "none") !== "none"
|
||||
? "blue"
|
||||
: "red"
|
||||
}
|
||||
selected={item.permissions?.[params.id] || "none"}
|
||||
items={["none", "read", "execute", "update"]}
|
||||
onSelect={(permission) => {
|
||||
client.update_user_permissions_on_target({
|
||||
user_id: params.id,
|
||||
target_type: PermissionsTarget.Group,
|
||||
target_id: getId(item),
|
||||
permission: permission as PermissionLevel,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
</For>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Show>
|
||||
</Grid>
|
||||
);
|
||||
|
||||
@@ -466,6 +466,7 @@ export enum PermissionsTarget {
|
||||
Deployment = "deployment",
|
||||
Build = "build",
|
||||
Procedure = "procedure",
|
||||
Group = "group",
|
||||
}
|
||||
|
||||
export enum Timelength {
|
||||
|
||||
@@ -194,6 +194,7 @@ pub enum PermissionsTarget {
|
||||
Deployment,
|
||||
Build,
|
||||
Procedure,
|
||||
Group,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
|
||||
Reference in New Issue
Block a user