Skip to content

Commit

Permalink
fix(brainManagement): fix shared brain access issue (#1641)
Browse files Browse the repository at this point in the history
# Description

Please include a summary of the changes and the related issue. Please
also include relevant motivation and context.

## Checklist before requesting a review

fix/brain-management-shared-brain-access
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented hard-to-understand areas
- [ ] I have ideally added tests that prove my fix is effective or that
my feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged

## Screenshots (if appropriate):
  • Loading branch information
gozineb authored Nov 16, 2023
1 parent 9522d6b commit efe4e8c
Show file tree
Hide file tree
Showing 10 changed files with 97 additions and 46 deletions.
9 changes: 8 additions & 1 deletion backend/routes/brain_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,14 @@ async def retrieve_default_brain(

@brain_router.get(
"/brains/{brain_id}/",
dependencies=[Depends(AuthBearer()), Depends(has_brain_authorization())],
dependencies=[
Depends(AuthBearer()),
Depends(
has_brain_authorization(
required_roles=[RoleEnum.Owner, RoleEnum.Editor, RoleEnum.Viewer]
)
),
],
tags=["Brain"],
)
async def retrieve_brain_by_id(brain_id: UUID):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const BrainManagementTabs = (): JSX.Element => {
isDeleteOrUnsubscribeModalOpened,
setIsDeleteOrUnsubscribeModalOpened,
hasEditRights,
isPublicBrain,
isOwnedByCurrentUser,
isDeleteOrUnsubscribeRequestPending,
} = useBrainManagementTabs();
Expand All @@ -51,7 +52,7 @@ export const BrainManagementTabs = (): JSX.Element => {
value="settings"
onChange={setSelectedTab}
/>
{hasEditRights && (
{(!isPublicBrain || hasEditRights) && (
<>
<BrainTabTrigger
selected={selectedTab === "people"}
Expand All @@ -74,10 +75,10 @@ export const BrainManagementTabs = (): JSX.Element => {
<SettingsTab brainId={brainId} />
</Content>
<Content value="people">
<PeopleTab brainId={brainId} />
<PeopleTab brainId={brainId} hasEditRights={hasEditRights} />
</Content>
<Content value="knowledge">
<KnowledgeTab brainId={brainId} />
<KnowledgeTab brainId={brainId} hasEditRights={hasEditRights} />
</Content>
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,22 @@ import { KnowledgeToFeedProvider } from "@/lib/context";

import { AddKnowledge } from "./components/AddKnowledge/AddKnowledge";
import { AddedKnowledge } from "./components/AddedKnowledge/AddedKnowledge";
import { NoAccess } from "../NoAccess";

type KnowledgeTabProps = {
brainId: UUID;
hasEditRights: boolean;
};
export const KnowledgeTab = ({ brainId }: KnowledgeTabProps): JSX.Element => {
export const KnowledgeTab = ({
brainId,
hasEditRights,
}: KnowledgeTabProps): JSX.Element => {
const { t } = useTranslation(["translation", "explore", "config"]);

if (!hasEditRights) {
return <NoAccess />;
}

return (
<KnowledgeToFeedProvider>
<main>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"use client";

import { useTranslation } from "react-i18next";

export const NoAccess = (): JSX.Element => {
const { t } = useTranslation(["translation", "config", "brain"]);

return (
<div className="flex justify-center items-center mt-5">
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative max-w-md">
<strong className="font-bold mr-1">
{t("ohno", { ns: "config" })}
</strong>
<span className="block sm:inline">
{t("roleRequired", { ns: "config" })}
</span>
<p>{t("requireAccess", { ns: "config" })}</p>
</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable max-lines */
"use client";

import { UUID } from "crypto";
Expand All @@ -11,12 +10,18 @@ import { UserToInvite } from "@/lib/components/UserToInvite";
import Button from "@/lib/components/ui/Button";
import { useShareBrain } from "@/lib/hooks/useShareBrain";

import { NoAccess } from "../NoAccess";

type ShareBrainModalProps = {
brainId: UUID;
hasEditRights: boolean;
};

export const PeopleTab = ({ brainId }: ShareBrainModalProps): JSX.Element => {
const { t } = useTranslation(["translation","config","brain"]);
export const PeopleTab = ({
brainId,
hasEditRights,
}: ShareBrainModalProps): JSX.Element => {
const { t } = useTranslation(["translation", "config", "brain"]);
const {
roleAssignations,
handleCopyInvitationLink,
Expand All @@ -26,21 +31,10 @@ export const PeopleTab = ({ brainId }: ShareBrainModalProps): JSX.Element => {
addNewRoleAssignationRole,
sendingInvitation,
canAddNewRow,
hasShareBrainRights,
} = useShareBrain(brainId);

if (!hasShareBrainRights) {
return (
<div className="flex justify-center items-center mt-5">
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative max-w-md">
<strong className="font-bold mr-1">{t("ohno",{ns:"config"})}</strong>
<span className="block sm:inline">
{t("roleRequired",{ns:"config"})}
</span>
<p>{t("requireAccess",{ns:"config"})}</p>
</div>
</div>
);
if (!hasEditRights) {
return <NoAccess />;
}

return (
Expand All @@ -62,7 +56,7 @@ export const PeopleTab = ({ brainId }: ShareBrainModalProps): JSX.Element => {
</div>
<div className="flex flex-row flex-1 items-center justify-center">
<span className="text-sm text-gray-700 dark:text-gray-200 w-full bg-gray-50 dark:bg-gray-900 px-4 py-2">
{t("shareBrainLink",{ns:"brain"})}
{t("shareBrainLink", { ns: "brain" })}
</span>
</div>
<Button type="button">
Expand All @@ -72,7 +66,9 @@ export const PeopleTab = ({ brainId }: ShareBrainModalProps): JSX.Element => {
</div>

<div className="bg-gray-100 h-0.5 my-10 border-gray-200 dark:border-gray-700" />
<p className="text-lg font-bold">{t("inviteUsers",{ns:"brain"})}</p>
<p className="text-lg font-bold">
{t("inviteUsers", { ns: "brain" })}
</p>

{roleAssignations.map((roleAssignation, index) => (
<UserToInvite
Expand All @@ -98,12 +94,14 @@ export const PeopleTab = ({ brainId }: ShareBrainModalProps): JSX.Element => {
disabled={roleAssignations.length === 0}
type="submit"
>
{t("shareButton",{ns:"translation"})}
{t("shareButton", { ns: "translation" })}
</Button>
</div>
</form>
<div className="bg-gray-100 h-0.5 my-10 border-gray-200 dark:border-gray-700" />
<p className="text-lg font-bold">{t("usersWithAccess",{ns:"brain"})}</p>
<p className="text-lg font-bold">
{t("usersWithAccess", { ns: "brain" })}
</p>
<BrainUsers brainId={brainId} />
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,11 @@ export const useBrainManagementTabs = () => {
const { t } = useTranslation(["delete_or_unsubscribe_from_brain"]);
const brainId = params?.brainId as UUID | undefined;

const { hasEditRights, isOwnedByCurrentUser } = getBrainPermissions({
brainId,
userAccessibleBrains: allBrains,
});
const { hasEditRights, isOwnedByCurrentUser, isPublicBrain } =
getBrainPermissions({
brainId,
userAccessibleBrains: allBrains,
});

const handleUnSubscription = async () => {
if (brainId === undefined) {
Expand Down Expand Up @@ -94,5 +95,6 @@ export const useBrainManagementTabs = () => {
hasEditRights,
isOwnedByCurrentUser,
isDeleteOrUnsubscribeRequestPending,
isPublicBrain,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { UUID } from "crypto";

import { MinimalBrainForUser } from "@/lib/context/BrainProvider/types";

import { isUserBrainOwner } from "./isUserBrainOwner";
import { isUserBrainOwner } from "./isUserBrainEditor";
import { isUserBrainEditor } from "./isUserBrainOwner";

type GetBrainPermissionsProps = {
brainId?: UUID;
Expand All @@ -22,11 +23,16 @@ export const getBrainPermissions = ({
userAccessibleBrains,
});

const userHasBrainEditorRights = isUserBrainEditor({
brainId,
userAccessibleBrains,
});

const isPublicBrain =
userAccessibleBrains.find((brain) => brain.id === brainId)?.status ===
"public";

const hasEditRights = !isPublicBrain || isOwnedByCurrentUser;
const hasEditRights = isOwnedByCurrentUser || userHasBrainEditorRights;

return { isPublicBrain, hasEditRights, isOwnedByCurrentUser };
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { UUID } from "crypto";

import { MinimalBrainForUser } from "@/lib/context/BrainProvider/types";

type IsUserBrainOwnerProps = {
userAccessibleBrains: MinimalBrainForUser[];
brainId?: UUID;
};
export const isUserBrainOwner = ({
brainId,
userAccessibleBrains,
}: IsUserBrainOwnerProps): boolean => {
const brain = userAccessibleBrains.find(({ id }) => id === brainId);
if (brain === undefined) {
return false;
}

return brain.role === "Owner";
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ type IsUserBrainOwnerProps = {
userAccessibleBrains: MinimalBrainForUser[];
brainId?: UUID;
};
export const isUserBrainOwner = ({
export const isUserBrainEditor = ({
brainId,
userAccessibleBrains,
}: IsUserBrainOwnerProps): boolean => {
Expand All @@ -15,5 +15,5 @@ export const isUserBrainOwner = ({
return false;
}

return brain.role === "Owner";
return brain.role === "Editor";
};
14 changes: 1 addition & 13 deletions frontend/lib/hooks/useShareBrain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,8 @@ import { Subscription } from "@/lib/api/brain/brain";
import { useBrainApi } from "@/lib/api/brain/useBrainApi";
import { useToast } from "@/lib/hooks";

import {
BrainRoleAssignation,
BrainRoleType,
} from "../components/BrainUsers/types";
import { BrainRoleAssignation } from "../components/BrainUsers/types";
import { generateBrainAssignation } from "../components/ShareBrain/utils/generateBrainAssignation";
import { useBrainContext } from "../context/BrainProvider/hooks/useBrainContext";

const requiredAccessToShareBrain: BrainRoleType[] = ["Owner", "Editor"];

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const useShareBrain = (brainId: string) => {
Expand All @@ -31,11 +25,6 @@ export const useShareBrain = (brainId: string) => {
const { publish } = useToast();
const { addBrainSubscriptions } = useBrainApi();

const { allBrains } = useBrainContext();
const hasShareBrainRights = requiredAccessToShareBrain.includes(
allBrains.find((brain) => brain.id === brainId)?.role ?? "Viewer"
);

const handleCopyInvitationLink = async () => {
await navigator.clipboard.writeText(brainShareLink);
publish({
Expand Down Expand Up @@ -132,6 +121,5 @@ export const useShareBrain = (brainId: string) => {
setIsShareModalOpen,
isShareModalOpen,
canAddNewRow,
hasShareBrainRights,
};
};

0 comments on commit efe4e8c

Please sign in to comment.