Source code for cobbler.web.views

from future import standard_library
standard_library.install_aliases()
from builtins import str
from django.http import HttpResponse
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.views.decorators.csrf import csrf_protect
from django.views.decorators.http import require_POST

import simplejson
import time
import xmlrpc.client

import cobbler.items.distro as item_distro
import cobbler.items.file as item_file
import cobbler.items.image as item_image
import cobbler.items.mgmtclass as item_mgmtclass
import cobbler.items.package as item_package
import cobbler.items.profile as item_profile
import cobbler.items.repo as item_repo
import cobbler.items.system as item_system
import cobbler.settings as item_settings
import cobbler.utils as utils
from cobbler.web import field_ui_info

url_cobbler_api = None
remote = None
username = None

# ==================================================================================


[docs]def index(request): """ This is the main greeting page for cobbler web. """ if not test_user_authenticated(request): return login(request, next="/cobbler_web", expired=True) html = render(request, 'index.tmpl', { 'version': remote.extended_version(request.session['token'])['version'], 'username': username, }) return HttpResponse(html)
# ========================================================================
[docs]def task_created(request): """ Let's the user know what to expect for event updates. """ if not test_user_authenticated(request): return login(request, next="/cobbler_web/task_created", expired=True) html = render(request, "task_created.tmpl", { 'version': remote.extended_version(request.session['token'])['version'], 'username': username }) return HttpResponse(html)
# ========================================================================
[docs]def error_page(request, message): """ This page is used to explain error messages to the user. """ if not test_user_authenticated(request): return login(request, expired=True) # FIXME: test and make sure we use this rather than throwing lots of tracebacks for # field errors message = message.replace("<Fault 1: \"<class 'cobbler.cexceptions.CX'>:'", "Remote exception: ") message = message.replace("'\">", "") html = render(request, 'error_page.tmpl', { 'version': remote.extended_version(request.session['token'])['version'], 'message': message, 'username': username }) return HttpResponse(html)
# ================================================================================== def _get_field_html_element(field_name): if field_name in field_ui_info.USES_SELECT: return "select" elif field_name in field_ui_info.USES_MULTI_SELECT: return "multiselect" elif field_name in field_ui_info.USES_RADIO: return "radio" elif field_name in field_ui_info.USES_CHECKBOX: return "checkbox" elif field_name in field_ui_info.USES_TEXTAREA: return "textarea" else: return "text"
[docs]def get_fields(what, is_subobject, seed_item=None): """ Helper function. Retrieves the field table from the cobbler objects and formats it in a way to make it useful for Django templating. The field structure indicates what fields to display and what the default values are, etc. """ if what == "distro": fields = item_distro.FIELDS if what == "profile": fields = item_profile.FIELDS if what == "system": fields = item_system.FIELDS if what == "repo": fields = item_repo.FIELDS if what == "image": fields = item_image.FIELDS if what == "mgmtclass": fields = item_mgmtclass.FIELDS if what == "package": fields = item_package.FIELDS if what == "file": fields = item_file.FIELDS if what == "setting": fields = item_settings.FIELDS settings = remote.get_settings() ui_fields = [] for field in fields: ui_field = { "name": field[0], "dname": field[0], "value": "?", "caption": field[3], "editable": field[4], "tooltip": field[5], "choices": field[6], "css_class": "generic", "html_ui_fieldent": "generic", } if not ui_field["editable"]: continue name = field[0] if seed_item is not None: ui_field["value"] = seed_item[name] elif is_subobject: ui_field["value"] = field[2] else: ui_field["value"] = field[1] if ui_field["value"] is None: ui_field["value"] = "" # we'll process this for display but still need to present the original to some # template logic ui_field["value_raw"] = ui_field["value"] if isinstance(ui_field["value"], str) and ui_field["value"].startswith("SETTINGS:"): key = ui_field["value"].replace("SETTINGS:", "", 1) ui_field["value"] = settings[key] # flatten dicts of all types, they can only be edited as text # as we have no HTML dict widget (yet) if isinstance(ui_field["value"], dict): if ui_field["name"] == "mgmt_parameters": # Render dictionary as YAML for Management Parameters field tokens = [] for (x, y) in list(ui_field["value"].items()): if y is not None: tokens.append("%s: %s" % (x, y)) else: tokens.append("%s: " % x) ui_field["value"] = "{ %s }" % ", ".join(tokens) else: tokens = [] for (x, y) in list(ui_field["value"].items()): if isinstance(y, str) and y.strip() != "~": y = y.replace(" ", "\\ ") tokens.append("%s=%s" % (x, y)) elif isinstance(y, list): for item in y: item = item.replace(" ", "\\ ") tokens.append("%s=%s" % (x, item)) elif y is not None: tokens.append("%s" % x) ui_field["value"] = " ".join(tokens) name = field[0] ui_field["html_element"] = _get_field_html_element(name) # flatten lists for those that aren't using select boxes if isinstance(ui_field["value"], list): if ui_field["html_element"] != "select": ui_field["value"] = " ".join(ui_field["value"]) ui_fields.append(ui_field) return ui_fields
[docs]def get_network_interface_fields(): """ Create network interface fields UI metadata based on network interface fields metadata @return list network interface fields UI metadata """ fields = item_system.NETWORK_INTERFACE_FIELDS fields_ui = [] for field in fields: field_ui = { "name": field[0], "dname": field[0], "value": "?", "caption": field[3], "editable": field[4], "tooltip": field[5], "choices": field[6], "css_class": "generic", "html_element": "generic", } if not field_ui["editable"]: continue # system's network interfaces are loaded later by javascript, # initial value on web UI is always empty string field_ui["value"] = "" # we'll process this for display but still need to present the original # to some template logic field_ui["value_raw"] = field_ui["value"] name = field[0] field_ui["html_element"] = _get_field_html_element(name) fields_ui.append(field_ui) return fields_ui
def _create_sections_metadata(what, sections_data, fields): sections = {} section_index = 0 for section_data in sections_data: for section_name, section_fields in list(section_data.items()): skey = "%d_%s" % (section_index, section_name) sections[skey] = {} sections[skey]['name'] = section_name sections[skey]['fields'] = [] for section_field in section_fields: found = False for field in fields: if field["name"] == section_field: sections[skey]['fields'].append(field) found = True break if not found: raise Exception("%s field %s referenced in UI section definition does not exist in UI fields definition" % (what, section_field)) section_index += 1 return sections # ================================================================================== def __tweak_field(fields, field_name, attribute, value): """ Helper function to insert extra data into the field list. """ # FIXME: eliminate this function. for x in fields: if x["name"] == field_name: x[attribute] = value # ================================================================================== def __format_columns(column_names, sort_field): """ Format items retrieved from XMLRPC for rendering by the generic_edit template """ dataset = [] # Default is sorting on name if sort_field is not None: sort_name = sort_field else: sort_name = "name" if sort_name.startswith("!"): sort_name = sort_name[1:] sort_order = "desc" else: sort_order = "asc" for fieldname in column_names: fieldorder = "none" if fieldname == sort_name: fieldorder = sort_order dataset.append([fieldname, fieldorder]) return dataset # ================================================================================== def __format_items(items, column_names): """ Format items retrieved from XMLRPC for rendering by the generic_edit template """ dataset = [] for item_dict in items: row = [] for fieldname in column_names: if fieldname == "name": html_element = "name" elif fieldname in ["system", "repo", "distro", "profile", "image", "mgmtclass", "package", "file"]: html_element = "editlink" elif fieldname in field_ui_info.USES_CHECKBOX: html_element = "checkbox" else: html_element = "text" row.append([fieldname, item_dict[fieldname], html_element]) dataset.append(row) return dataset # ==================================================================================
[docs]def genlist(request, what, page=None): """ Lists all object types, complete with links to actions on those objects. """ if not test_user_authenticated(request): return login(request, next="/cobbler_web/%s/list" % what, expired=True) # get details from the session if page is None: page = int(request.session.get("%s_page" % what, 1)) limit = int(request.session.get("%s_limit" % what, 50)) sort_field = request.session.get("%s_sort_field" % what, "name") filters = simplejson.loads(request.session.get("%s_filters" % what, "{}")) pageditems = remote.find_items_paged(what, utils.strip_none(filters), sort_field, page, limit) # what columns to show for each page? # we also setup the batch actions here since they're dependent # on what we're looking at profiles = [] # everythng gets batch delete batchactions = [ ["Delete", "delete", "delete"], ] if what == "distro": columns = ["name"] batchactions += [ ["Build ISO", "buildiso", "enable"], ] if what == "profile": columns = ["name", "distro"] batchactions += [ ["Build ISO", "buildiso", "enable"], ] if what == "system": # FIXME: also list network, once working columns = ["name", "profile", "status", "netboot_enabled"] profiles = sorted(remote.get_profiles(), key=lambda x: x['name']) batchactions += [ ["Power on", "power", "on"], ["Power off", "power", "off"], ["Reboot", "power", "reboot"], ["Change profile", "profile", ""], ["Netboot enable", "netboot", "enable"], ["Netboot disable", "netboot", "disable"], ["Build ISO", "buildiso", "enable"], ] if what == "repo": columns = ["name", "mirror"] batchactions += [ ["Reposync", "reposync", "go"], ] if what == "image": columns = ["name", "file"] if what == "network": columns = ["name"] if what == "mgmtclass": columns = ["name"] if what == "package": columns = ["name", "installer"] if what == "file": columns = ["name"] # render the list html = render(request, 'generic_list.tmpl', { 'what': what, 'columns': __format_columns(columns, sort_field), 'items': __format_items(pageditems["items"], columns), 'pageinfo': pageditems["pageinfo"], 'filters': filters, 'version': remote.extended_version(request.session['token'])['version'], 'username': username, 'limit': limit, 'batchactions': batchactions, 'profiles': profiles, }) return HttpResponse(html)
[docs]@require_POST @csrf_protect def modify_list(request, what, pref, value=None): """ This function is used in the generic list view to modify the page/column sort/number of items shown per page, and also modify the filters. This function modifies the session object to store these preferences persistently. """ if not test_user_authenticated(request): return login(request, next="/cobbler_web/%s/modifylist/%s/%s" % (what, pref, str(value)), expired=True) # what preference are we tweaking? if pref == "sort": # FIXME: this isn't exposed in the UI. # sorting list on columns old_sort = request.session.get("%s_sort_field" % what, "name") if old_sort.startswith("!"): old_sort = old_sort[1:] old_revsort = True else: old_revsort = False # User clicked on the column already sorted on, # so reverse the sorting list if old_sort == value and not old_revsort: value = "!" + value request.session["%s_sort_field" % what] = value request.session["%s_page" % what] = 1 elif pref == "limit": # number of items to show per page request.session["%s_limit" % what] = int(value) request.session["%s_page" % what] = 1 elif pref == "page": # what page are we currently on request.session["%s_page" % what] = int(value) elif pref in ("addfilter", "removefilter"): # filters limit what we show in the lists # they are stored in json format for marshalling filters = simplejson.loads(request.session.get("%s_filters" % what, "{}")) if pref == "addfilter": (field_name, field_value) = value.split(":", 1) # add this filter filters[field_name] = field_value else: # remove this filter, if it exists if value in filters: del filters[value] # save session variable request.session["%s_filters" % what] = simplejson.dumps(filters) # since we changed what is viewed, reset the page request.session["%s_page" % what] = 1 else: return error_page(request, "Invalid preference change request") # redirect to the list page return HttpResponseRedirect("/cobbler_web/%s/list" % what)
# ======================================================================
[docs]@require_POST @csrf_protect def generic_rename(request, what, obj_name=None, obj_newname=None): """ Renames an object. """ if not test_user_authenticated(request): return login(request, next="/cobbler_web/%s/rename/%s/%s" % (what, obj_name, obj_newname), expired=True) if obj_name is None: return error_page(request, "You must specify a %s to rename" % what) if not remote.has_item(what, obj_name): return error_page(request, "Unknown %s specified" % what) elif not remote.check_access_no_fail(request.session['token'], "modify_%s" % what, obj_name): return error_page(request, "You do not have permission to rename this %s" % what) else: obj_id = remote.get_item_handle(what, obj_name, request.session['token']) remote.rename_item(what, obj_id, obj_newname, request.session['token']) return HttpResponseRedirect("/cobbler_web/%s/list" % what)
# ======================================================================
[docs]@require_POST @csrf_protect def generic_copy(request, what, obj_name=None, obj_newname=None): """ Copies an object. """ if not test_user_authenticated(request): return login(request, next="/cobbler_web/%s/copy/%s/%s" % (what, obj_name, obj_newname), expired=True) # FIXME: shares all but one line with rename, merge it. if obj_name is None: return error_page(request, "You must specify a %s to rename" % what) if not remote.has_item(what, obj_name): return error_page(request, "Unknown %s specified" % what) elif not remote.check_access_no_fail(request.session['token'], "modify_%s" % what, obj_name): return error_page(request, "You do not have permission to copy this %s" % what) else: obj_id = remote.get_item_handle(what, obj_name, request.session['token']) remote.copy_item(what, obj_id, obj_newname, request.session['token']) return HttpResponseRedirect("/cobbler_web/%s/list" % what)
# ======================================================================
[docs]@require_POST @csrf_protect def generic_delete(request, what, obj_name=None): """ Deletes an object. """ if not test_user_authenticated(request): return login(request, next="/cobbler_web/%s/delete/%s" % (what, obj_name), expired=True) # FIXME: consolidate code with above functions. if obj_name is None: return error_page(request, "You must specify a %s to delete" % what) if not remote.has_item(what, obj_name): return error_page(request, "Unknown %s specified" % what) elif not remote.check_access_no_fail(request.session['token'], "remove_%s" % what, obj_name): return error_page(request, "You do not have permission to delete this %s" % what) else: # check whether object is to be deleted recursively recursive = simplejson.loads(request.POST.get("recursive", "false")) try: remote.xapi_object_edit(what, obj_name, "remove", {'name': obj_name, 'recursive': recursive}, request.session['token']) except Exception as e: return error_page(request, str(e)) return HttpResponseRedirect("/cobbler_web/%s/list" % what)
# ======================================================================
[docs]@require_POST @csrf_protect def generic_domulti(request, what, multi_mode=None, multi_arg=None): """ Process operations like profile reassignment, netboot toggling, and deletion which occur on all items that are checked on the list page. """ if not test_user_authenticated(request): return login(request, next="/cobbler_web/%s/multi/%s/%s" % (what, multi_mode, multi_arg), expired=True) names = request.POST.get('names', '').strip().split() if names == "": return error_page(request, "Need to select some '%s' objects first" % what) if multi_mode == "delete": # check whether the objects are to be deleted recursively recursive = simplejson.loads(request.POST.get("recursive_batch", "false")) for obj_name in names: try: remote.xapi_object_edit(what, obj_name, "remove", {'name': obj_name, 'recursive': recursive}, request.session['token']) except Exception as e: return error_page(request, str(e)) elif what == "system" and multi_mode == "netboot": netboot_enabled = multi_arg # values: enable or disable if netboot_enabled is None: return error_page(request, "Cannot modify systems without specifying netboot_enabled") if netboot_enabled == "enable": netboot_enabled = True elif netboot_enabled == "disable": netboot_enabled = False else: return error_page(request, "Invalid netboot option, expect enable or disable") for obj_name in names: obj_id = remote.get_system_handle(obj_name, request.session['token']) remote.modify_system(obj_id, "netboot_enabled", netboot_enabled, request.session['token']) remote.save_system(obj_id, request.session['token'], "edit") elif what == "system" and multi_mode == "profile": profile = multi_arg if profile is None: return error_page(request, "Cannot modify systems without specifying profile") for obj_name in names: obj_id = remote.get_system_handle(obj_name, request.session['token']) remote.modify_system(obj_id, "profile", profile, request.session['token']) remote.save_system(obj_id, request.session['token'], "edit") elif what == "system" and multi_mode == "power": power = multi_arg if power is None: return error_page(request, "Cannot modify systems without specifying power option") options = {"systems": names, "power": power} remote.background_power_system(options, request.session['token']) elif what == "system" and multi_mode == "buildiso": options = {"systems": names, "profiles": []} remote.background_buildiso(options, request.session['token']) elif what == "profile" and multi_mode == "buildiso": options = {"profiles": names, "systems": []} remote.background_buildiso(options, request.session['token']) elif what == "distro" and multi_mode == "buildiso": if len(names) > 1: return error_page(request, "You can only select one distro at a time to build an ISO for") options = {"standalone": True, "distro": str(names[0])} remote.background_buildiso(options, request.session['token']) elif what == "repo" and multi_mode == "reposync": options = {"repos": names, "tries": 3} remote.background_reposync(options, request.session['token']) else: return error_page(request, "Unknown batch operation on %ss: %s" % (what, str(multi_mode))) # FIXME: "operation complete" would make a lot more sense here than a redirect return HttpResponseRedirect("/cobbler_web/%s/list" % what)
# ======================================================================
[docs]def import_prompt(request): if not test_user_authenticated(request): return login(request, next="/cobbler_web/import/prompt", expired=True) html = render(request, 'import.tmpl', { 'version': remote.extended_version(request.session['token'])['version'], 'username': username, }) return HttpResponse(html)
# ======================================================================
[docs]def check(request): """ Shows a page with the results of 'cobbler check' """ if not test_user_authenticated(request): return login(request, next="/cobbler_web/check", expired=True) results = remote.check(request.session['token']) html = render(request, 'check.tmpl', { 'version': remote.extended_version(request.session['token'])['version'], 'username': username, 'results': results }) return HttpResponse(html)
# ======================================================================
[docs]@require_POST @csrf_protect def buildiso(request): if not test_user_authenticated(request): return login(request, next="/cobbler_web/buildiso", expired=True) remote.background_buildiso({}, request.session['token']) return HttpResponseRedirect('/cobbler_web/task_created')
# ======================================================================
[docs]@require_POST @csrf_protect def import_run(request): if not test_user_authenticated(request): return login(request, next="/cobbler_web/import/prompt", expired=True) options = { "name": request.POST.get("name", ""), "path": request.POST.get("path", ""), "breed": request.POST.get("breed", ""), "arch": request.POST.get("arch", "") } remote.background_import(options, request.session['token']) return HttpResponseRedirect('/cobbler_web/task_created')
# ======================================================================
[docs]def aifile_list(request, page=None): """ List all automatic OS installation templates and link to their edit pages. """ if not test_user_authenticated(request): return login(request, next="/cobbler_web/aifile/list", expired=True) aifiles = remote.get_autoinstall_templates(request.session['token']) aifile_list = [] for aifile in aifiles: aifile_list.append((aifile, 'editable')) html = render(request, 'aifile_list.tmpl', { 'what': 'aifile', 'ai_files': aifile_list, 'version': remote.extended_version(request.session['token'])['version'], 'username': username, 'item_count': len(aifile_list[0]), }) return HttpResponse(html)
# ======================================================================
[docs]@csrf_protect def aifile_edit(request, aifile_name=None, editmode='edit'): """ This is the page where an automatic OS installation file is edited. """ if not test_user_authenticated(request): return login(request, next="/cobbler_web/aifile/edit/file:%s" % aifile_name, expired=True) if editmode == 'edit': editable = False else: editable = True deleteable = False aidata = "" if aifile_name is not None: editable = remote.check_access_no_fail(request.session['token'], "modify_autoinst", aifile_name) deleteable = not remote.is_autoinstall_in_use(aifile_name, request.session['token']) aidata = remote.read_autoinstall_template(aifile_name, request.session['token']) html = render(request, 'aifile_edit.tmpl', { 'aifile_name': aifile_name, 'deleteable': deleteable, 'aidata': aidata, 'editable': editable, 'editmode': editmode, 'version': remote.extended_version(request.session['token'])['version'], 'username': username }) return HttpResponse(html)
# ======================================================================
[docs]@require_POST @csrf_protect def aifile_save(request): """ This page processes and saves edits to an automatic OS installation file. """ if not test_user_authenticated(request): return login(request, next="/cobbler_web/aifile/list", expired=True) # FIXME: error checking aifile_name = request.POST.get('aifile_name', None) aidata = request.POST.get('aidata', "").replace('\r\n', '\n') if aifile_name is None: return HttpResponse("NO AUTOMATIC INSTALLATION FILE NAME SPECIFIED") delete1 = request.POST.get('delete1', None) delete2 = request.POST.get('delete2', None) if delete1 and delete2: remote.remove_autoinstall_template(aifile_name, request.session['token']) return HttpResponseRedirect('/cobbler_web/aifile/list') else: remote.write_autoinstall_template(aifile_name, aidata, request.session['token']) return HttpResponseRedirect('/cobbler_web/aifile/list')
# ======================================================================
[docs]def snippet_list(request, page=None): """ This page lists all available snippets and has links to edit them. """ if not test_user_authenticated(request): return login(request, next="/cobbler_web/snippet/list", expired=True) snippets = remote.get_autoinstall_snippets(request.session['token']) snippet_list = [] for snippet in snippets: snippet_list.append((snippet, 'editable')) html = render(request, 'snippet_list.tmpl', { 'what': 'snippet', 'snippets': snippet_list, 'version': remote.extended_version(request.session['token'])['version'], 'username': username }) return HttpResponse(html)
# ======================================================================
[docs]@csrf_protect def snippet_edit(request, snippet_name=None, editmode='edit'): """ This page edits a specific snippet. """ if not test_user_authenticated(request): return login(request, next="/cobbler_web/edit/file:%s" % snippet_name, expired=True) if editmode == 'edit': editable = False else: editable = True deleteable = False snippetdata = "" if snippet_name is not None: editable = remote.check_access_no_fail(request.session['token'], "modify_snippet", snippet_name) deleteable = True snippetdata = remote.read_autoinstall_snippet(snippet_name, request.session['token']) html = render(request, 'snippet_edit.tmpl', { 'snippet_name': snippet_name, 'deleteable': deleteable, 'snippetdata': snippetdata, 'editable': editable, 'editmode': editmode, 'version': remote.extended_version(request.session['token'])['version'], 'username': username }) return HttpResponse(html)
# ======================================================================
[docs]@require_POST @csrf_protect def snippet_save(request): """ This snippet saves a snippet once edited. """ if not test_user_authenticated(request): return login(request, next="/cobbler_web/snippet/list", expired=True) # FIXME: error checking editmode = request.POST.get('editmode', 'edit') snippet_name = request.POST.get('snippet_name', None) snippetdata = request.POST.get('snippetdata', "").replace('\r\n', '\n') if snippet_name is None: return HttpResponse("NO SNIPPET NAME SPECIFIED") if editmode != 'edit': if snippet_name.find("/var/lib/cobbler/snippets/") != 0: snippet_name = "/var/lib/cobbler/snippets/" + snippet_name delete1 = request.POST.get('delete1', None) delete2 = request.POST.get('delete2', None) if delete1 and delete2: remote.remove_autoinstall_snippet(snippet_name, request.session['token']) return HttpResponseRedirect('/cobbler_web/snippet/list') else: remote.write_autoinstall_snippet(snippet_name, snippetdata, request.session['token']) return HttpResponseRedirect('/cobbler_web/snippet/list')
# ======================================================================
[docs]def setting_list(request): """ This page presents a list of all the settings to the user. They are not editable. """ if not test_user_authenticated(request): return login(request, next="/cobbler_web/setting/list", expired=True) settings = remote.get_settings() skeys = list(settings.keys()) skeys.sort() results = [] for k in skeys: results.append([k, settings[k]]) html = render(request, 'settings.tmpl', { 'settings': results, 'version': remote.extended_version(request.session['token'])['version'], 'username': username, }) return HttpResponse(html)
[docs]@csrf_protect def setting_edit(request, setting_name=None): if not setting_name: return HttpResponseRedirect('/cobbler_web/setting/list') if not test_user_authenticated(request): return login(request, next="/cobbler_web/setting/edit/%s" % setting_name, expired=True) settings = remote.get_settings() if setting_name not in settings: return error_page(request, "Unknown setting: %s" % setting_name) cur_setting = { 'name': setting_name, 'value': settings[setting_name], } fields = get_fields('setting', False, seed_item=cur_setting) # build UI tabs metadata sections_data = field_ui_info.SETTING_UI_FIELDS_MAPPING sections = _create_sections_metadata('setting', sections_data, fields) html = render(request, 'generic_edit.tmpl', { 'what': 'setting', 'sections': sections, 'subobject': False, 'editmode': 'edit', 'editable': True, 'version': remote.extended_version(request.session['token'])['version'], 'username': username, 'name': setting_name, }) return HttpResponse(html)
[docs]@csrf_protect def setting_save(request): if not test_user_authenticated(request): return login(request, next="/cobbler_web/setting/list", expired=True) # load request fields and see if they are valid setting_name = request.POST.get('name', "") setting_value = request.POST.get('value', None) if setting_name == "": return error_page(request, "The setting name was not specified") settings = remote.get_settings() if setting_name not in settings: return error_page(request, "Unknown setting: %s" % setting_name) if remote.modify_setting(setting_name, setting_value, request.session['token']): return error_page(request, "There was an error saving the setting") return HttpResponseRedirect("/cobbler_web/setting/list")
# ======================================================================
[docs]def events(request): """ This page presents a list of all the events and links to the event log viewer. """ if not test_user_authenticated(request): return login(request, next="/cobbler_web/events", expired=True) events = remote.get_events() events2 = [] for id in list(events.keys()): (ttime, name, state, read_by) = events[id] events2.append([id, time.asctime(time.localtime(ttime)), name, state]) events2 = sorted(events2) html = render(request, 'events.tmpl', { 'results': events2, 'version': remote.extended_version(request.session['token'])['version'], 'username': username }) return HttpResponse(html)
# ======================================================================
[docs]def eventlog(request, event=0): """ Shows the log for a given event. """ if not test_user_authenticated(request): return login(request, next="/cobbler_web/eventlog/%s" % str(event), expired=True) event_info = remote.get_events() if event not in event_info: return HttpResponse("event not found") data = event_info[event] eventname = data[0] eventtime = data[1] eventstate = data[2] eventlog = remote.get_event_log(event) html = render(request, 'eventlog.tmpl', { 'eventlog': eventlog, 'eventname': eventname, 'eventstate': eventstate, 'eventid': event, 'eventtime': eventtime, 'version': remote.extended_version(request.session['token'])['version'], 'username': username }) return HttpResponse(html)
# ======================================================================
[docs]def random_mac(request, virttype="xenpv"): """ Used in an ajax call to fill in a field with a mac address. """ # FIXME: not exposed in UI currently if not test_user_authenticated(request): return login(request, expired=True) random_mac = remote.get_random_mac(virttype, request.session['token']) return HttpResponse(random_mac)
# ======================================================================
[docs]@require_POST @csrf_protect def sync(request): """ Runs 'cobbler sync' from the API when the user presses the sync button. """ if not test_user_authenticated(request): return login(request, next="/cobbler_web/sync", expired=True) remote.background_sync({"verbose": "True"}, request.session['token']) return HttpResponseRedirect("/cobbler_web/task_created")
# ======================================================================
[docs]@require_POST @csrf_protect def reposync(request): """ Syncs all repos that are configured to be synced. """ if not test_user_authenticated(request): return login(request, next="/cobbler_web/reposync", expired=True) remote.background_reposync({"names": "", "tries": 3}, request.session['token']) return HttpResponseRedirect("/cobbler_web/task_created")
# ====================================================================== # ======================================================================
[docs]@require_POST @csrf_protect def replicate(request): """ Replicate configuration from the central cobbler server, configured in /etc/cobbler/settings (note: this is uni-directional!) FIXME: this is disabled because we really need a web page to provide options for this command. """ # settings = remote.get_settings() # options = settings # just load settings from file until we decide to ask user (later?) # remote.background_replicate(options, request.session['token']) if not test_user_authenticated(request): return login(request, next="/cobbler_web/replicate", expired=True) return HttpResponseRedirect("/cobbler_web/task_created")
# ====================================================================== def __names_from_dicts(lod, optional=True): """ Tiny helper function. Get the names out of an array of dictionaries that the remote interface returns. """ results = [] if optional: results.append("<<None>>") for x in lod: results.append(x["name"]) results.sort() return results # ======================================================================
[docs]@csrf_protect def generic_edit(request, what=None, obj_name=None, editmode="new"): """ Presents an editor page for any type of object. While this is generally standardized, systems are a little bit special. """ target = "" if obj_name is not None: target = "/%s" % obj_name if not test_user_authenticated(request): return login(request, next="/cobbler_web/%s/edit%s" % (what, target), expired=True) obj = None child = False if what == "subprofile": what = "profile" child = True if obj_name is not None: editable = remote.check_access_no_fail(request.session['token'], "modify_%s" % what, obj_name) obj = remote.get_item(what, obj_name, False) else: editable = remote.check_access_no_fail(request.session['token'], "new_%s" % what, None) obj = None interfaces = {} if what == "system": if obj: interfaces = obj.get("interfaces", {}) else: interfaces = {} fields = get_fields(what, child, obj) if what == "system": fields += get_network_interface_fields() # create the autoinstall pulldown list autoinstalls = remote.get_autoinstall_templates() autoinstall_list = ["", "<<inherit>>"] for autoinstall in autoinstalls: autoinstall_list.append(autoinstall) # populate some select boxes if what == "profile": if (obj and obj["parent"] not in (None, "")) or child: __tweak_field(fields, "parent", "choices", __names_from_dicts(remote.get_profiles())) else: __tweak_field(fields, "distro", "choices", __names_from_dicts(remote.get_distros())) __tweak_field(fields, "autoinstall", "choices", autoinstall_list) __tweak_field(fields, "repos", "choices", __names_from_dicts(remote.get_repos())) __tweak_field(fields, "mgmt_classes", "choices", __names_from_dicts(remote.get_mgmtclasses(), optional=False)) elif what == "system": __tweak_field(fields, "profile", "choices", __names_from_dicts(remote.get_profiles())) __tweak_field(fields, "image", "choices", __names_from_dicts(remote.get_images(), optional=True)) __tweak_field(fields, "autoinstall", "choices", autoinstall_list) __tweak_field(fields, "mgmt_classes", "choices", __names_from_dicts(remote.get_mgmtclasses(), optional=False)) elif what == "mgmtclass": __tweak_field(fields, "packages", "choices", __names_from_dicts(remote.get_packages())) __tweak_field(fields, "files", "choices", __names_from_dicts(remote.get_files())) elif what == "distro": __tweak_field(fields, "arch", "choices", remote.get_valid_archs()) __tweak_field(fields, "os_version", "choices", remote.get_valid_os_versions()) __tweak_field(fields, "breed", "choices", remote.get_valid_breeds()) __tweak_field(fields, "mgmt_classes", "choices", __names_from_dicts(remote.get_mgmtclasses(), optional=False)) elif what == "image": __tweak_field(fields, "arch", "choices", remote.get_valid_archs()) __tweak_field(fields, "breed", "choices", remote.get_valid_breeds()) __tweak_field(fields, "os_version", "choices", remote.get_valid_os_versions()) __tweak_field(fields, "autoinst", "choices", autoinstall_list) # if editing save the fields in the session for comparison later if editmode == "edit": request.session['%s_%s' % (what, obj_name)] = fields # build UI tabs metadata if what == "distro": sections_data = field_ui_info.DISTRO_UI_FIELDS_MAPPING elif what == "file": sections_data = field_ui_info.FILE_UI_FIELDS_MAPPING elif what == "image": sections_data = field_ui_info.IMAGE_UI_FIELDS_MAPPING elif what == "mgmtclass": sections_data = field_ui_info.MGMTCLASS_UI_FIELDS_MAPPING elif what == "package": sections_data = field_ui_info.PACKAGE_UI_FIELDS_MAPPING elif what == "profile": sections_data = field_ui_info.PROFILE_UI_FIELDS_MAPPING elif what == "repo": sections_data = field_ui_info.REPO_UI_FIELDS_MAPPING elif what == "system": sections_data = field_ui_info.SYSTEM_UI_FIELDS_MAPPING sections = _create_sections_metadata(what, sections_data, fields) inames = list(interfaces.keys()) inames.sort() html = render(request, 'generic_edit.tmpl', { 'what': what, 'sections': sections, 'subobject': child, 'editmode': editmode, 'editable': editable, 'interfaces': interfaces, 'interface_names': inames, 'interface_length': len(inames), 'version': remote.extended_version(request.session['token'])['version'], 'username': username, 'name': obj_name }) return HttpResponse(html)
# ======================================================================
[docs]@require_POST @csrf_protect def generic_save(request, what): """ Saves an object back using the cobbler API after clearing any 'generic_edit' page. """ if not test_user_authenticated(request): return login(request, next="/cobbler_web/%s/list" % what, expired=True) # load request fields and see if they are valid editmode = request.POST.get('editmode', 'edit') obj_name = request.POST.get('name', "") subobject = request.POST.get('subobject', "False") if subobject == "False": subobject = False else: subobject = True if obj_name == "": return error_page(request, "Required field name is missing") prev_fields = [] if "%s_%s" % (what, obj_name) in request.session and editmode == "edit": prev_fields = request.session["%s_%s" % (what, obj_name)] # grab the remote object handle # for edits, fail in the object cannot be found to be edited # for new objects, fail if the object already exists if editmode == "edit": if not remote.has_item(what, obj_name): return error_page(request, "Failure trying to access item %s, it may have been deleted." % (obj_name)) obj_id = remote.get_item_handle(what, obj_name, request.session['token']) else: if remote.has_item(what, obj_name): return error_page(request, "Could not create a new item %s, it already exists." % (obj_name)) obj_id = remote.new_item(what, request.session['token']) # system needs either profile or image to be set # fail if both are not set if what == "system": profile = request.POST.getlist('profile') image = request.POST.getlist('image') if "<<None>>" in profile and "<<None>>" in image: return error_page(request, "Please provide either a valid profile or image for the system") # walk through our fields list saving things we know how to save fields = get_fields(what, subobject) for field in fields: if field['name'] == 'name' and editmode == 'edit': # do not attempt renames here continue else: # check and see if the value exists in the fields stored in the session prev_value = None for prev_field in prev_fields: if prev_field['name'] == field['name']: prev_value = prev_field['value'] break value = request.POST.get(field['name'], None) # Checkboxes return the value of the field if checked, otherwise None # convert to True/False if field["html_element"] == "checkbox": if value == field['name']: value = True else: value = False # Multiselect fields are handled differently if field["html_element"] == "multiselect": values = request.POST.getlist(field['name']) value = [] if '<<inherit>>' in values: value = '<<inherit>>' else: for single_value in values: if single_value != "<<None>>": value.insert(0, single_value) if value is not None: if value == "<<None>>": value = "" if value is not None and (not subobject or field['name'] != 'distro') and value != prev_value: try: remote.modify_item(what, obj_id, field['name'], value, request.session['token']) except Exception as e: return error_page(request, str(e)) # special handling for system interface fields # which are the only objects in cobbler that will ever work this way if what == "system": network_interface_fields = get_network_interface_fields() interfaces = request.POST.get('interface_list', "").split(",") for interface in interfaces: if interface == "": continue ifdata = {} for field in network_interface_fields: ifdata["%s-%s" % (field["name"], interface)] = request.POST.get("%s-%s" % (field["name"], interface), "") ifdata = utils.strip_none(ifdata) # FIXME: I think this button is missing. present = request.POST.get("present-%s" % interface, "") original = request.POST.get("original-%s" % interface, "") try: if present == "0" and original == "1": remote.modify_system(obj_id, 'delete_interface', interface, request.session['token']) elif present == "1": remote.modify_system(obj_id, 'modify_interface', ifdata, request.session['token']) except Exception as e: return error_page(request, str(e)) try: remote.save_item(what, obj_id, request.session['token'], editmode) except Exception as e: return error_page(request, str(e)) return HttpResponseRedirect('/cobbler_web/%s/list' % what)
# ====================================================================== # Login/Logout views
[docs]def test_user_authenticated(request): global remote global username global url_cobbler_api if url_cobbler_api is None: url_cobbler_api = utils.local_get_cobbler_api_url() remote = xmlrpc.client.Server(url_cobbler_api, allow_none=True) # if we have a token, get the associated username from # the remote server via XMLRPC. We then compare that to # the value stored in the session. If everything matches up, # the user is considered successfully authenticated if 'token' in request.session and request.session['token'] != '': try: if remote.token_check(request.session['token']): token_user = remote.get_user_from_token(request.session['token']) if 'username' in request.session and request.session['username'] == token_user: username = request.session['username'] return True except: # just let it fall through to the 'return False' below pass return False
use_passthru = -1
[docs]@csrf_protect def login(request, next=None, message=None, expired=False): global use_passthru if use_passthru < 0: token = remote.login("", utils.get_shared_secret()) auth_module = remote.get_authn_module_name(token) use_passthru = auth_module == 'authn_passthru' if use_passthru: return accept_remote_user(request, next) if expired and not message: message = "Sorry, either you need to login or your session expired." return render(request, 'login.tmpl', {'next': next, 'message': message})
[docs]def accept_remote_user(request, nextsite): global username username = request.META['REMOTE_USER'] token = remote.login(username, utils.get_shared_secret()) request.session['username'] = username request.session['token'] = token if nextsite: return HttpResponseRedirect(nextsite) else: return HttpResponseRedirect("/cobbler_web")
[docs]@require_POST @csrf_protect def do_login(request): global remote global username global url_cobbler_api username = request.POST.get('username', '').strip() password = request.POST.get('password', '') nextsite = request.POST.get('next', None) if url_cobbler_api is None: url_cobbler_api = utils.local_get_cobbler_api_url() remote = xmlrpc.client.Server(url_cobbler_api, allow_none=True) try: token = remote.login(username, password) except: token = None if token: request.session['username'] = username request.session['token'] = token if nextsite: return HttpResponseRedirect(nextsite) else: return HttpResponseRedirect("/cobbler_web") else: return login(request, nextsite, message="Login failed, please try again")
[docs]@require_POST @csrf_protect def do_logout(request): request.session['username'] = "" request.session['token'] = "" return HttpResponseRedirect("/cobbler_web")