Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Admins are now forbidden from promoting users to Super Admin. #440

Open
wants to merge 24 commits into
base: staging
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 28 additions & 15 deletions components/forms/AddToGroupsForm.js
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useFormikContext } from "formik";
import { writeUsers } from "lib/Constants";
import { promoteToSuperAdmin, SUPER_ADMIN_ID, writeUsers } from "lib/Constants";
import AdminRenderer from "@/components/renderers/admin/AdminRenderer";
import { useContext } from "react";
import CacheContext from "../contexts/CacheContext";
Expand All @@ -16,21 +16,34 @@ export default function AddToGroupsForm({ groups }) {
<h4 className="mb-4">Groups</h4>
<h5>System supplied</h5>
<div className="groups__container">
{capabilities.includes(promoteToSuperAdmin) && <div className="groups__item" >
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not comfortable giving 'Super Admin' special treatment.

I suggest we use the parameterized capabilities, so we have something like:

promote_to(super_admin)
promote_to(admin)
promote_to(registered_user)
etc.

Copy link
Contributor Author

@JohnServo24 JohnServo24 May 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The promoteToSuperAdmin variable is somewhat similar to that. It's a variable that can be found from the constants folder

It is equivalent to this: const promoteToSuperAdmin = '${assignToGroup}(${SUPER_ADMIN_ID})';

Where assignToGroup is the assign_to_group capability and the SUPER_ADMIN_ID is the id # of the Super Admin group, therefore it is equal to assign_to_group(1)

Should I specifically change this to capabilities.includes('promote_to(super_admin)'), where promote_to(super_admin) is a string? (or maybe capabilities.includes('assign_to_group(1)')

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔

<AdminRenderer
title="Super Administrator"
value={SUPER_ADMIN_ID}
errors={errors}
touched={touched}
checked={values?.groups?.includes(SUPER_ADMIN_ID.toString())}
type="checkbox"
name="groups"
disabled={!capabilities.includes(writeUsers)}
/>
<p className="groups__description">Reserved group for KlaudSol installation and setup.</p>
</div>}
{systemSupplied.map((group) => (
<div className="groups__item" key={group.id}>
<AdminRenderer
title={group.name}
value={group.id}
errors={errors}
touched={touched}
checked={values.groups.includes(group.id.toString())}
type="checkbox"
name="groups"
disabled={!capabilities.includes(writeUsers)}
/>
<p className="groups__description">{group.description}</p>
</div>
))}
<div className="groups__item" key={group.id}>
<AdminRenderer
title={group.name}
value={group.id}
errors={errors}
touched={touched}
checked={values.groups.includes(group.id.toString())}
type="checkbox"
name="groups"
disabled={!capabilities.includes(writeUsers)}
/>
<p className="groups__description">{group.description}</p>
</div>
))}
</div>
{userCreated.length > 0 &&
<>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"up": [
"INSERT INTO capabilities (name, description, is_system_supplied) VALUES",
"('promote_to_super_admin', \"Can create/promote a user to Super Admin\", true)"
],
"down":[
"DELETE FROM capabilities WHERE name = 'promote_to_super_admin' AND is_system_supplied = true"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"up": [
"INSERT INTO group_capabilities (group_id, capabilities_id) VALUES",
"(1, (SELECT id FROM capabilities WHERE name = 'promote_to_super_admin' AND is_system_supplied = true))"
],
"down":[
"DELETE FROM group_capabilities WHERE group_id IN = 1",
"AND capabilities_id = (SELECT id FROM capabilities WHERE name = 'promote_to_super_admin' AND is_system_supplied = true);"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"up": [
"DELETE FROM group_capabilities WHERE group_id = 1 ",
"AND capabilities_id = (SELECT id FROM capabilities WHERE name = 'promote_to_super_admin' AND is_system_supplied = true);"
],
"down": [
"INSERT INTO group_capabilities (group_id, capabilities_id) VALUES",
"(1, (SELECT id FROM capabilities WHERE name = 'promote_to_super_admin' AND is_system_supplied = true))"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"up": [
"DELETE FROM capabilities WHERE name = 'promote_to_super_admin' AND is_system_supplied = true"
],
"down": [
"INSERT INTO capabilities (name, description, is_system_supplied) VALUES",
"('promote_to_super_admin', \"Can create/promote a user to Super Admin\", true)"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"up": [
"INSERT INTO capabilities (name, description, is_system_supplied) VALUES",
"('assign_to_group', \"Can assign a user to a group\", true)"
],
"down":[
"DELETE FROM capabilities WHERE name = 'assign_to_group' AND is_system_supplied = true"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"up": [
"INSERT INTO group_capabilities (group_id, capabilities_id, params1) VALUES ",
"(1, (SELECT id FROM capabilities WHERE name = 'assign_to_group' AND is_system_supplied = true), 1)"
],
"down":[
"DELETE FROM group_capabilities WHERE group_id IN = 1 ",
"AND capabilities_id = (SELECT id FROM capabilities WHERE name = 'assign_to_group' AND is_system_supplied = true);"
]
}
2 changes: 1 addition & 1 deletion db/migrations/plugins/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
This directory stores all KlaudSol CMS plugins.

This directory is being ignored by Git.
This directory is being ignored by Git.
9 changes: 7 additions & 2 deletions lib/Constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ export const maximumNumberOfPage = 10;
export const EntryValues = [10, 20, 30, 40, 50];
export const validImageTypes = "image/png, image/gif, image/jpeg, image/jfif,image/svg+xml";

// group id's
export const SUPER_ADMIN_ID = 1;

// capabilities
export const readContents = 'read_contents';
export const writeContents = 'write_contents';
Expand All @@ -26,9 +29,11 @@ export const editProfile = 'edit_profile';
export const approveUsers = 'approve_users';
export const rejectUsers = 'reject_users';
export const changeUserPassword = 'change_user_password';
export const deleteUsers = 'delete_users'

export const deleteUsers = 'delete_users';
export const assignToGroup = 'assign_to_group';
export const promoteToSuperAdmin = `${assignToGroup}(${SUPER_ADMIN_ID})`;

// password creation
export const AUTO_PASSWORD = 'autogenerated';
export const CUSTOM_PASSWORD = 'custom'

71 changes: 25 additions & 46 deletions pages/admin/users/[id]/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import Link from "next/link";
import { useRouter } from "next/router";
import { useEffect, useRef } from "react";
import { slsFetch } from "@klaudsol/commons/lib/Client";
import Groups from '@klaudsol/commons/models/Groups';
import People from '@klaudsol/commons/models/People';

import AppBackButton from "@/components/klaudsolcms/buttons/AppBackButton";
import AppButtonLg from "@/components/klaudsolcms/buttons/AppButtonLg";
Expand All @@ -16,7 +18,7 @@ import AppInfoModal from "@/components/klaudsolcms/modals/AppInfoModal";
import { FaCheck, FaTrash, FaArrowRight } from "react-icons/fa";
import { Formik, Form } from "formik";
import ContentManagerLayout from "components/layouts/ContentManagerLayout";
import { changeUserPassword, DEFAULT_SKELETON_ROW_COUNT, deleteUsers, writeUsers } from "lib/Constants";
import { changeUserPassword, DEFAULT_SKELETON_ROW_COUNT, deleteUsers, SUPER_ADMIN_ID, writeUsers } from "lib/Constants";

import {
LOADING,
Expand All @@ -31,7 +33,7 @@ import useUserReducer from "@/components/reducers/userReducer";
import UserForm from "@/components/forms/UserForm";
import AddToGroupsForm from "@/components/forms/AddToGroupsForm";

export default function UserInfo({ cache }) {
export default function UserInfo({ cache, groups, user }) {
const [state, setState] = useUserReducer();

const router = useRouter();
Expand All @@ -44,44 +46,6 @@ export default function UserInfo({ cache }) {
const { entity_type_slug, id } = router.query;
const formRef = useRef();

useEffect(() => {
(async () => {
try {
setState(LOADING, true);

const userDataUrl = `/api/admin/users/${id}`;
const userParams = {
headers: {
'Content-Type': 'application/json',
}
}

const groupsUrl = `/api/admin/groups`;
const groupsParams = {
headers: {
'Content-Type': 'application/json',
}
}

const groupsPromise = slsFetch(groupsUrl, groupsParams);
const userPromise = slsFetch(userDataUrl, userParams);
const [groupsRaw, userRaw] = await Promise.all([groupsPromise, userPromise]);

const { data: user } = await userRaw.json();
const { person, groups: userGroups } = user;

const { data: groups } = await groupsRaw.json();

setState(SET_VALUES, { ...person[0], groups: userGroups });
setState(SET_GROUPS, groups);
} catch (ex) {
errorHandler(ex);
} finally {
setState(LOADING, false);
}
})();
}, []);

const onSubmit = (e) => {
e.preventDefault();
formRef.current.handleSubmit();
Expand Down Expand Up @@ -112,16 +76,16 @@ export default function UserInfo({ cache }) {

const formikParams = {
innerRef: formRef,
initialValues: state.user,
initialValues: user,
onSubmit: (values) => {
(async () => {
try {
setState(SAVING, true);

const isSameEmail = values.email === state.user.email;
const isSameEmail = values.email === user.email;

const toAdd = values.groups.filter((group) => !state.user.groups.includes(group));
const toDelete = state.user.groups.filter((group) => !values.groups.includes(group));
const toAdd = values.groups.filter((group) => !user.groups.includes(group));
const toDelete = user.groups.filter((group) => !values.groups.includes(group));

const url = `/api/admin/users/${id}`
const params = {
Expand Down Expand Up @@ -190,7 +154,7 @@ export default function UserInfo({ cache }) {
<Formik {...formikParams}>
<Form>
<UserForm />
<AddToGroupsForm groups={state.groups} />
<AddToGroupsForm groups={groups} />
</Form>
</Formik>

Expand Down Expand Up @@ -234,4 +198,19 @@ export default function UserInfo({ cache }) {
</CacheContext.Provider>
);
}
export const getServerSideProps = getSessionCache();
export const getServerSideProps = getSessionCache(async ({ query }) => {
const { id } = query;

const [groups, userGroups, user] = await Promise.all([
Groups.all(),
Groups.findByUser({ id }),
People.get({ id })
])

return {
props: {
groups: await groups.filter((group) => group.id !== SUPER_ADMIN_ID),
user: { ...user[0], groups: userGroups },
}
}
});
39 changes: 11 additions & 28 deletions pages/admin/users/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useRouter } from "next/router";
import Link from "next/link";
import { useEffect, useRef, useState } from "react";
import { slsFetch } from "@klaudsol/commons/lib/Client";
import Groups from '@klaudsol/commons/models/Groups';

import AppBackButton from "@/components/klaudsolcms/buttons/AppBackButton";
import AppButtonLg from "@/components/klaudsolcms/buttons/AppButtonLg";
Expand All @@ -16,7 +17,7 @@ import AppInfoModal from "@/components/klaudsolcms/modals/AppInfoModal";
import { FaCheck } from "react-icons/fa";
import { Formik, Form } from "formik";
import ContentManagerLayout from "components/layouts/ContentManagerLayout";
import { DEFAULT_SKELETON_ROW_COUNT, writeGroups, writeUsers } from "@/lib/Constants";
import { DEFAULT_SKELETON_ROW_COUNT, SUPER_ADMIN_ID, writeGroups, writeUsers } from "@/lib/Constants";

import {
LOADING,
Expand All @@ -34,7 +35,7 @@ import PasswordForm from "@/components/forms/PasswordForm";
const USER_INFO = 'user_info';
const ADD_GROUPS = 'add_groups';

export default function Type({ cache }) {
export default function Type({ cache, groups }) {
const [state, setState] = useUserReducer();
const [currentPage, setCurrentPage] = useState(USER_INFO);

Expand All @@ -53,30 +54,6 @@ export default function Type({ cache }) {
formRef.current.handleSubmit();
};

useEffect(() => {
(async () => {
try {
setState(LOADING, true);

const url = `/api/admin/groups`;
const params = {
headers: {
'Content-Type': 'application/json',
}
}

const resRaw = await slsFetch(url, params);
const { data } = await resRaw.json();

setState(SET_GROUPS, data);
} catch (err) {
errorHandler(err);
} finally {
setState(LOADING, false);
}
})()
}, []);

const formikParams = {
innerRef: formRef,
initialValues: {
Expand Down Expand Up @@ -177,7 +154,7 @@ export default function Type({ cache }) {
<PasswordForm setState={setState} />
</>
}
{currentPage === ADD_GROUPS && <AddToGroupsForm groups={state.groups} />}
{currentPage === ADD_GROUPS && <AddToGroupsForm groups={groups} />}
</Form>
</Formik>
</div>
Expand Down Expand Up @@ -218,4 +195,10 @@ export default function Type({ cache }) {
</CacheContext.Provider>
);
}
export const getServerSideProps = getSessionCache();
export const getServerSideProps = getSessionCache(async (context) => {
const groupsRaw = await Groups.all();
const groups = await groupsRaw.filter((group) => group.id !== SUPER_ADMIN_ID);

return { props: { groups }}
});

24 changes: 0 additions & 24 deletions pages/api/admin/groups/index.js

This file was deleted.

Loading