Skip to content

Commit

Permalink
finished
Browse files Browse the repository at this point in the history
  • Loading branch information
hagen-danswer committed Nov 16, 2024
1 parent 0c86f2e commit f7c5f0f
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 43 deletions.
42 changes: 42 additions & 0 deletions backend/alembic/versions/dfbe9e93d3c7_extended_role_for_non_web.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""extended_role_for_non_web
Revision ID: dfbe9e93d3c7
Revises: 9cf5c00f72fe
Create Date: 2024-11-16 07:54:18.727906
"""
from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision = "dfbe9e93d3c7"
down_revision = "9cf5c00f72fe"
branch_labels = None
depends_on = None


def upgrade() -> None:
op.execute(
"""
UPDATE "user"
SET role = 'SLACK_USER'
WHERE has_web_login = false
"""
)
op.drop_column("user", "has_web_login")


def downgrade() -> None:
op.add_column(
"user",
sa.Column("has_web_login", sa.Boolean(), nullable=False, server_default="true"),
)

op.execute(
"""
UPDATE "user"
SET has_web_login = false,
role = 'BASIC'
WHERE role IN ('SLACK_USER', 'EXT_PERM_USER')
"""
)
4 changes: 2 additions & 2 deletions backend/danswer/auth/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ class UserRole(str, Enum):
CURATOR = "curator"
GLOBAL_CURATOR = "global_curator"
SLACK_USER = "slack_user"
EXTERNAL_PERMISSIONED_USER = "external_permissioned_user"
EXT_PERM_USER = "ext_perm_user"

def is_web_login(self) -> bool:
return self not in [
UserRole.SLACK_USER,
UserRole.EXTERNAL_PERMISSIONED_USER,
UserRole.EXT_PERM_USER,
]


Expand Down
2 changes: 1 addition & 1 deletion backend/danswer/db/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def get_total_users_count(db_session: Session) -> int:
db_session.query(User)
.filter(
~User.email.endswith(get_api_key_email_pattern()), # type: ignore
User.role != UserRole.EXTERNAL_PERMISSIONED_USER,
User.role != UserRole.EXT_PERM_USER,
)
.count()
)
Expand Down
56 changes: 54 additions & 2 deletions backend/danswer/db/users.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from collections.abc import Sequence
from uuid import UUID

from fastapi import HTTPException
from fastapi_users.password import PasswordHelper
from sqlalchemy import func
from sqlalchemy import select
Expand All @@ -10,14 +11,65 @@
from danswer.db.models import User


def validate_user_role_update(requested_role: UserRole, current_role: UserRole) -> None:
"""
Validate that a user role update is valid.
Assumed only admins can hit this endpoint.
raise if:
- requested role is a curator
- requested role is a slack user
- requested role is an external permissioned user
- current role is a slack user
- current role is an external permissioned user
"""

if current_role == UserRole.SLACK_USER:
raise HTTPException(
status_code=400,
detail="To change a Slack User's role, they must first login to Danswer via the web app.",
)

if current_role == UserRole.EXT_PERM_USER:
# This shouldn't happen, but just in case
raise HTTPException(
status_code=400,
detail="To change an External Permissioned User's role, they must first login to Danswer via the web app.",
)

if requested_role == UserRole.CURATOR:
raise HTTPException(
status_code=400,
detail="Curator role must be set via the User Group Menu",
)

if requested_role == UserRole.SLACK_USER:
raise HTTPException(
status_code=400,
detail=(
"A user cannot be set to a Slack User role. "
"This role is automatically assigned to users who only use Danswer via Slack."
),
)

if requested_role == UserRole.EXT_PERM_USER:
raise HTTPException(
status_code=400,
detail=(
"A user cannot be set to an External Permissioned User role. "
"This role is automatically assigned to users who have been "
"pulled in to the system via an external permissions system."
),
)


def list_users(
db_session: Session, email_filter_string: str = "", user: User | None = None
) -> Sequence[User]:
"""List all users. No pagination as of now, as the # of users
is assumed to be relatively small (<< 1 million)"""
stmt = select(User)

where_clause = [User.role != UserRole.EXTERNAL_PERMISSIONED_USER]
where_clause = [User.role != UserRole.EXT_PERM_USER]

if email_filter_string:
where_clause.append(User.email.ilike(f"%{email_filter_string}%")) # type: ignore
Expand Down Expand Up @@ -81,7 +133,7 @@ def _generate_non_web_permissioned_user(email: str) -> User:
return User(
email=email,
hashed_password=hashed_pass,
role=UserRole.EXTERNAL_PERMISSIONED_USER,
role=UserRole.EXT_PERM_USER,
)


Expand Down
22 changes: 13 additions & 9 deletions backend/danswer/server/manage/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
from danswer.db.models import User__UserGroup
from danswer.db.users import get_user_by_email
from danswer.db.users import list_users
from danswer.db.users import validate_user_role_update
from danswer.key_value_store.factory import get_kv_store
from danswer.server.manage.models import AllUsersResponse
from danswer.server.manage.models import UserByEmail
Expand Down Expand Up @@ -84,22 +85,25 @@ def set_user_role(
if not user_to_update:
raise HTTPException(status_code=404, detail="User not found")

if user_role_update_request.new_role == UserRole.CURATOR:
raise HTTPException(
status_code=400,
detail="Curator role must be set via the User Group Menu",
)

if user_to_update.role == user_role_update_request.new_role:
current_role = user_to_update.role
requested_role = user_role_update_request.new_role
if requested_role == current_role:
return

if current_user.id == user_to_update.id:
# This will raise an exception if the role update is invalid
validate_user_role_update(
requested_role=requested_role,
current_role=current_role,
)

if user_to_update.id == current_user.id:
raise HTTPException(
status_code=400,
detail="An admin cannot demote themselves from admin role!",
)

if user_to_update.role == UserRole.CURATOR:
if requested_role == UserRole.CURATOR:
# Remove all curator db relationships before changing role
fetch_ee_implementation_or_noop(
"danswer.db.user_group",
"remove_curator_status__no_commit",
Expand Down
63 changes: 37 additions & 26 deletions web/src/components/admin/users/SignedUpUserTable.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { type User, UserStatus, UserRole, USER_ROLE_LABELS } from "@/lib/types";
import {
type User,
UserStatus,
UserRole,
USER_ROLE_LABELS,
INVALID_ROLE_HOVER_TEXT,
} from "@/lib/types";
import CenteredPageSelector from "./CenteredPageSelector";
import { type PageSelectorProps } from "@/components/PageSelector";
import { HidableSection } from "@/app/admin/assistants/HidableSection";
Expand Down Expand Up @@ -87,31 +93,36 @@ const UserRoleDropdown = ({
<SelectValue />
</SelectTrigger>
<SelectContent>
{Object.entries(USER_ROLE_LABELS).map(([role, label]) =>
!isPaidEnterpriseFeaturesEnabled &&
(role === UserRole.CURATOR ||
role === UserRole.GLOBAL_CURATOR) ? null : (
<SelectItem
key={role}
value={role}
className={
role === UserRole.CURATOR ||
role === UserRole.EXTERNAL_PERMISSIONED_USER ||
role === UserRole.SLACK_USER
? "opacity-30 cursor-not-allowed"
: ""
}
title={
role === UserRole.CURATOR ||
role === UserRole.EXTERNAL_PERMISSIONED_USER ||
role === UserRole.SLACK_USER
? "Can't select"
: ""
}
>
{label}
</SelectItem>
)
{(Object.entries(USER_ROLE_LABELS) as [UserRole, string][]).map(
([role, label]) => {
// Dont want to ever show external permissioned users because it's scary
if (role === UserRole.EXT_PERM_USER) return null;

// Only want to show limited users if paid enterprise features are enabled
const isNotVisibleRole =
!isPaidEnterpriseFeaturesEnabled &&
(role === UserRole.CURATOR || role === UserRole.GLOBAL_CURATOR);

// These roles should be visible but not selectable
const isNotSelectableRole =
role === UserRole.CURATOR ||
role === UserRole.LIMITED ||
role === UserRole.SLACK_USER;

return isNotVisibleRole ? null : (
<SelectItem
key={role}
value={role}
className={
isNotSelectableRole ? "opacity-30 cursor-not-allowed" : ""
}
title={INVALID_ROLE_HOVER_TEXT[role] ?? ""}
data-tooltip-delay="0"
>
{label}
</SelectItem>
);
}
)}
</SelectContent>
</Select>
Expand Down
16 changes: 13 additions & 3 deletions web/src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,30 @@ export enum UserRole {
ADMIN = "admin",
CURATOR = "curator",
GLOBAL_CURATOR = "global_curator",
EXTERNAL_PERMISSIONED_USER = "external_permissioned_user",
EXT_PERM_USER = "ext_perm_user",
SLACK_USER = "slack_user",
}

export const USER_ROLE_LABELS: Record<UserRole, string> = {
[UserRole.LIMITED]: "Limited",
[UserRole.BASIC]: "Basic",
[UserRole.ADMIN]: "Admin",
[UserRole.GLOBAL_CURATOR]: "Global Curator",
[UserRole.CURATOR]: "Curator",
[UserRole.EXTERNAL_PERMISSIONED_USER]: "External Permissioned User",
[UserRole.LIMITED]: "Limited",
[UserRole.EXT_PERM_USER]: "External Permissioned User",
[UserRole.SLACK_USER]: "Slack User",
};

export const INVALID_ROLE_HOVER_TEXT: Partial<Record<UserRole, string>> = {
[UserRole.BASIC]: "Basic users can't perform any admin actions",
[UserRole.ADMIN]: "Admin users can perform all admin actions",
[UserRole.GLOBAL_CURATOR]:
"Global Curator users can perform admin actions for all groups they are a member of",
[UserRole.CURATOR]: "Curator role must be assigned in the Groups tab",
[UserRole.SLACK_USER]:
"This role is automatically assigned to users who only use Danswer via Slack",
};

export interface User {
id: string;
email: string;
Expand Down

0 comments on commit f7c5f0f

Please sign in to comment.