mirror of
https://github.com/langgenius/dify.git
synced 2026-09-08 11:04:27 +08:00
Merge branch 'main' into feat/refine-snippet-siderbar
This commit is contained in:
commit
5bb1ec77f6
@ -13,7 +13,7 @@ def valid_password(password):
|
|||||||
if re.match(pattern, password) is not None:
|
if re.match(pattern, password) is not None:
|
||||||
return password
|
return password
|
||||||
|
|
||||||
raise ValueError("Password must contain letters and numbers, and the length must be greater than 8.")
|
raise ValueError("Password must contain letters and numbers, and the length must be at least 8 characters.")
|
||||||
|
|
||||||
|
|
||||||
def hash_password(password_str, salt_byte):
|
def hash_password(password_str, salt_byte):
|
||||||
|
|||||||
@ -183,7 +183,11 @@ class EnterpriseRequest(BaseRequest):
|
|||||||
if account_id:
|
if account_id:
|
||||||
inner_headers[INNER_ACCOUNT_ID_HEADER] = account_id
|
inner_headers[INNER_ACCOUNT_ID_HEADER] = account_id
|
||||||
|
|
||||||
if not cls.base_url.startswith("http") or not cls.base_url.startswith("https") or not cls.base_url:
|
if (
|
||||||
|
not cls.rbac_base_url.startswith("http")
|
||||||
|
or not cls.rbac_base_url.startswith("https")
|
||||||
|
or not cls.rbac_base_url
|
||||||
|
):
|
||||||
raise ValueError("ENTERPRISE_RBAC_API_URL is required when RBAC_ENABLED=true")
|
raise ValueError("ENTERPRISE_RBAC_API_URL is required when RBAC_ENABLED=true")
|
||||||
|
|
||||||
url = f"{cls.rbac_base_url}{endpoint}"
|
url = f"{cls.rbac_base_url}{endpoint}"
|
||||||
|
|||||||
@ -534,6 +534,31 @@ def _legacy_role_permission_keys(role: TenantAccountRole) -> list[str]:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _legacy_member_roles_response(
|
||||||
|
tenant_id: str, member_account_id: str, role: TenantAccountRole | str | None
|
||||||
|
) -> MemberRolesResponse:
|
||||||
|
if not role:
|
||||||
|
return MemberRolesResponse(account_id=member_account_id, roles=[])
|
||||||
|
|
||||||
|
tenant_role = TenantAccountRole(role)
|
||||||
|
role_value = tenant_role.value
|
||||||
|
return MemberRolesResponse(
|
||||||
|
account_id=member_account_id,
|
||||||
|
roles=[
|
||||||
|
RBACRole(
|
||||||
|
id=role_value,
|
||||||
|
name=role_value,
|
||||||
|
description="",
|
||||||
|
is_builtin=True,
|
||||||
|
type="",
|
||||||
|
permission_keys=_legacy_role_permission_keys(tenant_role),
|
||||||
|
role_tag="owner" if tenant_role == TenantAccountRole.OWNER else role_value,
|
||||||
|
tenant_id=tenant_id,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _legacy_my_permissions(tenant_id: str, account_id: str | None) -> MyPermissionsResponse:
|
def _legacy_my_permissions(tenant_id: str, account_id: str | None) -> MyPermissionsResponse:
|
||||||
if not account_id:
|
if not account_id:
|
||||||
return MyPermissionsResponse()
|
return MyPermissionsResponse()
|
||||||
@ -1582,23 +1607,7 @@ class RBACService:
|
|||||||
TenantAccountJoin.account_id == member_account_id,
|
TenantAccountJoin.account_id == member_account_id,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return MemberRolesResponse(
|
return _legacy_member_roles_response(tenant_id, member_account_id, role)
|
||||||
account_id=member_account_id,
|
|
||||||
roles=[
|
|
||||||
RBACRole(
|
|
||||||
id=role,
|
|
||||||
name=role,
|
|
||||||
description="",
|
|
||||||
is_builtin=True,
|
|
||||||
type="",
|
|
||||||
permission_keys=_legacy_role_permission_keys(role),
|
|
||||||
role_tag="owner" if role == "owner" else role,
|
|
||||||
tenant_id=tenant_id,
|
|
||||||
)
|
|
||||||
]
|
|
||||||
if role
|
|
||||||
else [],
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def batch_get(
|
def batch_get(
|
||||||
@ -1629,6 +1638,36 @@ class RBACService:
|
|||||||
member_account_id: str,
|
member_account_id: str,
|
||||||
role_ids: list[str],
|
role_ids: list[str],
|
||||||
) -> MemberRolesResponse:
|
) -> MemberRolesResponse:
|
||||||
|
if not dify_config.RBAC_ENABLED:
|
||||||
|
if len(role_ids) != 1:
|
||||||
|
raise ValueError("Legacy workspace member role update requires exactly one role.")
|
||||||
|
|
||||||
|
tenant_role = TenantAccountRole(role_ids[0])
|
||||||
|
with session_factory.create_session() as session:
|
||||||
|
target_member_join = session.scalar(
|
||||||
|
select(TenantAccountJoin).where(
|
||||||
|
TenantAccountJoin.tenant_id == tenant_id,
|
||||||
|
TenantAccountJoin.account_id == member_account_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if not target_member_join:
|
||||||
|
raise ValueError("Member not in tenant.")
|
||||||
|
|
||||||
|
if tenant_role == TenantAccountRole.OWNER:
|
||||||
|
current_owner_join = session.scalar(
|
||||||
|
select(TenantAccountJoin).where(
|
||||||
|
TenantAccountJoin.tenant_id == tenant_id,
|
||||||
|
TenantAccountJoin.role == TenantAccountRole.OWNER,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if current_owner_join and current_owner_join.account_id != member_account_id:
|
||||||
|
current_owner_join.role = TenantAccountRole.ADMIN
|
||||||
|
|
||||||
|
target_member_join.role = tenant_role
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
return _legacy_member_roles_response(tenant_id, member_account_id, tenant_role)
|
||||||
|
|
||||||
data = _inner_call(
|
data = _inner_call(
|
||||||
"PUT",
|
"PUT",
|
||||||
f"{_INNER_PREFIX}/members/rbac-roles",
|
f"{_INNER_PREFIX}/members/rbac-roles",
|
||||||
|
|||||||
@ -35,6 +35,13 @@ class TestValidPassword:
|
|||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
valid_password("")
|
valid_password("")
|
||||||
|
|
||||||
|
def test_should_reject_password_shorter_than_minimum_length(self):
|
||||||
|
"""A 7-character password with letters and numbers is rejected for length."""
|
||||||
|
with pytest.raises(ValueError) as exc_info:
|
||||||
|
valid_password("abc1234")
|
||||||
|
|
||||||
|
assert "at least 8" in str(exc_info.value)
|
||||||
|
|
||||||
|
|
||||||
class TestPasswordHashing:
|
class TestPasswordHashing:
|
||||||
"""Test password hashing and comparison"""
|
"""Test password hashing and comparison"""
|
||||||
|
|||||||
@ -745,15 +745,54 @@ class TestMemberRoles:
|
|||||||
|
|
||||||
def test_replace(self, mock_send: MagicMock):
|
def test_replace(self, mock_send: MagicMock):
|
||||||
mock_send.return_value = {"account_id": "acct-2", "roles": []}
|
mock_send.return_value = {"account_id": "acct-2", "roles": []}
|
||||||
svc.RBACService.MemberRoles.replace(
|
with patch(f"{MODULE}.dify_config.RBAC_ENABLED", True):
|
||||||
"tenant-1", "acct-1", "acct-2", role_ids=["workspace.owner", "workspace.editor"]
|
svc.RBACService.MemberRoles.replace(
|
||||||
)
|
"tenant-1", "acct-1", "acct-2", role_ids=["workspace.owner", "workspace.editor"]
|
||||||
|
)
|
||||||
call = _call_args(mock_send)
|
call = _call_args(mock_send)
|
||||||
assert call.method == "PUT"
|
assert call.method == "PUT"
|
||||||
assert call.endpoint == "/rbac/members/rbac-roles"
|
assert call.endpoint == "/rbac/members/rbac-roles"
|
||||||
assert call.params == {"account_id": "acct-2"}
|
assert call.params == {"account_id": "acct-2"}
|
||||||
assert call.json == {"role_ids": ["workspace.owner", "workspace.editor"]}
|
assert call.json == {"role_ids": ["workspace.owner", "workspace.editor"]}
|
||||||
|
|
||||||
|
def test_replace_updates_legacy_join_role_when_rbac_disabled(self, mock_send: MagicMock):
|
||||||
|
session = MagicMock()
|
||||||
|
session.__enter__.return_value = session
|
||||||
|
target_join = SimpleNamespace(role=svc.TenantAccountRole.NORMAL, account_id="acct-2")
|
||||||
|
session.scalar.return_value = target_join
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch(f"{MODULE}.dify_config.RBAC_ENABLED", False),
|
||||||
|
patch(f"{MODULE}.session_factory.create_session", return_value=session),
|
||||||
|
):
|
||||||
|
out = svc.RBACService.MemberRoles.replace("tenant-1", "acct-1", "acct-2", role_ids=["editor"])
|
||||||
|
|
||||||
|
mock_send.assert_not_called()
|
||||||
|
session.commit.assert_called_once()
|
||||||
|
assert target_join.role == svc.TenantAccountRole.EDITOR
|
||||||
|
assert out.account_id == "acct-2"
|
||||||
|
assert out.roles[0].id == "editor"
|
||||||
|
assert "app.acl.preview" in out.roles[0].permission_keys
|
||||||
|
|
||||||
|
def test_replace_legacy_owner_demotes_current_owner_when_rbac_disabled(self, mock_send: MagicMock):
|
||||||
|
session = MagicMock()
|
||||||
|
session.__enter__.return_value = session
|
||||||
|
target_join = SimpleNamespace(role=svc.TenantAccountRole.NORMAL, account_id="acct-2")
|
||||||
|
owner_join = SimpleNamespace(role=svc.TenantAccountRole.OWNER, account_id="acct-owner")
|
||||||
|
session.scalar.side_effect = [target_join, owner_join]
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch(f"{MODULE}.dify_config.RBAC_ENABLED", False),
|
||||||
|
patch(f"{MODULE}.session_factory.create_session", return_value=session),
|
||||||
|
):
|
||||||
|
out = svc.RBACService.MemberRoles.replace("tenant-1", "acct-1", "acct-2", role_ids=["owner"])
|
||||||
|
|
||||||
|
mock_send.assert_not_called()
|
||||||
|
session.commit.assert_called_once()
|
||||||
|
assert target_join.role == svc.TenantAccountRole.OWNER
|
||||||
|
assert owner_join.role == svc.TenantAccountRole.ADMIN
|
||||||
|
assert out.roles[0].id == "owner"
|
||||||
|
|
||||||
def test_batch_get(self, mock_send: MagicMock):
|
def test_batch_get(self, mock_send: MagicMock):
|
||||||
mock_send.return_value = {
|
mock_send.return_value = {
|
||||||
"acct-2": [
|
"acct-2": [
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user