changeset 7686:93834966ae01

auth: global permissions given to the default user are the bare minimum and should apply to *all* other users too Drop the "subtractive permission" config option "inherit_from_default" that when set to false would give users less global permissions than the default unauthenticated user. Instead, think positive and merge all positive permissions. At the end, filter the global permissions to make sure we for each kind of permissions only keep the one with most weight.
author Mads Kiilerich <mads@kiilerich.com>
date Mon, 31 Dec 2018 02:25:11 +0100
parents 71713cf466b9
children b2634df81a11
files kallithea/config/rcextensions/__init__.py kallithea/controllers/admin/user_groups.py kallithea/controllers/admin/users.py kallithea/lib/auth.py kallithea/lib/hooks.py kallithea/model/db.py kallithea/model/forms.py kallithea/templates/admin/users/user_edit_ips.html kallithea/templates/base/default_perms_box.html kallithea/tests/functional/test_forks.py kallithea/tests/models/test_permissions.py
diffstat 11 files changed, 44 insertions(+), 129 deletions(-) [+]
line wrap: on
line diff
--- a/kallithea/config/rcextensions/__init__.py	Thu Jan 03 01:03:27 2019 +0100
+++ b/kallithea/config/rcextensions/__init__.py	Mon Dec 31 02:25:11 2018 +0100
@@ -105,7 +105,6 @@
       :param active:
       :param password:
       :param emails:
-      :param inherit_default_permissions:
       :param created_by:
     """
     return 0
@@ -169,7 +168,6 @@
       :param active:
       :param password:
       :param emails:
-      :param inherit_default_permissions:
       :param deleted_by:
     """
     return 0
--- a/kallithea/controllers/admin/user_groups.py	Thu Jan 03 01:03:27 2019 +0100
+++ b/kallithea/controllers/admin/user_groups.py	Mon Dec 31 02:25:11 2018 +0100
@@ -369,8 +369,6 @@
             form = CustomDefaultPermissionsForm()()
             form_result = form.to_python(request.POST)
 
-            inherit_perms = form_result['inherit_default_permissions']
-            user_group.inherit_default_permissions = inherit_perms
             usergroup_model = UserGroupModel()
 
             defs = UserGroupToPerm.query() \
--- a/kallithea/controllers/admin/users.py	Thu Jan 03 01:03:27 2019 +0100
+++ b/kallithea/controllers/admin/users.py	Mon Dec 31 02:25:11 2018 +0100
@@ -315,8 +315,6 @@
             form = CustomDefaultPermissionsForm()()
             form_result = form.to_python(request.POST)
 
-            inherit_perms = form_result['inherit_default_permissions']
-            user.inherit_default_permissions = inherit_perms
             user_model = UserModel()
 
             defs = UserToPerm.query() \
@@ -391,7 +389,6 @@
         c.user_ip_map = UserIpMap.query() \
             .filter(UserIpMap.user == c.user).all()
 
-        c.inherit_default_ips = c.user.inherit_default_permissions
         c.default_user_ip_map = UserIpMap.query() \
             .filter(UserIpMap.user == User.get_default_user()).all()
 
--- a/kallithea/lib/auth.py	Thu Jan 03 01:03:27 2019 +0100
+++ b/kallithea/lib/auth.py	Mon Dec 31 02:25:11 2018 +0100
@@ -132,7 +132,7 @@
                         % __platform__)
 
 
-def _cached_perms_data(user_id, user_is_admin, user_inherit_default_permissions,
+def _cached_perms_data(user_id, user_is_admin,
                        explicit):
     RK = 'repositories'
     GK = 'repositories_groups'
@@ -226,14 +226,8 @@
         permissions[UK][u_k] = p
 
     #======================================================================
-    # !! OVERRIDE GLOBALS !! with user permissions if any found
+    # !! Augment GLOBALS with user permissions if any found !!
     #======================================================================
-    # those can be configured from groups or users explicitly
-    _configurable = set([
-        'hg.fork.none', 'hg.fork.repository',
-        'hg.create.none', 'hg.create.repository',
-        'hg.usergroup.create.false', 'hg.usergroup.create.true'
-    ])
 
     # USER GROUPS comes first
     # user group global permissions
@@ -253,14 +247,6 @@
                 itertools.groupby(user_perms_from_users_groups,
                                   lambda x:x.users_group)]
     for gr, perms in _grouped:
-        # since user can be in multiple groups iterate over them and
-        # select the lowest permissions first (more explicit)
-        # TODO: do this^^
-        if not gr.inherit_default_permissions:
-            # NEED TO IGNORE all configurable permissions and
-            # replace them with explicitly set
-            permissions[GLOBAL] = permissions[GLOBAL] \
-                                            .difference(_configurable)
         for perm in perms:
             permissions[GLOBAL].add(perm.permission.permission_name)
 
@@ -269,14 +255,15 @@
             .options(joinedload(UserToPerm.permission)) \
             .filter(UserToPerm.user_id == user_id).all()
 
-    if not user_inherit_default_permissions:
-        # NEED TO IGNORE all configurable permissions and
-        # replace them with explicitly set
-        permissions[GLOBAL] = permissions[GLOBAL] \
-                                        .difference(_configurable)
+    for perm in user_perms:
+        permissions[GLOBAL].add(perm.permission.permission_name)
 
-        for perm in user_perms:
-            permissions[GLOBAL].add(perm.permission.permission_name)
+    # for each kind of global permissions, only keep the one with heighest weight
+    kind_max_perm = {}
+    for perm in sorted(permissions[GLOBAL], key=lambda n: PERM_WEIGHTS[n]):
+        kind = perm.rsplit('.', 1)[0]
+        kind_max_perm[kind] = perm
+    permissions[GLOBAL] = set(kind_max_perm.values())
     ## END GLOBAL PERMISSIONS
 
     #======================================================================
@@ -485,7 +472,6 @@
         self.lastname = ''
         self.email = ''
         self.admin = False
-        self.inherit_default_permissions = False
 
         # Look up database user, if necessary.
         if user_id is not None:
@@ -587,13 +573,11 @@
         """
         user_id = user.user_id
         user_is_admin = user.is_admin
-        user_inherit_default_permissions = user.inherit_default_permissions
 
         log.debug('Getting PERMISSION tree')
         compute = conditional_cache('short_term', 'cache_desc',
                                     condition=cache, func=_cached_perms_data)
-        return compute(user_id, user_is_admin,
-                       user_inherit_default_permissions, explicit)
+        return compute(user_id, user_is_admin, explicit)
 
     def _get_api_keys(self):
         api_keys = [self.api_key]
@@ -637,8 +621,7 @@
         Check if the given IP address (a `str`) is allowed for the given
         user (an `AuthUser` or `db.User`).
         """
-        allowed_ips = AuthUser.get_allowed_ips(user.user_id, cache=True,
-            inherit_from_default=user.inherit_default_permissions)
+        allowed_ips = AuthUser.get_allowed_ips(user.user_id, cache=True)
         if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
             log.debug('IP:%s is in range of %s', ip_addr, allowed_ips)
             return True
@@ -672,30 +655,26 @@
         return au
 
     @classmethod
-    def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
+    def get_allowed_ips(cls, user_id, cache=False):
         _set = set()
 
-        if inherit_from_default:
-            default_ips = UserIpMap.query().filter(UserIpMap.user_id ==
-                                            User.get_default_user(cache=True).user_id)
-            if cache:
-                default_ips = default_ips.options(FromCache("sql_cache_short",
-                                                  "get_user_ips_default"))
-
-            # populate from default user
-            for ip in default_ips:
-                try:
-                    _set.add(ip.ip_addr)
-                except ObjectDeletedError:
-                    # since we use heavy caching sometimes it happens that we get
-                    # deleted objects here, we just skip them
-                    pass
+        default_ips = UserIpMap.query().filter(UserIpMap.user_id ==
+                                        User.get_default_user(cache=True).user_id)
+        if cache:
+            default_ips = default_ips.options(FromCache("sql_cache_short",
+                                              "get_user_ips_default"))
+        for ip in default_ips:
+            try:
+                _set.add(ip.ip_addr)
+            except ObjectDeletedError:
+                # since we use heavy caching sometimes it happens that we get
+                # deleted objects here, we just skip them
+                pass
 
         user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
         if cache:
             user_ips = user_ips.options(FromCache("sql_cache_short",
                                                   "get_user_ips_%s" % user_id))
-
         for ip in user_ips:
             try:
                 _set.add(ip.ip_addr)
--- a/kallithea/lib/hooks.py	Thu Jan 03 01:03:27 2019 +0100
+++ b/kallithea/lib/hooks.py	Mon Dec 31 02:25:11 2018 +0100
@@ -212,7 +212,6 @@
      'active',
      'password',
      'emails',
-     'inherit_default_permissions'
 
     """
     from kallithea import EXTENSIONS
@@ -285,7 +284,6 @@
      'active',
      'password',
      'emails',
-     'inherit_default_permissions'
 
     """
     from kallithea import EXTENSIONS
--- a/kallithea/model/db.py	Thu Jan 03 01:03:27 2019 +0100
+++ b/kallithea/model/db.py	Mon Dec 31 02:25:11 2018 +0100
@@ -426,7 +426,6 @@
     extern_type = Column(String(255), nullable=True) # FIXME: not nullable?
     extern_name = Column(String(255), nullable=True) # FIXME: not nullable?
     api_key = Column(String(255), nullable=False)
-    inherit_default_permissions = Column(Boolean(), nullable=False, default=True)
     created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
     _user_data = Column("user_data", LargeBinary(), nullable=True)  # JSON data # FIXME: not nullable?
 
@@ -820,7 +819,6 @@
     users_group_name = Column(Unicode(255), nullable=False, unique=True)
     user_group_description = Column(Unicode(10000), nullable=True) # FIXME: not nullable?
     users_group_active = Column(Boolean(), nullable=False)
-    inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, default=True)
     owner_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
     created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
     _group_data = Column("group_data", LargeBinary(), nullable=True)  # JSON data # FIXME: not nullable?
--- a/kallithea/model/forms.py	Thu Jan 03 01:03:27 2019 +0100
+++ b/kallithea/model/forms.py	Mon Dec 31 02:25:11 2018 +0100
@@ -424,7 +424,6 @@
     class _CustomDefaultPermissionsForm(formencode.Schema):
         filter_extra_fields = True
         allow_extra_fields = True
-        inherit_default_permissions = v.StringBoolean(if_missing=False)
 
         create_repo_perm = v.StringBoolean(if_missing=False)
         create_user_group_perm = v.StringBoolean(if_missing=False)
--- a/kallithea/templates/admin/users/user_edit_ips.html	Thu Jan 03 01:03:27 2019 +0100
+++ b/kallithea/templates/admin/users/user_edit_ips.html	Mon Dec 31 02:25:11 2018 +0100
@@ -1,5 +1,5 @@
 <table class="table">
-    %if c.default_user_ip_map and c.inherit_default_ips:
+    %if c.default_user_ip_map:
         %for ip in c.default_user_ip_map:
           <tr>
             <td><div class="ip">${ip.ip_addr}</div></td>
--- a/kallithea/templates/base/default_perms_box.html	Thu Jan 03 01:03:27 2019 +0100
+++ b/kallithea/templates/base/default_perms_box.html	Mon Dec 31 02:25:11 2018 +0100
@@ -8,18 +8,6 @@
 ${h.form(form_url)}
     <div class="form">
             <div class="form-group">
-                <label class="control-label" for="inherit_default_permissions">${_('Inherit defaults')}:</label>
-                <div>
-                    ${h.checkbox('inherit_default_permissions',value=True)}
-                    <span class="help-block">
-                        ${(h.HTML(_('Select to inherit global settings, IP whitelist and permissions from the %s.'))
-                                % h.link_to(_('default permissions'), url('admin_permissions')))}
-                    </span>
-                </div>
-            </div>
-
-            <div id="inherit_overlay">
-            <div class="form-group">
                 <label class="control-label" for="create_repo_perm">${_('Create repositories')}:</label>
                 <div>
                     ${h.checkbox('create_repo_perm',value=True)}
@@ -49,8 +37,6 @@
                 </div>
             </div>
 
-            </div>
-
             <div class="form-group">
                 <div class="buttons">
                     ${h.submit('save',_('Save'),class_="btn btn-default")}
@@ -59,23 +45,4 @@
             </div>
     </div>
 ${h.end_form()}
-
-## JS
-<script>
-$(document).ready(function(e){
-    var show_custom_perms = function(inherit_default){
-        if(inherit_default){
-            $('#inherit_overlay').hide();
-        }else{
-            $('#inherit_overlay').show();
-        }
-    };
-
-    show_custom_perms($('#inherit_default_permissions').prop('checked'));
-    $('#inherit_default_permissions').change(function(){
-        show_custom_perms($('#inherit_default_permissions').prop('checked'));
-    });
-});
-</script>
-
 </%def>
--- a/kallithea/tests/functional/test_forks.py	Thu Jan 03 01:03:27 2019 +0100
+++ b/kallithea/tests/functional/test_forks.py	Mon Dec 31 02:25:11 2018 +0100
@@ -7,7 +7,7 @@
 from kallithea.tests.fixture import Fixture
 
 from kallithea.lib.utils2 import safe_str, safe_unicode
-from kallithea.model.db import Repository
+from kallithea.model.db import Repository, User
 from kallithea.model.repo import RepoModel
 from kallithea.model.user import UserModel
 from kallithea.model.meta import Session
@@ -44,20 +44,19 @@
         response.mustcontain("""There are no forks yet""")
 
     def test_no_permissions_to_fork(self):
-        usr = self.log_user(TEST_USER_REGULAR_LOGIN,
-                            TEST_USER_REGULAR_PASS)['user_id']
+        self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)['user_id']
         try:
             user_model = UserModel()
+            usr = User.get_default_user()
             user_model.revoke_perm(usr, 'hg.fork.repository')
             user_model.grant_perm(usr, 'hg.fork.none')
-            u = UserModel().get(usr)
-            u.inherit_default_permissions = False
             Session().commit()
             # try create a fork
             repo_name = self.REPO
             self.app.post(url(controller='forks', action='fork_create',
                               repo_name=repo_name), {'_authentication_token': self.authentication_token()}, status=403)
         finally:
+            usr = User.get_default_user()
             user_model.revoke_perm(usr, 'hg.fork.none')
             user_model.grant_perm(usr, 'hg.fork.repository')
             Session().commit()
--- a/kallithea/tests/models/test_permissions.py	Thu Jan 03 01:03:27 2019 +0100
+++ b/kallithea/tests/models/test_permissions.py	Mon Dec 31 02:25:11 2018 +0100
@@ -310,7 +310,7 @@
         u1_auth = AuthUser(user_id=self.u1.user_id)
         assert u1_auth.permissions['repositories_groups'] == {u'group1': u'group.read'}
 
-    def test_inherited_permissions_from_default_on_user_enabled(self):
+    def test_inherit_nice_permissions_from_default_user(self):
         user_model = UserModel()
         # enable fork and create on default user
         usr = 'default'
@@ -318,8 +318,6 @@
         user_model.grant_perm(usr, 'hg.create.repository')
         user_model.revoke_perm(usr, 'hg.fork.none')
         user_model.grant_perm(usr, 'hg.fork.repository')
-        # make sure inherit flag is turned on
-        self.u1.inherit_default_permissions = True
         Session().commit()
         u1_auth = AuthUser(user_id=self.u1.user_id)
         # this user will have inherited permissions from default user
@@ -329,7 +327,7 @@
                               'repository.read', 'group.read',
                               'usergroup.read', 'hg.create.write_on_repogroup.true'])
 
-    def test_inherited_permissions_from_default_on_user_disabled(self):
+    def test_inherit_sad_permissions_from_default_user(self):
         user_model = UserModel()
         # disable fork and create on default user
         usr = 'default'
@@ -337,8 +335,6 @@
         user_model.grant_perm(usr, 'hg.create.none')
         user_model.revoke_perm(usr, 'hg.fork.repository')
         user_model.grant_perm(usr, 'hg.fork.none')
-        # make sure inherit flag is turned on
-        self.u1.inherit_default_permissions = True
         Session().commit()
         u1_auth = AuthUser(user_id=self.u1.user_id)
         # this user will have inherited permissions from default user
@@ -348,7 +344,7 @@
                               'repository.read', 'group.read',
                               'usergroup.read', 'hg.create.write_on_repogroup.true'])
 
-    def test_non_inherited_permissions_from_default_on_user_enabled(self):
+    def test_inherit_more_permissions_from_default_user(self):
         user_model = UserModel()
         # enable fork and create on default user
         usr = 'default'
@@ -363,19 +359,18 @@
         user_model.revoke_perm(self.u1, 'hg.fork.repository')
         user_model.grant_perm(self.u1, 'hg.fork.none')
 
-        # make sure inherit flag is turned off
-        self.u1.inherit_default_permissions = False
         Session().commit()
         u1_auth = AuthUser(user_id=self.u1.user_id)
-        # this user will have non inherited permissions from he's
-        # explicitly set permissions
-        assert u1_auth.permissions['global'] == set(['hg.create.none', 'hg.fork.none',
+        # this user will have inherited more permissions from default user
+        assert u1_auth.permissions['global'] == set([
+                              'hg.create.repository',
+                              'hg.fork.repository',
                               'hg.register.manual_activate',
                               'hg.extern_activate.auto',
                               'repository.read', 'group.read',
                               'usergroup.read', 'hg.create.write_on_repogroup.true'])
 
-    def test_non_inherited_permissions_from_default_on_user_disabled(self):
+    def test_inherit_less_permissions_from_default_user(self):
         user_model = UserModel()
         # disable fork and create on default user
         usr = 'default'
@@ -390,25 +385,21 @@
         user_model.revoke_perm(self.u1, 'hg.fork.none')
         user_model.grant_perm(self.u1, 'hg.fork.repository')
 
-        # make sure inherit flag is turned off
-        self.u1.inherit_default_permissions = False
         Session().commit()
         u1_auth = AuthUser(user_id=self.u1.user_id)
-        # this user will have non inherited permissions from he's
-        # explicitly set permissions
-        assert u1_auth.permissions['global'] == set(['hg.create.repository', 'hg.fork.repository',
+        # this user will have inherited less permissions from default user
+        assert u1_auth.permissions['global'] == set([
+                              'hg.create.repository',
+                              'hg.fork.repository',
                               'hg.register.manual_activate',
                               'hg.extern_activate.auto',
                               'repository.read', 'group.read',
                               'usergroup.read', 'hg.create.write_on_repogroup.true'])
 
     def test_inactive_user_group_does_not_affect_global_permissions(self):
-        # Issue #138: Inactive User Groups affecting permissions
         # Add user to inactive user group, set specific permissions on user
-        # group and disable inherit-from-default. User permissions should still
-        # inherit from default.
+        # group and and verify it really is inactive.
         self.ug1 = fixture.create_user_group(u'G1')
-        self.ug1.inherit_default_permissions = False
         user_group_model = UserGroupModel()
         user_group_model.add_user_to_group(self.ug1, self.u1)
         user_group_model.update(self.ug1, {'users_group_active': False})
@@ -438,12 +429,9 @@
                               'hg.create.write_on_repogroup.true'])
 
     def test_inactive_user_group_does_not_affect_global_permissions_inverse(self):
-        # Issue #138: Inactive User Groups affecting permissions
         # Add user to inactive user group, set specific permissions on user
-        # group and disable inherit-from-default. User permissions should still
-        # inherit from default.
+        # group and and verify it really is inactive.
         self.ug1 = fixture.create_user_group(u'G1')
-        self.ug1.inherit_default_permissions = False
         user_group_model = UserGroupModel()
         user_group_model.add_user_to_group(self.ug1, self.u1)
         user_group_model.update(self.ug1, {'users_group_active': False})
@@ -474,7 +462,6 @@
 
     def test_inactive_user_group_does_not_affect_repo_permissions(self):
         self.ug1 = fixture.create_user_group(u'G1')
-        self.ug1.inherit_default_permissions = False
         user_group_model = UserGroupModel()
         user_group_model.add_user_to_group(self.ug1, self.u1)
         user_group_model.update(self.ug1, {'users_group_active': False})
@@ -499,7 +486,6 @@
 
     def test_inactive_user_group_does_not_affect_repo_permissions_inverse(self):
         self.ug1 = fixture.create_user_group(u'G1')
-        self.ug1.inherit_default_permissions = False
         user_group_model = UserGroupModel()
         user_group_model.add_user_to_group(self.ug1, self.u1)
         user_group_model.update(self.ug1, {'users_group_active': False})
@@ -524,7 +510,6 @@
 
     def test_inactive_user_group_does_not_affect_repo_group_permissions(self):
         self.ug1 = fixture.create_user_group(u'G1')
-        self.ug1.inherit_default_permissions = False
         user_group_model = UserGroupModel()
         user_group_model.add_user_to_group(self.ug1, self.u1)
         user_group_model.update(self.ug1, {'users_group_active': False})
@@ -545,7 +530,6 @@
 
     def test_inactive_user_group_does_not_affect_repo_group_permissions_inverse(self):
         self.ug1 = fixture.create_user_group(u'G1')
-        self.ug1.inherit_default_permissions = False
         user_group_model = UserGroupModel()
         user_group_model.add_user_to_group(self.ug1, self.u1)
         user_group_model.update(self.ug1, {'users_group_active': False})
@@ -566,7 +550,6 @@
 
     def test_inactive_user_group_does_not_affect_user_group_permissions(self):
         self.ug1 = fixture.create_user_group(u'G1')
-        self.ug1.inherit_default_permissions = False
         user_group_model = UserGroupModel()
         user_group_model.add_user_to_group(self.ug1, self.u1)
         user_group_model.update(self.ug1, {'users_group_active': False})
@@ -588,7 +571,6 @@
 
     def test_inactive_user_group_does_not_affect_user_group_permissions_inverse(self):
         self.ug1 = fixture.create_user_group(u'G1')
-        self.ug1.inherit_default_permissions = False
         user_group_model = UserGroupModel()
         user_group_model.add_user_to_group(self.ug1, self.u1)
         user_group_model.update(self.ug1, {'users_group_active': False})