def setacl(self, job, data): """ Set ACL of a given path. Takes the following parameters: `path` full path to directory or file. `dacl` "simplified" ACL here or a full ACL. `uid` the desired UID of the file user. If set to -1, then UID is not changed. `gid` the desired GID of the file group. If set to -1 then GID is not changed. `recursive` apply the ACL recursively `traverse` traverse filestem boundaries (ZFS datasets) `strip` convert ACL to trivial. ACL is trivial if it can be expressed as a file mode without losing any access rules. In all cases we replace USER_OBJ, GROUP_OBJ, and EVERYONE with owner@, group@, everyone@ for consistency with getfacl and setfacl. If one of aforementioned special tags is used, 'id' must be set to None. An inheriting empty everyone@ ACE is appended to non-trivial ACLs in order to enforce Windows expectations regarding permissions inheritance. This entry is removed from NT ACL returned to SMB clients when 'ixnas' samba VFS module is enabled. """ options = data['options'] dacl = data.get('dacl', []) if not os.path.exists(data['path']): raise CallError('Path not found.', errno.ENOENT) if dacl and options['stripacl']: raise CallError('Setting ACL and stripping ACL are not permitted simultaneously.', errno.EINVAL) uid = -1 if data.get('uid', None) is None else data['uid'] gid = -1 if data.get('gid', None) is None else data['gid'] if options['stripacl']: a = acl.ACL(file=data['path']) a.strip() a.apply(data['path']) else: cleaned_acl = [] lockace_is_present = False for entry in dacl: if entry['perms'].get('BASIC') == 'OTHER' or entry['flags'].get('BASIC') == 'OTHER': raise CallError('Unable to apply simplified ACL due to OTHER entry. Use full ACL.', errno.EINVAL) ace = { 'tag': (acl.ACLWho(entry['tag'])).name, 'id': entry['id'], 'type': entry['type'], 'perms': self.__convert_to_adv_permset(entry['perms']['BASIC']) if 'BASIC' in entry['perms'] else entry['perms'], 'flags': self.__convert_to_adv_flagset(entry['flags']['BASIC']) if 'BASIC' in entry['flags'] else entry['flags'], } if ace['tag'] == 'EVERYONE' and self.__convert_to_basic_permset(ace['perms']) == 'NOPERMS': lockace_is_present = True cleaned_acl.append(ace) if not lockace_is_present: locking_ace = { 'tag': 'EVERYONE', 'id': None, 'type': 'ALLOW', 'perms': self.__convert_to_adv_permset('NOPERMS'), 'flags': self.__convert_to_adv_flagset('INHERIT') } cleaned_acl.append(locking_ace) a = acl.ACL() a.__setstate__(cleaned_acl) a.apply(data['path']) if not options['recursive']: return True winacl = subprocess.run([ '/usr/local/bin/winacl', '-a', 'clone', '-O', str(uid), '-G', str(gid), '-rx' if options['traverse'] else '-r', '-p', data['path']], check=False, capture_output=True ) if winacl.returncode != 0: raise CallError(f"Failed to recursively apply ACL: {winacl.stderr.decode()}")
def setacl_nfs4(self, job, data): job.set_progress(0, 'Preparing to set acl.') options = data['options'] dacl = data.get('dacl', []) if osc.IS_LINUX or not os.pathconf(data['path'], 64): raise CallError( f"NFSv4 ACLS are not supported on path {data['path']}", errno.EOPNOTSUPP) self._common_perm_path_validate(data['path']) if dacl and options['stripacl']: raise CallError( 'Setting ACL and stripping ACL are not permitted simultaneously.', errno.EINVAL) uid = -1 if data.get('uid', None) is None else data['uid'] gid = -1 if data.get('gid', None) is None else data['gid'] if options['stripacl']: a = acl.ACL(file=data['path']) a.strip() a.apply(data['path']) else: inheritable_is_present = False cleaned_acl = [] lockace_is_present = False for entry in dacl: ace = { 'tag': (acl.ACLWho(entry['tag'])).name, 'id': entry['id'], 'type': entry['type'], 'perms': self.__convert_to_adv_permset(entry['perms']['BASIC']) if 'BASIC' in entry['perms'] else entry['perms'], 'flags': self.__convert_to_adv_flagset(entry['flags']['BASIC']) if 'BASIC' in entry['flags'] else entry['flags'], } if ace['flags'].get('INHERIT_ONLY') and not ace['flags'].get( 'DIRECTORY_INHERIT', False) and not ace['flags'].get( 'FILE_INHERIT', False): raise CallError( 'Invalid flag combination. DIRECTORY_INHERIT or FILE_INHERIT must be set if INHERIT_ONLY is set.', errno.EINVAL) if ace['tag'] == 'EVERYONE' and self.__convert_to_basic_permset( ace['perms']) == 'NOPERMS': lockace_is_present = True elif ace['flags'].get('DIRECTORY_INHERIT') or ace['flags'].get( 'FILE_INHERIT'): inheritable_is_present = True cleaned_acl.append(ace) if not inheritable_is_present: raise CallError( 'At least one inheritable ACL entry is required', errno.EINVAL) if options['canonicalize']: cleaned_acl = self.canonicalize_acl_order(cleaned_acl) if not lockace_is_present: locking_ace = { 'tag': 'EVERYONE', 'id': None, 'type': 'ALLOW', 'perms': self.__convert_to_adv_permset('NOPERMS'), 'flags': self.__convert_to_adv_flagset('INHERIT') } cleaned_acl.append(locking_ace) a = acl.ACL() a.__setstate__(cleaned_acl) a.apply(data['path']) if not options['recursive']: os.chown(data['path'], uid, gid) job.set_progress(100, 'Finished setting NFS4 ACL.') return job.set_progress(10, f'Recursively setting ACL on {data["path"]}.') self._winacl(data['path'], 'clone', uid, gid, options) job.set_progress(100, 'Finished setting NFS4 ACL.')
def setacl(self, job, data): """ Set ACL of a given path. Takes the following parameters: `path` full path to directory or file. `dacl` "simplified" ACL here or a full ACL. `uid` the desired UID of the file user. If set to None (the default), then user is not changed. `gid` the desired GID of the file group. If set to None (the default), then group is not changed. `recursive` apply the ACL recursively `traverse` traverse filestem boundaries (ZFS datasets) `strip` convert ACL to trivial. ACL is trivial if it can be expressed as a file mode without losing any access rules. `canonicalize` reorder ACL entries so that they are in concanical form as described in the Microsoft documentation MS-DTYP 2.4.5 (ACL) In all cases we replace USER_OBJ, GROUP_OBJ, and EVERYONE with owner@, group@, everyone@ for consistency with getfacl and setfacl. If one of aforementioned special tags is used, 'id' must be set to None. An inheriting empty everyone@ ACE is appended to non-trivial ACLs in order to enforce Windows expectations regarding permissions inheritance. This entry is removed from NT ACL returned to SMB clients when 'ixnas' samba VFS module is enabled. """ job.set_progress(0, 'Preparing to set acl.') options = data['options'] dacl = data.get('dacl', []) self._common_perm_path_validate(data['path']) if dacl and options['stripacl']: raise CallError('Setting ACL and stripping ACL are not permitted simultaneously.', errno.EINVAL) uid = -1 if data.get('uid', None) is None else data['uid'] gid = -1 if data.get('gid', None) is None else data['gid'] if options['stripacl']: a = acl.ACL(file=data['path']) a.strip() a.apply(data['path']) else: inheritable_is_present = False cleaned_acl = [] lockace_is_present = False for entry in dacl: ace = { 'tag': (acl.ACLWho(entry['tag'])).name, 'id': entry['id'], 'type': entry['type'], 'perms': self.__convert_to_adv_permset(entry['perms']['BASIC']) if 'BASIC' in entry['perms'] else entry['perms'], 'flags': self.__convert_to_adv_flagset(entry['flags']['BASIC']) if 'BASIC' in entry['flags'] else entry['flags'], } if ace['flags'].get('INHERIT_ONLY') and not ace['flags'].get('DIRECTORY_INHERIT', False) and not ace['flags'].get('FILE_INHERIT', False): raise CallError( 'Invalid flag combination. DIRECTORY_INHERIT or FILE_INHERIT must be set if INHERIT_ONLY is set.', errno.EINVAL ) if ace['tag'] == 'EVERYONE' and self.__convert_to_basic_permset(ace['perms']) == 'NOPERMS': lockace_is_present = True elif ace['flags'].get('DIRECTORY_INHERIT') or ace['flags'].get('FILE_INHERIT'): inheritable_is_present = True cleaned_acl.append(ace) if not inheritable_is_present: raise CallError('At least one inheritable ACL entry is required', errno.EINVAL) if options['canonicalize']: cleaned_acl = self.canonicalize_acl_order(cleaned_acl) if not lockace_is_present: locking_ace = { 'tag': 'EVERYONE', 'id': None, 'type': 'ALLOW', 'perms': self.__convert_to_adv_permset('NOPERMS'), 'flags': self.__convert_to_adv_flagset('INHERIT') } cleaned_acl.append(locking_ace) a = acl.ACL() a.__setstate__(cleaned_acl) a.apply(data['path']) if not options['recursive']: os.chown(data['path'], uid, gid) job.set_progress(100, 'Finished setting ACL.') return job.set_progress(10, f'Recursively setting ACL on {data["path"]}.') self._winacl(data['path'], 'clone', uid, gid, options) job.set_progress(100, 'Finished setting ACL.')
def setacl(self, job, path, dacl, options): """ Set ACL of a given path. Takes the following parameters: :path: realpath or relative path. We make a subsequent realpath call to resolve it. :dacl: Accept a "simplified" ACL here or a full ACL. If the simplified ACL contains ACE perms or flags that are "SPECIAL", then raise a validation error. :recursive: apply the ACL recursively :traverse: traverse filestem boundaries (ZFS datasets) :strip: convert ACL to trivial. ACL is trivial if it can be expressed as a file mode without losing any access rules. In all cases we replace USER_OBJ, GROUP_OBJ, and EVERYONE with owner@, group@, everyone@ for consistency with getfacl and setfacl. If one of aforementioned special tags is used, 'id' must be set to None. An inheriting empty everyone@ ACE is appended to non-trivial ACLs in order to enforce Windows expectations regarding permissions inheritance. This entry is removed from NT ACL returned to SMB clients when 'ixnas' samba VFS module is enabled. """ if not os.path.exists(path): raise CallError('Path not found.', errno.ENOENT) if dacl and options['stripacl']: raise CallError('Setting ACL and stripping ACL are not permitted simultaneously.', errno.EINVAL) if options['stripacl']: a = acl.ACL(file=path) a.strip() a.apply(path) else: cleaned_acl = [] lockace_is_present = False for entry in dacl: if entry['perms'].get('BASIC') == 'OTHER' or entry['flags'].get('BASIC') == 'OTHER': raise CallError('Unable to apply simplified ACL due to OTHER entry. Use full ACL.', errno.EINVAL) ace = { 'tag': (acl.ACLWho(entry['tag'])).name, 'id': entry['id'], 'type': entry['type'], 'perms': self.__convert_to_adv_permset(entry['perms']['BASIC']) if 'BASIC' in entry['perms'] else entry['perms'], 'flags': self.__convert_to_adv_flagset(entry['flags']['BASIC']) if 'BASIC' in entry['perms'] else entry['flags'], } if ace['tag'] == 'EVERYONE' and self.__convert_to_basic_permset(ace['perms']) == 'NOPERMS': lockace_is_present = True cleaned_acl.append(ace) if not lockace_is_present: locking_ace = { 'tag': 'EVERYONE', 'id': None, 'type': 'ALLOW', 'perms': self.__convert_to_adv_permset('NOPERMS'), 'flags': self.__convert_to_adv_flagset('INHERIT') } cleaned_acl.append(locking_ace) a = acl.ACL() a.__setstate__(cleaned_acl) a.apply(path) if not options['recursive']: self.logger.debug('exiting early on non-recursive task') return True winacl = subprocess.run([ '/usr/local/bin/winacl', '-a', 'clone', f"{'-rx' if options['traverse'] else '-r'}", '-p', path], check=False ) if winacl.returncode != 0: raise CallError(f"Failed to recursively apply ACL: {winacl.stderr.decode()}") return True