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

feat: knowledge admin role #5965

Merged
merged 47 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
5503b0d
add role knowledge admin
JzoNgKVO Jun 7, 2024
6db11d6
permission selector
JzoNgKVO Jun 20, 2024
d88a9f2
feat: dataset forbid editor user
ZhouhaoJiang Jun 20, 2024
6fef3b6
member selector
JzoNgKVO Jun 20, 2024
ba490e8
feat: add get MemberUpdateRoleApi
ZhouhaoJiang Jun 20, 2024
9c51bc0
add permissions
JzoNgKVO Jun 21, 2024
56c4e46
feat: add features dataset_operator_enabled
ZhouhaoJiang Jun 24, 2024
3583df0
feat: add dataset_operator_enabled env
ZhouhaoJiang Jun 24, 2024
4af95a5
Merge branch 'main' into tp
JzoNgKVO Jun 24, 2024
08bb171
Merge branch 'main' into tp
JzoNgKVO Jun 24, 2024
3d994be
feat: add table dataset_permissions
ZhouhaoJiang Jun 25, 2024
d452fd7
feat: add dataset permission service
ZhouhaoJiang Jun 25, 2024
cf2c431
feat: add create and get part_users_list api
ZhouhaoJiang Jun 25, 2024
af3bb80
feat: add tenant account role DATASET_OPERATOR
ZhouhaoJiang Jun 25, 2024
4bf111d
feat: update field name
ZhouhaoJiang Jun 25, 2024
97cd6bb
feat: get dataset info add partial_member_list
ZhouhaoJiang Jun 25, 2024
a5687d1
add role
JzoNgKVO Jun 25, 2024
a3c96bc
feat: add account is_non_owner_role
ZhouhaoJiang Jun 26, 2024
fb4ad13
feat: update migrate file
ZhouhaoJiang Jun 26, 2024
6313230
feat: get datasets when permission is partial_members
ZhouhaoJiang Jun 26, 2024
2ccb4e0
Merge branch 'refs/heads/main' into feat/knowledge-admin-role
ZhouhaoJiang Jun 28, 2024
95e5f8d
fix: DATASET_OPERATOR_ENABLED error
ZhouhaoJiang Jun 28, 2024
7b0c769
fix setting modal in configure
JzoNgKVO Jun 28, 2024
dff1787
fix migrate error
ZhouhaoJiang Jun 28, 2024
cbc096a
Merge remote-tracking branch 'origin/feat/knowledge-admin-role' into …
ZhouhaoJiang Jun 28, 2024
53c035e
fix: datasets patch error
ZhouhaoJiang Jun 28, 2024
f17bad6
feat: dataset_operator dataset visibility
ZhouhaoJiang Jun 28, 2024
2ad7ea1
feat: update filter datasets by permission
ZhouhaoJiang Jun 28, 2024
e2352dd
chore: remove print
ZhouhaoJiang Jun 28, 2024
81332e5
Merge branch 'refs/heads/main' into feat/knowledge-admin-role
ZhouhaoJiang Jun 30, 2024
84cd2bd
feat: patch dataset when not partial_member_list refresh datasetp per…
ZhouhaoJiang Jun 30, 2024
7c80d25
feat: dataset operator permission
ZhouhaoJiang Jun 30, 2024
12e3bf8
feat: update migration
ZhouhaoJiang Jul 1, 2024
cd3d7f4
Merge branch 'refs/heads/main' into feat/knowledge-admin-role
ZhouhaoJiang Jul 1, 2024
98364bc
feat: add tags permission
ZhouhaoJiang Jul 2, 2024
00ecb7c
add redirection for new role
JzoNgKVO Jul 2, 2024
27fa0fb
Merge remote-tracking branch 'origin/feat/knowledge-admin-role' into …
ZhouhaoJiang Jul 2, 2024
36794a4
feat: add dataset permission check_permission
ZhouhaoJiang Jul 2, 2024
bd4ba98
feat: change document permission
ZhouhaoJiang Jul 2, 2024
d78b751
feat: update tags post permission
ZhouhaoJiang Jul 2, 2024
e16add7
hide some menus
JzoNgKVO Jul 2, 2024
02ec351
fix redirection
JzoNgKVO Jul 2, 2024
72a2288
feat: update DATASET_OPERATOR_ENABLED default value
JohnJyong Jul 1, 2024
582edd4
Merge remote-tracking branch 'origin/feat/knowledge-admin-role' into …
ZhouhaoJiang Jul 2, 2024
eae3621
fix redirection
JzoNgKVO Jul 3, 2024
82f5778
Merge branch 'refs/heads/main' into feat/knowledge-admin-role
ZhouhaoJiang Jul 4, 2024
a515120
feat: dataset documents rename permission
ZhouhaoJiang Jul 4, 2024
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
5 changes: 5 additions & 0 deletions api/configs/feature/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,11 @@ class DataSetConfig(BaseModel):
default=30,
)

DATASET_OPERATOR_ENABLED: bool = Field(
description='whether to enable dataset operator',
default=False,
)


class WorkspaceConfig(BaseModel):
"""
Expand Down
67 changes: 59 additions & 8 deletions api/controllers/console/datasets/datasets.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from libs.login import login_required
from models.dataset import Dataset, Document, DocumentSegment
from models.model import ApiToken, UploadFile
from services.dataset_service import DatasetService, DocumentService
from services.dataset_service import DatasetPermissionService, DatasetService, DocumentService


def _validate_name(name):
Expand Down Expand Up @@ -85,6 +85,12 @@ def get(self):
else:
item['embedding_available'] = True

if item.get('permission') == 'partial_members':
part_users_list = DatasetPermissionService.get_dataset_partial_member_list(item['id'])
item.update({'partial_member_list': part_users_list})
else:
item.update({'partial_member_list': []})

response = {
'data': data,
'has_more': len(datasets) == limit,
Expand All @@ -108,7 +114,7 @@ def post(self):
help='Invalid indexing technique.')
args = parser.parse_args()

# The role of the current user in the ta table must be admin, owner, or editor
# The role of the current user in the ta table must be admin, owner, or editor, or dataset_operator
if not current_user.is_editor:
raise Forbidden()

Expand Down Expand Up @@ -140,6 +146,10 @@ def get(self, dataset_id):
except services.errors.account.NoPermissionError as e:
raise Forbidden(str(e))
data = marshal(dataset, dataset_detail_fields)
if data.get('permission') == 'partial_members':
part_users_list = DatasetPermissionService.get_dataset_partial_member_list(dataset_id_str)
data.update({'partial_member_list': part_users_list})

# check embedding setting
provider_manager = ProviderManager()
configurations = provider_manager.get_configurations(
Expand All @@ -163,6 +173,11 @@ def get(self, dataset_id):
data['embedding_available'] = False
else:
data['embedding_available'] = True

if data.get('permission') == 'partial_members':
part_users_list = DatasetPermissionService.get_dataset_partial_member_list(dataset_id_str)
data.update({'partial_member_list': part_users_list})

return data, 200

@setup_required
Expand All @@ -188,25 +203,39 @@ def patch(self, dataset_id):
nullable=True,
help='Invalid indexing technique.')
parser.add_argument('permission', type=str, location='json', choices=(
'only_me', 'all_team_members'), help='Invalid permission.')
'only_me', 'all_team_members', 'partial_members'), help='Invalid permission.'
)
parser.add_argument('embedding_model', type=str,
location='json', help='Invalid embedding model.')
parser.add_argument('embedding_model_provider', type=str,
location='json', help='Invalid embedding model provider.')
parser.add_argument('retrieval_model', type=dict, location='json', help='Invalid retrieval model.')
parser.add_argument('partial_member_list', type=list, location='json', help='Invalid parent user list.')
args = parser.parse_args()
data = request.get_json()

# The role of the current user in the ta table must be admin, owner, or editor
if not current_user.is_editor:
raise Forbidden()
# The role of the current user in the ta table must be admin, owner, editor, or dataset_operator
DatasetPermissionService.check_permission(
current_user, dataset, data.get('permission'), data.get('partial_member_list')
)

dataset = DatasetService.update_dataset(
dataset_id_str, args, current_user)

if dataset is None:
raise NotFound("Dataset not found.")

return marshal(dataset, dataset_detail_fields), 200
result_data = marshal(dataset, dataset_detail_fields)

if data.get('partial_member_list') and data.get('permission') == 'partial_members':
DatasetPermissionService.update_partial_member_list(dataset_id_str, data.get('partial_member_list'))
else:
DatasetPermissionService.clear_partial_member_list(dataset_id_str)

partial_member_list = DatasetPermissionService.get_dataset_partial_member_list(dataset_id_str)
result_data.update({'partial_member_list': partial_member_list})

return result_data, 200

@setup_required
@login_required
Expand All @@ -215,7 +244,7 @@ def delete(self, dataset_id):
dataset_id_str = str(dataset_id)

# The role of the current user in the ta table must be admin, owner, or editor
if not current_user.is_editor:
if not current_user.is_editor or current_user.is_dataset_operator:
raise Forbidden()

try:
Expand Down Expand Up @@ -569,6 +598,27 @@ def get(self, dataset_id):
}, 200


class DatasetPermissionUserListApi(Resource):
@setup_required
@login_required
@account_initialization_required
def get(self, dataset_id):
dataset_id_str = str(dataset_id)
dataset = DatasetService.get_dataset(dataset_id_str)
if dataset is None:
raise NotFound("Dataset not found.")
try:
DatasetService.check_dataset_permission(dataset, current_user)
except services.errors.account.NoPermissionError as e:
raise Forbidden(str(e))

partial_members_list = DatasetPermissionService.get_dataset_partial_member_list(dataset_id_str)

return {
'data': partial_members_list,
}, 200


api.add_resource(DatasetListApi, '/datasets')
api.add_resource(DatasetApi, '/datasets/<uuid:dataset_id>')
api.add_resource(DatasetUseCheckApi, '/datasets/<uuid:dataset_id>/use-check')
Expand All @@ -582,3 +632,4 @@ def get(self, dataset_id):
api.add_resource(DatasetApiBaseUrlApi, '/datasets/api-base-info')
api.add_resource(DatasetRetrievalSettingApi, '/datasets/retrieval-setting')
api.add_resource(DatasetRetrievalSettingMockApi, '/datasets/retrieval-setting/<string:vector_type>')
api.add_resource(DatasetPermissionUserListApi, '/datasets/<uuid:dataset_id>/permission-part-users')
26 changes: 18 additions & 8 deletions api/controllers/console/datasets/datasets_document.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ def post(self, dataset_id):
raise NotFound('Dataset not found.')

# The role of the current user in the ta table must be admin, owner, or editor
if not current_user.is_editor:
if not current_user.is_dataset_editor:
raise Forbidden()

try:
Expand Down Expand Up @@ -294,6 +294,11 @@ def post(self):
parser.add_argument('retrieval_model', type=dict, required=False, nullable=False,
location='json')
args = parser.parse_args()

# The role of the current user in the ta table must be admin, owner, or editor, or dataset_operator
if not current_user.is_dataset_editor:
raise Forbidden()

if args['indexing_technique'] == 'high_quality':
try:
model_manager = ModelManager()
Expand Down Expand Up @@ -757,14 +762,18 @@ def patch(self, dataset_id, document_id, action):
dataset = DatasetService.get_dataset(dataset_id)
if dataset is None:
raise NotFound("Dataset not found.")

# The role of the current user in the ta table must be admin, owner, or editor
if not current_user.is_dataset_editor:
raise Forbidden()

# check user's model setting
DatasetService.check_dataset_model_setting(dataset)

document = self.get_document(dataset_id, document_id)
# check user's permission
DatasetService.check_dataset_permission(dataset, current_user)

# The role of the current user in the ta table must be admin, owner, or editor
if not current_user.is_editor:
raise Forbidden()
document = self.get_document(dataset_id, document_id)

indexing_cache_key = 'document_{}_indexing'.format(document.id)
cache_result = redis_client.get(indexing_cache_key)
Expand Down Expand Up @@ -955,10 +964,11 @@ class DocumentRenameApi(DocumentResource):
@account_initialization_required
@marshal_with(document_fields)
def post(self, dataset_id, document_id):
# The role of the current user in the ta table must be admin or owner
if not current_user.is_admin_or_owner:
# The role of the current user in the ta table must be admin, owner, editor, or dataset_operator
if not current_user.is_dataset_editor:
raise Forbidden()

dataset = DatasetService.get_dataset(dataset_id)
DatasetService.check_dataset_operator_permission(current_user, dataset)
parser = reqparse.RequestParser()
parser.add_argument('name', type=str, required=True, nullable=False, location='json')
args = parser.parse_args()
Expand Down
12 changes: 6 additions & 6 deletions api/controllers/console/tag/tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def get(self):
@account_initialization_required
def post(self):
# The role of the current user in the ta table must be admin, owner, or editor
if not current_user.is_editor:
if not (current_user.is_editor or current_user.is_dataset_editor):
raise Forbidden()

parser = reqparse.RequestParser()
Expand Down Expand Up @@ -68,7 +68,7 @@ class TagUpdateDeleteApi(Resource):
def patch(self, tag_id):
tag_id = str(tag_id)
# The role of the current user in the ta table must be admin, owner, or editor
if not current_user.is_editor:
if not (current_user.is_editor or current_user.is_dataset_editor):
raise Forbidden()

parser = reqparse.RequestParser()
Expand Down Expand Up @@ -109,8 +109,8 @@ class TagBindingCreateApi(Resource):
@login_required
@account_initialization_required
def post(self):
# The role of the current user in the ta table must be admin, owner, or editor
if not current_user.is_editor:
# The role of the current user in the ta table must be admin, owner, editor, or dataset_operator
if not (current_user.is_editor or current_user.is_dataset_editor):
raise Forbidden()

parser = reqparse.RequestParser()
Expand All @@ -134,8 +134,8 @@ class TagBindingDeleteApi(Resource):
@login_required
@account_initialization_required
def post(self):
# The role of the current user in the ta table must be admin, owner, or editor
if not current_user.is_editor:
# The role of the current user in the ta table must be admin, owner, editor, or dataset_operator
if not (current_user.is_editor or current_user.is_dataset_editor):
raise Forbidden()

parser = reqparse.RequestParser()
Expand Down
13 changes: 13 additions & 0 deletions api/controllers/console/workspace/members.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,20 @@ def put(self, member_id):
return {'result': 'success'}


class DatasetOperatorMemberListApi(Resource):
"""List all members of current tenant."""

@setup_required
@login_required
@account_initialization_required
@marshal_with(account_with_role_list_fields)
def get(self):
members = TenantService.get_dataset_operator_members(current_user.current_tenant)
return {'result': 'success', 'accounts': members}, 200


api.add_resource(MemberListApi, '/workspaces/current/members')
api.add_resource(MemberInviteEmailApi, '/workspaces/current/members/invite-email')
api.add_resource(MemberCancelInviteApi, '/workspaces/current/members/<uuid:member_id>')
api.add_resource(MemberUpdateRoleApi, '/workspaces/current/members/<uuid:member_id>/update-role')
api.add_resource(DatasetOperatorMemberListApi, '/workspaces/current/dataset-operators')
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""add table dataset_permissions

Revision ID: 7e6a8693e07a
Revises: 4ff534e1eb11
Create Date: 2024-06-25 03:20:46.012193

"""
import sqlalchemy as sa
from alembic import op

import models as models

# revision identifiers, used by Alembic.
revision = '7e6a8693e07a'
down_revision = 'b2602e131636'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('dataset_permissions',
sa.Column('id', models.StringUUID(), server_default=sa.text('uuid_generate_v4()'), nullable=False),
sa.Column('dataset_id', models.StringUUID(), nullable=False),
sa.Column('account_id', models.StringUUID(), nullable=False),
sa.Column('has_permission', sa.Boolean(), server_default=sa.text('true'), nullable=False),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP(0)'), nullable=False),
sa.PrimaryKeyConstraint('id', name='dataset_permission_pkey')
)
with op.batch_alter_table('dataset_permissions', schema=None) as batch_op:
batch_op.create_index('idx_dataset_permissions_account_id', ['account_id'], unique=False)
batch_op.create_index('idx_dataset_permissions_dataset_id', ['dataset_id'], unique=False)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('dataset_permissions', schema=None) as batch_op:
batch_op.drop_index('idx_dataset_permissions_dataset_id')
batch_op.drop_index('idx_dataset_permissions_account_id')
op.drop_table('dataset_permissions')
# ### end Alembic commands ###
24 changes: 22 additions & 2 deletions api/models/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ def current_tenant_id(self, value):

self._current_tenant = tenant

@property
def current_role(self):
return self._current_tenant.current_role

def get_status(self) -> AccountStatus:
status_str = self.status
return AccountStatus(status_str)
Expand Down Expand Up @@ -110,6 +114,14 @@ def is_admin_or_owner(self):
def is_editor(self):
return TenantAccountRole.is_editing_role(self._current_tenant.current_role)

@property
def is_dataset_editor(self):
return TenantAccountRole.is_dataset_edit_role(self._current_tenant.current_role)

@property
def is_dataset_operator(self):
return self._current_tenant.current_role == TenantAccountRole.DATASET_OPERATOR

class TenantStatus(str, enum.Enum):
NORMAL = 'normal'
ARCHIVE = 'archive'
Expand All @@ -120,23 +132,30 @@ class TenantAccountRole(str, enum.Enum):
ADMIN = 'admin'
EDITOR = 'editor'
NORMAL = 'normal'
DATASET_OPERATOR = 'dataset_operator'

@staticmethod
def is_valid_role(role: str) -> bool:
return role and role in {TenantAccountRole.OWNER, TenantAccountRole.ADMIN, TenantAccountRole.EDITOR, TenantAccountRole.NORMAL}
return role and role in {TenantAccountRole.OWNER, TenantAccountRole.ADMIN, TenantAccountRole.EDITOR,
TenantAccountRole.NORMAL, TenantAccountRole.DATASET_OPERATOR}

@staticmethod
def is_privileged_role(role: str) -> bool:
return role and role in {TenantAccountRole.OWNER, TenantAccountRole.ADMIN}

@staticmethod
def is_non_owner_role(role: str) -> bool:
return role and role in {TenantAccountRole.ADMIN, TenantAccountRole.EDITOR, TenantAccountRole.NORMAL}
return role and role in {TenantAccountRole.ADMIN, TenantAccountRole.EDITOR, TenantAccountRole.NORMAL,
TenantAccountRole.DATASET_OPERATOR}

@staticmethod
def is_editing_role(role: str) -> bool:
return role and role in {TenantAccountRole.OWNER, TenantAccountRole.ADMIN, TenantAccountRole.EDITOR}

@staticmethod
def is_dataset_edit_role(role: str) -> bool:
return role and role in {TenantAccountRole.OWNER, TenantAccountRole.ADMIN, TenantAccountRole.EDITOR,
TenantAccountRole.DATASET_OPERATOR}

class Tenant(db.Model):
__tablename__ = 'tenants'
Expand Down Expand Up @@ -172,6 +191,7 @@ class TenantAccountJoinRole(enum.Enum):
OWNER = 'owner'
ADMIN = 'admin'
NORMAL = 'normal'
DATASET_OPERATOR = 'dataset_operator'


class TenantAccountJoin(db.Model):
Expand Down
Loading