This commit is contained in:
Karl MacMillan 0001-01-01 00:00:00 +00:00
commit ca118de76c
31 changed files with 866 additions and 122 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

@ -131,8 +131,10 @@ def main():
opts = [{'name':'comment', 'type':'comment', 'value':'File modified by ipa-client-install'},
{'name':'empty', 'type':'empty'}]
defopts.append({'name':'server', 'type':'option', 'value':ds.getServerName()})
defopts.append({'name':'realm', 'type':'option', 'value':ds.getRealmName()})
#[defaults]
defopts = [{'name':'server', 'type':'option', 'value':ds.getServerName()},
{'name':'realm', 'type':'option', 'value':ds.getRealmName()}]
opts.append({'name':'defaults', 'type':'section', 'value':defopts})
opts.append({'name':'empty', 'type':'empty'})

View File

@ -116,9 +116,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
@ -385,6 +382,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

@ -27,9 +27,7 @@ from random import Random
from time import gmtime
import os
import stat
import socket
from string import lower
import re
import xmlrpclib
import datetime
@ -79,7 +77,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:
@ -118,24 +116,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:

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

@ -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

@ -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,12 +13,16 @@ 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])
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))
krbmaxpwdlife = widgets.TextField(name="krbmaxpwdlife", label="Max. Password Lifetime (days)", attrs=dict(size=3,maxlength=3))
krbminpwdlife = widgets.TextField(name="krbminpwdlife", label="Min. Password Lifetime (hours)", 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))
@ -34,6 +39,9 @@ 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))
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

@ -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

@ -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,12 @@ 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 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__)
@ -83,36 +84,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 +113,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,27 +296,27 @@ 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'):
@ -403,12 +374,12 @@ class UserController(IPAController):
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

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,80 @@ from ipagui.helpers import ipahelper
py:content="tg.errors.get('ipadefaultprimarygroup')" />
</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,46 @@ edit_url = tg.url('/ipapolicy/edit')
</th>
<td>${ipapolicy.get("ipadefaultprimarygroup")}</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

@ -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

@ -27,11 +27,15 @@ 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' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27)
# ipaGroupObjectClasses - required objectclasses for groups
attributetypes: ( 2.16.840.1.113730.3.8.1.12 NAME 'ipaGroupObjectClasses' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27)
###############################################
##
## 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) )

View File

@ -14,7 +14,7 @@ krbMinPwdLife: 3600
krbPwdMinDiffChars: 0
krbPwdMinLength: 8
krbPwdHistoryLength: 0
krbMaxPwdLife: 864000
krbMaxPwdLife: 7776000
dn: cn=users,cn=accounts,$SUFFIX
changetype: add

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:
@ -185,25 +184,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 +216,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 +237,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 +247,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 +257,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 +267,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 +277,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 +291,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 +312,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 +326,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 +334,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 +343,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

@ -329,6 +329,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):
@ -423,6 +449,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)
@ -476,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
@ -1332,7 +1379,78 @@ 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
print exact_match_filter
print 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"""
@ -1397,6 +1515,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):
@ -1406,6 +1537,10 @@ class IPAServer:
except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
raise ipaerror.gen_exception(ipaerror.LDAP_NO_CONFIG)
# convert some values for display purposes
policy['krbmaxpwdlife'] = str(int(policy.get('krbmaxpwdlife')) / 86400)
policy['krbminpwdlife'] = str(int(policy.get('krbminpwdlife')) / 3600)
return policy
def update_password_policy(self, oldpolicy, newpolicy, opts=None):
@ -1414,11 +1549,18 @@ class IPAServer:
# The LDAP routines want strings, not ints, so convert a few
# things. Otherwise it sees a string -> int conversion as a change.
try:
newpolicy['krbmaxpwdlife'] = str(newpolicy.get('krbmaxpwdlife'))
newpolicy['krbminpwdlife'] = str(newpolicy.get('krbminpwdlife'))
newpolicy['krbpwdhistorylength'] = str(newpolicy.get('krbpwdhistorylength'))
newpolicy['krbpwdmindiffchars'] = str(newpolicy.get('krbpwdmindiffchars'))
newpolicy['krbpwdminlength'] = str(newpolicy.get('krbpwdminlength'))
for k in oldpolicy.iterkeys():
if k.startswith("krb", 0, 3):
oldpolicy[k] = str(oldpolicy[k])
for k in newpolicy.iterkeys():
if k.startswith("krb", 0, 3):
newpolicy[k] = str(newpolicy[k])
# Convert hours and days to seconds
oldpolicy['krbmaxpwdlife'] = str(int(oldpolicy.get('krbmaxpwdlife')) * 86400)
oldpolicy['krbminpwdlife'] = str(int(oldpolicy.get('krbminpwdlife')) * 3600)
newpolicy['krbmaxpwdlife'] = str(int(newpolicy.get('krbmaxpwdlife')) * 86400)
newpolicy['krbminpwdlife'] = str(int(newpolicy.get('krbminpwdlife')) * 3600)
except KeyError:
# These should all be there but if not, let things proceed
pass

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.handle_request(req)
finally: