import re
[docs]
class FilePermissions(object):
"""
Class for parsing `ls -l` line targeted at concrete file and handling parsed properties.
It is useful for checking file permissions and owner.
Attributes:
perms_owner (str): Owner permissions, e.g. 'rwx'
perms_group (str): Group permissions
perms_other (str): Other permissions
owner (str): Owner user name
group (str): Owner group name
path (str): Full path to file
Note:
This class does not support Access Control Lists (ACLs). If that is needed in the future,
it would be preferable to create another class than extend this one.
Advanced File Permissions - SUID, SGID and Sticky Bit - are not yet correctly parsed.
"""
_PERMISSIONS_PATTERN = re.compile('''
^
.([-rwxsS]{3})([-rwxsS]{3})([-rwxsS]{3}) # -rwxrwxrwx
# -rw-------. 1 root root 4308 Apr 22 15:57 /etc/ssh/sshd_config
# ^^^^^^^^^^
# Valid characters are -rwxsS
# s == execute bit and sticky bit
# S == sticky bit without execute bit
\S* # the character(s) after rwxrwxrwx for ACLs/xattrs
# -rw-------. 1 root root 4308 Apr 22 15:57 /etc/ssh/sshd_config
# ^
\s+\S+\s+ # the number of hardlinks and spaces around
# -rw-------. 1 root root 4308 Apr 22 15:57 /etc/ssh/sshd_config
# ^^^
([^\s:]+)\s+([^\s:]+) # owner, spaces, group
# -rw-------. 1 root root 4308 Apr 22 15:57 /etc/ssh/sshd_config
# ^^^^^^^^^
# Username and group name are strings without whitespace \s and without colon :.
\s+\S+\s+ # size and spaces around
# -rw-------. 1 root root 4308 Apr 22 15:57 /etc/ssh/sshd_config
# ^^^^^^
\S+\s+\S+ # month and day
# -rw-------. 1 root root 4308 Apr 22 15:57 /etc/ssh/sshd_config
# ^^^^^^
\s+\S+\s+ # time/year and spaces around
# -rw-------. 1 root root 4308 Apr 22 2009 /etc/ssh/sshd_config
# ^^^^^^
# -rw-------. 1 root root 4308 Apr 22 15:57 /etc/ssh/sshd_config
# ^^^^^^^
(.*) # file name or path
# -rw-------. 1 root root 4308 Apr 22 15:57 /etc/ssh/sshd_config
# ^^^^^^^^^^^^^^^^^^^^
# -rw-------. 1 root root 4308 Apr 22 15:57 file_name_without_path
# ^^^^^^^^^^^^^^^^^^^^^^
$
''', re.VERBOSE)
def __init__(self, line):
"""
Args:
line (str): A line from `ls -l /concrete/file` execution. Such as:
-rw-------. 1 root root 762 Sep 23 002 /etc/ssh/sshd_config
-rw-------. 1 root root 4308 Apr 22 15:57 /etc/ssh/sshd_config
-rw-r--r--. 1 root root 4179 Dec 1 2014 /boot/grub2/grub.cfg
Raises:
ValueError: If line is malformed
"""
self.line = line
r = self._PERMISSIONS_PATTERN.search(self.line)
if r:
(self.perms_owner, self.perms_group, self.perms_other,
self.owner, self.group, self.path) = r.groups()
else:
raise ValueError('Invalid `ls -l` line "{}"'.format(self.line))
[docs]
@classmethod
def from_dict(self, dirent):
"""
Create a new FilePermissions object from the given dictionary. This
works with the FileListing parser class, which has already done the
hard work of pulling many of these fields out. We create an object
with all the dictionary keys available as properties, and also split
the ``perms`` string up into owner, group
"""
# Check that we have at least as much data as the __init__ requires
for k in ['perms', 'owner', 'group', 'name', 'dir']:
if k not in dirent:
raise ValueError("Need required key '{k}'".format(k=k))
# Copy all values across
for k in dirent:
setattr(self, k, dirent[k])
# Create perms parts
self.perms_owner = self.perms[0:3]
self.perms_group = self.perms[3:6]
self.perms_other = self.perms[6:9]
return self
[docs]
def owned_by(self, owner, also_check_group=False):
"""
Checks if the specified user or user and group own the file.
Args:
owner (str): the user (or group) name for which we ask about ownership
also_check_group (bool): if set to True, both user owner and group owner checked
if set to False, only user owner checked
Returns:
bool: True if owner of the file is the specified owner
"""
if also_check_group:
return self.owner == owner and self.group == owner
else:
return self.owner == owner
[docs]
def owner_can_read(self):
"""
Checks if owner can read the file. Write and execute bits are not evaluated.
Returns:
bool: True if owner can read the file.
"""
return 'r' in self.perms_owner
[docs]
def group_can_read(self):
"""
Checks if group can read the file. Write and execute bits are not evaluated.
Returns:
bool: True if group can read the file.
"""
return 'r' in self.perms_group
[docs]
def others_can_read(self):
"""
Checks if 'others' can read the file. Write and execute bits are not evaluated. ('others' in
the sense of unix permissions that know about user, group, others.)
Returns:
bool: True if 'others' can read the file.
"""
return 'r' in self.perms_other
[docs]
def owner_can_only_read(self):
"""
Checks if owner has read-only permissions for the file.
Therefore, write and execute bits for owner must be unset and read bit must be set.
Returns:
bool: True if owner can only read the file.
"""
return 'r--' == self.perms_owner
[docs]
def group_can_only_read(self):
"""
Checks if group has read-only permissions for the file.
Therefore, write and execute bits for group must be unset and read bit must be set.
Returns:
bool: True if group can only read the file.
"""
return 'r--' == self.perms_group
[docs]
def others_can_only_read(self):
"""
Checks if 'others' has read-only permissions for the file.
Therefore, write and execute bits for 'others' must be unset and read bit must be set.
('others' in the sense of unix permissions that know about user, group, others.)
Returns:
bool: True if 'others' can only read the file.
"""
return 'r--' == self.perms_other
[docs]
def owner_can_write(self):
"""
Checks if owner can write the file. Read and execute bits are not evaluated.
Returns:
bool: True if owner can write the file.
"""
return 'w' in self.perms_owner
[docs]
def group_can_write(self):
"""
Checks if group can write the file. Read and execute bits are not evaluated.
Returns:
bool: True if group can write the file.
"""
return 'w' in self.perms_group
[docs]
def others_can_write(self):
"""
Checks if 'others' can write the file. Read and execute bits are not evaluated. ('others' in
the sense of unix permissions that know about user, group, others.)
Returns:
bool: True if 'others' can write the file.
"""
return 'w' in self.perms_other
[docs]
def only_root_can_read(self, root_group_can_read=True):
"""
Checks if only root is allowed to read the file (and anyone else is
forbidden from reading). Write and execute bits are not checked. The
read bits for root user/group are not checked because root can
read/write anything regardless of the read/write permissions.
When called with ``root_group_can_read`` = ``True``:
* owner must be root
* and 'others' permissions must not contain read
* and if group owner is not root, the 'group' permissions must not
contain read
Valid cases::
rwxrwxrwx owner ownergroup
-------------------------------
???-??-?? root nonroot
??????-?? root root
r--r----- root root
r-------- root nonroot
rwxrwx--- root root
rwxrwx-wx root root
Specifically, these cases are NOT valid because the owner can chmod
permissions and grant themselves permissions without root's
knowledge::
rwxrwxrwx owner ownergroup
-------------------------------
-??-??-?? nonroot nonroot
-??r??-?? nonroot root
--------- nonroot nonroot
When called with ``root_group_can_read`` = ``False``:
* owner must be root
* and 'group' and 'others' permissions must not contain read
Valid cases::
rwxrwxrwx owner ownergroup
-------------------------------
???-??-?? root ?
r-------- root root
r-------- root nonroot
rwx-wx--- root root
rwx-wx--- root nonroot
rwx-wxrwx root nonroot
Specifically, these cases are NOT valid because the owner can chmod
permissions and grant themselves permissions without root's
knowledge::
rwxrwxrwx owner ownergroup
-------------------------------
-??-??-?? nonroot nonroot
--------- nonroot nonroot
Args:
root_group_can_read (bool): if set to True, this tests whether the
'root' group can also read the file.
Returns:
bool: True if only root user (or optionally root group) can read
the file.
"""
requirements = True # The final answer is progressively assembled in this variable.
requirements &= self.owner == 'root'
requirements &= not self.others_can_read()
if root_group_can_read:
if self.group != 'root':
# if group is not root, group must not be able to read
requirements &= not self.group_can_read()
else: # root_group_can_read == False
requirements &= not self.group_can_read()
return requirements
[docs]
def only_root_can_write(self, root_group_can_write=True):
"""
Checks if only root is allowed to write the file (and anyone else is
barred from writing). Read and execute bits are not checked. The
write bits for root user/group are not checked because root can
read/write anything regardless of the read/write permissions.
When called with ``root_group_can_write`` = ``True``:
* owner must be root
* and 'others' permissions must not contain write
* and if group owner is not root, the 'group' permissions must not contain write
Valid cases::
rwxrwxrwx owner ownergroup
-------------------------------
????-??-? root nonroot
???????-? root root
-w--w---- root root
-w------- root root
rwxrwx--- root root
rwxrwxr-x root root
Specifically, these cases are NOT valid because the owner can chmod
permissions and grant themselves permissions without root's
knowledge::
rwxrwxrwx owner ownergroup
-------------------------------
?-??-??-? nonroot nonroot
?-??w??-? nonroot root
--------- nonroot nonroot
When called with ``root_group_can_write`` = ``False``:
* owner must be root
* and 'group' and 'others' permissions must not contain write
Valid cases::
rwxrwxrwx owner ownergroup
-------------------------------
????-??-? root ?
-w------- root root
-w------- root nonroot
rwxr-x--- root root
rwxr-x--- root nonroot
rwxr-xr-x root nonroot
Specifically, these cases are NOT valid because the owner can chmod
permissions and grant themselves permissions without root's
knowledge::
rwxrwxrwx owner ownergroup
-------------------------------
?-??-??-? nonroot nonroot
--------- nonroot nonroot
Args:
root_group_can_write (bool): if set to True, this tests whether
'root' group can also write to the file.
Returns:
bool: True if only root user (or optionally root group) can write
the file.
"""
requirements = True # The final answer is progressively assembled in this variable.
requirements &= self.owner == 'root'
requirements &= not self.others_can_write()
if root_group_can_write:
if self.group != 'root':
# if group is not root, group must not be able to write
requirements &= not self.group_can_write()
else: # root_group_can_write == False
requirements &= not self.group_can_write()
return requirements
[docs]
def all_zero(self):
"""
Checks that all permissions are zero ('---------' in ls -l) - nobody but root can read,
write, exec.
Returns:
bool: True if all permissions are zero ('---------')
"""
_PERM_NOTHING = '---'
return all((
self.perms_owner == _PERM_NOTHING,
self.perms_group == _PERM_NOTHING,
self.perms_other == _PERM_NOTHING,
))
def __repr__(self):
return 'FilePermissions(' + self.path + ')'