This commit is contained in:
Karl MacMillan 0001-01-01 00:00:00 +00:00
commit d2378f13d0
44 changed files with 1024 additions and 157 deletions

View File

@ -65,6 +65,9 @@ def main():
if counter == 0:
print "No entries found for", args[1]
return 2
elif counter == -1:
print "These results are truncated."
print "Please revine your search and try again."
for ent in groups:
try:

View File

@ -90,6 +90,9 @@ def main():
if counter == 0:
print "No entries found for", args[1]
return 2
elif counter == -1:
print "These results are truncated."
print "Please revine your search and try again."
for ent in users:
attr = ent.attrList()

View File

@ -117,9 +117,6 @@ class IPAClient:
user_dict = user.toDict()
# dn is set on the server-side
del user_dict['dn']
# convert to a regular dict before sending
result = self.transport.add_user(user_dict, user_container)
return result
@ -386,6 +383,20 @@ class IPAClient:
def add_service_principal(self, princ_name):
return self.transport.add_service_principal(princ_name)
def find_service_principal(self, criteria, sattrs=None, searchlimit=0, timelimit=-1):
"""Return a list: counter followed by a Entity object for each host that
matches the criteria. If the results are truncated, counter will
be set to -1"""
result = self.transport.find_service_principal(criteria, sattrs, searchlimit, timelimit)
counter = result[0]
hosts = [counter]
for attrs in result[1:]:
if attrs is not None:
hosts.append(entity.Entity(attrs))
return hosts
def get_keytab(self, princ_name):
return self.transport.get_keytab(princ_name)

View File

@ -177,3 +177,8 @@ CONFIG_DEFAULT_GROUP = gen_error_code(
CONFIGURATION_CATEGORY,
0x0002,
"You cannot remove the default users group.")
CONFIG_INVALID_OC = gen_error_code(
CONFIGURATION_CATEGORY,
0x0003,
"Invalid object class.")

View File

@ -25,14 +25,11 @@ import logging
import subprocess
from random import Random
from time import gmtime
import os
import os, sys, traceback, readline
import stat
import socket
import readline
import traceback
from types import *
from string import lower
import re
import xmlrpclib
import datetime
@ -82,7 +79,7 @@ def run(args, stdin=None):
logging.info(stderr)
if p.returncode != 0:
raise self.CalledProcessError(p.returncode, ' '.join(args))
raise CalledProcessError(p.returncode, ' '.join(args))
def file_exists(filename):
try:
@ -121,24 +118,24 @@ class CIDict(dict):
self.update(default or {})
def __getitem__(self,key):
return super(CIDict,self).__getitem__(lower(key))
return super(CIDict,self).__getitem__(string.lower(key))
def __setitem__(self,key,value):
lower_key = lower(key)
lower_key = string.lower(key)
self._keys[lower_key] = key
return super(CIDict,self).__setitem__(lower(key),value)
return super(CIDict,self).__setitem__(string.lower(key),value)
def __delitem__(self,key):
lower_key = lower(key)
lower_key = string.lower(key)
del self._keys[lower_key]
return super(CIDict,self).__delitem__(lower(key))
return super(CIDict,self).__delitem__(string.lower(key))
def update(self,dict):
for key in dict.keys():
self[key] = dict[key]
def has_key(self,key):
return super(CIDict, self).has_key(lower(key))
return super(CIDict, self).has_key(string.lower(key))
def get(self,key,failobj=None):
try:
@ -373,7 +370,7 @@ def format_list(items, quote=None, page_width=80):
'''
left_quote = right_quote = ''
num_items = len(items)
if not num_items: return text
if not num_items: return ""
if quote is not None:
if type(quote) in StringTypes:
@ -458,7 +455,7 @@ def read_pairs_file(filename):
fd = open(filename)
text = fd.read()
text = comment_re.sub('', text) # kill comments
pairs = ipautil.parse_key_value_pairs(text)
pairs = parse_key_value_pairs(text)
if fd != sys.stdin: fd.close()
return pairs
@ -470,7 +467,7 @@ def read_items_file(filename):
fd = open(filename)
text = fd.read()
text = comment_re.sub('', text) # kill comments
items = ipautil.parse_items(text)
items = parse_items(text)
if fd != sys.stdin: fd.close()
return items
@ -568,11 +565,6 @@ class AttributeValueCompleter:
readline.set_completer_delims(self.prev_completer_delims)
readline.set_completer(self.prev_completer)
def _debug(self):
print >> output_fd, "lhs='%s' lhs_complete=%s operator='%s' operator_complete=%s rhs='%s'" % \
(self.lhs, self.lhs_complete, self.operator, self.operator_complete, self.rhs)
def parse_input(self):
'''We are looking for 3 tokens: <lhs,op,rhs>
Extract as much of each token as possible.

View File

@ -703,6 +703,24 @@ class RPCClient:
return ipautil.unwrap_binary_data(result)
def find_service_principal (self, criteria, sattrs=None, searchlimit=0, timelimit=-1):
"""Return a list: counter followed by a Entity object for each host that
matches the criteria. If the results are truncated, counter will
be set to -1"""
server = self.setup_server()
try:
# None values are not allowed in XML-RPC
if sattrs is None:
sattrs = "__NONE__"
result = server.find_service_principal(criteria, sattrs, searchlimit, timelimit)
except xmlrpclib.Fault, fault:
raise ipaerror.gen_exception(fault.faultCode, fault.faultString)
except socket.error, (value, msg):
raise xmlrpclib.Fault(value, msg)
return ipautil.unwrap_binary_data(result)
def get_keytab(self, princ_name):
server = self.setup_server()

View File

@ -130,6 +130,15 @@ dnl ---------------------------------------------------------------------------
PKG_CHECK_MODULES(MOZLDAP, mozldap > 6)
dnl ---------------------------------------------------------------------------
dnl - Check for OpenSSL Crypto library
dnl ---------------------------------------------------------------------------
dnl This is a very simple check, we should probably check also for MD4_Init and
dnl probably also the version we are using is recent enough
SSL_LIBS=
AC_CHECK_LIB(crypto, DES_set_key_unchecked, [SSL_LIBS="-lcrypto"])
AC_SUBST(SSL_LIBS)
dnl ---------------------------------------------------------------------------
dnl - Check for Python
dnl ---------------------------------------------------------------------------
@ -251,5 +260,6 @@ echo "
cflags: ${CFLAGS}
LDAP libs: ${LDAP_LIBS}
KRB5 libs: ${KRB5_LIBS}
OpenSSL libs: ${SSL_LIBS}
Maintainer mode: ${USE_MAINTAINER_MODE}
"

View File

@ -19,6 +19,7 @@ from subcontrollers.group import GroupController
from subcontrollers.delegation import DelegationController
from subcontrollers.policy import PolicyController
from subcontrollers.ipapolicy import IPAPolicyController
from subcontrollers.principal import PrincipalController
ipa.config.init_config()
@ -31,6 +32,7 @@ class Root(controllers.RootController):
delegate = DelegationController()
policy = PolicyController()
ipapolicy = IPAPolicyController()
principal = PrincipalController()
@expose(template="ipagui.templates.welcome")
@identity.require(identity.not_anonymous())

View File

@ -7,6 +7,7 @@ app_PYTHON = \
ipapolicy.py \
user.py \
delegate.py \
principal.py \
$(NULL)
EXTRA_DIST = \

View File

@ -41,11 +41,12 @@ class GroupNewForm(widgets.Form):
class GroupEditValidator(validators.Schema):
cn = validators.String(not_empty=True)
cn = validators.String(not_empty=False)
gidnumber = validators.Int(not_empty=False)
description = validators.String(not_empty=False)
pre_validators = [
validators.RequireIfPresent(required='cn', present='editprotected'),
validators.RequireIfPresent(required='gidnumber', present='editprotected'),
]

View File

@ -1,5 +1,6 @@
import turbogears
from turbogears import validators, widgets
from tg_expanding_form_widget.tg_expanding_form_widget import ExpandingForm
class IPAPolicyFields(object):
# From cn=ipaConfig
@ -12,6 +13,11 @@ class IPAPolicyFields(object):
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))
ipauserobjectclasses = widgets.TextField(name="ipauserobjectclasses", label="Default User Object Classes", attrs=dict(size=50))
userobjectclasses = ExpandingForm(name="userobjectclasses", label="Default User Object Classes", fields=[ipauserobjectclasses])
ipagroupobjectclasses = widgets.TextField(name="ipagroupobjectclasses", label="Default Group Object Classes", attrs=dict(size=50))
groupobjectclasses = ExpandingForm(name="groupobjectclasses", label="Default User Object Classes", fields=[ipagroupobjectclasses])
ipadefaultemaildomain = widgets.TextField(name="ipadefaultemaildomain", label="Default E-mail Domain", attrs=dict(size=20))
ipapolicy_orig = widgets.HiddenField(name="ipapolicy_orig")
@ -34,6 +40,10 @@ class IPAPolicyValidator(validators.Schema):
ipahomesrootdir = validators.String(not_empty=True)
ipadefaultloginshell = validators.String(not_empty=True)
ipadefaultprimarygroup = validators.String(not_empty=True)
ipauserobjectclasses = validators.ForEach(validators.String(not_empty=True))
ipagroupobjectclasses = validators.ForEach(validators.String(not_empty=True))
ipadefaultemaildomain = validators.String(not_empty=True)
krbmaxpwdlife = validators.Number(not_empty=True)
krbminpwdlife = validators.Number(not_empty=True)
krbpwdmindiffchars = validators.Number(not_empty=True)

View File

@ -0,0 +1,39 @@
import turbogears
from turbogears import validators, widgets
from tg_expanding_form_widget.tg_expanding_form_widget import ExpandingForm
class PrincipalFields(object):
hostname = widgets.TextField(name="hostname", label="Host Name")
service = widgets.SingleSelectField(name="service",
label="Service Type",
options = [
("cifs", "cifs"),
("dhcp", "dhcp"),
("dns", "dns"),
("host", "host"),
("HTTP", "HTTP"),
("ldap", "ldap"),
("other", "other"),
("rpc", "rpc"),
("snmp", "snmp")
],
attrs=dict(onchange="toggleOther(this.id)"))
other = widgets.TextField(name="other", label="Other Service", attrs=dict(size=10))
class PrincipalNewValidator(validators.Schema):
hostname = validators.String(not_empty=True)
service = validators.String(not_empty=True)
other = validators.String(not_empty=False)
class PrincipalNewForm(widgets.Form):
params = ['principal_fields']
validator = PrincipalNewValidator()
def __init__(self, *args, **kw):
super(PrincipalNewForm,self).__init__(*args, **kw)
(self.template_c, self.template) = widgets.meta.load_kid_template("ipagui.templates.principalnewform")
self.principal_fields = PrincipalFields
def update_params(self, params):
super(PrincipalNewForm,self).update_params(params)

View File

@ -11,7 +11,7 @@ class UserFields(object):
displayname = widgets.TextField(name="displayname", label="Display Name")
initials = widgets.TextField(name="initials", label="Initials")
uid = widgets.TextField(name="uid", label="Login")
uid = widgets.TextField(name="uid", label="Login", attrs=dict(onchange="warnRDN(this.id)"))
userpassword = widgets.PasswordField(name="userpassword", label="Password")
userpassword_confirm = widgets.PasswordField(name="userpassword_confirm",
label="Confirm Password")
@ -56,9 +56,7 @@ class UserFields(object):
label="Account Status",
options = [("", "active"), ("true", "inactive")])
uid_hidden = widgets.HiddenField(name="uid")
uidnumber_hidden = widgets.HiddenField(name="uidnumber")
gidnumber_hidden = widgets.HiddenField(name="gidnumber")
uid_hidden = widgets.HiddenField(name="uid_hidden")
krbPasswordExpiration_hidden = widgets.HiddenField(name="krbPasswordExpiration")
editprotected_hidden = widgets.HiddenField(name="editprotected")
@ -111,11 +109,12 @@ class UserEditValidator(validators.Schema):
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)
mail = validators.Email(not_empty=False)
uidnumber = validators.Int(not_empty=False)
gidnumber = validators.Int(not_empty=False)
pre_validators = [
validators.RequireIfPresent(required='uid', present='editprotected'),
validators.RequireIfPresent(required='uidnumber', present='editprotected'),
validators.RequireIfPresent(required='gidnumber', present='editprotected'),
]

View File

@ -7,3 +7,34 @@ def javascript_string_escape(input):
return re.sub(r'[\'\"\\]',
lambda match: "\\%s" % match.group(),
input)
def setup_mv_fields(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:
for v in field:
if v:
mvlist.append({ fieldname : v } )
if len(mvlist) == 0:
# 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(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

View File

@ -83,7 +83,7 @@ class ProxyIdentity(object):
return self._user.groups
except AttributeError:
# Groups haven't been computed yet
return None
return []
groups= property(_get_groups)
def logout(self):

View File

@ -339,14 +339,18 @@ table.formtable td input[type="text"], input#criteria {
border: 1px inset #dcdcdc;
font-size: medium;
padding: 2px 1px;
/*
background-color: #f5faff;
*/
}
table.formtable td select {
border: 1px inset #dcdcdc;
font-size: small;
padding: 2px 1px;
/*
background-color: #f5faff;
*/
}
p.empty-message {
@ -402,3 +406,20 @@ ul.checkboxlist li input {
.sortdesc {
background-image: url(/static/images/down.gif) !important;
}
.warning_message {
font-size: 120%;
/*
color: #ee0000;
*/
font-weight: bolder;
}
.fielderror {
color: red !important;
font-weight: bold;
}
.requiredfield {
background-color: #eebbbb !important;
}

View File

@ -9,6 +9,7 @@ app_PYTHON = \
policy.py \
user.py \
delegation.py \
principal.py \
$(NULL)
EXTRA_DIST = \

View File

@ -267,14 +267,6 @@ class GroupController(IPAController):
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)
#
@ -313,7 +305,7 @@ class GroupController(IPAController):
kw['dnadd'] = failed_adds
group_modified = True
except ipaerror.IPAError, e:
turbogears.flash("Group update failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
turbogears.flash("Updating group membership failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
return dict(form=group_edit_form, group=kw, members=member_dicts,
tg_template='ipagui.templates.groupedit')
@ -331,7 +323,7 @@ class GroupController(IPAController):
kw['dndel'] = failed_dels
group_modified = True
except ipaerror.IPAError, e:
turbogears.flash("Group update failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
turbogears.flash("Updating group membership failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
return dict(form=group_edit_form, group=kw, members=member_dicts,
tg_template='ipagui.templates.groupedit')

View File

@ -17,6 +17,7 @@ from ipa.entity import utf8_encode_values
from ipa import ipaerror
import ipa.entity
import ipagui.forms.ipapolicy
from ipagui.helpers import ipahelper
import ldap.dn
@ -71,6 +72,15 @@ class IPAPolicyController(IPAController):
# Combine the 2 dicts to make the form easier
ipapolicy_dict.update(password_dict)
# Load potential multi-valued fields
if isinstance(ipapolicy_dict.get('ipauserobjectclasses',''), str):
ipapolicy_dict['ipauserobjectclasses'] = [ipapolicy_dict.get('ipauserobjectclasses')]
ipapolicy_dict['userobjectclasses'] = ipahelper.setup_mv_fields(ipapolicy_dict.get('ipauserobjectclasses'), 'ipauserobjectclasses')
if isinstance(ipapolicy_dict.get('ipagroupobjectclasses',''), str):
ipapolicy_dict['ipagroupobjectclasses'] = [ipapolicy_dict.get('ipagroupobjectclasses')]
ipapolicy_dict['groupobjectclasses'] = ipahelper.setup_mv_fields(ipapolicy_dict.get('ipagroupobjectclasses'), 'ipagroupobjectclasses')
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))
@ -88,6 +98,10 @@ class IPAPolicyController(IPAController):
turbogears.flash("Edit policy cancelled")
raise turbogears.redirect('/ipapolicy/show')
# Fix incoming multi-valued fields we created for the form
kw = ipahelper.fix_incoming_fields(kw, 'ipauserobjectclasses', 'userobjectclasses')
kw = ipahelper.fix_incoming_fields(kw, 'ipagroupobjectclasses', 'groupobjectclasses')
tg_errors, kw = self.ipapolicyupdatevalidate(**kw)
if tg_errors:
turbogears.flash("There were validation errors.<br/>" +
@ -132,6 +146,15 @@ class IPAPolicyController(IPAController):
if new_ipapolicy.ipadefaultprimarygroup != kw.get('ipadefaultprimarygroup'):
policy_modified = True
new_ipapolicy.setValue('ipadefaultprimarygroup', kw.get('ipadefaultprimarygroup'))
if new_ipapolicy.ipauserobjectclasses != kw.get('ipauserobjectclasses'):
policy_modified = True
new_ipapolicy.setValue('ipauserobjectclasses', kw.get('ipauserobjectclasses'))
if new_ipapolicy.ipagroupobjectclasses != kw.get('ipagroupobjectclasses'):
policy_modified = True
new_ipapolicy.setValue('ipagroupobjectclasses', kw.get('ipagroupobjectclasses'))
if new_ipapolicy.ipadefaultemaildomain != kw.get('ipadefaultemaildomain'):
policy_modified = True
new_ipapolicy.setValue('ipadefaultemaildomain', kw.get('ipadefaultemaildomain'))
if policy_modified:
rv = client.update_ipa_config(new_ipapolicy)

View File

@ -0,0 +1,153 @@
import os
from pickle import dumps, loads
from base64 import b64encode, b64decode
import copy
import logging
import cherrypy
import turbogears
from turbogears import controllers, expose, flash
from turbogears import validators, validate
from turbogears import widgets, paginate
from turbogears import error_handler
from turbogears import identity
from ipacontroller import IPAController
from ipa.entity import utf8_encode_values
from ipa import ipaerror
import ipagui.forms.principal
import ldap.dn
log = logging.getLogger(__name__)
principal_new_form = ipagui.forms.principal.PrincipalNewForm()
principal_fields = ['*']
class PrincipalController(IPAController):
@expose()
@identity.require(identity.in_group("admins"))
def index(self, tg_errors=None):
raise turbogears.redirect("/principal/list")
@expose("ipagui.templates.principalnew")
@identity.require(identity.in_group("admins"))
def new(self, tg_errors=None):
"""Displays the new service principal form"""
if tg_errors:
turbogears.flash("There were validation errors.<br/>" +
"Please see the messages below for details.")
client = self.get_ipaclient()
return dict(form=principal_new_form, principal={})
@expose()
@identity.require(identity.in_group("admins"))
def create(self, **kw):
"""Creates a service principal group"""
self.restrict_post()
client = self.get_ipaclient()
if kw.get('submit') == 'Cancel':
turbogears.flash("Add principal cancelled")
raise turbogears.redirect('/')
tg_errors, kw = self.principalcreatevalidate(**kw)
if tg_errors:
turbogears.flash("There were validation errors.<br/>" +
"Please see the messages below for details.")
return dict(form=principal_new_form, principal=kw,
tg_template='ipagui.templates.principalnew')
principal_name = ""
hostname = kw.get('hostname')
#
# Create the principal itself
#
try:
if kw.get('service') == "other":
service = kw.get('other')
if not service:
turbogears.flash("Service type must be provided")
return dict(form=principal_new_form, principal=kw,
tg_template='ipagui.templates.principalnew')
else:
service = kw.get('service')
# The realm is added by add_service_principal
principal_name = utf8_encode_values(service + "/" + kw.get('hostname'))
rv = client.add_service_principal(principal_name)
except ipaerror.exception_for(ipaerror.LDAP_DUPLICATE):
turbogears.flash("Service principal '%s' already exists" %
principal_name)
return dict(form=principal_new_form, principal=kw,
tg_template='ipagui.templates.principalnew')
except ipaerror.IPAError, e:
turbogears.flash("Service principal add failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
return dict(form=principal_new_form, principal=kw,
tg_template='ipagui.templates.principalnew')
turbogears.flash("%s added!" % principal_name)
raise turbogears.redirect('/principal/list', hostname=hostname)
@expose("ipagui.templates.principallist")
@identity.require(identity.not_anonymous())
def list(self, **kw):
"""Searches for service principals and displays list of results"""
client = self.get_ipaclient()
principals = None
counter = 0
hostname = kw.get('hostname')
if hostname != None and len(hostname) > 0:
try:
principals = client.find_service_principal(hostname.encode('utf-8'), principal_fields, 0, 2)
counter = principals[0]
principals = principals[1:]
if counter == -1:
turbogears.flash("These results are truncated.<br />" +
"Please refine your search and try again.")
# For each entry break out service type and hostname
for i in range(len(principals)):
(service,host) = principals[i].krbprincipalname.split('/')
h = host.split('@')
principals[i].setValue('service', service)
principals[i].setValue('hostname', h[0])
except ipaerror.IPAError, e:
turbogears.flash("principal list failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
raise turbogears.redirect("/principal/list")
return dict(principals=principals, hostname=hostname, fields=ipagui.forms.principal.PrincipalFields())
@expose()
@identity.require(identity.not_anonymous())
def show(self, **kw):
"""Returns the keytab for a given principal"""
client = self.get_ipaclient()
principal = kw.get('principal')
if principal != None and len(principal) > 0:
try:
p = principal.split('@')
keytab = client.get_keytab(p[0].encode('utf-8'))
cherrypy.response.headers['Content-Type'] = "application/x-download"
cherrypy.response.headers['Content-Disposition'] = 'attachment; filename=krb5.keytab'
cherrypy.response.headers['Content-Length'] = len(keytab)
cherrypy.response.body = keytab
return cherrypy.response.body
except ipaerror.IPAError, e:
turbogears.flash("keytab retrieval failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
raise turbogears.redirect("/principal/list")
raise turbogears.redirect("/principal/list")
@validate(form=principal_new_form)
@identity.require(identity.not_anonymous())
def principalcreatevalidate(self, tg_errors=None, **kw):
return tg_errors, kw

View File

@ -18,6 +18,7 @@ from ipa.entity import utf8_encode_values
from ipa import ipaerror
import ipagui.forms.user
import ipa.config
from ipagui.helpers import ipahelper
log = logging.getLogger(__name__)
@ -28,14 +29,20 @@ user_edit_form = ipagui.forms.user.UserEditForm()
user_fields = ['*', 'nsAccountLock']
email_domain = ipa.config.config.default_realm.lower()
class UserController(IPAController):
def __init__(self, *args, **kw):
super(UserController,self).__init__(*args, **kw)
# self.load_custom_fields()
def get_email_domain(self):
client = self.get_ipaclient()
conf = client.get_ipa_config()
email_domain = conf.ipadefaultemaildomain
return email_domain
def load_custom_fields(self):
client = self.get_ipaclient()
@ -83,36 +90,6 @@ 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")
@ -142,12 +119,12 @@ class UserController(IPAController):
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')
kw = ipahelper.fix_incoming_fields(kw, 'cn', 'cns')
kw = ipahelper.fix_incoming_fields(kw, 'telephonenumber', 'telephonenumbers')
kw = ipahelper.fix_incoming_fields(kw, 'facsimiletelephonenumber', 'facsimiletelephonenumbers')
kw = ipahelper.fix_incoming_fields(kw, 'mobile', 'mobiles')
kw = ipahelper.fix_incoming_fields(kw, 'pager', 'pagers')
kw = ipahelper.fix_incoming_fields(kw, 'homephone', 'homephones')
if tg_errors:
turbogears.flash("There were validation errors.<br/>" +
@ -325,32 +302,34 @@ class UserController(IPAController):
# 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')
user_dict['cns'] = ipahelper.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')
user_dict['telephonenumber'] = [user_dict.get('telephonenumber')]
user_dict['telephonenumbers'] = ipahelper.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')
user_dict['facsimiletelephonenumber'] = [user_dict.get('facsimiletelephonenumber')]
user_dict['facsimiletelephonenumbers'] = ipahelper.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')
user_dict['mobile'] = [user_dict.get('mobile')]
user_dict['mobiles'] = ipahelper.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')
user_dict['pager'] = [user_dict.get('pager')]
user_dict['pagers'] = ipahelper.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')
user_dict['homephone'] = [user_dict.get('homephone')]
user_dict['homephones'] = ipahelper.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'])
user_dict['uid_hidden'] = user_dict.get('uid')
user_groups = client.get_groups_by_member(user.dn, ['dn', 'cn'])
user_groups.sort(self.sort_by_cn)
user_groups_dicts = map(lambda group: group.toDict(), user_groups)
@ -398,17 +377,20 @@ class UserController(IPAController):
self.restrict_post()
client = self.get_ipaclient()
if not kw.get('uid'):
kw['uid'] = kw.get('uid_hidden')
if kw.get('submit') == 'Cancel Edit':
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')
kw = ipahelper.fix_incoming_fields(kw, 'cn', 'cns')
kw = ipahelper.fix_incoming_fields(kw, 'telephonenumber', 'telephonenumbers')
kw = ipahelper.fix_incoming_fields(kw, 'facsimiletelephonenumber', 'facsimiletelephonenumbers')
kw = ipahelper.fix_incoming_fields(kw, 'mobile', 'mobiles')
kw = ipahelper.fix_incoming_fields(kw, 'pager', 'pagers')
kw = ipahelper.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
@ -430,6 +412,12 @@ class UserController(IPAController):
user_groups=user_groups_dicts,
tg_template='ipagui.templates.useredit')
# We don't want to inadvertantly add this to a record
try:
del kw['uid_hidden']
except KeyError:
pass
password_change = False
user_modified = False
@ -488,6 +476,7 @@ class UserController(IPAController):
new_user.setValue('uidnumber', str(kw.get('uidnumber')))
new_user.setValue('gidnumber', str(kw.get('gidnumber')))
new_user.setValue('homedirectory', str(kw.get('homedirectory')))
new_user.setValue('uid', str(kw.get('uid')))
for custom_field in user_edit_form.custom_fields:
new_user.setValue(custom_field.name,
@ -750,13 +739,13 @@ class UserController(IPAController):
givenname = givenname.lower()
sn = sn.lower()
email = "%s.%s@%s" % (givenname, sn, email_domain)
email = "%s.%s@%s" % (givenname, sn, self.get_email_domain())
try:
client.get_user_by_email(email)
except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
return email
email = "%s@%s" % (self.suggest_uid(givenname, sn), email_domain)
email = "%s@%s" % (self.suggest_uid(givenname, sn), self.get_email_domain())
try:
client.get_user_by_email(email)
except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):

View File

@ -27,6 +27,10 @@ app_DATA = \
master.kid \
policyindex.kid \
policylayout.kid \
principallayout.kid \
principallist.kid \
principalnewform.kid \
principalnew.kid \
usereditform.kid \
useredit.kid \
userlayout.kid \

View File

@ -180,4 +180,10 @@
</script>
</form>
<script type="text/javascript">
document.getElementById("form_name").focus();
</script>
</div>

View File

@ -97,6 +97,10 @@ from ipagui.helpers import ipahelper
</form>
<script type="text/javascript">
document.getElementById("form_cn").focus();
</script>
<script type="text/javascript">
/*
* This section restores the contents of the add and remove lists

View File

@ -15,6 +15,8 @@ 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>
<div py:for="field in hidden_fields"
py:replace="field.display(value_for(field), **params_for(field))"
@ -170,7 +172,90 @@ from ipagui.helpers import ipahelper
py:content="tg.errors.get('ipadefaultprimarygroup')" />
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="ipapolicy_fields.ipadefaultemaildomain.label" />:
</th>
<td>
<span py:replace="ipapolicy_fields.ipadefaultemaildomain.display(value_for(ipapolicy_fields.ipadefaultemaildomain))" />
<span py:if="tg.errors.get('ipadefaultemaildomain')" class="fielderror"
py:content="tg.errors.get('ipadefaultemaildomain')" />
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" for="${ipapolicy_fields.userobjectclasses.field_id}"
py:content="ipapolicy_fields.userobjectclasses.label" />:
</th>
<td colspan="3">
<table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${ipapolicy_fields.userobjectclasses.field_id}">
<tbody>
<?python repetition = 0
fld_index = 0
fld_error = tg.errors.get('ipauserobjectclasses')
?>
<tr py:for="fld in value_for(ipapolicy_fields.ipauserobjectclasses)"
id="${ipapolicy_fields.userobjectclasses.field_id}_${repetition}"
class="${ipapolicy_fields.userobjectclasses.field_class}">
<td py:for="field in ipapolicy_fields.userobjectclasses.fields">
<span><input class="textfield" type="text" id="${ipapolicy_fields.userobjectclasses.field_id}_${repetition}_ipauserobjectclasses" name="userobjectclasses-${repetition}.ipauserobjectclasses" value="${fld}"/></span>
<span py:if="fld_error and fld_error[fld_index]" class="fielderror"
py:content="tg.errors.get('ipauserobjectclasses')" />
</td>
<?python fld_index = fld_index + 1 ?>
<td>
<a
href="javascript:ExpandingForm.removeItem('${ipapolicy_fields.userobjectclasses.field_id}_${repetition}')">Remove</a>
</td>
<?python repetition = repetition + 1?>
</tr>
</tbody>
</table>
<a id="${ipapolicy_fields.userobjectclasses.field_id}_doclink" href="javascript:ExpandingForm.addItem('${ipapolicy_fields.userobjectclasses.field_id}');">Add User Object Class</a>
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" for="${ipapolicy_fields.groupobjectclasses.field_id}"
py:content="ipapolicy_fields.groupobjectclasses.label" />:
</th>
<td colspan="3">
<table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${ipapolicy_fields.groupobjectclasses.field_id}">
<tbody>
<?python repetition = 0
fld_index = 0
fld_error = tg.errors.get('ipagroupobjectclasses')
?>
<tr py:for="fld in value_for(ipapolicy_fields.ipagroupobjectclasses)"
id="${ipapolicy_fields.groupobjectclasses.field_id}_${repetition}"
class="${ipapolicy_fields.groupobjectclasses.field_class}">
<td py:for="field in ipapolicy_fields.groupobjectclasses.fields">
<span><input class="textfield" type="text" id="${ipapolicy_fields.groupobjectclasses.field_id}_${repetition}_ipagroupobjectclasses" name="groupobjectclasses-${repetition}.ipagroupobjectclasses" value="${fld}"/></span>
<span py:if="fld_error and fld_error[fld_index]" class="fielderror"
py:content="tg.errors.get('ipagroupobjectclasses')" />
</td>
<?python fld_index = fld_index + 1 ?>
<td>
<a
href="javascript:ExpandingForm.removeItem('${ipapolicy_fields.groupobjectclasses.field_id}_${repetition}')">Remove</a>
</td>
<?python repetition = repetition + 1?>
</tr>
</tbody>
</table>
<a id="${ipapolicy_fields.groupobjectclasses.field_id}_doclink" href="javascript:ExpandingForm.addItem('${ipapolicy_fields.groupobjectclasses.field_id}');">Add Group Object Class</a>
</td>
</tr>
</table>
<hr/>
<input type="submit" class="submitbutton" name="submit"
value="Update Policy"/>
<input type="submit" class="submitbutton" name="submit"
value="Cancel Edit" />
</form>
</div>

View File

@ -15,6 +15,9 @@ 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>
<input class="submitbutton" type="button"
onclick="document.location.href='${edit_url}'"
value="Edit Policy" />
<h2 class="formsection">Search</h2>
<table class="formtable" cellpadding="2" cellspacing="0" border="0">
@ -109,6 +112,52 @@ edit_url = tg.url('/ipapolicy/edit')
</th>
<td>${ipapolicy.get("ipadefaultprimarygroup")}</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="fields.ipadefaultemaildomain.label" />:
</th>
<td>${ipapolicy.get("ipadefaultemaildomain")}</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="fields.ipauserobjectclasses.label" />:
</th>
<td>
<table cellpadding="2" cellspacing="0" border="0">
<tbody>
<?python
index = 0
values = ipapolicy.get("ipauserobjectclasses", '')
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.ipagroupobjectclasses.label" />:
</th>
<td>
<table cellpadding="2" cellspacing="0" border="0">
<tbody>
<?python
index = 0
values = ipapolicy.get("ipagroupobjectclasses", '')
if isinstance(values, str):
values = [values]
?>
<tr py:for="index in range(len(values))">
<td>${values[index]}</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
<hr />
<input class="submitbutton" type="button"

View File

@ -78,10 +78,14 @@
<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('/principal/new')}">Add Service Principal</a></li>
<li><a href="${tg.url('/principal/list')}">Find Service Principal</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('/user/edit/', principal=tg.identity.user.display_name)}">Self Service</a></li>
<li py:if="not tg.identity.anonymous"><a href="${tg.url('/user/edit/', principal=tg.identity.user.display_name)}">Self Service</a></li>
</ul>
<ul py:if="'admins' in tg.identity.groups">
<li><a href="${tg.url('/delegate/list')}">Delegations</a></li>

View File

@ -0,0 +1,19 @@
<!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>

View File

@ -0,0 +1,64 @@
<!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="'principallayout.kid'">
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/>
<title>Find Service Principals</title>
</head>
<body>
<h1>Find Service Principals</h1>
<script type="text/javascript" charset="utf-8" src="${tg.url('/static/javascript/tablekit.js')}"></script>
<div id="search">
<form action="${tg.url('/principal/list')}" method="get">
<input id="hostname" type="text" name="hostname" value="${hostname}" />
<input class="searchbutton" type="submit" value="Find Hosts"/>
</form>
<script type="text/javascript">
document.getElementById("hostname").focus();
</script>
</div>
<div py:if='(principals != None) and (len(principals) > 0)'>
<h2>${len(principals)} results returned:</h2>
<table id="resultstable" class="details sortable resizable" cellspacing="0">
<thead>
<tr>
<th>
Hostname
</th>
<th>
Service
</th>
</tr>
</thead>
<tbody>
<tr py:for="principal in principals">
<td>
<a href="${tg.url('/principal/show',principal=principal.krbprincipalname)}"
>${principal.hostname}</a>
</td>
<td>
${principal.service}
</td>
</tr>
</tbody>
</table>
</div>
<div id="alertbox" py:if='(principals != None) and (len(principals) == 0)'>
<p id="alertbox">No results found for "${hostname}"</p>
</div>
<div class="instructions" py:if='principals == None'>
<p>
Exact matches are listed first, followed by partial matches. If your search
is too broad, you will get a warning that the search returned too many
results. Try being more specific.
</p>
<p>
The results that come back are sortable. Simply click on a column
header to sort on that header. A triangle will indicate the sorted
column, along with its direction. Clicking and dragging between headers
will allow you to resize the header.
</p>
</div>
</body>
</html>

View File

@ -0,0 +1,13 @@
<!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="'principallayout.kid'">
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/>
<title>Add Service Principal</title>
</head>
<body>
<h1>Add Service Principal</h1>
${form.display(action=tg.url('/principal/create'), value=principal)}
</body>
</html>

View File

@ -0,0 +1,102 @@
<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="Add Principal"/>
<?python
from ipagui.helpers import ipahelper
?>
<script type="text/javascript" charset="utf-8"
src="${tg.url('/static/javascript/dynamicedit.js')}"></script>
<?python searchurl = tg.url('/principal/edit_search') ?>
<script type="text/javascript">
function toggleOther(field) {
otherField = document.getElementById('form_other');
var e=document.getElementById(field).value;
if ( e == "other") {
otherField.disabled = false;
} else {
otherField.disabled =true;
}
}
function doSearch() {
$('searchresults').update("Searching...");
new Ajax.Updater('searchresults',
'${searchurl}',
{ asynchronous:true,
parameters: { criteria: $('criteria').value },
evalScripts: true });
return false;
}
</script>
<div py:for="field in hidden_fields"
py:replace="field.display(value_for(field), **params_for(field))"
/>
<h2 class="formsection">Service Principal Details</h2>
<table class="formtable" cellpadding="2" cellspacing="0" border="0">
<tr>
<th>
<label class="fieldlabel" for="${principal_fields.hostname.field_id}"
py:content="principal_fields.hostname.label" />:
</th>
<td>
<span py:replace="principal_fields.hostname.display(value_for(principal_fields.hostname))" />
<span py:if="tg.errors.get('hostname')" class="fielderror"
py:content="tg.errors.get('hostname')" />
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" for="${principal_fields.service.field_id}"
py:content="principal_fields.service.label" />:
</th>
<td>
<span py:replace="principal_fields.service.display(value_for(principal_fields.service))" />
<span py:if="tg.errors.get('service')" class="fielderror"
py:content="tg.errors.get('service')" />
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" for="${principal_fields.other.field_id}"
py:content="principal_fields.other.label" />:
</th>
<td>
<span py:replace="principal_fields.other.display(value_for(principal_fields.other))" />
<span py:if="tg.errors.get('other')" class="fielderror"
py:content="tg.errors.get('other')" />
<script type="text/javascript">
var e=document.getElementById('form_service').value;
if ( e != "other") {
document.getElementById('form_other').disabled = true;
}
</script>
</td>
</tr>
</table>
<hr />
<input type="submit" class="submitbutton" name="submit" value="Add Principal"/>
</form>
<script type="text/javascript">
document.getElementById("form_hostname").focus();
</script>
</div>

View File

@ -38,12 +38,14 @@ from ipagui.helpers import ipahelper
function toggleProtectedFields(checkbox) {
passwordField = document.getElementById('form_userpassword');
passwordConfirmField = document.getElementById('form_userpassword_confirm');
uidField = document.getElementById('form_uid');
uidnumberField = document.getElementById('form_uidnumber');
gidnumberField = document.getElementById('form_gidnumber');
homedirectoryField = document.getElementById('form_homedirectory');
if (checkbox.checked) {
passwordField.disabled = false;
passwordConfirmField.disabled = false;
uidField.disabled = false;
uidnumberField.disabled = false;
gidnumberField.disabled = false;
homedirectoryField.disabled = false;
@ -51,6 +53,7 @@ from ipagui.helpers import ipahelper
} else {
passwordField.disabled = true;
passwordConfirmField.disabled = true;
uidField.disabled = true;
uidnumberField.disabled = true;
gidnumberField.disabled = true;
homedirectoryField.disabled = true;
@ -58,6 +61,13 @@ from ipagui.helpers import ipahelper
}
}
function warnRDN() {
if (confirm("Are you sure you want to change the login name?<br/>This can have unexpected results. A password change is required.")) {
return true;
}
return false;
}
function doSearch() {
$('searchresults').update("Searching...");
new Ajax.Updater('searchresults',
@ -215,13 +225,21 @@ from ipagui.helpers import ipahelper
py:content="tg.errors.get('nsAccountLock')" />
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" for="${user_fields.uid.field_id}"
py:content="user_fields.uid.label" />:
</th>
<td>
${value_for(user_fields.uid)}
<span py:replace="user_fields.uid.display(
value_for(user_fields.uid))" />
<span py:if="tg.errors.get('uid')" class="fielderror"
py:content="tg.errors.get('uid')" />
<script type="text/javascript">
document.getElementById('form_uid').disabled = true;
</script>
</td>
</tr>

View File

@ -790,6 +790,10 @@ from ipagui.helpers import ipahelper
</form>
<script type="text/javascript">
document.getElementById("form_title").focus();
</script>
<script type="text/javascript">
/*
* This section restores the contents of the add and remove lists

View File

@ -11,7 +11,7 @@ edit_url = tg.url('/user/edit', uid=user.get('uid'))
?>
<h1>View User</h1>
<input py:if="'editors' in tg.identity.groups or 'admins' in tg.identity.groups"
<input py:if="'editors' in tg.identity.groups or 'admins' in tg.identity.groups or tg.identity.display_name == user.get('uid')"
class="submitbutton" type="button"
onclick="document.location.href='${edit_url}'"
value="Edit User" />
@ -374,7 +374,7 @@ else:
<br/>
<hr />
<input py:if="'editors' in tg.identity.groups or 'admins' in tg.identity.groups"
<input py:if="'editors' in tg.identity.groups or 'admins' in tg.identity.groups or tg.identity.display_name == user.get('uid')"
class="submitbutton" type="button"
onclick="document.location.href='${edit_url}'"
value="Edit User" />

View File

@ -226,7 +226,7 @@ def read_dm_password():
print "Certain directory server operations require an administrative user."
print "This user is referred to as the Directory Manager and has full access"
print "to the Directory for system management tasks."
print "The password must be at least 8 characters long, and contain no spaces."
print "The password must be at least 8 characters long."
print ""
#TODO: provide the option of generating a random password
dm_password = read_password("Directory Manager")
@ -288,7 +288,7 @@ def main():
if options.setup_bind:
if not bind.check_inst():
print "--setup-bind was specified but bind is not installed on the system"
print "Please install bind (you also need the package 'caching-nameserver') and restart the setup program"
print "Please install bind (you may also need the package 'caching-nameserver') and restart the setup program"
return "-Fatal Error-"
# check the hostname is correctly configured, it must be as the kldap

View File

@ -27,11 +27,16 @@ attributetypes: ( 2.16.840.1.113730.3.8.1.7 NAME 'ipaDefaultLoginShell' EQUALITY
attributetypes: ( 2.16.840.1.113730.3.8.1.8 NAME 'ipaDefaultPrimaryGroup' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE)
## ipaMaxUsernameLength - maximum username length to allow in the UI
attributetypes: ( 2.16.840.1.113730.3.8.1.9 NAME 'ipaMaxUsernameLength' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE)
## ipaPwdExpAdvNotify - time in days to send out paswwrod expiration notification before passwpord actually expires
## ipaPwdExpAdvNotify - time in days to send out paswword expiration notification before passwpord actually expires
attributetypes: ( 2.16.840.1.113730.3.8.1.10 NAME 'ipaPwdExpAdvNotify' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE)
# ipaUserObjectClasses - required objectclasses for users
attributetypes: ( 2.16.840.1.113730.3.8.1.11 NAME 'ipaUserObjectClasses' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15)
# ipaGroupObjectClasses - required objectclasses for groups
attributetypes: ( 2.16.840.1.113730.3.8.1.12 NAME 'ipaGroupObjectClasses' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15)
attributetypes: ( 2.16.840.1.113730.3.8.1.13 NAME 'ipaDefaultEmailDomain' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15)
###############################################
##
## ObjectClasses
##
## ipaGuiConfig - GUI config parameters objectclass
objectClasses: ( 2.16.840.1.113730.3.8.2.1 NAME 'ipaGuiConfig' AUXILIARY MAY ( ipaUserSearchFields $ ipaGroupSearchFields $ ipaSearchTimeLimit $ ipaSearchRecordsLimit $ ipaCustomFields $ ipaHomesRootDir $ ipaDefaultLoginShell $ ipaDefaultPrimaryGroup $ ipaMaxUsernameLength $ ipaPwdExpAdvNotify ) )
objectClasses: ( 2.16.840.1.113730.3.8.2.1 NAME 'ipaGuiConfig' AUXILIARY MAY ( ipaUserSearchFields $ ipaGroupSearchFields $ ipaSearchTimeLimit $ ipaSearchRecordsLimit $ ipaCustomFields $ ipaHomesRootDir $ ipaDefaultLoginShell $ ipaDefaultPrimaryGroup $ ipaMaxUsernameLength $ ipaPwdExpAdvNotify $ ipaUserObjectClasses $ ipaGroupObjectClasses $ ipaDefaultEmailDomain) )

View File

@ -147,6 +147,19 @@ ipaDefaultLoginShell: /bin/sh
ipaDefaultPrimaryGroup: ipausers
ipaMaxUsernameLength: 8
ipaPwdExpAdvNotify: 4
ipaGroupObjectClasses: top
ipaGroupObjectClasses: groupofnames
ipaGroupObjectClasses: posixGroup
ipaGroupObjectClasses: inetUser
ipaUserObjectClasses: top
ipaUserObjectClasses: person
ipaUserObjectClasses: organizationalPerson
ipaUserObjectClasses: inetOrgPerson
ipaUserObjectClasses: inetUser
ipaUserObjectClasses: posixAccount
ipaUserObjectClasses: krbPrincipalAux
ipaUserObjectClasses: radiusprofile
ipaDefaultEmailDomain: $DOMAIN
dn: cn=account inactivation,cn=accounts,$SUFFIX
changetype: add

View File

@ -664,6 +664,8 @@ void handle_krb_packets(uint8_t *buf, ssize_t buflen,
auth_context = NULL;
krep.length = 0;
krep.data = NULL;
kdec.length = 0;
kdec.data = NULL;
kprincpw = NULL;
context = NULL;
ticket = NULL;
@ -859,6 +861,7 @@ void handle_krb_packets(uint8_t *buf, ssize_t buflen,
/* make sure password is cleared off before we free the memory */
memset(kdec.data, 0, kdec.length);
free(kdec.data);
kdec.length = 0;
kpreply:
@ -867,6 +870,7 @@ kpreply:
kdec.data = malloc(kdec.length);
if (!kdec.data) {
syslog(LOG_ERR, "Out of memory!");
kdec.length = 0;
goto done;
}

View File

@ -15,6 +15,7 @@ BuildRequires: openssl-devel
BuildRequires: openldap-devel
BuildRequires: krb5-devel
BuildRequires: nss-devel
BuildRequires: libcap-devel
Requires: ipa-python
Requires: ipa-admintools
@ -38,6 +39,7 @@ Requires: python-tgexpandingformwidget
Requires: acl
Requires: freeradius
Requires: pyasn1
Requires: libcap
%define httpd_conf /etc/httpd/conf.d
%define plugin_dir %{_libdir}/dirsrv/plugins

View File

@ -11,6 +11,7 @@ INCLUDES = \
$(MOZLDAP_CFLAGS) \
$(LDAP_CFLAGS) \
$(KRB5_CFLAGS) \
$(SSL_CFLAGS) \
$(WARN_CFLAGS) \
$(NULL)
@ -27,6 +28,7 @@ libipa_pwd_extop_la_LDFLAGS = -avoid-version
libipa_pwd_extop_la_LIBADD = \
$(KRB5_LIBS) \
$(SSL_LIBS) \
$(MOZLDAP_LIBS) \
$(NULL)

View File

@ -18,16 +18,15 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
import subprocess
import string
import tempfile
import shutil
import logging
import pwd
import glob
import sys
import os
from ipa import ipautil
from ipa.ipautil import *
import service
import installutils
@ -36,7 +35,7 @@ SERVER_ROOT_32 = "/usr/lib/dirsrv"
def ldap_mod(fd, dn, pwd):
args = ["/usr/bin/ldapmodify", "-h", "127.0.0.1", "-xv", "-D", dn, "-w", pwd, "-f", fd.name]
run(args)
ipautil.run(args)
def realm_to_suffix(realm_name):
s = realm_name.split(".")
@ -44,7 +43,7 @@ def realm_to_suffix(realm_name):
return ",".join(terms)
def find_server_root():
if dir_exists(SERVER_ROOT_64):
if ipautil.dir_exists(SERVER_ROOT_64):
return SERVER_ROOT_64
else:
return SERVER_ROOT_32
@ -83,7 +82,7 @@ def check_existing_installation():
sys.exit(1)
try:
run(["/sbin/service", "dirsrv", "stop"])
ipautil.run(["/sbin/service", "dirsrv", "stop"])
except:
pass
for d in dirs:
@ -126,6 +125,7 @@ class DsInstance(service.Service):
self.host_name = None
self.dm_password = None
self.sub_dict = None
self.domain = None
def create_instance(self, ds_user, realm_name, host_name, dm_password, ro_replica=False):
self.ds_user = ds_user
@ -134,6 +134,7 @@ class DsInstance(service.Service):
self.suffix = realm_to_suffix(self.realm_name)
self.host_name = host_name
self.dm_password = dm_password
self.domain = host_name[host_name.find(".")+1:]
self.__setup_sub_dict()
if ro_replica:
@ -174,7 +175,7 @@ class DsInstance(service.Service):
self.sub_dict = dict(FQHN=self.host_name, SERVERID=self.serverid,
PASSWORD=self.dm_password, SUFFIX=self.suffix.lower(),
REALM=self.realm_name, USER=self.ds_user,
SERVER_ROOT=server_root)
SERVER_ROOT=server_root, DOMAIN=self.domain)
def __create_ds_user(self):
self.step("creating directory server user")
@ -185,25 +186,25 @@ class DsInstance(service.Service):
logging.debug("adding ds user %s" % self.ds_user)
args = ["/usr/sbin/useradd", "-c", "DS System User", "-d", "/var/lib/dirsrv", "-M", "-r", "-s", "/sbin/nologin", self.ds_user]
try:
run(args)
ipautil.run(args)
logging.debug("done adding user")
except ipautil.CalledProcessError, e:
logging.critical("failed to add user %s" % e)
def __create_instance(self):
self.step("creating directory server instance")
inf_txt = template_str(INF_TEMPLATE, self.sub_dict)
inf_txt = ipautil.template_str(INF_TEMPLATE, self.sub_dict)
logging.debug(inf_txt)
inf_fd = write_tmp_file(inf_txt)
inf_fd = ipautil.write_tmp_file(inf_txt)
logging.debug("writing inf template")
if file_exists("/usr/sbin/setup-ds.pl"):
if ipautil.file_exists("/usr/sbin/setup-ds.pl"):
args = ["/usr/sbin/setup-ds.pl", "--silent", "--logfile", "-", "-f", inf_fd.name]
logging.debug("calling setup-ds.pl")
else:
args = ["/usr/bin/ds_newinst.pl", inf_fd.name]
logging.debug("calling ds_newinst.pl")
try:
run(args)
ipautil.run(args)
logging.debug("completed creating ds instance")
except ipautil.CalledProcessError, e:
logging.critical("failed to restart ds instance %s" % e)
@ -217,19 +218,19 @@ class DsInstance(service.Service):
def __add_default_schemas(self):
self.step("adding default schema")
shutil.copyfile(SHARE_DIR + "60kerberos.ldif",
shutil.copyfile(ipautil.SHARE_DIR + "60kerberos.ldif",
schema_dirname(self.realm_name) + "60kerberos.ldif")
shutil.copyfile(SHARE_DIR + "60samba.ldif",
shutil.copyfile(ipautil.SHARE_DIR + "60samba.ldif",
schema_dirname(self.realm_name) + "60samba.ldif")
shutil.copyfile(SHARE_DIR + "60radius.ldif",
shutil.copyfile(ipautil.SHARE_DIR + "60radius.ldif",
schema_dirname(self.realm_name) + "60radius.ldif")
shutil.copyfile(SHARE_DIR + "60ipaconfig.ldif",
shutil.copyfile(ipautil.SHARE_DIR + "60ipaconfig.ldif",
schema_dirname(self.realm_name) + "60ipaconfig.ldif")
def __add_memberof_module(self):
self.step("enabling memboerof plugin")
memberof_txt = template_file(SHARE_DIR + "memberof-conf.ldif", self.sub_dict)
memberof_fd = write_tmp_file(memberof_txt)
memberof_txt = ipautil.template_file(ipautil.SHARE_DIR + "memberof-conf.ldif", self.sub_dict)
memberof_fd = ipautil.write_tmp_file(memberof_txt)
try:
ldap_mod(memberof_fd, "cn=Directory Manager", self.dm_password)
except ipautil.CalledProcessError, e:
@ -238,8 +239,8 @@ class DsInstance(service.Service):
def __init_memberof(self):
self.step("initializing group membership")
memberof_txt = template_file(SHARE_DIR + "memberof-task.ldif", self.sub_dict)
memberof_fd = write_tmp_file(memberof_txt)
memberof_txt = ipautil.template_file(ipautil.SHARE_DIR + "memberof-task.ldif", self.sub_dict)
memberof_fd = ipautil.write_tmp_file(memberof_txt)
try:
ldap_mod(memberof_fd, "cn=Directory Manager", self.dm_password)
except ipautil.CalledProcessError, e:
@ -248,8 +249,8 @@ class DsInstance(service.Service):
def __add_referint_module(self):
self.step("enabling referential integrity plugin")
referint_txt = template_file(SHARE_DIR + "referint-conf.ldif", self.sub_dict)
referint_fd = write_tmp_file(referint_txt)
referint_txt = ipautil.template_file(ipautil.SHARE_DIR + "referint-conf.ldif", self.sub_dict)
referint_fd = ipautil.write_tmp_file(referint_txt)
try:
ldap_mod(referint_fd, "cn=Directory Manager", self.dm_password)
except ipautil.CalledProcessError, e:
@ -258,8 +259,8 @@ class DsInstance(service.Service):
def __add_dna_module(self):
self.step("enabling distributed numeric assignment plugin")
dna_txt = template_file(SHARE_DIR + "dna-conf.ldif", self.sub_dict)
dna_fd = write_tmp_file(dna_txt)
dna_txt = ipautil.template_file(ipautil.SHARE_DIR + "dna-conf.ldif", self.sub_dict)
dna_fd = ipautil.write_tmp_file(dna_txt)
try:
ldap_mod(dna_fd, "cn=Directory Manager", self.dm_password)
except ipautil.CalledProcessError, e:
@ -268,8 +269,8 @@ class DsInstance(service.Service):
def __config_uidgid_gen_first_master(self):
self.step("configuring Posix uid/gid generation as first master")
dna_txt = template_file(SHARE_DIR + "dna-posix.ldif", self.sub_dict)
dna_fd = write_tmp_file(dna_txt)
dna_txt = ipautil.template_file(ipautil.SHARE_DIR + "dna-posix.ldif", self.sub_dict)
dna_fd = ipautil.write_tmp_file(dna_txt)
try:
ldap_mod(dna_fd, "cn=Directory Manager", self.dm_password)
except ipautil.CalledProcessError, e:
@ -278,8 +279,8 @@ class DsInstance(service.Service):
def __add_master_entry_first_master(self):
self.step("adding master entry as first master")
master_txt = template_file(SHARE_DIR + "master-entry.ldif", self.sub_dict)
master_fd = write_tmp_file(master_txt)
master_txt = ipautil.template_file(ipautil.SHARE_DIR + "master-entry.ldif", self.sub_dict)
master_fd = ipautil.write_tmp_file(master_txt)
try:
ldap_mod(master_fd, "cn=Directory Manager", self.dm_password)
except ipautil.CalledProcessError, e:
@ -292,20 +293,20 @@ class DsInstance(service.Service):
args = ["/usr/share/ipa/ipa-server-setupssl", self.dm_password,
dirname, self.host_name]
try:
run(args)
ipautil.run(args)
logging.debug("done configuring ssl for ds instance")
except ipautil.CalledProcessError, e:
logging.critical("Failed to configure ssl in ds instance %s" % e)
def __add_default_layout(self):
self.step("adding default layout")
txt = template_file(SHARE_DIR + "bootstrap-template.ldif", self.sub_dict)
inf_fd = write_tmp_file(txt)
txt = ipautil.template_file(ipautil.SHARE_DIR + "bootstrap-template.ldif", self.sub_dict)
inf_fd = ipautil.write_tmp_file(txt)
logging.debug("adding default dfrom ipa.ipautil import *s layout")
args = ["/usr/bin/ldapmodify", "-xv", "-D", "cn=Directory Manager",
"-w", self.dm_password, "-f", inf_fd.name]
try:
run(args)
ipautil.run(args)
logging.debug("done adding default ds layout")
except ipautil.CalledProcessError, e:
print "Failed to add default ds layout", e
@ -313,13 +314,13 @@ class DsInstance(service.Service):
def __create_indeces(self):
self.step("creating indeces")
txt = template_file(SHARE_DIR + "indeces.ldif", self.sub_dict)
inf_fd = write_tmp_file(txt)
txt = ipautil.template_file(ipautil.SHARE_DIR + "indeces.ldif", self.sub_dict)
inf_fd = ipautil.write_tmp_file(txt)
logging.debug("adding/updating indeces")
args = ["/usr/bin/ldapmodify", "-xv", "-D", "cn=Directory Manager",
"-w", self.dm_password, "-f", inf_fd.name]
try:
run(args)
ipautil.run(args)
logging.debug("done adding/updating indeces")
except ipautil.CalledProcessError, e:
logging.critical("Failed to add/update indeces %s" % str(e))
@ -327,7 +328,7 @@ class DsInstance(service.Service):
def __certmap_conf(self):
self.step("configuring certmap.conf")
dirname = config_dirname(self.realm_name)
certmap_conf = template_file(SHARE_DIR+"certmap.conf.template", self.sub_dict)
certmap_conf = ipautil.template_file(ipautil.SHARE_DIR + "certmap.conf.template", self.sub_dict)
certmap_fd = open(dirname+"certmap.conf", "w+")
certmap_fd.write(certmap_conf)
certmap_fd.close()
@ -335,7 +336,7 @@ class DsInstance(service.Service):
def change_admin_password(self, password):
logging.debug("Changing admin password")
dirname = config_dirname(self.realm_name)
if dir_exists("/usr/lib64/mozldap"):
if ipautil.dir_exists("/usr/lib64/mozldap"):
app = "/usr/lib64/mozldap/ldappasswd"
else:
app = "/usr/lib/mozldap/ldappasswd"
@ -344,7 +345,7 @@ class DsInstance(service.Service):
"-P", dirname+"/cert8.db", "-ZZZ", "-s", password,
"uid=admin,cn=sysaccounts,cn=etc,"+self.suffix]
try:
run(args)
ipautil.run(args)
logging.debug("ldappasswd done")
except ipautil.CalledProcessError, e:
print "Unable to set admin password", e

View File

@ -356,13 +356,13 @@ class IPAdmin(SimpleLDAPObject):
type, obj = self.result(res)
except ldap.NO_SUCH_OBJECT:
raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND,
"no such entry for " + str(args))
notfound(args))
except ldap.LDAPError, e:
raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e)
if not obj:
raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND,
"no such entry for " + str(args))
notfound(args))
elif isinstance(obj,Entry):
return obj
else: # assume list/tuple
@ -386,7 +386,7 @@ class IPAdmin(SimpleLDAPObject):
if not obj:
raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND,
"no such entry for " + str(args))
notfound(args))
all_users = []
for s in obj:
@ -424,7 +424,7 @@ class IPAdmin(SimpleLDAPObject):
if not entries:
raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND,
"no such entry for " + str(args))
notfound(args))
if partial == 1:
counter = -1
@ -717,3 +717,16 @@ class IPAdmin(SimpleLDAPObject):
"""Returns True if the given string is a DN, False otherwise."""
return (dn.find("=") > 0)
is_a_dn = staticmethod(is_a_dn)
def notfound(args):
"""Return a string suitable for displaying as an error when a
search returns no results.
This just returns whatever is after the equals sign"""
filter = args[2]
try:
target = re.match(r'\(.*=(.*)\)', filter).group(1)
except:
target = filter
return "%s not found" % str(target)

View File

@ -330,6 +330,32 @@ class IPAServer:
return (exact_match_filter, partial_match_filter)
def __get_schema(self, opts=None):
"""Retrieves the current LDAP schema from the LDAP server."""
schema_entry = self.__get_base_entry("", "objectclass=*", ['dn','subschemasubentry'], opts)
schema_cn = schema_entry.get('subschemasubentry')
schema = self.__get_base_entry(schema_cn, "objectclass=*", ['*'], opts)
return schema
def __get_objectclasses(self, opts=None):
"""Returns a list of available objectclasses that the LDAP
server supports. This parses out the syntax, attributes, etc
and JUST returns a lower-case list of the names."""
schema = self.__get_schema(opts)
objectclasses = schema.get('objectclasses')
# Convert this list into something more readable
result = []
for i in range(len(objectclasses)):
oc = objectclasses[i].lower().split(" ")
result.append(oc[3].replace("'",""))
return result
# Higher-level API
def get_aci_entry(self, sattrs, opts=None):
@ -424,6 +450,19 @@ class IPAServer:
if self.__is_user_unique(user['uid'], opts) == 0:
raise ipaerror.gen_exception(ipaerror.LDAP_DUPLICATE)
# dn is set here, not by the user
try:
del user['dn']
except KeyError:
pass
# No need to set empty fields, and they can cause issues when they
# get to LDAP, like:
# TypeError: ('expected a string in the list', None)
for k in user.keys():
if not user[k] or len(user[k]) == 0 or (len(user[k]) == 1 and '' in user[k]):
del user[k]
dn="uid=%s,%s,%s" % (ldap.dn.escape_dn_chars(user['uid']),
user_container,self.basedn)
entry = ipaserver.ipaldap.Entry(dn)
@ -468,8 +507,7 @@ class IPAServer:
del user['gn']
# some required objectclasses
entry.setValues('objectClass', 'top', 'person', 'organizationalPerson',
'inetOrgPerson', 'inetUser', 'posixAccount', 'krbPrincipalAux', 'radiusprofile')
entry.setValues('objectClass', (config.get('ipauserobjectclasses')))
# fill in our new entry with everything sent by the user
for u in user:
@ -477,8 +515,16 @@ class IPAServer:
conn = self.getConnection(opts)
try:
res = conn.addEntry(entry)
self.add_user_to_group(user.get('uid'), group_dn, opts)
try:
res = conn.addEntry(entry)
except TypeError, e:
raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, "There is a problem with one of the data types.")
except Exception, e:
raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, e)
try:
self.add_user_to_group(user.get('uid'), group_dn, opts)
except Exception, e:
raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, "The user was created but adding to group %s failed" % group_dn)
finally:
self.releaseConnection(conn)
return res
@ -867,6 +913,12 @@ class IPAServer:
finally:
self.releaseConnection(conn)
# Get our configuration
config = self.get_ipa_config(opts)
# Make sure we have the latest object classes
newentry['objectclass'] = uniq_list(newentry.get('objectclass') + config.get('ipauserobjectclasses'))
try:
rv = self.update_entry(oldentry, newentry, opts)
return rv
@ -1026,13 +1078,15 @@ class IPAServer:
if self.__is_group_unique(group['cn'], opts) == 0:
raise ipaerror.gen_exception(ipaerror.LDAP_DUPLICATE)
# Get our configuration
config = self.get_ipa_config(opts)
dn="cn=%s,%s,%s" % (ldap.dn.escape_dn_chars(group['cn']),
group_container,self.basedn)
entry = ipaserver.ipaldap.Entry(dn)
# some required objectclasses
entry.setValues('objectClass', 'top', 'groupofnames', 'posixGroup',
'inetUser')
entry.setValues('objectClass', (config.get('ipagroupobjectclasses')))
# No need to explicitly set gidNumber. The dna_plugin will do this
# for us if the value isn't provided by the user.
@ -1357,23 +1411,32 @@ class IPAServer:
try:
res = conn.updateRDN(oldentry.get('dn'), "cn=" + newcn[0])
newdn = oldentry.get('dn')
newcn = newentry.get('cn')
if isinstance(newcn, str):
newcn = [newcn]
# Ick. Need to find the exact cn used in the old DN so we'll
# walk the list of cns and skip the obviously bad ones:
for c in oldentry.get('dn').split("cn="):
if c and c != "groups" and not c.startswith("accounts"):
newdn = newdn.replace("cn=%s" % c, "uid=%s" % newentry.get('cn')[0])
newdn = newdn.replace("cn=%s" % c, "cn=%s," % newcn[0])
break
# Now fix up the dns and cns so they aren't seen as having
# changed.
oldentry['dn'] = newdn
newentry['dn'] = newdn
oldentry['cn'] = newentry['cn']
oldentry['cn'] = newentry.get('cn')
newrdn = 1
finally:
self.releaseConnection(conn)
# Get our configuration
config = self.get_ipa_config(opts)
# Make sure we have the latest object classes
newentry['objectclass'] = uniq_list(newentry.get('objectclass') + config.get('ipagroupobjectclasses'))
try:
rv = self.update_entry(oldentry, newentry, opts)
return rv
@ -1527,7 +1590,76 @@ class IPAServer:
finally:
self.releaseConnection(conn)
return res
def find_service_principal(self, criteria, sattrs, searchlimit=-1,
timelimit=-1, opts=None):
"""Returns a list: counter followed by the results.
If the results are truncated, counter will be set to -1."""
config = self.get_ipa_config(opts)
if timelimit < 0:
timelimit = float(config.get('ipasearchtimelimit'))
if searchlimit < 0:
searchlimit = float(config.get('ipasearchrecordslimit'))
search_fields = ["krbprincipalname"]
criteria = self.__safe_filter(criteria)
criteria_words = re.split(r'\s+', criteria)
criteria_words = filter(lambda value:value!="", criteria_words)
if len(criteria_words) == 0:
return [0]
(exact_match_filter, partial_match_filter) = self.__generate_match_filters(
search_fields, criteria_words)
#
# further constrain search to just the objectClass
# TODO - need to parameterize this into generate_match_filters,
# and work it into the field-specification search feature
#
exact_match_filter = "(&(objectclass=krbPrincipalAux)(!(objectClass=person))(!(krbprincipalname=kadmin/*))%s)" % exact_match_filter
partial_match_filter = "(&(objectclass=krbPrincipalAux)(!(objectClass=person))(!(krbprincipalname=kadmin/*))%s)" % partial_match_filter
conn = self.getConnection(opts)
try:
try:
exact_results = conn.getListAsync(self.basedn, self.scope,
exact_match_filter, sattrs, 0, None, None, timelimit,
searchlimit)
except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
exact_results = [0]
try:
partial_results = conn.getListAsync(self.basedn, self.scope,
partial_match_filter, sattrs, 0, None, None, timelimit,
searchlimit)
except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
partial_results = [0]
finally:
self.releaseConnection(conn)
exact_counter = exact_results[0]
partial_counter = partial_results[0]
exact_results = exact_results[1:]
partial_results = partial_results[1:]
# Remove exact matches from the partial_match list
exact_dns = set(map(lambda e: e.dn, exact_results))
partial_results = filter(lambda e: e.dn not in exact_dns,
partial_results)
if (exact_counter == -1) or (partial_counter == -1):
counter = -1
else:
counter = len(exact_results) + len(partial_results)
entries = [counter]
for e in exact_results + partial_results:
entries.append(self.convert_entry(e))
return entries
def get_keytab(self, name, opts=None):
"""get a keytab"""
@ -1592,6 +1724,19 @@ class IPAServer:
except:
raise
# Run through the list of User and Group object classes to make
# sure they are all valid. This doesn't handle dependencies but it
# will at least catch typos.
classes = self.__get_objectclasses(opts)
oc = newconfig['ipauserobjectclasses']
for i in range(len(oc)):
if not oc[i].lower() in classes:
raise ipaerror.gen_exception(ipaerror.CONFIG_INVALID_OC)
oc = newconfig['ipagroupobjectclasses']
for i in range(len(oc)):
if not oc[i].lower() in classes:
raise ipaerror.gen_exception(ipaerror.CONFIG_INVALID_OC)
return self.update_entry(oldconfig, newconfig, opts)
def get_password_policy(self, opts=None):
@ -1654,3 +1799,8 @@ def ldap_search_escape(match):
return r'\00'
else:
return value
def uniq_list(x):
"""Return a unique list, preserving order and ignoring case"""
set = {}
return [set.setdefault(e,e) for e in x if e.lower() not in set]

View File

@ -360,6 +360,7 @@ def handler(req, profiling=False):
h.register_function(f.get_password_policy)
h.register_function(f.update_password_policy)
h.register_function(f.add_service_principal)
h.register_function(f.find_service_principal)
h.register_function(f.get_keytab)
h.register_function(f.get_radius_client_by_ip_addr)
h.register_function(f.add_radius_client)