From 0b0f382d2e989782fda39a1596449f3b1a017b9d Mon Sep 17 00:00:00 2001 From: zhengxie Date: Wed, 16 Jan 2019 15:36:09 +0800 Subject: [PATCH] wip: Add org role permissions --- seahub/api2/endpoints/admin/organizations.py | 12 +++- seahub/base/accounts.py | 63 ++++++++++++++----- seahub/constants.py | 2 + seahub/role_permissions/settings.py | 29 ++++++++- seahub/role_permissions/utils.py | 19 +++++- .../templates/sysadmin/org_admin_table.html | 24 ++++++- seahub/views/sysadmin.py | 17 ++++- .../{test_orgs.py => test_organizations.py} | 48 +++++++++++++- 8 files changed, 190 insertions(+), 24 deletions(-) rename tests/api/endpoints/admin/{test_orgs.py => test_organizations.py} (59%) diff --git a/seahub/api2/endpoints/admin/organizations.py b/seahub/api2/endpoints/admin/organizations.py index 32032323a8..151fed9d71 100644 --- a/seahub/api2/endpoints/admin/organizations.py +++ b/seahub/api2/endpoints/admin/organizations.py @@ -17,6 +17,7 @@ from seahub.api2.authentication import TokenAuthentication from seahub.api2.throttling import UserRateThrottle from seahub.api2.utils import api_error from seahub.api2.permissions import IsProVersion +from seahub.role_permissions.utils import get_available_org_roles try: from seahub.settings import ORG_MEMBER_QUOTA_ENABLED @@ -33,13 +34,13 @@ except ImportError: try: from seahub.settings import MULTI_TENANCY + from seahub_extra.organizations.models import OrgSettings except ImportError: MULTI_TENANCY = False logger = logging.getLogger(__name__) def get_org_info(org): - org_id = org.org_id org_info = {} @@ -47,6 +48,7 @@ def get_org_info(org): org_info['org_name'] = org.org_name org_info['ctime'] = timestamp_to_isoformat_timestr(org.ctime) org_info['org_url_prefix'] = org.url_prefix + org_info['role'] = OrgSettings.objects.get_role_by_org(org) creator = org.creator org_info['creator_email'] = creator @@ -203,6 +205,14 @@ class AdminOrganization(APIView): error_msg = 'Internal Server Error' return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + role = request.data.get('role', None) + if role: + if role not in get_available_org_roles(): + error_msg = 'Role %s invalid.' % role + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + OrgSettings.objects.add_or_update(org, role) + org = ccnet_api.get_org_by_id(org_id) org_info = get_org_info(org) return Response(org_info) diff --git a/seahub/base/accounts.py b/seahub/base/accounts.py index a9737c590f..a5822c1d43 100644 --- a/seahub/base/accounts.py +++ b/seahub/base/accounts.py @@ -19,7 +19,8 @@ from seahub.auth import login from seahub.profile.models import Profile, DetailedProfile from seahub.role_permissions.models import AdminRole from seahub.role_permissions.utils import get_enabled_role_permissions_by_role, \ - get_enabled_admin_role_permissions_by_role + get_enabled_admin_role_permissions_by_role, \ + get_enabled_org_role_permissions_by_role from seahub.utils import is_user_password_strong, get_site_name, \ clear_token, get_system_admins, is_pro_version, IS_EMAIL_CONFIGURED from seahub.utils.mail import send_html_email_with_dj_template, MAIL_PRIORITY @@ -125,20 +126,36 @@ class UserPermissions(object): def __init__(self, user): self.user = user + def _get_perm_by_roles(self, perm_name): + role = self.user.role + perm = get_enabled_role_permissions_by_role(role)[perm_name] + if perm is False: + return False + + org_role = self.user.org_role + if org_role is None: + return perm + + perm2 = get_enabled_org_role_permissions_by_role(org_role)[perm_name] + if perm2 is False: + return False + + return True + def can_add_repo(self): - return get_enabled_role_permissions_by_role(self.user.role)['can_add_repo'] + return self._get_perm_by_roles('can_add_repo') def can_add_group(self): - return get_enabled_role_permissions_by_role(self.user.role)['can_add_group'] + return self._get_perm_by_roles('can_add_group') def can_generate_share_link(self): - return get_enabled_role_permissions_by_role(self.user.role)['can_generate_share_link'] + return self._get_perm_by_roles('can_generate_share_link') def can_generate_upload_link(self): - return get_enabled_role_permissions_by_role(self.user.role)['can_generate_upload_link'] + return self._get_perm_by_roles('can_generate_upload_link') def can_use_global_address_book(self): - return get_enabled_role_permissions_by_role(self.user.role)['can_use_global_address_book'] + return self._get_perm_by_roles('can_use_global_address_book') def can_view_org(self): if MULTI_TENANCY: @@ -147,7 +164,7 @@ class UserPermissions(object): if CLOUD_MODE: return False - return get_enabled_role_permissions_by_role(self.user.role)['can_view_org'] + return self._get_perm_by_roles('can_view_org') def can_add_public_repo(self): """ Check if user can create public repo or share existed repo to public. @@ -162,28 +179,28 @@ class UserPermissions(object): return False elif self.user.is_staff: return True - elif get_enabled_role_permissions_by_role(self.user.role)['can_add_public_repo']: + elif self._get_perm_by_roles('can_add_public_repo'): return True else: return bool(config.ENABLE_USER_CREATE_ORG_REPO) def can_drag_drop_folder_to_sync(self): - return get_enabled_role_permissions_by_role(self.user.role)['can_drag_drop_folder_to_sync'] + return self._get_perm_by_roles('can_drag_drop_folder_to_sync') def can_connect_with_android_clients(self): - return get_enabled_role_permissions_by_role(self.user.role)['can_connect_with_android_clients'] + return self._get_perm_by_roles('can_connect_with_android_clients') def can_connect_with_ios_clients(self): - return get_enabled_role_permissions_by_role(self.user.role)['can_connect_with_ios_clients'] + return self._get_perm_by_roles('can_connect_with_ios_clients') def can_connect_with_desktop_clients(self): - return get_enabled_role_permissions_by_role(self.user.role)['can_connect_with_desktop_clients'] + return self._get_perm_by_roles('can_connect_with_desktop_clients') def can_invite_guest(self): - return get_enabled_role_permissions_by_role(self.user.role)['can_invite_guest'] + return self._get_perm_by_roles('can_invite_guest') def can_export_files_via_mobile_client(self): - return get_enabled_role_permissions_by_role(self.user.role)['can_export_files_via_mobile_client'] + return self._get_perm_by_roles('can_export_files_via_mobile_client') # Add default value for compatible issue when EMAILBE_ROLE_PERMISSIONS # is not updated with newly added permissions. @@ -237,6 +254,24 @@ class User(object): org = None objects = UserManager() + @property + def org_role(self): + if not MULTI_TENANCY: + return None + + if not hasattr(self, '_cached_orgs'): + self._cached_orgs = ccnet_api.get_orgs_by_user(self.username) + + if not self._cached_orgs: + return None + + if not hasattr(self, '_cached_org_role'): + from seahub_extra.organizations.models import OrgSettings + self._cached_org_role = OrgSettings.objects.get_role_by_org( + self._cached_orgs[0]) + + return self._cached_org_role + class DoesNotExist(Exception): pass diff --git a/seahub/constants.py b/seahub/constants.py index 9510d1226b..23bd047de3 100644 --- a/seahub/constants.py +++ b/seahub/constants.py @@ -18,6 +18,8 @@ SYSTEM_ADMIN = 'system_admin' DAILY_ADMIN = 'daily_admin' AUDIT_ADMIN = 'audit_admin' +DEFAULT_ORG = 'default' + HASH_URLS = { 'GROUP_MEMBERS': settings.SITE_ROOT + '#group/%(group_id)s/members/', 'GROUP_DISCUSS': settings.SITE_ROOT + '#group/%(group_id)s/discussions/', diff --git a/seahub/role_permissions/settings.py b/seahub/role_permissions/settings.py index 6fef3cf22c..32129f81d8 100644 --- a/seahub/role_permissions/settings.py +++ b/seahub/role_permissions/settings.py @@ -2,7 +2,7 @@ import logging from django.conf import settings -from seahub.constants import DEFAULT_USER, GUEST_USER, \ +from seahub.constants import DEFAULT_USER, GUEST_USER, DEFAULT_ORG, \ DEFAULT_ADMIN, SYSTEM_ADMIN, DAILY_ADMIN, AUDIT_ADMIN # Get an instance of a logger @@ -141,3 +141,30 @@ def get_enabled_admin_role_permissions(): return permissions ENABLED_ADMIN_ROLE_PERMISSIONS = get_enabled_admin_role_permissions() + +# role permissions for Org +def merge_roles(default, custom): + """Merge custom dict into the copy of default dict, and return the copy.""" + copy = default.copy() + for key in custom: + if key in default: + copy[key].update(custom[key]) + else: + default_copy = default['default'].copy() + default_copy.update(custom[key]) + copy[key] = default_copy + + return copy + +DEFAULT_ENABLED_ORG_ROLE_PERMISSIONS = { + DEFAULT_ORG: DEFAULT_ENABLED_ROLE_PERMISSIONS[DEFAULT_USER] +} + +try: + custom_org_role_permission = settings.ENABLED_ORG_ROLE_PERMISSIONS +except AttributeError: + custom_org_role_permission = {} + +ENABLED_ORG_ROLE_PERMISSIONS = merge_roles( + DEFAULT_ENABLED_ORG_ROLE_PERMISSIONS, custom_org_role_permission +) diff --git a/seahub/role_permissions/utils.py b/seahub/role_permissions/utils.py index 747557f618..d948de93b2 100644 --- a/seahub/role_permissions/utils.py +++ b/seahub/role_permissions/utils.py @@ -1,10 +1,10 @@ # Copyright (c) 2012-2016 Seafile Ltd. import logging -from .settings import ENABLED_ROLE_PERMISSIONS, \ +from .settings import ENABLED_ROLE_PERMISSIONS, ENABLED_ORG_ROLE_PERMISSIONS, \ ENABLED_ADMIN_ROLE_PERMISSIONS -from seahub.constants import DEFAULT_USER, DEFAULT_ADMIN +from seahub.constants import DEFAULT_USER, DEFAULT_ADMIN, DEFAULT_ORG logger = logging.getLogger(__name__) @@ -13,6 +13,11 @@ def get_available_roles(): """ return ENABLED_ROLE_PERMISSIONS.keys() +def get_available_org_roles(): + """Get available roles defined in `ENABLED_ORG_ROLE_PERMISSIONS`. + """ + return ENABLED_ORG_ROLE_PERMISSIONS.keys() + def get_enabled_role_permissions_by_role(role): """Get permissions dict(perm_name: bool) of a role. """ @@ -25,6 +30,16 @@ def get_enabled_role_permissions_by_role(role): return ENABLED_ROLE_PERMISSIONS[role] +def get_enabled_org_role_permissions_by_role(role): + if not role: + role = DEFAULT_ORG + + if role not in ENABLED_ORG_ROLE_PERMISSIONS.keys(): + logger.warn('%s is not a valid org role, use default role.' % role) + role = DEFAULT_ORG + + return ENABLED_ORG_ROLE_PERMISSIONS[role] + def get_available_admin_roles(): """Get available admin roles defined in `ENABLED_ADMIN_ROLE_PERMISSIONS`. """ diff --git a/seahub/templates/sysadmin/org_admin_table.html b/seahub/templates/sysadmin/org_admin_table.html index 33945fa148..c61e5548f8 100644 --- a/seahub/templates/sysadmin/org_admin_table.html +++ b/seahub/templates/sysadmin/org_admin_table.html @@ -1,11 +1,12 @@ {% load seahub_tags i18n %} - - + + + - + {% for org in orgs %} @@ -16,6 +17,23 @@ {% endif %} + diff --git a/seahub/views/sysadmin.py b/seahub/views/sysadmin.py index 77db2f0b47..56971fbc56 100644 --- a/seahub/views/sysadmin.py +++ b/seahub/views/sysadmin.py @@ -36,13 +36,13 @@ from seahub.base.templatetags.seahub_tags import tsstr_sec, email2nickname from seahub.auth import authenticate from seahub.auth.decorators import login_required, login_required_ajax from seahub.constants import GUEST_USER, DEFAULT_USER, DEFAULT_ADMIN, \ - SYSTEM_ADMIN, DAILY_ADMIN, AUDIT_ADMIN, HASH_URLS + SYSTEM_ADMIN, DAILY_ADMIN, AUDIT_ADMIN, HASH_URLS, DEFAULT_ORG from seahub.institutions.models import (Institution, InstitutionAdmin, InstitutionQuota) from seahub.institutions.utils import get_institution_space_usage from seahub.invitations.models import Invitation from seahub.role_permissions.utils import get_available_roles, \ - get_available_admin_roles + get_available_admin_roles, get_available_org_roles from seahub.role_permissions.models import AdminRole from seahub.two_factor.models import default_device from seahub.utils import IS_EMAIL_CONFIGURED, string2list, is_valid_username, \ @@ -84,6 +84,7 @@ if ENABLE_TRIAL_ACCOUNT: from seahub_extra.trialaccount.models import TrialAccount try: from seahub.settings import MULTI_TENANCY + from seahub_extra.organizations.models import OrgSettings except ImportError: MULTI_TENANCY = False from seahub.utils.two_factor_auth import has_two_factor_auth @@ -1320,6 +1321,11 @@ def sys_org_admin(request): else: trial_orgs = [] + org_roles = OrgSettings.objects.get_by_orgs(orgs) + org_roles_dict = {} + for x in org_roles: + org_roles_dict[x.org_id] = x.role + for org in orgs: org.quota_usage = seafserv_threaded_rpc.get_org_quota_usage(org.org_id) org.total_quota = seafserv_threaded_rpc.get_org_quota(org.org_id) @@ -1341,6 +1347,11 @@ def sys_org_admin(request): else: org.is_expired = False + org.role = org_roles_dict.get(org.org_id, DEFAULT_ORG) + org.is_default_role = True if org.role == DEFAULT_ORG else False + + extra_org_roles = [x for x in get_available_org_roles() if x != DEFAULT_ORG] + return render(request, 'sysadmin/sys_org_admin.html', { 'orgs': orgs, 'current_page': current_page, @@ -1350,6 +1361,8 @@ def sys_org_admin(request): 'page_next': page_next, 'enable_org_plan': enable_org_plan, 'all_page': True, + 'extra_org_roles': extra_org_roles, + 'default_org': DEFAULT_ORG, }) @login_required diff --git a/tests/api/endpoints/admin/test_orgs.py b/tests/api/endpoints/admin/test_organizations.py similarity index 59% rename from tests/api/endpoints/admin/test_orgs.py rename to tests/api/endpoints/admin/test_organizations.py index 8275eb3ee1..54c4f70049 100644 --- a/tests/api/endpoints/admin/test_orgs.py +++ b/tests/api/endpoints/admin/test_organizations.py @@ -1,7 +1,10 @@ import json +from mock import patch from seaserv import ccnet_api from django.core.urlresolvers import reverse +from django.test import override_settings + from seahub.test_utils import BaseTestCase from tests.common.utils import randstring @@ -30,7 +33,7 @@ def remove_org(org_id): # remove org ccnet_api.remove_org(org_id) -class OrgsTest(BaseTestCase): +class AdminOrganizationsTest(BaseTestCase): def setUp(self): @@ -83,3 +86,46 @@ class OrgsTest(BaseTestCase): self.login_as(self.user) resp = self.client.get(self.orgs_url) self.assertEqual(403, resp.status_code) + + +class AdminOrganizationTest(BaseTestCase): + def setUp(self): + org_name = randstring(6) + org_url_prefix = randstring(6) + tmp_user = self.create_user(email='%s@%s.com' % (randstring(6), randstring(6))) + org_creator = tmp_user.username + org_id = ccnet_api.create_org( + org_name, org_url_prefix, org_creator) + + self.org = ccnet_api.get_org_by_id(org_id) + self.url = reverse('api-v2.1-admin-organization', args=[self.org.org_id]) + self.login_as(self.admin) + + def tearDown(self, ): + users = ccnet_api.get_org_emailusers(self.org.url_prefix, -1, -1) + for u in users: + ccnet_api.remove_org_user(self.org.org_id, u.email) + + ccnet_api.remove_org(self.org.org_id) + + def test_can_get(self, ): + resp = self.client.get(self.url) + self.assertEqual(200, resp.status_code) + + json_resp = json.loads(resp.content) + assert json_resp['org_id'] == self.org.org_id + assert json_resp['role'] == 'default' + + @patch('seahub.api2.endpoints.admin.organizations.get_available_org_roles') + @patch('seahub_extra.organizations.models.get_available_org_roles') + def test_can_update_role(self, mock_1, mock_2): + mock_1.return_value = ['default', 'custom'] + mock_2.return_value = ['default', 'custom'] + + resp = self.client.put(self.url, 'role=custom', + 'application/x-www-form-urlencoded') + self.assertEqual(200, resp.status_code) + + json_resp = json.loads(resp.content) + assert json_resp['org_id'] == self.org.org_id + assert json_resp['role'] == 'custom'
{% trans "Name" %}{% trans "Creator" %}{% trans "Name" %}{% trans "Creator" %}{% trans "Role" %} {% trans "Space Used" %} {% trans "Created At / Expiration" %}{% trans "Operations" %}{% trans "Operations" %}
{{ org.creator }} +
+ {% if org.is_default_role %} + {% trans "Default" %} + {% else %} + {{ org.role }} + {% endif %} +
+ + + +
{{ org.quota_usage|seahub_filesizeformat }} {% if org.total_quota > 0 %} / {{ org.total_quota|seahub_filesizeformat }} {% endif %}