diff options
Diffstat (limited to 'ipa-server/ipa-gui')
34 files changed, 1567 insertions, 250 deletions
diff --git a/ipa-server/ipa-gui/README.multivalue b/ipa-server/ipa-gui/README.multivalue new file mode 100644 index 000000000..ba315181d --- /dev/null +++ b/ipa-server/ipa-gui/README.multivalue @@ -0,0 +1,27 @@ +The way multi-valued fields work is this: + - A new widget is added to the form. I name it as the attribute + s. + For example, I use cns for the cn attribute. + - If you need a new validator use a ForEach() so that each value is + checked. + - This attribute is populated from the incoming attribute from the + user or group record. The widget can support multiple fields at once + but I'm using it for just one field. In fact, I don't know if it + will work with more the way I'm using it. + - In the GUI an operator can add/remove values to each multi-valued field. + - Naming is very important in the widget. TurboGears automatically + re-assembles the data into a list of dict entries if you name things + properly. For example, the cns (multiple CN entries) looks like: + cns-0.cn=Rob+Crittenden&cns-1.cn=Robert+Crittenden&cns-2.cn=rcrit + - This gets converted to: + [{'cn': u'Rob Crittenden'}, {'cn': u'Robert Crittenden'}, {'cn': u'rcrit'}] + - I take this list of dicts and pull out each value and append it to a new + list that represents the original multi-valued field + - Then the list/dict version is removed (in this case, kw['cns']). + +When adding a new field you have to update: + +1. The form to add the new ExpandingForm() field and perhaps a validator +2. The edit template to add the boilerplate to display the field +3. The show template to be able to display all the fields separately +4. The new template if you want to be able to enter these on new entries +5. The subcontroller so you can do the input and output conversions diff --git a/ipa-server/ipa-gui/ipagui/controllers.py b/ipa-server/ipa-gui/ipagui/controllers.py index 5d0bfee03..d1ee22e01 100644 --- a/ipa-server/ipa-gui/ipagui/controllers.py +++ b/ipa-server/ipa-gui/ipagui/controllers.py @@ -17,6 +17,8 @@ import ipa.ipaclient from subcontrollers.user import UserController from subcontrollers.group import GroupController from subcontrollers.delegation import DelegationController +from subcontrollers.policy import PolicyController +from subcontrollers.ipapolicy import IPAPolicyController ipa.config.init_config() @@ -27,6 +29,8 @@ class Root(controllers.RootController): user = UserController() group = GroupController() delegate = DelegationController() + policy = PolicyController() + ipapolicy = IPAPolicyController() @expose(template="ipagui.templates.welcome") @identity.require(identity.not_anonymous()) diff --git a/ipa-server/ipa-gui/ipagui/forms/Makefile.am b/ipa-server/ipa-gui/ipagui/forms/Makefile.am index 5f07e4cb0..c075b9f47 100644 --- a/ipa-server/ipa-gui/ipagui/forms/Makefile.am +++ b/ipa-server/ipa-gui/ipagui/forms/Makefile.am @@ -4,8 +4,9 @@ appdir = $(IPA_DATA_DIR)/ipagui/forms app_PYTHON = \ __init__.py \ group.py \ + ipapolicy.py \ user.py \ - delegate.py \ + delegate.py \ $(NULL) EXTRA_DIST = \ diff --git a/ipa-server/ipa-gui/ipagui/forms/delegate.py b/ipa-server/ipa-gui/ipagui/forms/delegate.py index 89011f4a2..419df4fc7 100644 --- a/ipa-server/ipa-gui/ipagui/forms/delegate.py +++ b/ipa-server/ipa-gui/ipagui/forms/delegate.py @@ -44,7 +44,7 @@ aci_checkbox_attrs = [(field.name, field.label) for field in aci_attrs] aci_name_to_label = dict(aci_checkbox_attrs) class DelegateFields(): - name = widgets.TextField(name="name", label="Name") + name = widgets.TextField(name="name", label="Delegation Name") source_group_dn = widgets.HiddenField(name="source_group_dn") dest_group_dn = widgets.HiddenField(name="dest_group_dn") diff --git a/ipa-server/ipa-gui/ipagui/forms/group.py b/ipa-server/ipa-gui/ipagui/forms/group.py index 380c904a4..b67156641 100644 --- a/ipa-server/ipa-gui/ipagui/forms/group.py +++ b/ipa-server/ipa-gui/ipagui/forms/group.py @@ -1,14 +1,18 @@ import turbogears from turbogears import validators, widgets +from tg_expanding_form_widget.tg_expanding_form_widget import ExpandingForm class GroupFields(): cn = widgets.TextField(name="cn", label="Name") gidnumber = widgets.TextField(name="gidnumber", label="GID") description = widgets.TextField(name="description", label="Description") - cn_hidden = widgets.HiddenField(name="cn") editprotected_hidden = widgets.HiddenField(name="editprotected") + nsAccountLock = widgets.SingleSelectField(name="nsAccountLock", + label="Group Status", + options = [("", "active"), ("true", "inactive")]) + group_orig = widgets.HiddenField(name="group_orig") member_data = widgets.HiddenField(name="member_data") dn_to_info_json = widgets.HiddenField(name="dn_to_info_json") @@ -37,6 +41,7 @@ class GroupNewForm(widgets.Form): class GroupEditValidator(validators.Schema): + cn = validators.String(not_empty=True) gidnumber = validators.Int(not_empty=False) description = validators.String(not_empty=False) @@ -48,7 +53,7 @@ class GroupEditForm(widgets.Form): params = ['members', 'group_fields'] hidden_fields = [ - GroupFields.cn_hidden, GroupFields.editprotected_hidden, + GroupFields.editprotected_hidden, GroupFields.group_orig, GroupFields.member_data, GroupFields.dn_to_info_json ] diff --git a/ipa-server/ipa-gui/ipagui/forms/ipapolicy.py b/ipa-server/ipa-gui/ipagui/forms/ipapolicy.py new file mode 100644 index 000000000..ec0e8c6f8 --- /dev/null +++ b/ipa-server/ipa-gui/ipagui/forms/ipapolicy.py @@ -0,0 +1,59 @@ +import turbogears +from turbogears import validators, widgets + +class IPAPolicyFields(): + # From cn=ipaConfig + ipausersearchfields = widgets.TextField(name="ipausersearchfields", label="User Search Fields") + ipagroupsearchfields = widgets.TextField(name="ipagroupsearchfields", label="Group Search Fields") + ipasearchtimelimit = widgets.TextField(name="ipasearchtimelimit", label="Search Time Limit (sec.)", attrs=dict(size=6,maxlength=6)) + ipasearchrecordslimit = widgets.TextField(name="ipasearchrecordslimit", label="Search Records Limit", attrs=dict(size=6,maxlength=6)) + ipahomesrootdir = widgets.TextField(name="ipahomesrootdir", label="Root for Home Directories") + ipadefaultloginshell = widgets.TextField(name="ipadefaultloginshell", label="Default shell") + ipadefaultprimarygroup = widgets.TextField(name="ipadefaultprimarygroup", label="Default Users group") + ipamaxusernamelength = widgets.TextField(name="ipamaxusernamelength", label="Max. Username Length", attrs=dict(size=3,maxlength=3)) + ipapwdexpadvnotify = widgets.TextField(name="ipapwdexpadvnotify", label="Password Expiration Notification (days)", attrs=dict(size=3,maxlength=3)) + + ipapolicy_orig = widgets.HiddenField(name="ipapolicy_orig") + + # From cn=accounts + krbmaxpwdlife = widgets.TextField(name="krbmaxpwdlife", label="Max. Password Lifetime", attrs=dict(size=3,maxlength=3)) + krbminpwdlife = widgets.TextField(name="krbminpwdlife", label="Min. Password Lifetime", attrs=dict(size=3,maxlength=3)) + krbpwdmindiffchars = widgets.TextField(name="krbpwdmindiffchars", label="Min. number of character classes", attrs=dict(size=3,maxlength=3)) + krbpwdminlength = widgets.TextField(name="krbpwdminlength", label="Min. Length of password", attrs=dict(size=3,maxlength=3)) + krbpwdhistorylength = widgets.TextField(name="krbpwdhistorylength", label="Password History size", attrs=dict(size=3,maxlength=3)) + + password_orig = widgets.HiddenField(name="password_orig") + +class IPAPolicyValidator(validators.Schema): + ipausersearchfields = validators.String(not_empty=True) + ipagroupsearchfields = validators.String(not_empty=True) + ipasearchtimelimit = validators.Number(not_empty=True) + ipasearchrecordslimit = validators.Number(not_empty=True) + ipamaxusernamelength = validators.Number(not_empty=True) + ipapwdexpadvnotify = validators.Number(not_empty=True) + ipahomesrootdir = validators.String(not_empty=True) + ipadefaultloginshell = validators.String(not_empty=True) + ipadefaultprimarygroup = validators.String(not_empty=True) + krbmaxpwdlife = validators.Number(not_empty=True) + krbminpwdlife = validators.Number(not_empty=True) + krbpwdmindiffchars = validators.Number(not_empty=True) + krbpwdminlength = validators.Number(not_empty=True) + krbpwdhistorylength = validators.Number(not_empty=True) + +class IPAPolicyForm(widgets.Form): + params = ['ipapolicy_fields'] + + hidden_fields = [ + IPAPolicyFields.ipapolicy_orig, IPAPolicyFields.password_orig + ] + + validator = IPAPolicyValidator() + + def __init__(self, *args, **kw): + super(IPAPolicyForm,self).__init__(*args, **kw) + (self.template_c, self.template) = widgets.meta.load_kid_template( + "ipagui.templates.ipapolicyeditform") + self.ipapolicy_fields = IPAPolicyFields + + def update_params(self, params): + super(IPAPolicyForm,self).update_params(params) diff --git a/ipa-server/ipa-gui/ipagui/forms/user.py b/ipa-server/ipa-gui/ipagui/forms/user.py index 1a35b4e07..7d3d37193 100644 --- a/ipa-server/ipa-gui/ipagui/forms/user.py +++ b/ipa-server/ipa-gui/ipagui/forms/user.py @@ -1,10 +1,12 @@ import turbogears from turbogears import validators, widgets +from tg_expanding_form_widget.tg_expanding_form_widget import ExpandingForm class UserFields(): givenname = widgets.TextField(name="givenname", label="Given Name") sn = widgets.TextField(name="sn", label="Family Name") cn = widgets.TextField(name="cn", label="Common Names") + cns = ExpandingForm(name="cns", label="Common Names", fields=[cn]) title = widgets.TextField(name="title", label="Title") displayname = widgets.TextField(name="displayname", label="Display Name") initials = widgets.TextField(name="initials", label="Initials") @@ -21,11 +23,16 @@ class UserFields(): mail = widgets.TextField(name="mail", label="E-mail Address") telephonenumber = widgets.TextField(name="telephonenumber", label="Work Number") + telephonenumbers = ExpandingForm(name="telephonenumbers", label="Work Numbers", fields=[telephonenumber]) facsimiletelephonenumber = widgets.TextField(name="facsimiletelephonenumber", label="Fax Number") + facsimiletelephonenumbers = ExpandingForm(name="facsimiletelephonenumbers", label="Fax Numbers", fields=[facsimiletelephonenumber]) mobile = widgets.TextField(name="mobile", label="Cell Number") + mobiles = ExpandingForm(name="mobiles", label="Cell Numbers", fields=[mobile]) pager = widgets.TextField(name="pager", label="Pager Number") + pagers = ExpandingForm(name="pagers", label="Pager Numbers", fields=[pager]) homephone = widgets.TextField(name="homephone", label="Home Number") + homephones = ExpandingForm(name="homephones", label="Home Numbers", fields=[homephone]) street = widgets.TextField(name="street", label="Street Address") l = widgets.TextField(name="l", label="City") @@ -67,7 +74,8 @@ class UserNewValidator(validators.Schema): userpassword_confirm = validators.String(not_empty=False) givenname = validators.String(not_empty=True) sn = validators.String(not_empty=True) - mail = validators.Email(not_empty=True) + cn = validators.ForEach(validators.String(not_empty=True)) + mail = validators.Email(not_empty=False) chained_validators = [ validators.FieldsMatch('userpassword', 'userpassword_confirm') @@ -102,6 +110,7 @@ class UserEditValidator(validators.Schema): userpassword_confirm = validators.String(not_empty=False) givenname = validators.String(not_empty=True) sn = validators.String(not_empty=True) + cn = validators.ForEach(validators.String(not_empty=True)) mail = validators.Email(not_empty=True) uidnumber = validators.Int(not_empty=False) gidnumber = validators.Int(not_empty=False) diff --git a/ipa-server/ipa-gui/ipagui/helpers/userhelper.py b/ipa-server/ipa-gui/ipagui/helpers/userhelper.py index e1ade3a2c..52d37c9e4 100644 --- a/ipa-server/ipa-gui/ipagui/helpers/userhelper.py +++ b/ipa-server/ipa-gui/ipagui/helpers/userhelper.py @@ -13,7 +13,7 @@ def password_expires_in(datestr): if not expdate: return sys.maxint - delta = expdate - datetime.datetime.now() + delta = expdate - datetime.datetime.now(ipautil.GeneralizedTimeZone()) return delta.days def password_is_expired(days): diff --git a/ipa-server/ipa-gui/ipagui/proxyprovider.py b/ipa-server/ipa-gui/ipagui/proxyprovider.py index e8ef69830..bd9cf87a8 100644 --- a/ipa-server/ipa-gui/ipagui/proxyprovider.py +++ b/ipa-server/ipa-gui/ipagui/proxyprovider.py @@ -2,6 +2,11 @@ from turbogears.identity.soprovider import * from turbogears.identity.visitor import * import logging import os +import ipa.ipaclient +from ipaserver import funcs +import ipa.config +import ipa.group +import ipa.user log = logging.getLogger("turbogears.identity") @@ -15,7 +20,25 @@ class IPA_User(object): (principal, realm) = user_name.split('@') self.display_name = principal self.permissions = None - self.groups = None + transport = funcs.IPAServer() + client = ipa.ipaclient.IPAClient(transport) + client.set_krbccache(os.environ["KRB5CCNAME"]) + try: + user = client.get_user_by_principal(user_name, ['dn']) + self.groups = [] + groups = client.get_groups_by_member(user.dn, ['dn', 'cn']) + if isinstance(groups, str): + groups = [groups] + for ginfo in groups: + # cn may be multi-valued, add them all just in case + cn = ginfo.getValue('cn') + if isinstance(cn, str): + cn = [cn] + for c in cn: + self.groups.append(c) + except: + raise + return class ProxyIdentity(object): @@ -57,7 +80,7 @@ class ProxyIdentity(object): def _get_groups(self): try: - return self._groups + return self._user.groups except AttributeError: # Groups haven't been computed yet return None @@ -87,10 +110,14 @@ class ProxyIdentityProvider(SqlObjectIdentityProvider): pass def validate_identity(self, user_name, password, visit_key): - user = IPA_User(user_name) - log.debug( "validate_identity %s" % user_name) - - return ProxyIdentity(visit_key, user) + try: + user = IPA_User(user_name) + log.debug( "validate_identity %s" % user_name) + return ProxyIdentity(visit_key, user) + except: + # Something went wrong in fetching the user. Set to + # anonymous which will deny access. + return ProxyIdentity( None ) def validate_password(self, user, user_name, password): '''Validation has already occurred in the proxy''' diff --git a/ipa-server/ipa-gui/ipagui/static/css/style.css b/ipa-server/ipa-gui/ipagui/static/css/style.css index 28f501352..1a7cbb1fb 100644 --- a/ipa-server/ipa-gui/ipagui/static/css/style.css +++ b/ipa-server/ipa-gui/ipagui/static/css/style.css @@ -383,3 +383,22 @@ ul.checkboxlist li input { #inactive { background-color: silver; } + +/* + * * TableKit css + * + */ + +.sortcol { + cursor: pointer; + padding-right: 20px !important; + background-repeat: no-repeat !important; + background-position: right center !important; + text-decoration: underline; +} +.sortasc { + background-image: url(/static/images/up.gif) !important; +} +.sortdesc { + background-image: url(/static/images/down.gif) !important; +} diff --git a/ipa-server/ipa-gui/ipagui/subcontrollers/Makefile.am b/ipa-server/ipa-gui/ipagui/subcontrollers/Makefile.am index d409bac7d..2f596f2ef 100644 --- a/ipa-server/ipa-gui/ipagui/subcontrollers/Makefile.am +++ b/ipa-server/ipa-gui/ipagui/subcontrollers/Makefile.am @@ -5,6 +5,8 @@ app_PYTHON = \ __init__.py \ group.py \ ipacontroller.py \ + ipapolicy.py \ + policy.py \ user.py \ delegation.py \ $(NULL) diff --git a/ipa-server/ipa-gui/ipagui/subcontrollers/delegation.py b/ipa-server/ipa-gui/ipagui/subcontrollers/delegation.py index 1515b04c1..142d34430 100644 --- a/ipa-server/ipa-gui/ipagui/subcontrollers/delegation.py +++ b/ipa-server/ipa-gui/ipagui/subcontrollers/delegation.py @@ -19,6 +19,7 @@ import ipagui.forms.delegate import ipa.aci import ldap.dn +import operator log = logging.getLogger(__name__) @@ -34,7 +35,7 @@ class DelegationController(IPAController): raise turbogears.redirect("/delegate/list") @expose("ipagui.templates.delegatenew") - @identity.require(identity.not_anonymous()) + @identity.require(identity.in_group("admins")) def new(self): """Display delegate page""" client = self.get_ipaclient() @@ -45,7 +46,7 @@ class DelegationController(IPAController): return dict(form=delegate_form, delegate=delegate) @expose() - @identity.require(identity.not_anonymous()) + @identity.require(identity.in_group("admins")) def create(self, **kw): """Creates a new delegation""" self.restrict_post() @@ -63,11 +64,34 @@ class DelegationController(IPAController): tg_template='ipagui.templates.delegatenew') try: + aci_entry = client.get_aci_entry(aci_fields) + new_aci = ipa.aci.ACI() new_aci.name = kw.get('name') new_aci.source_group = kw.get('source_group_dn') new_aci.dest_group = kw.get('dest_group_dn') new_aci.attrs = kw.get('attrs') + if (new_aci.attrs, str): + new_aci.attrs = [new_aci.attrs] + + # Look for an existing ACI of the same name + aci_str_list = aci_entry.getValues('aci') + if aci_str_list is None: + aci_str_list = [] + if not(isinstance(aci_str_list,list) or isinstance(aci_str_list,tuple)): + aci_str_list = [aci_str_list] + + for aci_str in aci_str_list: + try: + old_aci = ipa.aci.ACI(aci_str) + if old_aci.name == new_aci.name: + turbogears.flash("Delgate add failed: a delegation of that name already exists") + return dict(form=delegate_form, delegate=kw, + tg_template='ipagui.templates.delegatenew') + except SyntaxError: + # ignore aci_str's that ACI can't parse + pass + # not pulling down existing aci attributes aci_entry = client.get_aci_entry(['dn']) @@ -75,7 +99,7 @@ class DelegationController(IPAController): client.update_entry(aci_entry) except ipaerror.IPAError, e: - turbogears.flash("Delgate add failed: " + str(e)) + turbogears.flash("Delgate add failed: " + str(e) + "<br/>" + e.detail[0]['desc']) return dict(form=delegate_form, delegate=kw, tg_template='ipagui.templates.delegatenew') @@ -83,7 +107,7 @@ class DelegationController(IPAController): raise turbogears.redirect('/delegate/list') @expose("ipagui.templates.delegateedit") - @identity.require(identity.not_anonymous()) + @identity.require(identity.in_group("admins")) def edit(self, acistr, tg_errors=None): """Display delegate page""" if tg_errors: @@ -105,12 +129,12 @@ class DelegationController(IPAController): return dict(form=delegate_form, delegate=delegate) except (SyntaxError, ipaerror.IPAError), e: - turbogears.flash("Delegation edit failed: " + str(e)) + turbogears.flash("Delegation edit failed: " + str(e) + "<br/>" + e.detail[0]['desc']) raise turbogears.redirect('/delegate/list') @expose() - @identity.require(identity.not_anonymous()) + @identity.require(identity.in_group("admins")) def update(self, **kw): """Display delegate page""" self.restrict_post() @@ -162,7 +186,7 @@ class DelegationController(IPAController): turbogears.flash("delegate updated") raise turbogears.redirect('/delegate/list') except (SyntaxError, ipaerror.IPAError), e: - turbogears.flash("Delegation update failed: " + str(e)) + turbogears.flash("Delegation update failed: " + str(e) + "<br/>" + e.detail[0]['desc']) return dict(form=delegate_form, delegate=kw, tg_template='ipagui.templates.delegateedit') @@ -175,7 +199,7 @@ class DelegationController(IPAController): try: aci_entry = client.get_aci_entry(aci_fields) except ipaerror.IPAError, e: - turbogears.flash("Delegation list failed: " + str(e)) + turbogears.flash("Delegation list failed: " + str(e) + "<br/>" + e.detail[0]['desc']) raise turbogears.redirect('/') aci_str_list = aci_entry.getValues('aci') @@ -194,6 +218,7 @@ class DelegationController(IPAController): pass group_dn_to_cn = ipa.aci.extract_group_cns(aci_list, client) + aci_list = sorted(aci_list, key=operator.itemgetter(0)) # The list page needs to display field labels, not raw # LDAP attributes for aci in aci_list: @@ -205,7 +230,7 @@ class DelegationController(IPAController): fields=ipagui.forms.delegate.DelegateFields()) @expose() - @identity.require(identity.not_anonymous()) + @identity.require(identity.in_group("admins")) def delete(self, acistr): """Display delegate page""" self.restrict_post() @@ -237,7 +262,7 @@ class DelegationController(IPAController): turbogears.flash("delegate deleted") raise turbogears.redirect('/delegate/list') except (SyntaxError, ipaerror.IPAError), e: - turbogears.flash("Delegation deletion failed: " + str(e)) + turbogears.flash("Delegation deletion failed: " + str(e) + "<br/>" + e.detail[0]['desc']) raise turbogears.redirect('/delegate/list') @expose("ipagui.templates.delegategroupsearch") diff --git a/ipa-server/ipa-gui/ipagui/subcontrollers/group.py b/ipa-server/ipa-gui/ipagui/subcontrollers/group.py index f0574a21c..dbcc77b9a 100644 --- a/ipa-server/ipa-gui/ipagui/subcontrollers/group.py +++ b/ipa-server/ipa-gui/ipagui/subcontrollers/group.py @@ -22,7 +22,7 @@ log = logging.getLogger(__name__) group_new_form = ipagui.forms.group.GroupNewForm() group_edit_form = ipagui.forms.group.GroupEditForm() -group_fields = ['*'] +group_fields = ['*', 'nsAccountLock'] class GroupController(IPAController): @@ -37,7 +37,7 @@ class GroupController(IPAController): raise turbogears.redirect("/group/list") @expose("ipagui.templates.groupnew") - @identity.require(identity.not_anonymous()) + @identity.require(identity.in_group("admins")) def new(self, tg_errors=None): """Displays the new group form""" if tg_errors: @@ -49,7 +49,7 @@ class GroupController(IPAController): return dict(form=group_new_form, group={}) @expose() - @identity.require(identity.not_anonymous()) + @identity.require(identity.in_group("admins")) def create(self, **kw): """Creates a new group""" self.restrict_post() @@ -75,13 +75,16 @@ class GroupController(IPAController): new_group.setValue('description', kw.get('description')) rv = client.add_group(new_group) + + if kw.get('nsAccountLock'): + client.mark_group_inactive(kw.get('cn')) except ipaerror.exception_for(ipaerror.LDAP_DUPLICATE): turbogears.flash("Group with name '%s' already exists" % kw.get('cn')) return dict(form=group_new_form, group=kw, tg_template='ipagui.templates.groupnew') except ipaerror.IPAError, e: - turbogears.flash("Group add failed: " + str(e) + "<br/>" + str(e.detail)) + turbogears.flash("Group add failed: " + str(e) + "<br/>" + e.detail[0]['desc']) return dict(form=group_new_form, group=kw, tg_template='ipagui.templates.groupnew') @@ -90,7 +93,11 @@ class GroupController(IPAController): # on any error, we redirect to the _edit_ group page. # this code does data setup, similar to groupedit() # - group = client.get_entry_by_cn(kw['cn'], group_fields) + if isinstance(kw['cn'], list): + cn0 = kw['cn'][0] + else: + cn0 = kw['cn'] + group = client.get_entry_by_cn(cn0, group_fields) group_dict = group.toDict() member_dicts = [] @@ -166,7 +173,7 @@ class GroupController(IPAController): @expose("ipagui.templates.groupedit") - @identity.require(identity.not_anonymous()) + @identity.require(identity.in_group("admins")) def edit(self, cn, tg_errors=None): """Displays the edit group form""" if tg_errors: @@ -204,20 +211,31 @@ class GroupController(IPAController): raise turbogears.redirect('/group/show', uid=cn) @expose() - @identity.require(identity.not_anonymous()) + @identity.require(identity.in_group("admins")) def update(self, **kw): """Updates an existing group""" self.restrict_post() client = self.get_ipaclient() if kw.get('submit') == 'Cancel Edit': + orig_group_dict = loads(b64decode(kw.get('group_orig'))) + # if cancelling need to use the original group because the one + # in kw may not exist yet. + cn = orig_group_dict.get('cn') + if (isinstance(cn,str)): + cn = [cn] turbogears.flash("Edit group cancelled") - raise turbogears.redirect('/group/show', cn=kw.get('cn')) + raise turbogears.redirect('/group/show', cn=cn[0]) + + if kw.get('editprotected') == '': + # if editprotected set these don't get sent in kw + orig_group_dict = loads(b64decode(kw.get('group_orig'))) + kw['cn'] = orig_group_dict['cn'] + kw['gidnumber'] = orig_group_dict['gidnumber'] # Decode the member data, in case we need to round trip member_dicts = loads(b64decode(kw.get('member_data'))) - tg_errors, kw = self.groupupdatevalidate(**kw) if tg_errors: turbogears.flash("There were validation errors.<br/>" + @@ -242,6 +260,20 @@ class GroupController(IPAController): if new_group.gidnumber != new_gid: group_modified = True new_group.setValue('gidnumber', new_gid) + else: + new_group.setValue('gidnumber', orig_group_dict.get('gidnumber')) + new_group.setValue('cn', orig_group_dict.get('cn')) + if new_group.cn != kw.get('cn'): + group_modified = True + new_group.setValue('cn', kw['cn']) + + if group_modified: + rv = client.update_group(new_group) + # + # If the group update succeeds, but below operations fail, we + if new_group.cn != kw.get('cn'): + group_modified = True + new_group.setValue('cn', kw['cn']) if group_modified: rv = client.update_group(new_group) @@ -252,10 +284,21 @@ class GroupController(IPAController): # kw['group_orig'] = b64encode(dumps(new_group.toDict())) except ipaerror.IPAError, e: - turbogears.flash("Group update failed: " + str(e)) + turbogears.flash("Group update failed: " + str(e) + "<br/>" + e.detail[0]['desc']) return dict(form=group_edit_form, group=kw, members=member_dicts, tg_template='ipagui.templates.groupedit') + if kw.get('nsAccountLock') == '': + kw['nsAccountLock'] = "false" + + modify_no_update = False + if kw.get('nsAccountLock') == "false" and new_group.getValues('nsaccountlock') == "true": + client.mark_group_active(kw.get('cn')) + modify_no_update = True + elif kw.get('nsAccountLock') == "true" and new_group.nsaccountlock != "true": + client.mark_group_inactive(kw.get('cn')) + modify_no_update = True + # # Add members # @@ -268,8 +311,9 @@ class GroupController(IPAController): failed_adds = client.add_members_to_group( utf8_encode_values(dnadds), new_group.dn) kw['dnadd'] = failed_adds + group_modified = True except ipaerror.IPAError, e: - turbogears.flash("Group update failed: " + str(e)) + turbogears.flash("Group update failed: " + str(e) + "<br/>" + e.detail[0]['desc']) return dict(form=group_edit_form, group=kw, members=member_dicts, tg_template='ipagui.templates.groupedit') @@ -285,8 +329,9 @@ class GroupController(IPAController): failed_dels = client.remove_members_from_group( utf8_encode_values(dndels), new_group.dn) kw['dndel'] = failed_dels + group_modified = True except ipaerror.IPAError, e: - turbogears.flash("Group update failed: " + str(e)) + turbogears.flash("Group update failed: " + str(e) + "<br/>" + e.detail[0]['desc']) return dict(form=group_edit_form, group=kw, members=member_dicts, tg_template='ipagui.templates.groupedit') @@ -308,8 +353,15 @@ class GroupController(IPAController): return dict(form=group_edit_form, group=kw, members=member_dicts, tg_template='ipagui.templates.groupedit') - turbogears.flash("%s updated!" % kw['cn']) - raise turbogears.redirect('/group/show', cn=kw['cn']) + if isinstance(kw['cn'], list): + cn0 = kw['cn'][0] + else: + cn0 = kw['cn'] + if group_modified == True or modify_no_update == True: + turbogears.flash("%s updated!" % cn0) + else: + turbogears.flash("No modifications requested.") + raise turbogears.redirect('/group/show', cn=cn0) @expose("ipagui.templates.grouplist") @@ -330,7 +382,7 @@ class GroupController(IPAController): turbogears.flash("These results are truncated.<br />" + "Please refine your search and try again.") except ipaerror.IPAError, e: - turbogears.flash("Find groups failed: " + str(e)) + turbogears.flash("Find groups failed: " + str(e) + "<br/>" + e.detail[0]['desc']) raise turbogears.redirect("/group/list") return dict(groups=groups, criteria=criteria, @@ -374,7 +426,7 @@ class GroupController(IPAController): turbogears.flash("group deleted") raise turbogears.redirect('/group/list') except (SyntaxError, ipaerror.IPAError), e: - turbogears.flash("Group deletion failed: " + str(e) + "<br/>" + str(e.detail)) + turbogears.flash("Group deletion failed: " + str(e) + "<br/>" + e.detail[0]['desc']) raise turbogears.redirect('/group/list') @validate(form=group_new_form) diff --git a/ipa-server/ipa-gui/ipagui/subcontrollers/ipapolicy.py b/ipa-server/ipa-gui/ipagui/subcontrollers/ipapolicy.py new file mode 100644 index 000000000..781ca35d4 --- /dev/null +++ b/ipa-server/ipa-gui/ipagui/subcontrollers/ipapolicy.py @@ -0,0 +1,168 @@ +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 ipa.entity +import ipagui.forms.ipapolicy + +import ldap.dn + +log = logging.getLogger(__name__) + +ipapolicy_edit_form = ipagui.forms.ipapolicy.IPAPolicyForm() + +class IPAPolicyController(IPAController): + + @expose() + @identity.require(identity.in_group("admins")) + def index(self): + raise turbogears.redirect("/ipapolicy/show") + + @expose("ipagui.templates.ipapolicyshow") + @identity.require(identity.in_group("admins")) + def show(self, tg_errors=None): + """Displays the one policy page""" + client = self.get_ipaclient() + config = client.get_ipa_config() + ipapolicy = config.toDict() + + ppolicy = client.get_password_policy() + password = ppolicy.toDict() + + return dict(ipapolicy=ipapolicy,password=password,fields=ipagui.forms.ipapolicy.IPAPolicyFields()) + + @expose("ipagui.templates.ipapolicyedit") + @identity.require(identity.in_group("admins")) + def edit(self, tg_errors=None): + """Displays the edit IPA policy form""" + if tg_errors: + turbogears.flash("There were validation errors.<br/>" + + "Please see the messages below for details.") + + try: + client = self.get_ipaclient() + config = client.get_ipa_config() + ipapolicy_dict = config.toDict() + + ppolicy = client.get_password_policy() + password_dict = ppolicy.toDict() + + # store a copy of the original policy for the update later + ipapolicy_data = b64encode(dumps(ipapolicy_dict)) + ipapolicy_dict['ipapolicy_orig'] = ipapolicy_data + + # store a copy of the original policy for the update later + password_data = b64encode(dumps(password_dict)) + password_dict['password_orig'] = password_data + + # Combine the 2 dicts to make the form easier + ipapolicy_dict.update(password_dict) + + return dict(form=ipapolicy_edit_form, ipapolicy=ipapolicy_dict) + except ipaerror.IPAError, e: + turbogears.flash("IPA Policy edit failed: " + str(e) + "<br/>" + str(e.detail)) + raise turbogears.redirect('/ipapolicy/show') + + + @expose() + @identity.require(identity.in_group("admins")) + def update(self, **kw): + """Display delegate page""" + self.restrict_post() + client = self.get_ipaclient() + + if kw.get('submit', '').startswith('Cancel'): + turbogears.flash("Edit policy cancelled") + raise turbogears.redirect('/ipapolicy/show') + + tg_errors, kw = self.ipapolicyupdatevalidate(**kw) + if tg_errors: + turbogears.flash("There were validation errors.<br/>" + + "Please see the messages below for details.") + return dict(form=ipapolicy_edit_form, ipapolicy=kw, + tg_template='ipagui.templates.ipapolicyedit') + + policy_modified = False + password_modified = False + + try: + orig_ipapolicy_dict = loads(b64decode(kw.get('ipapolicy_orig'))) + orig_password_dict = loads(b64decode(kw.get('password_orig'))) + + new_ipapolicy = ipa.entity.Entity(orig_ipapolicy_dict) + new_password = ipa.entity.Entity(orig_password_dict) + + if str(new_ipapolicy.ipasearchtimelimit) != str(kw.get('ipasearchtimelimit')): + policy_modified = True + new_ipapolicy.setValue('ipasearchtimelimit', kw.get('ipasearchtimelimit')) + if str(new_ipapolicy.ipasearchrecordslimit) != str(kw.get('ipasearchrecordslimit')): + policy_modified = True + new_ipapolicy.setValue('ipasearchrecordslimit', kw.get('ipasearchrecordslimit')) + if new_ipapolicy.ipausersearchfields != kw.get('ipausersearchfields'): + policy_modified = True + new_ipapolicy.setValue('ipausersearchfields', kw.get('ipausersearchfields')) + if new_ipapolicy.ipagroupsearchfields != kw.get('ipagroupsearchfields'): + policy_modified = True + new_ipapolicy.setValue('ipagroupsearchfields', kw.get('ipagroupsearchfields')) + if str(new_ipapolicy.ipapwdexpadvnotify) != str(kw.get('ipapwdexpadvnotify')): + policy_modified = True + new_ipapolicy.setValue('ipapwdexpadvnotify', kw.get('ipapwdexpadvnotify')) + if str(new_ipapolicy.ipamaxusernamelength) != str(kw.get('ipamaxusernamelength')): + policy_modified = True + new_ipapolicy.setValue('ipamaxusernamelength', kw.get('ipamaxusernamelength')) + if new_ipapolicy.ipahomesrootdir != kw.get('ipahomesrootdir'): + policy_modified = True + new_ipapolicy.setValue('ipahomesrootdir', kw.get('ipahomesrootdir')) + if new_ipapolicy.ipadefaultloginshell != kw.get('ipadefaultloginshell'): + policy_modified = True + new_ipapolicy.setValue('ipadefaultloginshell', kw.get('ipadefaultloginshell')) + if new_ipapolicy.ipadefaultprimarygroup != kw.get('ipadefaultprimarygroup'): + policy_modified = True + new_ipapolicy.setValue('ipadefaultprimarygroup', kw.get('ipadefaultprimarygroup')) + + if policy_modified: + rv = client.update_ipa_config(new_ipapolicy) + + # Now check the password policy for updates + if str(new_password.krbmaxpwdlife) != str(kw.get('krbmaxpwdlife')): + password_modified = True + new_password.setValue('krbmaxpwdlife', str(kw.get('krbmaxpwdlife'))) + if str(new_password.krbminpwdlife) != str(kw.get('krbminpwdlife')): + password_modified = True + new_password.setValue('krbminpwdlife', str(kw.get('krbminpwdlife'))) + if str(new_password.krbpwdhistorylength) != str(kw.get('krbpwdhistorylength')): + password_modified = True + new_password.setValue('krbpwdhistorylength', str(kw.get('krbpwdhistorylength'))) + if str(new_password.krbpwdmindiffchars) != str(kw.get('krbpwdmindiffchars')): + password_modified = True + new_password.setValue('krbpwdmindiffchars', str(kw.get('krbpwdmindiffchars'))) + if str(new_password.krbpwdminlength) != str(kw.get('krbpwdminlength')): + password_modified = True + new_password.setValue('krbpwdminlength', str(kw.get('krbpwdminlength'))) + if password_modified: + rv = client.update_password_policy(new_password) + + turbogears.flash("IPA Policy updated") + raise turbogears.redirect('/ipapolicy/show') + except ipaerror.IPAError, e: + turbogears.flash("Policy update failed: " + str(e) + e.detail[0]['desc']) + return dict(form=ipapolicy_edit_form, ipapolicy=kw, + tg_template='ipagui.templates.ipapolicyedit') + + @validate(form=ipapolicy_edit_form) + @identity.require(identity.not_anonymous()) + def ipapolicyupdatevalidate(self, tg_errors=None, **kw): + return tg_errors, kw diff --git a/ipa-server/ipa-gui/ipagui/subcontrollers/policy.py b/ipa-server/ipa-gui/ipagui/subcontrollers/policy.py new file mode 100644 index 000000000..1f2e45876 --- /dev/null +++ b/ipa-server/ipa-gui/ipagui/subcontrollers/policy.py @@ -0,0 +1,32 @@ +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 ldap.dn + +log = logging.getLogger(__name__) + +class PolicyController(IPAController): + + @expose("ipagui.templates.policyindex") + @identity.require(identity.in_group("admins")) + def index(self, tg_errors=None): + """Displays the one policy page""" + + # TODO: return a dict of the items and URLs to display on + # Manage Policy + return dict() diff --git a/ipa-server/ipa-gui/ipagui/subcontrollers/user.py b/ipa-server/ipa-gui/ipagui/subcontrollers/user.py index d328052b1..39343b595 100644 --- a/ipa-server/ipa-gui/ipagui/subcontrollers/user.py +++ b/ipa-server/ipa-gui/ipagui/subcontrollers/user.py @@ -34,26 +34,48 @@ class UserController(IPAController): def __init__(self, *args, **kw): super(UserController,self).__init__(*args, **kw) - self.load_custom_fields() +# self.load_custom_fields() def load_custom_fields(self): - # client = self.get_ipaclient() - # schema = client.get_user_custom_schema() - schema = [ - { 'label': 'See Also', - 'field': 'seeAlso', - 'required': 'true', } , - { 'label': 'O O O', - 'field': 'o', - 'required': 'false', } , - ] + + client = self.get_ipaclient() + schema = client.get_custom_fields() + + # FIXME: Don't load from LDAP every single time it is called + + # FIXME: Is removing the attributes on the fly thread-safe? Do we + # need to lock here? for s in schema: required=False - if (s['required'] == "true"): + if (s['required'].lower() == "true"): required=True field = widgets.TextField(name=s['field'],label=s['label']) validator = validators.String(not_empty=required) + # Don't allow dupes on the new form + try: + for i in range(len(user_new_form.custom_fields)): + if user_new_form.custom_fields[i].name == s['field']: + user_new_form.custom_fields.pop(i) + except: + pass + + # Don't allow dupes on the edit form + try: + for i in range(len(user_edit_form.custom_fields)): + if user_edit_form.custom_fields[i].name == s['field']: + user_edit_form.custom_fields.pop(i) + except: + pass + + # Don't allow dupes in the list of user fields + try: + for i in range(len(ipagui.forms.user.UserFields.custom_fields)): + if ipagui.forms.user.UserFields.custom_fields[i].name == s['field']: + ipagui.forms.user.UserFields.custom_fields.pop(i) + except: + pass + ipagui.forms.user.UserFields.custom_fields.append(field) user_new_form.custom_fields.append(field) user_edit_form.custom_fields.append(field) @@ -61,15 +83,45 @@ class UserController(IPAController): user_new_form.validator.add_field(s['field'], validator) user_edit_form.validator.add_field(s['field'], validator) + def setup_mv_fields(self, field, fieldname): + """Given a field (must be a list) and field name, convert that + field into a list of dictionaries of the form: + [ { fieldname : v1}, { fieldname : v2 }, .. ] + + This is how we pre-fill values for multi-valued fields. + """ + mvlist = [] + if field is not None: + for v in field: + mvlist.append({ fieldname : v } ) + else: + # We need to return an empty value so something can be + # displayed on the edit page. Otherwise only an Add link + # will show, not an empty field. + mvlist.append({ fieldname : '' } ) + return mvlist + + def fix_incoming_fields(self, fields, fieldname, multifieldname): + """This is called by the update() function. It takes the incoming + list of dictionaries and converts it into back into the original + field, then removes the multiple field. + """ + fields[fieldname] = [] + for i in range(len(fields[multifieldname])): + fields[fieldname].append(fields[multifieldname][i][fieldname]) + del(fields[multifieldname]) + + return fields @expose() def index(self): raise turbogears.redirect("/user/list") @expose("ipagui.templates.usernew") - @identity.require(identity.not_anonymous()) + @identity.require(identity.in_any_group("admins","editors")) def new(self, tg_errors=None): """Displays the new user form""" + self.load_custom_fields() if tg_errors: turbogears.flash("There were validation errors.<br/>" + "Please see the messages below for details.") @@ -77,7 +129,7 @@ class UserController(IPAController): return dict(form=user_new_form, user={}) @expose() - @identity.require(identity.not_anonymous()) + @identity.require(identity.in_any_group("admins","editors")) def create(self, **kw): """Creates a new user""" self.restrict_post() @@ -88,6 +140,15 @@ class UserController(IPAController): raise turbogears.redirect('/user/list') tg_errors, kw = self.usercreatevalidate(**kw) + + # Fix incoming multi-valued fields we created for the form + kw = self.fix_incoming_fields(kw, 'cn', 'cns') + kw = self.fix_incoming_fields(kw, 'telephonenumber', 'telephonenumbers') + kw = self.fix_incoming_fields(kw, 'facsimiletelephonenumber', 'facsimiletelephonenumbers') + kw = self.fix_incoming_fields(kw, 'mobile', 'mobiles') + kw = self.fix_incoming_fields(kw, 'pager', 'pagers') + kw = self.fix_incoming_fields(kw, 'homephone', 'homephones') + if tg_errors: turbogears.flash("There were validation errors.<br/>" + "Please see the messages below for details.") @@ -136,21 +197,21 @@ class UserController(IPAController): new_user.setValue('carlicense', kw.get('carlicense')) new_user.setValue('labeleduri', kw.get('labeleduri')) - if kw.get('nsAccountLock'): - new_user.setValue('nsAccountLock', 'true') - for custom_field in user_new_form.custom_fields: new_user.setValue(custom_field.name, kw.get(custom_field.name, '')) rv = client.add_user(new_user) + + if kw.get('nsAccountLock'): + client.mark_user_inactive(kw.get('uid')) except ipaerror.exception_for(ipaerror.LDAP_DUPLICATE): - turbogears.flash("Person with login '%s' already exists" % + turbogears.flash("User with login '%s' already exists" % kw.get('uid')) return dict(form=user_new_form, user=kw, tg_template='ipagui.templates.usernew') except ipaerror.IPAError, e: - turbogears.flash("User add failed: " + str(e)) + turbogears.flash("User add failed: " + str(e) + "<br/>" + e.detail[0]['desc']) return dict(form=user_new_form, user=kw, tg_template='ipagui.templates.usernew') @@ -181,7 +242,7 @@ class UserController(IPAController): try: client.modifyPassword(user_dict['krbprincipalname'], "", kw.get('userpassword')) except ipaerror.IPAError, e: - message = "Person successfully created.<br />" + message = "User successfully created.<br />" message += "There was an error setting the password.<br />" turbogears.flash(message) return dict(form=user_edit_form, user=user_dict, @@ -204,7 +265,7 @@ class UserController(IPAController): failed_adds = dnadds if len(failed_adds) > 0: - message = "Person successfully created.<br />" + message = "User successfully created.<br />" message += "There was an error adding groups.<br />" message += "Failures have been preserved in the add/remove lists." turbogears.flash(message) @@ -243,6 +304,7 @@ class UserController(IPAController): @identity.require(identity.not_anonymous()) def edit(self, uid=None, principal=None, tg_errors=None): """Displays the edit user form""" + self.load_custom_fields() if tg_errors: turbogears.flash("There were validation errors.<br/>" + "Please see the messages below for details.") @@ -259,6 +321,32 @@ class UserController(IPAController): turbogears.flash("User edit failed: No uid or principal provided") raise turbogears.redirect('/') user_dict = user.toDict() + + # Load potential multi-valued fields + if isinstance(user_dict['cn'], str): + user_dict['cn'] = [user_dict['cn']] + user_dict['cns'] = self.setup_mv_fields(user_dict['cn'], 'cn') + + if isinstance(user_dict.get('telephonenumber',''), str): + user_dict['telephonenumber'] = [user_dict.get('telephonenumber'),''] + user_dict['telephonenumbers'] = self.setup_mv_fields(user_dict.get('telephonenumber'), 'telephonenumber') + + if isinstance(user_dict.get('facsimiletelephonenumber',''), str): + user_dict['facsimiletelephonenumber'] = [user_dict.get('facsimiletelephonenumber'),''] + user_dict['facsimiletelephonenumbers'] = self.setup_mv_fields(user_dict.get('facsimiletelephonenumber'), 'facsimiletelephonenumber') + + if isinstance(user_dict.get('mobile',''), str): + user_dict['mobile'] = [user_dict.get('mobile'),''] + user_dict['mobiles'] = self.setup_mv_fields(user_dict.get('mobile'), 'mobile') + + if isinstance(user_dict.get('pager',''), str): + user_dict['pager'] = [user_dict.get('pager'),''] + user_dict['pagers'] = self.setup_mv_fields(user_dict.get('pager'), 'pager') + + if isinstance(user_dict.get('homephone',''), str): + user_dict['homephone'] = [user_dict.get('homephone'),''] + user_dict['homephones'] = self.setup_mv_fields(user_dict.get('homephone'), 'homephone') + # Edit shouldn't fill in the password field. if user_dict.has_key('userpassword'): del(user_dict['userpassword']) @@ -300,7 +388,7 @@ class UserController(IPAController): except ipaerror.IPAError, e: if uid is None: uid = principal - turbogears.flash("User edit failed: " + str(e)) + turbogears.flash("User edit failed: " + str(e) + "<br/>" + e.detail[0]['desc']) raise turbogears.redirect('/user/show', uid=uid) @expose() @@ -314,6 +402,23 @@ class UserController(IPAController): turbogears.flash("Edit user cancelled") raise turbogears.redirect('/user/show', uid=kw.get('uid')) + # Fix incoming multi-valued fields we created for the form + kw = self.fix_incoming_fields(kw, 'cn', 'cns') + kw = self.fix_incoming_fields(kw, 'telephonenumber', 'telephonenumbers') + kw = self.fix_incoming_fields(kw, 'facsimiletelephonenumber', 'facsimiletelephonenumbers') + kw = self.fix_incoming_fields(kw, 'mobile', 'mobiles') + kw = self.fix_incoming_fields(kw, 'pager', 'pagers') + kw = self.fix_incoming_fields(kw, 'homephone', 'homephones') + + # admins and editors can update anybody. A user can only update + # themselves. We need this check because it is very easy to guess + # the edit URI. + if ((not 'admins' in turbogears.identity.current.groups and + not 'editors' in turbogears.identity.current.groups) and + (kw.get('uid') != turbogears.identity.current.display_name)): + turbogears.flash("You do not have permission to update this user.") + raise turbogears.redirect('/user/show', uid=kw.get('uid')) + # Decode the group data, in case we need to round trip user_groups_dicts = loads(b64decode(kw.get('user_groups_data'))) @@ -334,6 +439,14 @@ class UserController(IPAController): try: orig_user_dict = loads(b64decode(kw.get('user_orig'))) + # remove multi-valued fields we created for the form + del(orig_user_dict['cns']) + del(orig_user_dict['telephonenumbers']) + del(orig_user_dict['facsimiletelephonenumbers']) + del(orig_user_dict['mobiles']) + del(orig_user_dict['pagers']) + del(orig_user_dict['homephones']) + new_user = ipa.user.User(orig_user_dict) new_user.setValue('title', kw.get('title')) new_user.setValue('givenname', kw.get('givenname')) @@ -369,12 +482,6 @@ class UserController(IPAController): new_user.setValue('carlicense', kw.get('carlicense')) new_user.setValue('labeleduri', kw.get('labeleduri')) - - if kw.get('nsAccountLock'): - new_user.setValue('nsAccountLock', 'true') - else: - new_user.setValue('nsAccountLock', None) - if kw.get('editprotected') == 'true': if kw.get('userpassword'): password_change = True @@ -400,7 +507,7 @@ class UserController(IPAController): # too much work to figure out unless someone really screams pass except ipaerror.IPAError, e: - turbogears.flash("User update failed: " + str(e)) + turbogears.flash("User update failed: " + str(e) + "<br/>" + e.detail[0]['desc']) return dict(form=user_edit_form, user=kw, user_groups=user_groups_dicts, tg_template='ipagui.templates.useredit') @@ -412,7 +519,7 @@ class UserController(IPAController): if password_change: rv = client.modifyPassword(kw['krbprincipalname'], "", kw.get('userpassword')) except ipaerror.IPAError, e: - turbogears.flash("User password change failed: " + str(e)) + turbogears.flash("User password change failed: " + str(e) + "<br/>" + e.detail[0]['desc']) return dict(form=user_edit_form, user=kw, user_groups=user_groups_dicts, tg_template='ipagui.templates.useredit') @@ -459,6 +566,20 @@ class UserController(IPAController): user_groups=user_groups_dicts, tg_template='ipagui.templates.useredit') + if kw.get('nsAccountLock') == '': + kw['nsAccountLock'] = "false" + + try: + if kw.get('nsAccountLock') == "false" and new_user.getValues('nsaccountlock') == "true": + client.mark_user_active(kw.get('uid')) + elif kw.get('nsAccountLock') == "true" and new_user.nsaccountlock != "true": + client.mark_user_inactive(kw.get('uid')) + except ipaerror.IPAError, e: + turbogears.flash("User status change failed: " + str(e) + "<br/>" + e.detail[0]['desc']) + return dict(form=user_edit_form, user=kw, + user_groups=user_groups_dicts, + tg_template='ipagui.templates.useredit') + turbogears.flash("%s updated!" % kw['uid']) raise turbogears.redirect('/user/show', uid=kw['uid']) @@ -481,7 +602,7 @@ class UserController(IPAController): turbogears.flash("These results are truncated.<br />" + "Please refine your search and try again.") except ipaerror.IPAError, e: - turbogears.flash("User list failed: " + str(e)) + turbogears.flash("User list failed: " + str(e) + "<br/>" + e.detail[0]['desc']) raise turbogears.redirect("/user/list") return dict(users=users, uid=uid, fields=ipagui.forms.user.UserFields()) @@ -492,6 +613,7 @@ class UserController(IPAController): def show(self, uid): """Retrieve a single user for display""" client = self.get_ipaclient() + self.load_custom_fields() try: user = client.get_user_by_uid(uid, user_fields) @@ -523,7 +645,7 @@ class UserController(IPAController): user_groups=user_groups, user_reports=user_reports, user_manager=user_manager, user_secretary=user_secretary) except ipaerror.IPAError, e: - turbogears.flash("User show failed: " + str(e)) + turbogears.flash("User show failed: " + str(e) + "<br/>" + e.detail[0]['desc']) raise turbogears.redirect("/") @expose() @@ -539,7 +661,7 @@ class UserController(IPAController): turbogears.flash("user deleted") raise turbogears.redirect('/user/list') except (SyntaxError, ipaerror.IPAError), e: - turbogears.flash("User deletion failed: " + str(e)) + turbogears.flash("User deletion failed: " + str(e) + "<br/>" + e.detail[0]['desc']) raise turbogears.redirect('/user/list') @validate(form=user_new_form) @@ -661,7 +783,7 @@ class UserController(IPAController): users_counter = users[0] users = users[1:] except ipaerror.IPAError, e: - turbogears.flash("search failed: " + str(e)) + turbogears.flash("search failed: " + str(e) + "<br/>" + e.detail[0]['desc']) return dict(users=users, criteria=criteria, which_select=kw.get('which_select'), diff --git a/ipa-server/ipa-gui/ipagui/templates/Makefile.am b/ipa-server/ipa-gui/ipagui/templates/Makefile.am index 18db5fffc..6626ad8c2 100644 --- a/ipa-server/ipa-gui/ipagui/templates/Makefile.am +++ b/ipa-server/ipa-gui/ipagui/templates/Makefile.am @@ -20,8 +20,13 @@ app_DATA = \ groupnewform.kid \ groupnew.kid \ groupshow.kid \ + ipapolicyeditform.kid \ + ipapolicyedit.kid \ + ipapolicyshow.kid \ loginfailed.kid \ master.kid \ + policyindex.kid \ + policylayout.kid \ usereditform.kid \ useredit.kid \ userlayout.kid \ diff --git a/ipa-server/ipa-gui/ipagui/templates/groupeditform.kid b/ipa-server/ipa-gui/ipagui/templates/groupeditform.kid index cab585fcc..6a5c5adb8 100644 --- a/ipa-server/ipa-gui/ipagui/templates/groupeditform.kid +++ b/ipa-server/ipa-gui/ipagui/templates/groupeditform.kid @@ -25,17 +25,22 @@ from ipagui.helpers import ipahelper <script type="text/javascript" charset="utf-8" src="${tg.url('/static/javascript/dynamicedit.js')}"></script> + <script type="text/javascript" charset="utf-8" + src="${tg.url('/tg_widgets/tg_expanding_form_widget/javascript/expanding_form.js')}"></script> <?python searchurl = tg.url('/group/edit_search') ?> <script type="text/javascript"> function toggleProtectedFields(checkbox) { var gidnumberField = $('form_gidnumber'); + var cnField = $('form_cn'); if (checkbox.checked) { gidnumberField.disabled = false; + cnField.disabled = false; $('form_editprotected').value = 'true'; } else { gidnumberField.disabled = true; + cnField.disabled = true; $('form_editprotected').value = ''; } } @@ -70,11 +75,9 @@ from ipagui.helpers import ipahelper py:content="group_fields.cn.label" />: </th> <td> - <!-- <span py:replace="group_fields.cn.display(value_for(group_fields.cn))" /> + <span py:replace="group_fields.cn.display(value_for(group_fields.cn))" /> <span py:if="tg.errors.get('cn')" class="fielderror" - py:content="tg.errors.get('cn')" /> --> - ${value_for(group_fields.cn)} - + py:content="tg.errors.get('cn')" /> </td> </tr> @@ -88,6 +91,9 @@ from ipagui.helpers import ipahelper <span py:if="tg.errors.get('description')" class="fielderror" py:content="tg.errors.get('description')" /> + <script type="text/javascript"> + document.getElementById('form_cn').disabled = true; + </script> </td> </tr> @@ -106,6 +112,16 @@ from ipagui.helpers import ipahelper </script> </td> </tr> + <tr> + <th> + <label class="fieldlabel" for="${group_fields.nsAccountLock.field_id}" py:content="group_fields.nsAccountLock.label" />: + </th> + <td> + <span py:replace="group_fields.nsAccountLock.display(value_for(group_fields.nsAccountLock))" /> + <span py:if="tg.errors.get('nsAccountLock')" class="fielderror" + py:content="tg.errors.get('nsAccountLock')" /> + </td> + </tr> </table> <div> @@ -160,6 +176,7 @@ from ipagui.helpers import ipahelper div_counter = div_counter + 1 ?> </div> + <!-- a space here to prevent an empty div --> </div> </div> diff --git a/ipa-server/ipa-gui/ipagui/templates/grouplist.kid b/ipa-server/ipa-gui/ipagui/templates/grouplist.kid index 9f9bc4840..9489b3744 100644 --- a/ipa-server/ipa-gui/ipagui/templates/grouplist.kid +++ b/ipa-server/ipa-gui/ipagui/templates/grouplist.kid @@ -20,7 +20,7 @@ </div> <div py:if='(groups != None) and (len(groups) > 0)'> <h2>${len(groups)} results returned:</h2> - <table id="resultstable" class="details sortable resizable"> + <table id="resultstable" class="details sortable resizable" cellspacing="0"> <thead> <tr> <th> @@ -32,7 +32,15 @@ </tr> </thead> <tbody> - <tr py:for="group in groups"> + <tr py:for="group in groups" py:if="group.nsAccountLock != 'true'"> + <td> + <a href="${tg.url('/group/show',cn=group.cn)}">${group.cn}</a> + </td> + <td> + ${group.description} + </td> + </tr> + <tr id="inactive" py:for="group in groups" py:if="group.nsAccountLock == 'true'"> <td> <a href="${tg.url('/group/show',cn=group.cn)}">${group.cn}</a> </td> diff --git a/ipa-server/ipa-gui/ipagui/templates/groupshow.kid b/ipa-server/ipa-gui/ipagui/templates/groupshow.kid index 7a66acdbe..8713742d5 100644 --- a/ipa-server/ipa-gui/ipagui/templates/groupshow.kid +++ b/ipa-server/ipa-gui/ipagui/templates/groupshow.kid @@ -7,12 +7,17 @@ </head> <body> <?python -edit_url = tg.url('/group/edit', cn=group.get('cn')) +cn = group.get('cn') +if isinstance(cn, list): + cn = cn[0] +edit_url = tg.url('/group/edit', cn=cn) +from ipagui.helpers import userhelper ?> <div id="details"> <h1>View Group</h1> - <input class="submitbutton" type="button" + <input py:if="'editors' in tg.identity.groups or 'admins' in tg.identity.groups" + class="submitbutton" type="button" onclick="document.location.href='${edit_url}'" value="Edit Group" /> @@ -38,6 +43,12 @@ edit_url = tg.url('/group/edit', cn=group.get('cn')) </th> <td>${group.get("gidnumber")}</td> </tr> + <tr> + <th> + <label class="fieldlabel" py:content="fields.nsAccountLock.label" />: + </th> + <td>${userhelper.account_status_display(group.get("nsAccountLock"))}</td> + </tr> </table> <h2 class="formsection">Group Members</h2> @@ -51,7 +62,10 @@ edit_url = tg.url('/group/edit', cn=group.get('cn')) member_type = "user" view_url = tg.url('/user/show', uid=member_uid) else: - member_cn = "%s" % member.get('cn') + mem = member.get('cn') + if isinstance(mem, list): + mem = mem[0] + member_cn = "%s" % mem member_desc = "[group]" member_type = "group" view_url = tg.url('/group/show', cn=member_cn) @@ -70,7 +84,8 @@ edit_url = tg.url('/group/edit', cn=group.get('cn')) <br/> <hr /> - <input class="submitbutton" type="button" + <input py:if="'editors' in tg.identity.groups or 'admins' in tg.identity.groups" + class="submitbutton" type="button" onclick="document.location.href='${edit_url}'" value="Edit Group" /> </div> diff --git a/ipa-server/ipa-gui/ipagui/templates/ipapolicyedit.kid b/ipa-server/ipa-gui/ipagui/templates/ipapolicyedit.kid new file mode 100644 index 000000000..5987cc40a --- /dev/null +++ b/ipa-server/ipa-gui/ipagui/templates/ipapolicyedit.kid @@ -0,0 +1,15 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#" + py:extends="'policylayout.kid'"> +<head> + <meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/> + <title>Edit IPA Policy</title> +</head> +<body> + <div> + <h1>Edit IPA Policy</h1> + + ${form.display(action=tg.url('/ipapolicy/update'), value=ipapolicy)} +</div> +</body> +</html> diff --git a/ipa-server/ipa-gui/ipagui/templates/ipapolicyeditform.kid b/ipa-server/ipa-gui/ipagui/templates/ipapolicyeditform.kid new file mode 100644 index 000000000..106657636 --- /dev/null +++ b/ipa-server/ipa-gui/ipagui/templates/ipapolicyeditform.kid @@ -0,0 +1,176 @@ +<div xmlns:py="http://purl.org/kid/ns#" + class="simpleroster"> + + <form action="${action}" name="${name}" method="${method}" class="tableform" + onsubmit="preSubmit()" > + + <input type="submit" class="submitbutton" name="submit" + value="Update Policy"/> + <input type="submit" class="submitbutton" name="submit" + value="Cancel Edit" /> + +<?python +from ipagui.helpers import ipahelper +?> + + <script type="text/javascript" charset="utf-8" + src="${tg.url('/static/javascript/dynamicedit.js')}"></script> + + <div py:for="field in hidden_fields" + py:replace="field.display(value_for(field), **params_for(field))" + /> + + <h2 class="formsection">Search</h2> + <table class="formtable" cellpadding="2" cellspacing="0" border="0"> + <tr> + <th> + <label class="fieldlabel" py:content="ipapolicy_fields.ipasearchtimelimit.label" />: + </th> + <td> + <span py:replace="ipapolicy_fields.ipasearchtimelimit.display(value_for(ipapolicy_fields.ipasearchtimelimit))" /> + <span py:if="tg.errors.get('ipasearchtimelimit')" class="fielderror" + py:content="tg.errors.get('ipasearchtimelimit')" /> + </td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="ipapolicy_fields.ipasearchrecordslimit.label" />: + </th> + <td> + <span py:replace="ipapolicy_fields.ipasearchrecordslimit.display(value_for(ipapolicy_fields.ipasearchrecordslimit))" /> + <span py:if="tg.errors.get('ipasearchrecordslimit')" class="fielderror" + py:content="tg.errors.get('ipasearchrecordslimit')" /> + </td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="ipapolicy_fields.ipausersearchfields.label" />: + </th> + <td> + <span py:replace="ipapolicy_fields.ipausersearchfields.display(value_for(ipapolicy_fields.ipausersearchfields))" /> + <span py:if="tg.errors.get('ipausersearchfields')" class="fielderror" + py:content="tg.errors.get('ipausersearchfields')" /> + </td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="ipapolicy_fields.ipagroupsearchfields.label" />: + </th> + <td> + <span py:replace="ipapolicy_fields.ipagroupsearchfields.display(value_for(ipapolicy_fields.ipagroupsearchfields))" /> + <span py:if="tg.errors.get('ipagroupsearchfields')" class="fielderror" + py:content="tg.errors.get('ipagroupsearchfields')" /> + </td> + </tr> + </table> + + <h2 class="formsection">Password Policy</h2> + <table class="formtable" cellpadding="2" cellspacing="0" border="0"> + <tr> + <th> + <label class="fieldlabel" py:content="ipapolicy_fields.ipapwdexpadvnotify.label" />: + </th> + <td> + <span py:replace="ipapolicy_fields.ipapwdexpadvnotify.display(value_for(ipapolicy_fields.ipapwdexpadvnotify))" /> + <span py:if="tg.errors.get('ipapwdexpadvnotify')" class="fielderror" + py:content="tg.errors.get('ipapwdexpadvnotify')" /> + </td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="ipapolicy_fields.krbminpwdlife.label" />: + </th> + <td> + <span py:replace="ipapolicy_fields.krbminpwdlife.display(value_for(ipapolicy_fields.krbminpwdlife))" /> + <span py:if="tg.errors.get('krbminpwdlife')" class="fielderror" + py:content="tg.errors.get('krbminpwdlife')" /> + </td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="ipapolicy_fields.krbmaxpwdlife.label" />: + </th> + <td> + <span py:replace="ipapolicy_fields.krbmaxpwdlife.display(value_for(ipapolicy_fields.krbmaxpwdlife))" /> + <span py:if="tg.errors.get('krbmaxpwdlife')" class="fielderror" + py:content="tg.errors.get('krbmaxpwdlife')" /> + </td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="ipapolicy_fields.krbpwdmindiffchars.label" />: + </th> + <td> + <span py:replace="ipapolicy_fields.krbpwdmindiffchars.display(value_for(ipapolicy_fields.krbpwdmindiffchars))" /> + <span py:if="tg.errors.get('krbpwdmindiffchars')" class="fielderror" + py:content="tg.errors.get('krbpwdmindiffchars')" /> + </td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="ipapolicy_fields.krbpwdminlength.label" />: + </th> + <td> + <span py:replace="ipapolicy_fields.krbpwdminlength.display(value_for(ipapolicy_fields.krbpwdminlength))" /> + <span py:if="tg.errors.get('krbpwdminlength')" class="fielderror" + py:content="tg.errors.get('krbpwdminlength')" /> + </td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="ipapolicy_fields.krbpwdhistorylength.label" />: + </th> + <td> + <span py:replace="ipapolicy_fields.krbpwdhistorylength.display(value_for(ipapolicy_fields.krbpwdhistorylength))" /> + <span py:if="tg.errors.get('krbpwdhistorylength')" class="fielderror" + py:content="tg.errors.get('krbpwdhistorylength')" /> + </td> + </tr> + </table> + + <h2 class="formsection">User Settings</h2> + <table class="formtable" cellpadding="2" cellspacing="0" border="0"> + <tr> + <th> + <label class="fieldlabel" py:content="ipapolicy_fields.ipamaxusernamelength.label" />: + </th> + <td> + <span py:replace="ipapolicy_fields.ipamaxusernamelength.display(value_for(ipapolicy_fields.ipamaxusernamelength))" /> + <span py:if="tg.errors.get('ipamaxusernamelength')" class="fielderror" + py:content="tg.errors.get('ipamaxusernamelength')" /> + </td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="ipapolicy_fields.ipahomesrootdir.label" />: + </th> + <td> + <span py:replace="ipapolicy_fields.ipahomesrootdir.display(value_for(ipapolicy_fields.ipahomesrootdir))" /> + <span py:if="tg.errors.get('ipahomesrootdir')" class="fielderror" + py:content="tg.errors.get('ipahomesrootdir')" /> + </td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="ipapolicy_fields.ipadefaultloginshell.label" />: + </th> + <td> + <span py:replace="ipapolicy_fields.ipadefaultloginshell.display(value_for(ipapolicy_fields.ipadefaultloginshell))" /> + <span py:if="tg.errors.get('ipadefaultloginshell')" class="fielderror" + py:content="tg.errors.get('ipadefaultloginshell')" /> + </td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="ipapolicy_fields.ipadefaultprimarygroup.label" />: + </th> + <td> + <span py:replace="ipapolicy_fields.ipadefaultprimarygroup.display(value_for(ipapolicy_fields.ipadefaultprimarygroup))" /> + <span py:if="tg.errors.get('ipadefaultprimarygroup')" class="fielderror" + py:content="tg.errors.get('ipadefaultprimarygroup')" /> + </td> + </tr> + </table> + </form> + +</div> diff --git a/ipa-server/ipa-gui/ipagui/templates/ipapolicyshow.kid b/ipa-server/ipa-gui/ipagui/templates/ipapolicyshow.kid new file mode 100644 index 000000000..089fb494e --- /dev/null +++ b/ipa-server/ipa-gui/ipagui/templates/ipapolicyshow.kid @@ -0,0 +1,120 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#" + py:extends="'policylayout.kid'"> +<head> +<meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/> +<title>Manage IPA Policy</title> +</head> +<body> + +<?python +from ipagui.helpers import ipahelper +edit_url = tg.url('/ipapolicy/edit') +?> + + <script type="text/javascript" charset="utf-8" src="${tg.url('/static/javascript/tablekit.js')}"></script> + + <h1>Manage IPA Policy</h1> + + <h2 class="formsection">Search</h2> + <table class="formtable" cellpadding="2" cellspacing="0" border="0"> + <tr> + <th> + <label class="fieldlabel" py:content="fields.ipasearchtimelimit.label" />: + </th> + <td>${ipapolicy.get("ipasearchtimelimit")}</td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="fields.ipasearchrecordslimit.label" />: + </th> + <td>${ipapolicy.get("ipasearchrecordslimit")}</td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="fields.ipausersearchfields.label" />: + </th> + <td>${ipapolicy.get("ipausersearchfields")}</td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="fields.ipagroupsearchfields.label" />: + </th> + <td>${ipapolicy.get("ipagroupsearchfields")}</td> + </tr> + </table> + + <h2 class="formsection">Password Policy</h2> + <table class="formtable" cellpadding="2" cellspacing="0" border="0"> + <tr> + <th> + <label class="fieldlabel" py:content="fields.ipapwdexpadvnotify.label" />: + </th> + <td>${ipapolicy.get("ipapwdexpadvnotify")}</td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="fields.krbminpwdlife.label" />: + </th> + <td>${password.get("krbminpwdlife")}</td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="fields.krbmaxpwdlife.label" />: + </th> + <td>${password.get("krbmaxpwdlife")}</td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="fields.krbpwdmindiffchars.label" />: + </th> + <td>${password.get("krbpwdmindiffchars")}</td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="fields.krbpwdminlength.label" />: + </th> + <td>${password.get("krbpwdminlength")}</td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="fields.krbpwdhistorylength.label" />: + </th> + <td>${password.get("krbpwdhistorylength")}</td> + </tr> + </table> + <h2 class="formsection">User Settings</h2> + <table class="formtable" cellpadding="2" cellspacing="0" border="0"> + <tr> + <th> + <label class="fieldlabel" py:content="fields.ipamaxusernamelength.label" />: + </th> + <td>${ipapolicy.get("ipamaxusernamelength")}</td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="fields.ipahomesrootdir.label" />: + </th> + <td>${ipapolicy.get("ipahomesrootdir")}</td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="fields.ipadefaultloginshell.label" />: + </th> + <td>${ipapolicy.get("ipadefaultloginshell")}</td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="fields.ipadefaultprimarygroup.label" />: + </th> + <td>${ipapolicy.get("ipadefaultprimarygroup")}</td> + </tr> + </table> +<hr /> + <input class="submitbutton" type="button" + onclick="document.location.href='${edit_url}'" + value="Edit Policy" /> + + +</body> +</html> diff --git a/ipa-server/ipa-gui/ipagui/templates/loginfailed.kid b/ipa-server/ipa-gui/ipagui/templates/loginfailed.kid index 84896be5c..b31db82a7 100644 --- a/ipa-server/ipa-gui/ipagui/templates/loginfailed.kid +++ b/ipa-server/ipa-gui/ipagui/templates/loginfailed.kid @@ -1,35 +1,24 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" - xmlns:py="http://purl.org/kid/ns#"> - +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#" + py:extends="'master.kid'"> <head> - <meta content="text/html; charset=UTF-8" - http-equiv="content-type" py:replace="''"/> - <title>Login Failure</title> +<meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/> +<title>Permission Denied</title> </head> <body> - <div id="header"> - <div id="logo"> - <a href="${tg.url('/')}"><img - src="${tg.url('/static/images/logo.png')}" - border="0" alt="homepage" - /></a> - </div> - <div id="headerinfo"> - <div id="login"> - <div py:if="tg.config('identity.on') and not defined('logging_in')" id="page -Login"> - <span py:if="tg.identity.anonymous"> - Kerberos login failed. - </span> - <span py:if="not tg.identity.anonymous"> - Logged in as: ${tg.identity.user.display_name} - </span> + <div id="main_content"> + <div id="details"> + <div id="alertbox" py:if="value_of('tg_flash', None)"> + <p py:content="XML(tg_flash)"></p></div> + <h1>Permission Denied</h1> + <div class="instructions"> + <p> + You do not have permission to access this page. + </p> </div> + </div> </div> - </div> - </div> </body> + </html> diff --git a/ipa-server/ipa-gui/ipagui/templates/master.kid b/ipa-server/ipa-gui/ipagui/templates/master.kid index fd527a278..12e54fa1d 100644 --- a/ipa-server/ipa-gui/ipagui/templates/master.kid +++ b/ipa-server/ipa-gui/ipagui/templates/master.kid @@ -70,18 +70,20 @@ <div id="sidebar"> <h2>Tasks</h2> <ul> - <li><a href="${tg.url('/user/new')}">Add Person</a></li> - <li><a href="${tg.url('/user/list')}">Find People</a></li> + <li py:if="'admins' in tg.identity.groups"><a href="${tg.url('/user/new')}">Add User</a></li> + <li><a href="${tg.url('/user/list')}">Find Users</a></li> </ul> <ul> - <li><a href="${tg.url('/group/new')}">Add Group</a></li> + <li py:if="'admins' in tg.identity.groups"><a href="${tg.url('/group/new')}">Add Group</a></li> <li><a href="${tg.url('/group/list')}">Find Groups</a></li> </ul> + <ul py:if="'admins' in tg.identity.groups"> + <li><a href="${tg.url('/policy/index')}">Manage Policy</a></li> + </ul> <ul> - <li><a href="${tg.url('/')}">Manage Policy</a></li> <li><a href="${tg.url('/user/edit/', principal=tg.identity.user.display_name)}">Self Service</a></li> </ul> - <ul> + <ul py:if="'admins' in tg.identity.groups"> <li><a href="${tg.url('/delegate/list')}">Delegations</a></li> </ul> </div> diff --git a/ipa-server/ipa-gui/ipagui/templates/policyindex.kid b/ipa-server/ipa-gui/ipagui/templates/policyindex.kid new file mode 100644 index 000000000..88fa4bcc2 --- /dev/null +++ b/ipa-server/ipa-gui/ipagui/templates/policyindex.kid @@ -0,0 +1,31 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#" + py:extends="'policylayout.kid'"> +<head> +<meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/> +<title>Manage Policy</title> +</head> +<body> + +<?python +from ipagui.helpers import ipahelper +?> + + <script type="text/javascript" charset="utf-8" src="${tg.url('/static/javascript/tablekit.js')}"></script> + + <h1>Manage Policy</h1> + + <table> + <tbody> + <tr> + <td> + <a href="${tg.url('/ipapolicy/show')}" + >IPA Policy</a> + </td> + </tr> + </tbody> + </table> + + +</body> +</html> diff --git a/ipa-server/ipa-gui/ipagui/templates/policylayout.kid b/ipa-server/ipa-gui/ipagui/templates/policylayout.kid new file mode 100644 index 000000000..171326539 --- /dev/null +++ b/ipa-server/ipa-gui/ipagui/templates/policylayout.kid @@ -0,0 +1,17 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#" + py:extends="'master.kid'"> +<head> +</head> + +<body py:match="item.tag=='{http://www.w3.org/1999/xhtml}body'" py:attrs="item.items()"> + <div id="main_content"> +<div id="details"> + <div id="alertbox" py:if="value_of('tg_flash', None)"><p py:content="XML(tg_flash)"></p></div> + + <div py:replace="[item.text]+item[:]"></div> + </div> +</div> +</body> + +</html> diff --git a/ipa-server/ipa-gui/ipagui/templates/useredit.kid b/ipa-server/ipa-gui/ipagui/templates/useredit.kid index 3f9482a3d..f5cb1b02e 100644 --- a/ipa-server/ipa-gui/ipagui/templates/useredit.kid +++ b/ipa-server/ipa-gui/ipagui/templates/useredit.kid @@ -3,7 +3,7 @@ py:extends="'userlayout.kid'"> <head> <meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/> -<title>Edit Person</title> +<title>Edit User</title> </head> <body> @@ -14,7 +14,7 @@ <span class="small">edit protected fields</span> </input> </div> - <h1>Edit Person</h1> + <h1>Edit User</h1> </div> <?python diff --git a/ipa-server/ipa-gui/ipagui/templates/usereditform.kid b/ipa-server/ipa-gui/ipagui/templates/usereditform.kid index f6da48870..88b778d8c 100644 --- a/ipa-server/ipa-gui/ipagui/templates/usereditform.kid +++ b/ipa-server/ipa-gui/ipagui/templates/usereditform.kid @@ -10,11 +10,11 @@ onsubmit="preSubmit()"> <input type="submit" class="submitbutton" name="submit" - value="Update Person"/> + value="Update User"/> <input type="submit" class="submitbutton" name="submit" value="Cancel Edit" /> <input type="button" class="submitbutton" - value="Delete Person" + value="Delete User" onclick="return confirmDelete();" /> @@ -26,6 +26,8 @@ from ipagui.helpers import ipahelper src="${tg.url('/static/javascript/dynamicedit.js')}"></script> <script type="text/javascript" charset="utf-8" src="${tg.url('/static/javascript/dynamicselect.js')}"></script> + <script type="text/javascript" charset="utf-8" + src="${tg.url('/tg_widgets/tg_expanding_form_widget/javascript/expanding_form.js')}"></script> <?python searchurl = tg.url('/user/edit_search') @@ -141,14 +143,35 @@ from ipagui.helpers import ipahelper <tr> <th> - <label class="fieldlabel" for="${user_fields.cn.field_id}" - py:content="user_fields.cn.label" />: - </th> - <td> - <span py:replace="user_fields.cn.display(value_for(user_fields.cn))" /> - <span py:if="tg.errors.get('cn')" class="fielderror" - py:content="tg.errors.get('cn')" /> - + <label class="fieldlabel" for="${user_fields.cns.field_id}" + py:content="user_fields.cns.label" />: + </th> + <td colspan="3"> + <table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${user_fields.cns.field_id}"> + <tbody> + <?python repetition = 0 + cn_index = 0 + cn_error = tg.errors.get('cn') + ?> + <tr py:for="cn in value_for(user_fields.cn)" + id="${user_fields.cns.field_id}_${repetition}" + class="${user_fields.cns.field_class}"> + + <td py:for="field in user_fields.cns.fields"> + <span><input class="textfield" type="text" id="${user_fields.cns.field_id}_${repetition}_cn" name="cns-${repetition}.cn" value="${cn}"/></span> + <span py:if="cn_error and cn_error[cn_index]" class="fielderror" + py:content="tg.errors.get('cn')" /> + </td> + <?python cn_index = cn_index + 1 ?> + <td> + <a + href="javascript:ExpandingForm.removeItem('${user_fields.cns.field_id}_${repetition}')">Remove</a> + </td> + <?python repetition = repetition + 1?> + </tr> + </tbody> + </table> + <a id="${user_fields.cns.field_id}_doclink" href="javascript:ExpandingForm.addItem('${user_fields.cns.field_id}');">Add Common Name</a> </td> </tr> @@ -364,61 +387,170 @@ from ipagui.helpers import ipahelper <tr> <th> - <label class="fieldlabel" for="${user_fields.telephonenumber.field_id}" - py:content="user_fields.telephonenumber.label" />: - </th> - <td> - <span py:replace="user_fields.telephonenumber.display(value_for(user_fields.telephonenumber))" /> - <span py:if="tg.errors.get('telephonenumber')" class="fielderror" - py:content="tg.errors.get('telephonenumber')" /> + <label class="fieldlabel" for="${user_fields.telephonenumbers.field_id}" + py:content="user_fields.telephonenumbers.label" />: + </th> + <td colspan="3"> + <table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${user_fields.telephonenumbers.field_id}"> + <tbody> + <?python repetition = 0 + tele_index = 0 + tele_error = tg.errors.get('telephonenumber') + ?> + <tr py:for="tele in value_for(user_fields.telephonenumber)" + id="${user_fields.telephonenumbers.field_id}_${repetition}" + class="${user_fields.telephonenumbers.field_class}"> + + <td py:for="field in user_fields.telephonenumbers.fields"> + <span><input class="textfield" type="text" id="${user_fields.telephonenumbers.field_id}_${repetition}_telephonenumber" name="telephonenumbers-${repetition}.telephonenumber" value="${tele}"/></span> + <span py:if="tele_error and tele_error[tele_index]" class="fielderror" + py:content="tg.errors.get('telephonenumber')" /> + </td> + <?python tele_index = tele_index + 1 ?> + <td> + <a + href="javascript:ExpandingForm.removeItem('${user_fields.telephonenumbers.field_id}_${repetition}')">Remove</a> + </td> + <?python repetition = repetition + 1?> + </tr> + </tbody> + </table> + <a id="${user_fields.telephonenumbers.field_id}_doclink" href="javascript:ExpandingForm.addItem('${user_fields.telephonenumbers.field_id}');">Add Work Number</a> </td> </tr> - <tr> <th> - <label class="fieldlabel" for="${user_fields.facsimiletelephonenumber.field_id}" - py:content="user_fields.facsimiletelephonenumber.label" />: - </th> - <td> - <span py:replace="user_fields.facsimiletelephonenumber.display(value_for(user_fields.facsimiletelephonenumber))" /> - <span py:if="tg.errors.get('facsimiletelephonenumber')" class="fielderror" - py:content="tg.errors.get('facsimiletelephonenumber')" /> + <label class="fieldlabel" for="${user_fields.facsimiletelephonenumbers.field_id}" + py:content="user_fields.facsimiletelephonenumbers.label" />: + </th> + <td colspan="3"> + <table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${user_fields.facsimiletelephonenumbers.field_id}"> + <tbody> + <?python repetition = 0 + fax_index = 0 + fax_error = tg.errors.get('facsimiletelephonenumber') + ?> + <tr py:for="fax in value_for(user_fields.facsimiletelephonenumber)" + id="${user_fields.facsimiletelephonenumbers.field_id}_${repetition}" + class="${user_fields.facsimiletelephonenumbers.field_class}"> + + <td py:for="field in user_fields.facsimiletelephonenumbers.fields"> + <span><input class="textfield" type="text" id="${user_fields.facsimiletelephonenumbers.field_id}_${repetition}_facsimiletelephonenumber" name="facsimiletelephonenumbers-${repetition}.facsimiletelephonenumber" value="${fax}"/></span> + <span py:if="fax_error and fax_error[fax_index]" class="fielderror" + py:content="tg.errors.get('facsimiletelephonenumber')" /> + </td> + <?python fax_index = fax_index + 1 ?> + <td> + <a + href="javascript:ExpandingForm.removeItem('${user_fields.facsimiletelephonenumbers.field_id}_${repetition}')">Remove</a> + </td> + <?python repetition = repetition + 1?> + </tr> + </tbody> + </table> + <a id="${user_fields.facsimiletelephonenumbers.field_id}_doclink" href="javascript:ExpandingForm.addItem('${user_fields.facsimiletelephonenumbers.field_id}');">Add Fax Number</a> </td> </tr> <tr> <th> - <label class="fieldlabel" for="${user_fields.mobile.field_id}" - py:content="user_fields.mobile.label" />: - </th> - <td> - <span py:replace="user_fields.mobile.display(value_for(user_fields.mobile))" /> - <span py:if="tg.errors.get('mobile')" class="fielderror" - py:content="tg.errors.get('mobile')" /> + <label class="fieldlabel" for="${user_fields.mobiles.field_id}" + py:content="user_fields.mobiles.label" />: + </th> + <td colspan="3"> + <table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${user_fields.mobiles.field_id}"> + <tbody> + <?python repetition = 0 + mobile_index = 0 + mobile_error = tg.errors.get('mobile') + ?> + <tr py:for="mobile in value_for(user_fields.mobile)" + id="${user_fields.mobiles.field_id}_${repetition}" + class="${user_fields.mobiles.field_class}"> + + <td py:for="field in user_fields.mobiles.fields"> + <span><input class="textfield" type="text" id="${user_fields.mobiles.field_id}_${repetition}_mobile" name="mobiles-${repetition}.mobile" value="${mobile}"/></span> + <span py:if="mobile_error and mobile_error[mobile_index]" class="fielderror" + py:content="tg.errors.get('mobile')" /> + </td> + <?python mobile_index = mobile_index + 1 ?> + <td> + <a + href="javascript:ExpandingForm.removeItem('${user_fields.mobiles.field_id}_${repetition}')">Remove</a> + </td> + <?python repetition = repetition + 1?> + </tr> + </tbody> + </table> + <a id="${user_fields.mobiles.field_id}_doclink" href="javascript:ExpandingForm.addItem('${user_fields.mobiles.field_id}');">Add Cell Number</a> </td> </tr> <tr> <th> - <label class="fieldlabel" for="${user_fields.pager.field_id}" - py:content="user_fields.pager.label" />: - </th> - <td> - <span py:replace="user_fields.pager.display(value_for(user_fields.pager))" /> - <span py:if="tg.errors.get('pager')" class="fielderror" - py:content="tg.errors.get('pager')" /> + <label class="fieldlabel" for="${user_fields.pagers.field_id}" + py:content="user_fields.pagers.label" />: + </th> + <td colspan="3"> + <table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${user_fields.pagers.field_id}"> + <tbody> + <?python repetition = 0 + pager_index = 0 + pager_error = tg.errors.get('pager') + ?> + <tr py:for="pager in value_for(user_fields.pager)" + id="${user_fields.pagers.field_id}_${repetition}" + class="${user_fields.pagers.field_class}"> + + <td py:for="field in user_fields.pagers.fields"> + <span><input class="textfield" type="text" id="${user_fields.pagers.field_id}_${repetition}_pager" name="pagers-${repetition}.pager" value="${pager}"/></span> + <span py:if="pager_error and pager_error[pager_index]" class="fielderror" + py:content="tg.errors.get('pager')" /> + </td> + <?python pager_index = pager_index + 1 ?> + <td> + <a + href="javascript:ExpandingForm.removeItem('${user_fields.pagers.field_id}_${repetition}')">Remove</a> + </td> + <?python repetition = repetition + 1?> + </tr> + </tbody> + </table> + <a id="${user_fields.pagers.field_id}_doclink" href="javascript:ExpandingForm.addItem('${user_fields.pagers.field_id}');">Add Pager Number</a> </td> </tr> <tr> <th> - <label class="fieldlabel" for="${user_fields.homephone.field_id}" - py:content="user_fields.homephone.label" />: - </th> - <td> - <span py:replace="user_fields.homephone.display(value_for(user_fields.homephone))" /> - <span py:if="tg.errors.get('homephone')" class="fielderror" - py:content="tg.errors.get('homephone')" /> + <label class="fieldlabel" for="${user_fields.homephones.field_id}" + py:content="user_fields.homephones.label" />: + </th> + <td colspan="3"> + <table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${user_fields.homephones.field_id}"> + <tbody> + <?python repetition = 0 + homephone_index = 0 + homephone_error = tg.errors.get('homephone') + ?> + <tr py:for="homephone in value_for(user_fields.homephone)" + id="${user_fields.homephones.field_id}_${repetition}" + class="${user_fields.homephones.field_class}"> + + <td py:for="field in user_fields.homephones.fields"> + <span><input class="textfield" type="text" id="${user_fields.homephones.field_id}_${repetition}_homephone" name="homephones-${repetition}.homephone" value="${homephone}"/></span> + <span py:if="homephone_error and homephone_error[homephone_index]" class="fielderror" + py:content="tg.errors.get('homephone')" /> + </td> + <?python homephone_index = homephone_index + 1 ?> + <td> + <a + href="javascript:ExpandingForm.removeItem('${user_fields.homephones.field_id}_${repetition}')">Remove</a> + </td> + <?python repetition = repetition + 1?> + </tr> + </tbody> + </table> + <a id="${user_fields.homephones.field_id}_doclink" href="javascript:ExpandingForm.addItem('${user_fields.homephones.field_id}');">Add Home Phone</a> </td> </tr> </table> @@ -685,6 +817,7 @@ from ipagui.helpers import ipahelper div_counter = div_counter + 1 ?> </div> + <!-- a space here to prevent an empty div --> </div> </div> @@ -714,11 +847,11 @@ from ipagui.helpers import ipahelper <hr/> <input type="submit" class="submitbutton" name="submit" - value="Update Person"/> + value="Update User"/> <input type="submit" class="submitbutton" name="submit" value="Cancel Edit" /> <input type="button" class="submitbutton" - value="Delete Person" + value="Delete User" onclick="return confirmDelete();" /> diff --git a/ipa-server/ipa-gui/ipagui/templates/userlayout.kid b/ipa-server/ipa-gui/ipagui/templates/userlayout.kid index bbeb81399..c4e8104e6 100644 --- a/ipa-server/ipa-gui/ipagui/templates/userlayout.kid +++ b/ipa-server/ipa-gui/ipagui/templates/userlayout.kid @@ -15,8 +15,8 @@ <!-- <div id="sidebar"> <h2>Tools</h2> - <a href="${tg.url('/user/new')}">Add Person</a><br/> - <a href="${tg.url('/user/list')}">Find People</a><br/> + <a href="${tg.url('/user/new')}">Add User</a><br/> + <a href="${tg.url('/user/list')}">Find Users</a><br/> </div> --> </div> </body> diff --git a/ipa-server/ipa-gui/ipagui/templates/userlist.kid b/ipa-server/ipa-gui/ipagui/templates/userlist.kid index fdeeb3169..9ca3753d0 100644 --- a/ipa-server/ipa-gui/ipagui/templates/userlist.kid +++ b/ipa-server/ipa-gui/ipagui/templates/userlist.kid @@ -3,15 +3,15 @@ py:extends="'userlayout.kid'"> <head> <meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/> -<title>Find People</title> +<title>Find Users</title> </head> <body> - <h1>Find People</h1> + <h1>Find Users</h1> <script type="text/javascript" charset="utf-8" src="${tg.url('/static/javascript/tablekit.js')}"></script> <div id="search"> <form action="${tg.url('/user/list')}" method="get"> <input id="uid" type="text" name="uid" value="${uid}" /> - <input class="searchbutton" type="submit" value="Find People"/> + <input class="searchbutton" type="submit" value="Find Users"/> </form> <script type="text/javascript"> document.getElementById("uid").focus(); @@ -23,7 +23,7 @@ <thead> <tr> <th> - Person + User </th> <th> Phone diff --git a/ipa-server/ipa-gui/ipagui/templates/usernew.kid b/ipa-server/ipa-gui/ipagui/templates/usernew.kid index 16f0e66b9..b740bca22 100644 --- a/ipa-server/ipa-gui/ipagui/templates/usernew.kid +++ b/ipa-server/ipa-gui/ipagui/templates/usernew.kid @@ -3,10 +3,10 @@ py:extends="'userlayout.kid'"> <head> <meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/> - <title>Add Person</title> + <title>Add User</title> </head> <body> - <h1>Add Person</h1> + <h1>Add User</h1> ${form.display(action=tg.url("/user/create"), value=user)} </body> diff --git a/ipa-server/ipa-gui/ipagui/templates/usernewform.kid b/ipa-server/ipa-gui/ipagui/templates/usernewform.kid index eeaa87fa4..97be52732 100644 --- a/ipa-server/ipa-gui/ipagui/templates/usernewform.kid +++ b/ipa-server/ipa-gui/ipagui/templates/usernewform.kid @@ -2,8 +2,8 @@ class="simpleroster"> <form action="${action}" name="${name}" method="${method}" class="tableform" onsubmit="preSubmit()"> - -<input type="submit" class="submitbutton" name="submit" value="Add Person"/> + +<input type="submit" class="submitbutton" name="submit" value="Add User"/> <?python from ipagui.helpers import ipahelper @@ -13,6 +13,8 @@ from ipagui.helpers import ipahelper src="${tg.url('/static/javascript/dynamicedit.js')}"></script> <script type="text/javascript" charset="utf-8" src="${tg.url('/static/javascript/dynamicselect.js')}"></script> + <script type="text/javascript" charset="utf-8" + src="${tg.url('/tg_widgets/tg_expanding_form_widget/javascript/expanding_form.js')}"></script> <?python searchurl = tg.url('/user/edit_search') @@ -51,7 +53,7 @@ from ipagui.helpers import ipahelper </script> <div py:for="field in hidden_fields" - py:replace="field.display(value_for(field), **params_for(field))" + py:replace="field.display(value_for(field), **params_for(field))" /> <h2 class="formsection">Identity Details</h2> @@ -107,7 +109,7 @@ from ipagui.helpers import ipahelper var uid = $('form_uid'); var mail = $('form_mail'); - var cn = $('form_cn'); + var cn = $('form_cns_0_cn'); var displayname = $('form_displayname'); var initials = $('form_initials'); @@ -164,14 +166,38 @@ from ipagui.helpers import ipahelper <tr> <th> - <label class="fieldlabel" for="${user_fields.cn.field_id}" - py:content="user_fields.cn.label" />: + <label class="fieldlabel" for="${user_fields.cns.field_id}" + py:content="user_fields.cns.label" />: </th> - <td> - <span py:replace="user_fields.cn.display(value_for(user_fields.cn))" /> - <span py:if="tg.errors.get('cn')" class="fielderror" - py:content="tg.errors.get('cn')" /> + <td colspan="3"> + <table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${user_fields.cns.field_id}"> + <tbody> + <?python repetition = 0 + cn_index = 0 + cn_error = tg.errors.get('cn') + values = value_for(user_fields.cn) + if values is None: + values=[''] + ?> + <tr py:for="cn in values" + id="${user_fields.cns.field_id}_${repetition}" + class="${user_fields.cns.field_class}"> + <td py:for="field in user_fields.cns.fields"> + <span><input class="textfield" type="text" id="${user_fields.cns.field_id}_${repetition}_cn" name="cns-${repetition}.cn" value="${cn}"/></span> + <span py:if="cn_error and cn_error[cn_index]" class="fielderror" + py:content="tg.errors.get('cn')" /> + </td> + <?python cn_index = cn_index + 1 ?> + <td> + <a + href="javascript:ExpandingForm.removeItem('${user_fields.cns.field_id}_${repetition}')">Remove</a> + </td> + <?python repetition = repetition + 1?> + </tr> + </tbody> + </table> + <a id="${user_fields.cns.field_id}_doclink" href="javascript:ExpandingForm.addItem('${user_fields.cns.field_id}');">Add Common Name</a> </td> </tr> @@ -337,63 +363,188 @@ from ipagui.helpers import ipahelper <tr> <th> - <label class="fieldlabel" for="${user_fields.telephonenumber.field_id}" - py:content="user_fields.telephonenumber.label" />: - </th> - <td> - <span py:replace="user_fields.telephonenumber.display(value_for(user_fields.telephonenumber))" /> - <span py:if="tg.errors.get('telephonenumber')" class="fielderror" - py:content="tg.errors.get('telephonenumber')" /> - </td> - </tr> - - <tr> - <th> - <label class="fieldlabel" for="${user_fields.facsimiletelephonenumber.field_id}" - py:content="user_fields.facsimiletelephonenumber.label" />: - </th> - <td> - <span py:replace="user_fields.facsimiletelephonenumber.display(value_for(user_fields.facsimiletelephonenumber))" /> - <span py:if="tg.errors.get('facsimiletelephonenumber')" class="fielderror" - py:content="tg.errors.get('facsimiletelephonenumber')" /> - </td> - </tr> - - <tr> - <th> - <label class="fieldlabel" for="${user_fields.mobile.field_id}" - py:content="user_fields.mobile.label" />: - </th> - <td> - <span py:replace="user_fields.mobile.display(value_for(user_fields.mobile))" /> - <span py:if="tg.errors.get('mobile')" class="fielderror" - py:content="tg.errors.get('mobile')" /> - </td> - </tr> - - <tr> - <th> - <label class="fieldlabel" for="${user_fields.pager.field_id}" - py:content="user_fields.pager.label" />: - </th> - <td> - <span py:replace="user_fields.pager.display(value_for(user_fields.pager))" /> - <span py:if="tg.errors.get('pager')" class="fielderror" - py:content="tg.errors.get('pager')" /> + <label class="fieldlabel" for="${user_fields.telephonenumbers.field_id}" + py:content="user_fields.telephonenumbers.label" />: + </th> + <td colspan="3"> + <table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${user_fields.telephonenumbers.field_id}"> + <tbody> + <?python repetition = 0 + tele_index = 0 + tele_error = tg.errors.get('telephonenumber') + values = value_for(user_fields.telephonenumber) + if values is None: + values=[''] + ?> + <tr py:for="tele in values" + id="${user_fields.telephonenumbers.field_id}_${repetition}" + class="${user_fields.telephonenumbers.field_class}"> + + <td py:if="user_fields.telephonenumbers.fields is not None" py:for="field in user_fields.telephonenumbers.fields"> + <span><input class="textfield" type="text" id="${user_fields.telephonenumbers.field_id}_${repetition}_telephonenumber" name="telephonenumbers-${repetition}.telephonenumber" value="${tele}"/></span> + <span py:if="tele_error and tele_error[tele_index]" class="fielderror" + py:content="tg.errors.get('telephonenumber')" /> + </td> + <?python tele_index = tele_index + 1 ?> + <td> + <a + href="javascript:ExpandingForm.removeItem('${user_fields.telephonenumbers.field_id}_${repetition}')">Remove</a> + </td> + <?python repetition = repetition + 1?> + </tr> + </tbody> + </table> + <a id="${user_fields.telephonenumbers.field_id}_doclink" href="javascript:ExpandingForm.addItem('${user_fields.telephonenumbers.field_id}');">Add Work Number</a> + </td> + </tr> + <tr> + <th> + <label class="fieldlabel" for="${user_fields.facsimiletelephonenumbers.field_id}" + py:content="user_fields.facsimiletelephonenumbers.label" />: + </th> + <td colspan="3"> + <table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${user_fields.facsimiletelephonenumbers.field_id}"> + <tbody> + <?python repetition = 0 + fax_index = 0 + fax_error = tg.errors.get('facsimiletelephonenumber') + values = value_for(user_fields.facsimiletelephonenumber) + if values is None: + values=[''] + ?> + <tr py:for="fax in values" + id="${user_fields.facsimiletelephonenumbers.field_id}_${repetition}" + class="${user_fields.facsimiletelephonenumbers.field_class}"> + + <td py:for="field in user_fields.facsimiletelephonenumbers.fields"> + <span><input class="textfield" type="text" id="${user_fields.facsimiletelephonenumbers.field_id}_${repetition}_facsimiletelephonenumber" name="facsimiletelephonenumbers-${repetition}.facsimiletelephonenumber" value="${fax}"/></span> + <span py:if="fax_error and fax_error[fax_index]" class="fielderror" + py:content="tg.errors.get('facsimiletelephonenumber')" /> + </td> + <?python fax_index = fax_index + 1 ?> + <td> + <a + href="javascript:ExpandingForm.removeItem('${user_fields.facsimiletelephonenumbers.field_id}_${repetition}')">Remove</a> + </td> + <?python repetition = repetition + 1?> + </tr> + </tbody> + </table> + <a id="${user_fields.facsimiletelephonenumbers.field_id}_doclink" href="javascript:ExpandingForm.addItem('${user_fields.facsimiletelephonenumbers.field_id}');">Add Fax Number</a> + </td> + </tr> + + <tr> + <th> + <label class="fieldlabel" for="${user_fields.mobiles.field_id}" + py:content="user_fields.mobiles.label" />: + </th> + <td colspan="3"> + <table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${user_fields.mobiles.field_id}"> + <tbody> + <?python repetition = 0 + mobile_index = 0 + mobile_error = tg.errors.get('mobile') + values = value_for(user_fields.mobile) + if values is None: + values=[''] + ?> + <tr py:for="mobile in values" + id="${user_fields.mobiles.field_id}_${repetition}" + class="${user_fields.mobiles.field_class}"> + + <td py:for="field in user_fields.mobiles.fields"> + <span><input class="textfield" type="text" id="${user_fields.mobiles.field_id}_${repetition}_mobile" name="mobiles-${repetition}.mobile" value="${mobile}"/></span> + <span py:if="mobile_error and mobile_error[mobile_index]" class="fielderror" + py:content="tg.errors.get('mobile')" /> + </td> + <?python mobile_index = mobile_index + 1 ?> + <td> + <a + href="javascript:ExpandingForm.removeItem('${user_fields.mobiles.field_id}_${repetition}')">Remove</a> + </td> + <?python repetition = repetition + 1?> + </tr> + </tbody> + </table> + <a id="${user_fields.mobiles.field_id}_doclink" href="javascript:ExpandingForm.addItem('${user_fields.mobiles.field_id}');">Add Cell Number</a> + </td> + </tr> + + <tr> + <th> + <label class="fieldlabel" for="${user_fields.pagers.field_id}" + py:content="user_fields.pagers.label" />: + </th> + <td colspan="3"> + <table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${user_fields.pagers.field_id}"> + <tbody> + <?python repetition = 0 + pager_index = 0 + pager_error = tg.errors.get('pager') + values = value_for(user_fields.pager) + if values is None: + values=[''] + ?> + <tr py:for="pager in values" + id="${user_fields.pagers.field_id}_${repetition}" + class="${user_fields.pagers.field_class}"> + + <td py:for="field in user_fields.pagers.fields"> + <span><input class="textfield" type="text" id="${user_fields.pagers.field_id}_${repetition}_pager" name="pagers-${repetition}.pager" value="${pager}"/></span> + <span py:if="pager_error and pager_error[pager_index]" class="fielderror" + py:content="tg.errors.get('pager')" /> + </td> + <?python pager_index = pager_index + 1 ?> + <td> + <a + href="javascript:ExpandingForm.removeItem('${user_fields.pagers.field_id}_${repetition}')">Remove</a> + </td> + <?python repetition = repetition + 1?> + </tr> + </tbody> + </table> + <a id="${user_fields.pagers.field_id}_doclink" href="javascript:ExpandingForm.addItem('${user_fields.pagers.field_id}');">Add Pager Number</a> + </td> + </tr> + + <tr> + <th> + <label class="fieldlabel" for="${user_fields.homephones.field_id}" + py:content="user_fields.homephones.label" />: + </th> + <td colspan="3"> + <table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${user_fields.homephones.field_id}"> + <tbody> + <?python repetition = 0 + homephone_index = 0 + homephone_error = tg.errors.get('homephone') + values = value_for(user_fields.homephone) + if values is None: + values=[''] + ?> + <tr py:for="homephone in values" + id="${user_fields.homephones.field_id}_${repetition}" + class="${user_fields.homephones.field_class}"> + + <td py:for="field in user_fields.homephones.fields"> + <span><input class="textfield" type="text" id="${user_fields.homephones.field_id}_${repetition}_homephone" name="homephones-${repetition}.homephone" value="${homephone}"/></span> + <span py:if="homephone_error and homephone_error[homephone_index]" class="fielderror" + py:content="tg.errors.get('homephone')" /> + </td> + <?python homephone_index = homephone_index + 1 ?> + <td> + <a + href="javascript:ExpandingForm.removeItem('${user_fields.homephones.field_id}_${repetition}')">Remove</a> + </td> + <?python repetition = repetition + 1?> + </tr> + </tbody> + </table> + <a id="${user_fields.homephones.field_id}_doclink" href="javascript:ExpandingForm.addItem('${user_fields.homephones.field_id}');">Add Home Phone</a> </td> </tr> - <tr> - <th> - <label class="fieldlabel" for="${user_fields.homephone.field_id}" - py:content="user_fields.homephone.label" />: - </th> - <td> - <span py:replace="user_fields.homephone.display(value_for(user_fields.homephone))" /> - <span py:if="tg.errors.get('homephone')" class="fielderror" - py:content="tg.errors.get('homephone')" /> - </td> - </tr> </table> <h2 class="formsection">Mailing Address</h2> @@ -635,7 +786,7 @@ from ipagui.helpers import ipahelper </div> <hr /> -<input type="submit" class="submitbutton" name="submit" value="Add Person"/> +<input type="submit" class="submitbutton" name="submit" value="Add User"/> </form> diff --git a/ipa-server/ipa-gui/ipagui/templates/usershow.kid b/ipa-server/ipa-gui/ipagui/templates/usershow.kid index cc56340d9..8cc356b89 100644 --- a/ipa-server/ipa-gui/ipagui/templates/usershow.kid +++ b/ipa-server/ipa-gui/ipagui/templates/usershow.kid @@ -3,17 +3,18 @@ py:extends="'userlayout.kid'"> <head> <meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/> - <title>View Person</title> + <title>View User</title> </head> <body> <?python edit_url = tg.url('/user/edit', uid=user.get('uid')) ?> - <h1>View Person</h1> + <h1>View User</h1> - <input class="submitbutton" type="button" + <input py:if="'editors' in tg.identity.groups or 'admins' in tg.identity.groups" + class="submitbutton" type="button" onclick="document.location.href='${edit_url}'" - value="Edit Person" /> + value="Edit User" /> <?python from ipagui.helpers import userhelper @@ -57,7 +58,21 @@ else: <th> <label class="fieldlabel" py:content="fields.cn.label" />: </th> - <td>${user.get("cn")}</td> + <td> + <table cellpadding="2" cellspacing="0" border="0"> + <tbody> + <?python + index = 0 + values = user.get("cn") + if isinstance(values, str): + values = [values] + ?> + <tr py:for="index in range(len(values))"> + <td>${values[index]}</td> + </tr> + </tbody> + </table> + </td> </tr> <tr> <th> @@ -132,31 +147,101 @@ else: <th> <label class="fieldlabel" py:content="fields.telephonenumber.label" />: </th> - <td>${user.get("telephonenumber")}</td> + <td> + <table cellpadding="2" cellspacing="0" border="0"> + <tbody> + <?python + index = 0 + values = user.get("telephonenumber", '') + if isinstance(values, str): + values = [values] + ?> + <tr py:for="index in range(len(values))"> + <td>${values[index]}</td> + </tr> + </tbody> + </table> + </td> </tr> <tr> <th> <label class="fieldlabel" py:content="fields.facsimiletelephonenumber.label" />: </th> - <td>${user.get("facsimiletelephonenumber")}</td> + <td> + <table cellpadding="2" cellspacing="0" border="0"> + <tbody> + <?python + index = 0 + values = user.get("facsimiletelephonenumber", '') + if isinstance(values, str): + values = [values] + ?> + <tr py:for="index in range(len(values))"> + <td>${values[index]}</td> + </tr> + </tbody> + </table> + </td> </tr> <tr> <th> <label class="fieldlabel" py:content="fields.mobile.label" />: </th> - <td>${user.get("mobile")}</td> + <td> + <table cellpadding="2" cellspacing="0" border="0"> + <tbody> + <?python + index = 0 + values = user.get("mobile", '') + if isinstance(values, str): + values = [values] + ?> + <tr py:for="index in range(len(values))"> + <td>${values[index]}</td> + </tr> + </tbody> + </table> + </td> </tr> <tr> <th> <label class="fieldlabel" py:content="fields.pager.label" />: </th> - <td>${user.get("pager")}</td> + <td> + <table cellpadding="2" cellspacing="0" border="0"> + <tbody> + <?python + index = 0 + values = user.get("pager", '') + if isinstance(values, str): + values = [values] + ?> + <tr py:for="index in range(len(values))"> + <td>${values[index]}</td> + </tr> + </tbody> + </table> + </td> </tr> <tr> <th> <label class="fieldlabel" py:content="fields.homephone.label" />: </th> - <td>${user.get("homephone")}</td> + <td> + <table cellpadding="2" cellspacing="0" border="0"> + <tbody> + <?python + index = 0 + values = user.get("homephone", '') + if isinstance(values, str): + values = [values] + ?> + <tr py:for="index in range(len(values))"> + <td>${values[index]}</td> + </tr> + </tbody> + </table> + </td> </tr> </table> @@ -260,7 +345,7 @@ else: </table> <div py:if='len(fields.custom_fields) > 0'> - <div class="formsection" >Custom Fields</div> + <h2 class="formsection">Custom Fields</h2> <table class="formtable" cellpadding="2" cellspacing="0" border="0"> <tr py:for='custom_field in fields.custom_fields'> <th> @@ -289,8 +374,9 @@ else: <br/> <hr /> - <input class="submitbutton" type="button" + <input py:if="'editors' in tg.identity.groups or 'admins' in tg.identity.groups" + class="submitbutton" type="button" onclick="document.location.href='${edit_url}'" - value="Edit Person" /> + value="Edit User" /> </body> </html> |