From 15b7dc6ff9c202dee00f1403139c206b5969c0f3 Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Wed, 5 Dec 2007 15:17:11 -0500 Subject: [PATCH] Add UI for service principal creation and keytab retrieval --- ipa-python/ipaclient.py | 14 ++ ipa-python/rpcclient.py | 18 +++ ipa-server/ipa-gui/ipagui/controllers.py | 2 + ipa-server/ipa-gui/ipagui/forms/principal.py | 39 +++++ .../ipagui/subcontrollers/principal.py | 153 ++++++++++++++++++ .../ipa-gui/ipagui/templates/master.kid | 6 +- .../ipagui/templates/principallayout.kid | 19 +++ .../ipagui/templates/principallist.kid | 64 ++++++++ .../ipa-gui/ipagui/templates/principalnew.kid | 13 ++ .../ipagui/templates/principalnewform.kid | 98 +++++++++++ ipa-server/xmlrpc-server/funcs.py | 73 ++++++++- ipa-server/xmlrpc-server/ipaxmlrpc.py | 1 + 12 files changed, 498 insertions(+), 2 deletions(-) create mode 100644 ipa-server/ipa-gui/ipagui/forms/principal.py create mode 100644 ipa-server/ipa-gui/ipagui/subcontrollers/principal.py create mode 100644 ipa-server/ipa-gui/ipagui/templates/principallayout.kid create mode 100644 ipa-server/ipa-gui/ipagui/templates/principallist.kid create mode 100644 ipa-server/ipa-gui/ipagui/templates/principalnew.kid create mode 100644 ipa-server/ipa-gui/ipagui/templates/principalnewform.kid diff --git a/ipa-python/ipaclient.py b/ipa-python/ipaclient.py index c551f0435..0a4d64f11 100644 --- a/ipa-python/ipaclient.py +++ b/ipa-python/ipaclient.py @@ -385,6 +385,20 @@ class IPAClient: def add_service_principal(self, princ_name): return self.transport.add_service_principal(princ_name) + def find_service_principal(self, criteria, sattrs=None, searchlimit=0, timelimit=-1): + """Return a list: counter followed by a Entity object for each host that + matches the criteria. If the results are truncated, counter will + be set to -1""" + result = self.transport.find_service_principal(criteria, sattrs, searchlimit, timelimit) + counter = result[0] + + hosts = [counter] + for attrs in result[1:]: + if attrs is not None: + hosts.append(entity.Entity(attrs)) + + return hosts + def get_keytab(self, princ_name): return self.transport.get_keytab(princ_name) diff --git a/ipa-python/rpcclient.py b/ipa-python/rpcclient.py index d7ff97405..de32e9beb 100644 --- a/ipa-python/rpcclient.py +++ b/ipa-python/rpcclient.py @@ -703,6 +703,24 @@ class RPCClient: return ipautil.unwrap_binary_data(result) + def find_service_principal (self, criteria, sattrs=None, searchlimit=0, timelimit=-1): + """Return a list: counter followed by a Entity object for each host that + matches the criteria. If the results are truncated, counter will + be set to -1""" + + server = self.setup_server() + try: + # None values are not allowed in XML-RPC + if sattrs is None: + sattrs = "__NONE__" + result = server.find_service_principal(criteria, sattrs, searchlimit, timelimit) + except xmlrpclib.Fault, fault: + raise ipaerror.gen_exception(fault.faultCode, fault.faultString) + except socket.error, (value, msg): + raise xmlrpclib.Fault(value, msg) + + return ipautil.unwrap_binary_data(result) + def get_keytab(self, princ_name): server = self.setup_server() diff --git a/ipa-server/ipa-gui/ipagui/controllers.py b/ipa-server/ipa-gui/ipagui/controllers.py index d1ee22e01..70a29246a 100644 --- a/ipa-server/ipa-gui/ipagui/controllers.py +++ b/ipa-server/ipa-gui/ipagui/controllers.py @@ -19,6 +19,7 @@ from subcontrollers.group import GroupController from subcontrollers.delegation import DelegationController from subcontrollers.policy import PolicyController from subcontrollers.ipapolicy import IPAPolicyController +from subcontrollers.principal import PrincipalController ipa.config.init_config() @@ -31,6 +32,7 @@ class Root(controllers.RootController): delegate = DelegationController() policy = PolicyController() ipapolicy = IPAPolicyController() + principal = PrincipalController() @expose(template="ipagui.templates.welcome") @identity.require(identity.not_anonymous()) diff --git a/ipa-server/ipa-gui/ipagui/forms/principal.py b/ipa-server/ipa-gui/ipagui/forms/principal.py new file mode 100644 index 000000000..a830c8a34 --- /dev/null +++ b/ipa-server/ipa-gui/ipagui/forms/principal.py @@ -0,0 +1,39 @@ +import turbogears +from turbogears import validators, widgets +from tg_expanding_form_widget.tg_expanding_form_widget import ExpandingForm + +class PrincipalFields(object): + hostname = widgets.TextField(name="hostname", label="Host Name") + service = widgets.SingleSelectField(name="service", + label="Service Type", + options = [ + ("cifs", "cifs"), + ("dhcp", "dhcp"), + ("dns", "dns"), + ("host", "host"), + ("HTTP", "HTTP"), + ("ldap", "ldap"), + ("other", "other"), + ("rpc", "rpc"), + ("snmp", "snmp") + ], + attrs=dict(onchange="toggleOther(this.id)")) + other = widgets.TextField(name="other", label="Other Service", attrs=dict(size=10)) + +class PrincipalNewValidator(validators.Schema): + hostname = validators.String(not_empty=True) + service = validators.String(not_empty=True) + other = validators.String(not_empty=False) + +class PrincipalNewForm(widgets.Form): + params = ['principal_fields'] + + validator = PrincipalNewValidator() + + def __init__(self, *args, **kw): + super(PrincipalNewForm,self).__init__(*args, **kw) + (self.template_c, self.template) = widgets.meta.load_kid_template("ipagui.templates.principalnewform") + self.principal_fields = PrincipalFields + + def update_params(self, params): + super(PrincipalNewForm,self).update_params(params) diff --git a/ipa-server/ipa-gui/ipagui/subcontrollers/principal.py b/ipa-server/ipa-gui/ipagui/subcontrollers/principal.py new file mode 100644 index 000000000..1b2ad6942 --- /dev/null +++ b/ipa-server/ipa-gui/ipagui/subcontrollers/principal.py @@ -0,0 +1,153 @@ +import os +from pickle import dumps, loads +from base64 import b64encode, b64decode +import copy +import logging + +import cherrypy +import turbogears +from turbogears import controllers, expose, flash +from turbogears import validators, validate +from turbogears import widgets, paginate +from turbogears import error_handler +from turbogears import identity + +from ipacontroller import IPAController +from ipa.entity import utf8_encode_values +from ipa import ipaerror +import ipagui.forms.principal + +import ldap.dn + +log = logging.getLogger(__name__) + +principal_new_form = ipagui.forms.principal.PrincipalNewForm() +principal_fields = ['*'] + +class PrincipalController(IPAController): + + @expose() + @identity.require(identity.in_group("admins")) + def index(self, tg_errors=None): + raise turbogears.redirect("/principal/list") + + @expose("ipagui.templates.principalnew") + @identity.require(identity.in_group("admins")) + def new(self, tg_errors=None): + """Displays the new service principal form""" + if tg_errors: + turbogears.flash("There were validation errors.
" + + "Please see the messages below for details.") + + client = self.get_ipaclient() + + return dict(form=principal_new_form, principal={}) + + @expose() + @identity.require(identity.in_group("admins")) + def create(self, **kw): + """Creates a service principal group""" + self.restrict_post() + client = self.get_ipaclient() + + if kw.get('submit') == 'Cancel': + turbogears.flash("Add principal cancelled") + raise turbogears.redirect('/') + + tg_errors, kw = self.principalcreatevalidate(**kw) + if tg_errors: + turbogears.flash("There were validation errors.
" + + "Please see the messages below for details.") + return dict(form=principal_new_form, principal=kw, + tg_template='ipagui.templates.principalnew') + + principal_name = "" + hostname = kw.get('hostname') + # + # Create the principal itself + # + try: + if kw.get('service') == "other": + service = kw.get('other') + if not service: + turbogears.flash("Service type must be provided") + return dict(form=principal_new_form, principal=kw, + tg_template='ipagui.templates.principalnew') + else: + service = kw.get('service') + + # The realm is added by add_service_principal + principal_name = utf8_encode_values(service + "/" + kw.get('hostname')) + + rv = client.add_service_principal(principal_name) + except ipaerror.exception_for(ipaerror.LDAP_DUPLICATE): + turbogears.flash("Service principal '%s' already exists" % + principal_name) + return dict(form=principal_new_form, principal=kw, + tg_template='ipagui.templates.principalnew') + except ipaerror.IPAError, e: + turbogears.flash("Service principal add failed: " + str(e) + "
" + e.detail[0]['desc']) + return dict(form=principal_new_form, principal=kw, + tg_template='ipagui.templates.principalnew') + + turbogears.flash("%s added!" % principal_name) + raise turbogears.redirect('/principal/list', hostname=hostname) + + @expose("ipagui.templates.principallist") + @identity.require(identity.not_anonymous()) + def list(self, **kw): + """Searches for service principals and displays list of results""" + client = self.get_ipaclient() + + principals = None + counter = 0 + hostname = kw.get('hostname') + if hostname != None and len(hostname) > 0: + try: + principals = client.find_service_principal(hostname.encode('utf-8'), principal_fields, 0, 2) + counter = principals[0] + principals = principals[1:] + + if counter == -1: + turbogears.flash("These results are truncated.
" + + "Please refine your search and try again.") + + # For each entry break out service type and hostname + for i in range(len(principals)): + (service,host) = principals[i].krbprincipalname.split('/') + h = host.split('@') + principals[i].setValue('service', service) + principals[i].setValue('hostname', h[0]) + + except ipaerror.IPAError, e: + turbogears.flash("principal list failed: " + str(e) + "
" + e.detail[0]['desc']) + raise turbogears.redirect("/principal/list") + + return dict(principals=principals, hostname=hostname, fields=ipagui.forms.principal.PrincipalFields()) + + @expose() + @identity.require(identity.not_anonymous()) + def show(self, **kw): + """Returns the keytab for a given principal""" + client = self.get_ipaclient() + + principal = kw.get('principal') + if principal != None and len(principal) > 0: + try: + p = principal.split('@') + keytab = client.get_keytab(p[0].encode('utf-8')) + + cherrypy.response.headers['Content-Type'] = "application/x-download" + cherrypy.response.headers['Content-Disposition'] = 'attachment; filename=krb5.keytab' + cherrypy.response.headers['Content-Length'] = len(keytab) + cherrypy.response.body = keytab + return cherrypy.response.body + except ipaerror.IPAError, e: + turbogears.flash("keytab retrieval failed: " + str(e) + "
" + e.detail[0]['desc']) + raise turbogears.redirect("/principal/list") + raise turbogears.redirect("/principal/list") + + @validate(form=principal_new_form) + @identity.require(identity.not_anonymous()) + def principalcreatevalidate(self, tg_errors=None, **kw): + return tg_errors, kw diff --git a/ipa-server/ipa-gui/ipagui/templates/master.kid b/ipa-server/ipa-gui/ipagui/templates/master.kid index 12e54fa1d..e11497546 100644 --- a/ipa-server/ipa-gui/ipagui/templates/master.kid +++ b/ipa-server/ipa-gui/ipagui/templates/master.kid @@ -78,10 +78,14 @@
  • Find Groups
  • +