"""
Authorization module that allow users listed in
/etc/cobbler/users.conf to be permitted to access resources, with
the further restriction that cobbler objects can be edited to only
allow certain users/groups to access those specific objects.
"""
from future import standard_library
standard_library.install_aliases()
from builtins import str
from configparser import ConfigParser
import os
from cobbler.cexceptions import CX
from cobbler.utils import _
[docs]def register():
"""
The mandatory cobbler module registration hook.
"""
return "authz"
def __parse_config():
etcfile = '/etc/cobbler/users.conf'
if not os.path.exists(etcfile):
raise CX(_("/etc/cobbler/users.conf does not exist"))
config = ConfigParser()
config.read(etcfile)
alldata = {}
sections = config.sections()
for g in sections:
alldata[str(g)] = {}
opts = config.options(g)
for o in opts:
alldata[g][o] = 1
return alldata
def __authorize_autoinst(api_handle, groups, user, autoinst):
# the authorization rules for automatic installation file editing are a bit
# of a special case. Non-admin users can edit a automatic installation file
# only if all objects that depend on that automatic installation file are
# editable by the user in question.
#
# Example:
# if Pinky owns ProfileA
# and the Brain owns ProfileB
# and both profiles use the same automatic installation template
# and neither Pinky nor the Brain is an admin
# neither is allowed to edit the automatic installation template
# because they would make unwanted changes to each other
#
# In the above scenario the UI will explain the problem
# and ask that the user asks the admin to resolve it if required.
# NOTE: this function is only called by authorize so admin users are
# cleared before this function is called.
lst = api_handle.find_profile(autoinst=autoinst, return_list=True)
lst.extend(api_handle.find_system(autoinst=autoinst, return_list=True))
for obj in lst:
if not __is_user_allowed(obj, groups, user, "write_autoinst", autoinst, None):
return 0
return 1
def __authorize_snippet(api_handle, groups, user, autoinst):
# only allow admins to edit snippets -- since we don't have detection to see
# where each snippet is in use
for group in groups:
if group not in ["admins", "admin"]:
return False
return True
def __is_user_allowed(obj, groups, user, resource, arg1, arg2):
if user == "<DIRECT>":
# system user, logged in via web.ss
return True
for group in groups:
if group in ["admins", "admin"]:
return True
if obj.owners == []:
return True
for allowed in obj.owners:
if user == allowed:
# user match
return True
# else look for a group match
for group in groups:
if group == allowed:
return True
return 0
[docs]def authorize(api_handle, user, resource, arg1=None, arg2=None):
"""
Validate a user against a resource.
All users in the file are permitted by this module.
"""
if user == "<DIRECT>":
# CLI should always be permitted
return True
# everybody can get read-only access to everything
# if they pass authorization, they don't have to be in users.conf
if resource is not None:
# FIXME: /cobbler/web should not be subject to user check in any case
for x in ["get", "read", "/cobbler/web"]:
if resource.startswith(x):
return 1 # read operation is always ok.
user_groups = __parse_config()
# classify the type of operation
modify_operation = False
for criteria in ["save", "copy", "rename", "remove", "modify", "edit", "xapi", "background"]:
if resource.find(criteria) != -1:
modify_operation = True
# FIXME: is everyone allowed to copy? I think so.
# FIXME: deal with the problem of deleted parents and promotion
found_user = False
found_groups = []
grouplist = list(user_groups.keys())
for g in grouplist:
for x in user_groups[g]:
if x == user:
found_groups.append(g)
found_user = True
# if user is in the admin group, always authorize
# regardless of the ownership of the object.
if g == "admins" or g == "admin":
return True
if not found_user:
# if the user isn't anywhere in the file, reject regardless
# they can still use read-only XMLRPC
return 0
if not modify_operation:
# sufficient to allow access for non save/remove ops to all
# users for now, may want to refine later.
return True
# now we have a modify_operation op, so we must check ownership
# of the object. remove ops pass in arg1 as a string name,
# saves pass in actual objects, so we must treat them differently.
# automatic installaton files are even more special so we call those
# out to another function, rather than going through the rest of the
# code here.
if resource.find("write_autoinstall_template") != -1:
return __authorize_autoinst(api_handle, found_groups, user, arg1)
elif resource.find("read_autoinstall_template") != -1:
return True
# the API for editing snippets also needs to do something similar.
# as with automatic installation files, though since they are more
# widely used it's more restrictive
if resource.find("write_autoinstall_snippet") != -1:
return __authorize_snippet(api_handle, found_groups, user, arg1)
elif resource.find("read_autoinstall_snipppet") != -1:
return True
obj = None
if resource.find("remove") != -1:
if resource == "remove_distro":
obj = api_handle.find_distro(arg1)
elif resource == "remove_profile":
obj = api_handle.find_profile(arg1)
elif resource == "remove_system":
obj = api_handle.find_system(arg1)
elif resource == "remove_repo":
obj = api_handle.find_repo(arg1)
elif resource == "remove_image":
obj = api_handle.find_image(arg1)
elif resource.find("save") != -1 or resource.find("modify") != -1:
obj = arg1
# if the object has no ownership data, allow access regardless
if obj is None or obj.owners is None or obj.owners == []:
return True
return __is_user_allowed(obj, found_groups, user, resource, arg1, arg2)