merged radius work with latest mainline tip

This commit is contained in:
John Dennis
2007-11-28 07:49:07 -05:00
100 changed files with 5561 additions and 1025 deletions

View File

@@ -1,6 +1,6 @@
SUBDIRS=ipa-server ipa-admintools ipa-python ipa-client
PRJ_PREFIX=freeipa
PRJ_PREFIX=ipa
RPMBUILD ?= $(PWD)/rpmbuild
@@ -8,45 +8,47 @@ RPMBUILD ?= $(PWD)/rpmbuild
# updating this you should run the version-update
# target.
SERV_MAJOR=0
SERV_MINOR=4
SERV_RELEASE=1
SERV_MINOR=5
SERV_RELEASE=0
SERV_VERSION=$(SERV_MAJOR).$(SERV_MINOR).$(SERV_RELEASE)
SERV_TARBALL_PREFIX=$(PRJ_PREFIX)-server-$(SERV_VERSION)
SERV_TARBALL=$(SERV_TARBALL_PREFIX).tgz
ADMIN_MAJOR=0
ADMIN_MINOR=4
ADMIN_RELEASE=1
ADMIN_MINOR=5
ADMIN_RELEASE=0
ADMIN_VERSION=$(ADMIN_MAJOR).$(ADMIN_MINOR).$(ADMIN_RELEASE)
ADMIN_TARBALL_PREFIX=$(PRJ_PREFIX)-admintools-$(ADMIN_VERSION)
ADMIN_TARBALL=$(ADMIN_TARBALL_PREFIX).tgz
PYTHON_MAJOR=0
PYTHON_MINOR=4
PYTHON_RELEASE=1
PYTHON_MINOR=5
PYTHON_RELEASE=0
PYTHON_VERSION=$(PYTHON_MAJOR).$(PYTHON_MINOR).$(PYTHON_RELEASE)
PYTHON_TARBALL_PREFIX=$(PRJ_PREFIX)-python-$(PYTHON_VERSION)
PYTHON_TARBALL=$(PYTHON_TARBALL_PREFIX).tgz
CLI_MAJOR=0
CLI_MINOR=3
CLI_RELEASE=1
CLI_MINOR=5
CLI_RELEASE=0
CLI_VERSION=$(CLI_MAJOR).$(CLI_MINOR).$(CLI_RELEASE)
CLI_TARBALL_PREFIX=$(PRJ_PREFIX)-client-$(CLI_VERSION)
CLI_TARBALL=$(CLI_TARBALL_PREFIX).tgz
LIBDIR ?= /usr/lib
all: bootstrap-autogen
@for subdir in $(SUBDIRS); do \
(cd $$subdir && $(MAKE) $@) || exit 1; \
done
bootstrap-autogen:
cd ipa-server; if [ ! -e Makefile ]; then ./autogen.sh --prefix=/usr --sysconfdir=/etc; fi
cd ipa-client; if [ ! -e Makefile ]; then ./autogen.sh --prefix=/usr --sysconfdir=/etc; fi
cd ipa-server; if [ ! -e Makefile ]; then ./autogen.sh --prefix=/usr --sysconfdir=/etc --libdir=$(LIBDIR); fi
cd ipa-client; if [ ! -e Makefile ]; then ./autogen.sh --prefix=/usr --sysconfdir=/etc --libdir=$(LIBDIR); fi
autogen:
cd ipa-server; ./autogen.sh --prefix=/usr --sysconfdir=/etc;
cd ipa-client; ./autogen.sh --prefix=/usr --sysconfdir=/etc;
cd ipa-server; ./autogen.sh --prefix=/usr --sysconfdir=/etc --libdir=$(LIBDIR)
cd ipa-client; ./autogen.sh --prefix=/usr --sysconfdir=/etc --libdir=$(LIBDIR)
configure:
cd ipa-server; ./configure --prefix=/usr --sysconfdir=/etc
@@ -63,56 +65,56 @@ test:
done
version-update:
sed s/VERSION/$(SERV_VERSION)/ ipa-server/freeipa-server.spec.in \
> ipa-server/freeipa-server.spec
sed s/VERSION/$(SERV_VERSION)/ ipa-server/ipa-server.spec.in \
> ipa-server/ipa-server.spec
sed s/VERSION/$(ADMIN_VERSION)/ ipa-admintools/freeipa-admintools.spec.in \
> ipa-admintools/freeipa-admintools.spec
sed s/VERSION/$(ADMIN_VERSION)/ ipa-admintools/ipa-admintools.spec.in \
> ipa-admintools/ipa-admintools.spec
sed s/VERSION/$(PYTHON_VERSION)/ ipa-python/freeipa-python.spec.in \
> ipa-python/freeipa-python.spec
sed s/VERSION/$(PYTHON_VERSION)/ ipa-python/ipa-python.spec.in \
> ipa-python/ipa-python.spec
sed s/VERSION/$(CLI_VERSION)/ ipa-client/freeipa-client.spec.in \
> ipa-client/freeipa-client.spec
sed s/VERSION/$(CLI_VERSION)/ ipa-client/ipa-client.spec.in \
> ipa-client/ipa-client.spec
archive:
-mkdir -p dist
hg archive -t files dist/freeipa
hg archive -t files dist/ipa
local-archive:
-mkdir -p dist/freeipa
-mkdir -p dist/ipa
@for subdir in $(SUBDIRS); do \
cp -pr $$subdir dist/freeipa/.; \
cp -pr $$subdir dist/ipa/.; \
done
archive-cleanup:
rm -fr dist/freeipa
rm -fr dist/ipa
tarballs:
-mkdir -p dist/sources
# ipa-server
mv dist/freeipa/ipa-server dist/$(SERV_TARBALL_PREFIX)
mv dist/ipa/ipa-server dist/$(SERV_TARBALL_PREFIX)
rm -f dist/sources/$(SERV_TARBALL)
cd dist/$(SERV_TARBALL_PREFIX); ./autogen.sh; make distclean
cd dist; tar cfz sources/$(SERV_TARBALL) $(SERV_TARBALL_PREFIX)
rm -fr dist/$(SERV_TARBALL_PREFIX)
# ipa-admintools
mv dist/freeipa/ipa-admintools dist/$(ADMIN_TARBALL_PREFIX)
mv dist/ipa/ipa-admintools dist/$(ADMIN_TARBALL_PREFIX)
rm -f dist/sources/$(ADMIN_TARBALL)
cd dist; tar cfz sources/$(ADMIN_TARBALL) $(ADMIN_TARBALL_PREFIX)
rm -fr dist/$(ADMIN_TARBALL_PREFIX)
# ipa-python
mv dist/freeipa/ipa-python dist/$(PYTHON_TARBALL_PREFIX)
mv dist/ipa/ipa-python dist/$(PYTHON_TARBALL_PREFIX)
rm -f dist/sources/$(PYTHON_TARBALL)
cd dist; tar cfz sources/$(PYTHON_TARBALL) $(PYTHON_TARBALL_PREFIX)
rm -fr dist/$(PYTHON_TARBALL_PREFIX)
# ipa-client
mv dist/freeipa/ipa-client dist/$(CLI_TARBALL_PREFIX)
mv dist/ipa/ipa-client dist/$(CLI_TARBALL_PREFIX)
rm -f dist/sources/$(CLI_TARBALL)
cd dist/$(CLI_TARBALL_PREFIX); ./autogen.sh; make distclean
cd dist; tar cfz sources/$(CLI_TARBALL) $(CLI_TARBALL_PREFIX)
@@ -131,25 +133,25 @@ rpmdistdir:
rpm-ipa-server:
cp dist/sources/$(SERV_TARBALL) $(RPMBUILD)/SOURCES/.
rpmbuild --define "_topdir $(RPMBUILD)" -ba ipa-server/freeipa-server.spec
rpmbuild --define "_topdir $(RPMBUILD)" -ba ipa-server/ipa-server.spec
cp rpmbuild/RPMS/*/$(PRJ_PREFIX)-server-$(SERV_VERSION)-*.rpm dist/rpms/
cp rpmbuild/SRPMS/$(PRJ_PREFIX)-server-$(SERV_VERSION)-*.src.rpm dist/srpms/
rpm-ipa-admin:
cp dist/sources/$(ADMIN_TARBALL) $(RPMBUILD)/SOURCES/.
rpmbuild --define "_topdir $(RPMBUILD)" -ba ipa-admintools/freeipa-admintools.spec
rpmbuild --define "_topdir $(RPMBUILD)" -ba ipa-admintools/ipa-admintools.spec
cp rpmbuild/RPMS/noarch/$(PRJ_PREFIX)-admintools-$(ADMIN_VERSION)-*.rpm dist/rpms/
cp rpmbuild/SRPMS/$(PRJ_PREFIX)-admintools-$(ADMIN_VERSION)-*.src.rpm dist/srpms/
rpm-ipa-python:
cp dist/sources/$(PYTHON_TARBALL) $(RPMBUILD)/SOURCES/.
rpmbuild --define "_topdir $(RPMBUILD)" -ba ipa-python/freeipa-python.spec
rpmbuild --define "_topdir $(RPMBUILD)" -ba ipa-python/ipa-python.spec
cp rpmbuild/RPMS/noarch/$(PRJ_PREFIX)-python-$(PYTHON_VERSION)-*.rpm dist/rpms/
cp rpmbuild/SRPMS/$(PRJ_PREFIX)-python-$(PYTHON_VERSION)-*.src.rpm dist/srpms/
rpm-ipa-client:
cp dist/sources/$(CLI_TARBALL) $(RPMBUILD)/SOURCES/.
rpmbuild --define "_topdir $(RPMBUILD)" -ba ipa-client/freeipa-client.spec
rpmbuild --define "_topdir $(RPMBUILD)" -ba ipa-client/ipa-client.spec
cp rpmbuild/RPMS/*/$(PRJ_PREFIX)-client-$(CLI_VERSION)-*.rpm dist/rpms/
cp rpmbuild/SRPMS/$(PRJ_PREFIX)-client-$(CLI_VERSION)-*.src.rpm dist/srpms/

View File

@@ -21,6 +21,7 @@ install:
install -m 755 ipa-deldelegation $(SBINDIR)
install -m 755 ipa-listdelegation $(SBINDIR)
install -m 755 ipa-moddelegation $(SBINDIR)
install -m 755 ipa-getkeytab $(SBINDIR)
install -m 755 ipa-addradiusclient $(SBINDIR)
install -m 755 ipa-radiusclientmod $(SBINDIR)
install -m 755 ipa-delradiusclient $(SBINDIR)

View File

@@ -31,6 +31,7 @@ import xmlrpclib
import kerberos
import krbV
import ldap
import errno
def usage():
print "ipa-adddelgation [-a|--attributes attr1,attr2,..,attrn] [-s|--source STRING] [-t|--target STRING] name"
@@ -90,12 +91,34 @@ def main():
new_aci.dest_group = target_grp[1].dn
new_aci.attrs = attr_list
aci_entry = client.get_aci_entry(['*', 'aci'])
# Look for an existing ACI of the same name
aci_str_list = aci_entry.getValues('aci')
if aci_str_list is None:
aci_str_list = []
if not(isinstance(aci_str_list,list) or isinstance(aci_str_list,tuple)):
aci_str_list = [aci_str_list]
for aci_str in aci_str_list:
try:
old_aci = ipa.aci.ACI(aci_str)
if old_aci.name == new_aci.name:
print "A delegation of that name already exists"
return 2
except SyntaxError:
# ignore aci_str's that ACI can't parse
pass
aci_entry = client.get_aci_entry(['dn'])
aci_entry.setValue('aci', new_aci.export_to_string())
client.update_entry(aci_entry)
except xmlrpclib.Fault, f:
print f.faultString
except xmlrpclib.Fault, fault:
if fault.faultCode == errno.ECONNREFUSED:
print "The IPA XML-RPC service is not responding."
else:
print fault.faultString
return 1
except kerberos.GSSError, e:
print "Could not initialize GSSAPI: %s/%s" % (e[0][0][0], e[0][1][0])

View File

@@ -30,6 +30,7 @@ import ipa.ipaerror
import xmlrpclib
import kerberos
import ldap
import errno
def usage():
print "ipa-addgroup [-d|--description STRING] group"
@@ -95,8 +96,11 @@ def main():
client = ipaclient.IPAClient()
client.add_group(group)
print cn + " successfully added"
except xmlrpclib.Fault, f:
print f.faultString
except xmlrpclib.Fault, fault:
if fault.faultCode == errno.ECONNREFUSED:
print "The IPA XML-RPC service is not responding."
else:
print fault.faultString
return 1
except kerberos.GSSError, e:
print "Could not initialize GSSAPI: %s/%s" % (e[0][0][0], e[0][1][0])

View File

@@ -31,6 +31,7 @@ import kerberos
import krbV
import ldap
import getpass
import errno
def usage():
print "ipa-adduser [-c|--gecos STRING] [-d|--directory STRING] [-f|--firstname STRING] [-l|--lastname STRING] user"
@@ -204,14 +205,15 @@ def main():
user.setValue('homedirectory', directory)
if shell:
user.setValue('loginshell', shell)
else:
user.setValue('loginshell', "/bin/sh")
try:
client = ipaclient.IPAClient()
client.add_user(user)
except xmlrpclib.Fault, f:
print f.faultString
except xmlrpclib.Fault, fault:
if fault.faultCode == errno.ECONNREFUSED:
print "The IPA XML-RPC service is not responding."
else:
print fault.faultString
return 1
except kerberos.GSSError, e:
print "Could not initialize GSSAPI: %s/%s" % (e[0][0][0], e[0][1][0])

View File

@@ -1,7 +1,7 @@
Name: freeipa-admintools
Version: 0.4.1
Name: ipa-admintools
Version: 0.5.0
Release: 1%{?dist}
Summary: FreeIPA authentication server
Summary: IPA authentication server
Group: System Environment/Base
License: GPL
@@ -10,10 +10,10 @@ Source0: %{name}-%{version}.tgz
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
BuildArch: noarch
Requires: python python-krbV freeipa-python
Requires: python python-krbV ipa-python
%description
FreeIPA is a server for identity, policy, and audit.
IPA is a server for identity, policy, and audit.
%prep
%setup -q
@@ -35,6 +35,9 @@ rm -rf %{buildroot}
%{_mandir}/man1/*
%changelog
* Wed Nov 21 2007 Karl MacMillan <kmacmill@redhat.com> - 0.5.0-1
- Version bump for release and rpm name change
* Thu Nov 1 2007 Karl MacMillan <kmacmill@redhat.com> - 0.4.1-1
- Version bump for release

View File

@@ -1,7 +1,7 @@
Name: freeipa-admintools
Name: ipa-admintools
Version: VERSION
Release: 1%{?dist}
Summary: FreeIPA authentication server
Summary: IPA authentication server
Group: System Environment/Base
License: GPL
@@ -10,10 +10,10 @@ Source0: %{name}-%{version}.tgz
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
BuildArch: noarch
Requires: python python-krbV freeipa-python
Requires: python python-krbV ipa-python
%description
FreeIPA is a server for identity, policy, and audit.
IPA is a server for identity, policy, and audit.
%prep
%setup -q
@@ -35,6 +35,9 @@ rm -rf %{buildroot}
%{_mandir}/man1/*
%changelog
* Wed Nov 21 2007 Karl MacMillan <kmacmill@redhat.com> - 0.5.0-1
- Version bump for release and rpm name change
* Thu Nov 1 2007 Karl MacMillan <kmacmill@redhat.com> - 0.4.1-1
- Version bump for release

View File

@@ -26,6 +26,7 @@ import ipa.config
import xmlrpclib
import kerberos
import copy
import errno
import ipa.aci
from ipa import ipaerror
@@ -85,12 +86,24 @@ def main():
aci_entry.setValue('aci', new_aci_str_list)
client.update_entry(aci_entry)
except xmlrpclib.Fault, fault:
if fault.faultCode == errno.ECONNREFUSED:
print "The IPA XML-RPC service is not responding."
else:
print fault.faultString
return 1
except (SyntaxError, ipaerror.IPAError), e:
print "Delegation deletion failed: " + str(e)
return 1
except kerberos.GSSError, e:
print "Could not initialize GSSAPI: %s/%s" % (e[0][0][0], e[0][1][0])
return 1
except xmlrpclib.ProtocolError, e:
print "Unable to connect to IPA server: %s" % (e.errmsg)
return 1
except ipa.ipaerror.IPAError, e:
print "%s" % (e.message)
return 1
return 0

View File

@@ -23,6 +23,7 @@ from optparse import OptionParser
import ipa
import ipa.ipaclient as ipaclient
import ipa.config
import errno
import xmlrpclib
import kerberos
@@ -54,8 +55,11 @@ def main():
print args[1] + " successfully deleted"
else:
print args[1] + " " + ret
except xmlrpclib.Fault, f:
print f.faultString
except xmlrpclib.Fault, fault:
if fault.faultCode == errno.ECONNREFUSED:
print "The IPA XML-RPC service is not responding."
else:
print fault.faultString
return 1
except kerberos.GSSError, e:
print "Could not initialize GSSAPI: %s/%s" % (e[0][0][0], e[0][1][0])

View File

@@ -23,6 +23,7 @@ from optparse import OptionParser
import ipa
import ipa.ipaclient as ipaclient
import ipa.config
import errno
import xmlrpclib
import kerberos
@@ -33,6 +34,8 @@ def usage():
def parse_options():
parser = OptionParser()
parser.add_option("-d", "--delete", action="store_true", dest="deluser",
help="Delete the user, don't inactivate them.")
parser.add_option("--usage", action="store_true",
help="Program usage")
@@ -47,15 +50,26 @@ def main():
if len(args) != 2:
usage()
msg = "inactivated"
try:
client = ipaclient.IPAClient()
ret = client.mark_user_deleted(args[1])
if (ret == "Success"):
print args[1] + " successfully deleted"
if options.deluser:
ret = client.delete_user(args[1])
msg = "deleted"
else:
print args[1] + " " + ret
except xmlrpclib.Fault, f:
print f.faultString
try:
ret = client.mark_user_inactive(args[1])
except ipa.ipaerror.exception_for(ipa.ipaerror.LDAP_EMPTY_MODLIST):
print "User is already marked inactive"
return 0
except:
raise
print args[1] + " successfully %s" % msg
except xmlrpclib.Fault, fault:
if fault.faultCode == errno.ECONNREFUSED:
print "The IPA XML-RPC service is not responding."
else:
print fault.faultString
return 1
except kerberos.GSSError, e:
print "Could not initialize GSSAPI: %s/%s" % (e[0][0][0], e[0][1][0])

View File

@@ -23,6 +23,7 @@ from optparse import OptionParser
import ipa.ipaclient as ipaclient
import ipa.config
import errno
import sys
import xmlrpclib
import kerberos
@@ -34,6 +35,12 @@ def usage():
def parse_options():
parser = OptionParser()
parser.add_option("-a", "--all", action="store_true", dest="all",
help="Show all group attributes")
parser.add_option("-n", "--notranslate", action="store_true",
dest="notranslate",
help="Don't translate LDAP attributes into readable labels")
args = ipa.config.init_config(sys.argv)
options, args = parser.parse_args(args)
@@ -48,7 +55,10 @@ def main():
try:
client = ipaclient.IPAClient()
groups = client.find_groups(args[1], ['cn','description','gidnumber'])
if options.all is None:
groups = client.find_groups(args[1], ['cn','description','gidnumber','nsAccountLock'])
else:
groups = client.find_groups(args[1], sattrs=['*','nsAccountLock'])
counter = groups[0]
groups = groups[1:]
@@ -64,15 +74,21 @@ def main():
print str(e)
continue
attr = ent.attrList()
if options.notranslate:
labels = {}
for a in attr:
labels[a] = a
else:
labels = client.attrs_to_labels(attr)
print "dn: " + ent.dn
for a in attr:
value = ent.getValues(a)
if isinstance(value,str):
print a + ": " + value
print labels[a] + ": " + value
else:
print a + ": "
print labels[a] + ": "
for l in value:
print "\t" + l
@@ -87,7 +103,10 @@ def main():
print
except xmlrpclib.Fault, fault:
print fault.faultString
if fault.faultCode == errno.ECONNREFUSED:
print "The IPA XML-RPC service is not responding."
else:
print fault.faultString
return 1
except kerberos.GSSError, e:
print "Could not initialize GSSAPI: %s/%s" % (e[0][0][0], e[0][1][0])

View File

@@ -25,6 +25,7 @@ import ipa.config
import ipa.ipautil as ipautil
import base64
import errno
import sys
import xmlrpclib
import kerberos
@@ -38,6 +39,9 @@ def parse_options():
parser.add_option("-a", "--all", action="store_true", dest="all",
help="Set user's e-mail address")
parser.add_option("-n", "--notranslate", action="store_true",
dest="notranslate",
help="Don't translate LDAP attributes into readable labels")
parser.add_option("--usage", action="store_true",
help="Program usage")
@@ -90,6 +94,12 @@ def main():
for ent in users:
attr = ent.attrList()
attr.sort()
if options.notranslate:
labels = {}
for a in attr:
labels[a] = a
else:
labels = client.attrs_to_labels(attr)
if options.all is True:
print "dn: " + ent.dn
@@ -97,16 +107,19 @@ def main():
for a in attr:
value = ent.getValues(a)
if isinstance(value,str):
print a + ": " + str(wrap_binary_data(value)).rstrip()
print labels[a] + ": " + str(wrap_binary_data(value)).rstrip()
else:
print a + ": "
print labels[a] + ": "
for l in value:
print "\t" + wrap_binary_data(l)
# blank line between results
print
except xmlrpclib.Fault, fault:
print fault.faultString
if fault.faultCode == errno.ECONNREFUSED:
print "The IPA XML-RPC service is not responding."
else:
print fault.faultString
return 1
except kerberos.GSSError, e:
print "Could not initialize GSSAPI: %s/%s" % (e[0][0][0], e[0][1][0])

View File

@@ -0,0 +1,83 @@
#! /usr/bin/python -E
# Authors: Karl MacMillan <kmacmill@redhat.com>
#
# Copyright (C) 2007 Red Hat
# see file 'COPYING' for use and warranty information
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; version 2 only
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
import sys
from optparse import OptionParser
import ipa
import ipa.user
import ipa.ipaclient as ipaclient
import ipa.ipavalidate as ipavalidate
import ipa.config
import base64
import xmlrpclib
import kerberos
import krbV
import ldap
import getpass
import errno
def usage():
print "ipa-getkeytab [-a] principal filename"
sys.exit(1)
def parse_options():
parser = OptionParser()
parser.add_option("-a", "--add", dest="add_princ", action="store_true",
help="add the principal")
args = ipa.config.init_config(sys.argv)
options, args = parser.parse_args(args)
return options, args
def main():
# The following fields are required
princ_name = ""
options, args = parse_options()
if len(args) != 3:
usage()
princ_name = args[1]
file_name = args[2]
client = ipaclient.IPAClient()
try:
if options.add_princ:
client.add_service_principal(princ_name)
princs = client.get_keytab(princ_name)
if princs is None:
print "could not generate keytab"
sys.exit(1)
fd = open(file_name, "w")
fd.write(princs)
except Exception, e:
print str(e)
if __name__ == "__main__":
sys.exit(main())

View File

@@ -29,12 +29,16 @@ import ipa.ipaerror
import xmlrpclib
import kerberos
import ldap
import errno
def usage():
print "ipa-groupmod [-a] [-r] user group"
print "ipa-groupmod [-d|--desc description STRING] group"
print "ipa-groupmod [-a|--add] [-r|--remove] user group"
print "ipa-groupmod [-d|--desc description STRING] [--addattr attribute=value] [--delattr attribute] [--setattr attribute=value] group"
sys.exit(1)
def set_add_usage(which):
print "%s option usage: --%s NAME=VALUE" % (which, which)
def parse_options():
parser = OptionParser()
parser.add_option("-a", "--add", dest="add", action="store_true",
@@ -43,20 +47,38 @@ def parse_options():
help="Remove a user from the group")
parser.add_option("-d", "--description", dest="desc",
help="Modify the description of the group")
parser.add_option("--addattr", dest="addattr",
help="Adds an attribute or values to that attribute, attr=value",
action="append")
parser.add_option("--delattr", dest="delattr",
help="Remove an attribute", action="append")
parser.add_option("--setattr", dest="setattr",
help="Set an attribute, dropping any existing values that may exist",
action="append")
parser.add_option("--usage", action="store_true",
help="Program usage")
args = ipa.config.init_config(sys.argv)
options, args = parser.parse_args(args)
if (not options.add and not options.remove) and (not options.desc):
if (not options.add and not options.remove) and (not options.desc and
not options.addattr and not options.delattr and not options.setattr):
usage()
return options, args
def get_group(client, group_cn):
def get_group(client, options, group_cn):
try:
group = client.get_entry_by_cn(group_cn)
attrs = ['*']
# in case any attributes being modified are operational such as
# nsaccountlock. Any attribute to be deleted needs to be included
# in the original record so it can be seen as being removed.
if options.delattr:
for d in options.delattr:
attrs.append(d)
group = client.get_entry_by_cn(group_cn, sattrs=attrs)
except ipa.ipaerror.IPAError, e:
print "%s" % e.message
return None
@@ -69,32 +91,69 @@ def main():
if (options.add or options.remove) and (len(args) != 3):
usage()
if (options.desc and (len(args) != 2)):
elif ((options.desc or options.addattr or options.delattr or options.setattr) and (len(args) != 2)):
usage()
try:
client = ipaclient.IPAClient()
if options.add:
group = get_group(client, args[2])
group = get_group(client, options, args[2])
if group is None:
return 1
client.add_user_to_group(args[1], group.dn)
print args[1] + " successfully added to " + args[2]
users = args[1].split(',')
for user in users:
client.add_user_to_group(user, group.dn)
print user + " successfully added to " + args[2]
elif options.remove:
group = get_group(client, args[2])
group = get_group(client, options, args[2])
if group is None:
return 1
client.remove_user_from_group(args[1], group.dn)
print args[1] + " successfully removed"
elif options.desc:
group = get_group(client, args[1])
users = args[1].split(',')
for user in users:
client.remove_user_from_group(user, group.dn)
print user + " successfully removed"
else:
group = get_group(client, options, args[1])
if group is None:
return 1
group.setValue('description', options.desc)
if options.desc:
group.setValue('description', options.desc)
if options.delattr:
for d in options.delattr:
group.delValue(d)
if options.setattr:
for s in options.setattr:
s = s.split('=')
if len(s) != 2:
set_add_usage("set")
sys.exit(1)
(attr,value) = s
group.setValue(attr, value)
if options.addattr:
for a in options.addattr:
a = a.split('=')
if len(a) != 2:
set_add_usage("add")
sys.exit(1)
(attr,value) = a
cvalue = group.getValue(attr)
if cvalue:
if isinstance(cvalue,str):
cvalue = [cvalue]
value = cvalue + [value]
group.setValue(attr, value)
client.update_group(group)
print args[1] + " successfully updated"
except xmlrpclib.Fault, f:
print f.faultString
except xmlrpclib.Fault, fault:
if fault.faultCode == errno.ECONNREFUSED:
print "The IPA XML-RPC service is not responding."
else:
print fault.faultString
return 1
except kerberos.GSSError, e:
print "Could not initialize GSSAPI: %s/%s" % (e[0][0][0], e[0][1][0])

View File

@@ -23,8 +23,10 @@ from optparse import OptionParser
import ipa.ipaclient as ipaclient
import ipa.config
import operator
import xmlrpclib
import kerberos
import errno
import ipa.aci
from ipa import ipaerror
@@ -51,12 +53,21 @@ def main():
client = ipaclient.IPAClient()
try:
aci_entry = client.get_aci_entry(aci_fields)
except ipaerror.IPAError, e:
print("Delegation list failed: " + str(e))
except xmlrpclib.Fault, fault:
if fault.faultCode == errno.ECONNREFUSED:
print "The IPA XML-RPC service is not responding."
else:
print fault.faultString
return 1
except kerberos.GSSError, e:
print "Could not initialize GSSAPI: %s/%s" % (e[0][0][0], e[0][1][0])
return 1
except xmlrpclib.ProtocolError, e:
print "Unable to connect to IPA server: %s" % (e.errmsg)
return 1
except ipaerror.IPAError, e:
print("Delegation list failed: " + str(e))
return 1
aci_str_list = aci_entry.getValues('aci')
if aci_str_list is None:
@@ -75,7 +86,8 @@ def main():
group_dn_to_cn = ipa.aci.extract_group_cns(aci_list, client)
for a in aci_list:
# the operator.itemgetter(0) lets us sort by the name field
for a in sorted(aci_list, key=operator.itemgetter(0)):
labels = client.attrs_to_labels(a.attrs)
print "Delegation Name: " + a.name
print "Group " + group_dn_to_cn[a.source_group]

View File

@@ -32,6 +32,7 @@ import kerberos
import krbV
import ldap
import copy
import errno
aci_fields = ['*', 'aci']
@@ -142,8 +143,11 @@ def main():
aci_entry.setValue('aci', new_aci_str_list)
client.update_entry(aci_entry)
except xmlrpclib.Fault, f:
print f.faultString
except xmlrpclib.Fault, fault:
if fault.faultCode == errno.ECONNREFUSED:
print "The IPA XML-RPC service is not responding."
else:
print fault.faultString
return 1
except kerberos.GSSError, e:
print "Could not initialize GSSAPI: %s/%s" % (e[0][0][0], e[0][1][0])

View File

@@ -29,6 +29,7 @@ import kerberos
import krbV
import ldap
import getpass
import errno
def usage():
print "ipa-passwd [user]"
@@ -98,6 +99,18 @@ def main():
try:
client = ipaclient.IPAClient()
client.modifyPassword(principal, None, password)
except xmlrpclib.Fault, fault:
if fault.faultCode == errno.ECONNREFUSED:
print "The IPA XML-RPC service is not responding."
else:
print fault.faultString
return 1
except kerberos.GSSError, e:
print "Could not initialize GSSAPI: %s/%s" % (e[0][0][0], e[0][1][0])
return 1
except xmlrpclib.ProtocolError, e:
print "Unable to connect to IPA server: %s" % (e.errmsg)
return 1
except ipa.ipaerror.IPAError, e:
print "%s" % (e.message)
return 1

View File

@@ -29,9 +29,10 @@ import ipa.config
import xmlrpclib
import kerberos
import ldap
import errno
def usage():
print "ipa-usermod [-c|--gecos STRING] [-d|--directory STRING] [-f|--firstname STRING] [-l|--lastname STRING] [-s|--shell STRING] [--add attribute=value] [--del attribute] [--set attribute=value] user"
print "ipa-usermod [-a|--activate] [-c|--gecos STRING] [-d|--directory STRING] [-f|--firstname STRING] [-l|--lastname STRING] [-s|--shell STRING] [--add attribute=value] [--del attribute] [--set attribute=value] user"
sys.exit(1)
def set_add_usage(which):
@@ -39,6 +40,8 @@ def set_add_usage(which):
def parse_options():
parser = OptionParser()
parser.add_option("-a", "--activate", dest="activate", action="store_true",
help="Activate the user")
parser.add_option("-c", "--gecos", dest="gecos",
help="Set the GECOS field")
parser.add_option("-d", "--directory", dest="directory",
@@ -49,12 +52,12 @@ def parse_options():
help="User's last name")
parser.add_option("-s", "--shell", dest="shell",
help="Set user's login shell to shell")
parser.add_option("--add", dest="addattr",
parser.add_option("--addattr", dest="addattr",
help="Adds an attribute or values to that attribute, attr=value",
action="append")
parser.add_option("--del", dest="delattr",
parser.add_option("--delattr", dest="delattr",
help="Remove an attribute", action="append")
parser.add_option("--set", dest="setattr",
parser.add_option("--setattr", dest="setattr",
help="Set an attribute, dropping any existing values that may exist",
action="append")
parser.add_option("-M", "--mailAddress", dest="mail",
@@ -90,7 +93,15 @@ def main():
client = ipaclient.IPAClient()
try:
user = client.get_user_by_uid(username)
attrs = ['*']
# in case any attributes being modified are operational such as
# nsaccountlock. Any attribute to be deleted needs to be included
# in the original record so it can be seen as being removed.
if options.delattr:
for d in options.delattr:
attrs.append(d)
user = client.get_user_by_uid(username, sattrs=attrs)
except ipa.ipaerror.exception_for(ipa.ipaerror.LDAP_NOT_FOUND):
print "User %s not found" % username
return 1
@@ -102,7 +113,7 @@ def main():
return 1
# If any options are set we use just those. Otherwise ask for all of them.
if options.gn or options.sn or options.directory or options.gecos or options.mail or options.shell or options.addattr or options.delattr or options.setattr:
if options.gn or options.sn or options.directory or options.gecos or options.mail or options.shell or options.addattr or options.delattr or options.setattr or options.activate:
givenname = options.gn
lastname = options.sn
gecos = options.gecos
@@ -202,8 +213,7 @@ def main():
if options.delattr:
for d in options.delattr:
# doesn't truly delete the attribute but does null out the value
user.setValue(d, '')
user.delValue(d)
if options.setattr:
for s in options.setattr:
@@ -228,11 +238,22 @@ def main():
value = cvalue + [value]
user.setValue(attr, value)
try:
if options.activate:
try:
client.mark_user_active(user.getValues('uid'))
print "User activated successfully."
except ipa.ipaerror.exception_for(ipa.ipaerror.LDAP_EMPTY_MODLIST):
print "User is already marked active"
return 0
except:
raise
client.update_user(user)
except xmlrpclib.Fault, f:
print f.faultString
except xmlrpclib.Fault, fault:
if fault.faultCode == errno.ECONNREFUSED:
print "The IPA XML-RPC service is not responding."
else:
print fault.faultString
return 1
except kerberos.GSSError, e:
print "Could not initialize GSSAPI: %s/%s" % (e[0][0][0], e[0][1][0])

View File

@@ -19,14 +19,20 @@
.\"
.TH "ipa-deluser" "1" "Oct 10 2007" "freeipa" ""
.SH "NAME"
ipa\-deluser \- Delete a user
ipa\-deluser \- Delete or inactivate a user
.SH "SYNOPSIS"
ipa\-deluser \fIuser\fR
ipa\-deluser [\fIOPTION\fR]... \fIuser\fR
.SH "DESCRIPTION"
Deletes a user with name \fIname\fR.
Inactivates a user with login name \fIname\fR.
Users are not completely removed with the command, just marked as inactive.
By default users are not completely removed. They are marked as inactive. Use the [\-d|\-\-delete] option to completely remove them.
Users are automatically removed from groups when they are deleted. The are not when inactivated.
.SH "OPTIONS"
.TP
\fB\-d\fR, \fB\-\-delete
Completely remove the user from the database. The default is to mark the user inactive.
.SH "EXIT STATUS"
The exit status is 0 on success, nonzero on error.

View File

@@ -37,5 +37,17 @@ Modify the description of the group
.TP
\fB\-r\fR, \fB\-\-remove\fR=\fIuser1,user2,...usern\fR
Remove one or more users from the group
.TP
\fB\-\-addattr\fR=\fIattr=value\fR
Add a new attribute, or value to an existing attribute
.TP
\fB\-\-delattr\fR=\fIattr=value\fR
Remove an attribute and all values
.TP
\fB\-\-setattr\fR=\fIattr=value\fR
Set an attribute to a new value, removing all old ones
.SH "EXIT STATUS"
The exit status is 0 on success, nonzero on error.

View File

@@ -21,10 +21,10 @@
.SH "NAME"
ipa\-usermod \- Modify a user
.SH "SYNOPSIS"
ipa\-usermod [\fIOPTION\fR]... \fIgroup\fR
ipa\-usermod [\fIOPTION\fR]... \fIname\fR
.SH "DESCRIPTION"
Updates the members or description of \fIgroup\fR.
Updates the user \fIname\fR.
.SH "OPTIONS"
.TP
\fB\-a\fR, \fB\-\-add\fR=\fIuser1,user2,...usern\fR
@@ -37,5 +37,17 @@ Modify the description of the group
.TP
\fB\-r\fR, \fB\-\-remove\fR=\fIuser1,user2,...usern\fR
Remove one or more users from the group
.TP
\fB\-\-addattr\fR=\fIattr=value\fR
Add a new attribute, or value to an existing attribute
.TP
\fB\-\-delattr\fR=\fIattr=value\fR
Remove an attribute and all values
.TP
\fB\-\-setattr\fR=\fIattr=value\fR
Set an attribute to a new value, removing all old ones
.SH "EXIT STATUS"
The exit status is 0 on success, nonzero on error.

View File

@@ -1,6 +1,6 @@
AC_PREREQ(2.59c)
AC_INIT([freeipa-client],
[0.3.1],
AC_INIT([ipa-client],
[0.5.0],
[https://hosted.fedoraproject.org/projects/freeipa/newticket])
AC_CONFIG_SRCDIR([ipaclient/__init__.py])
@@ -47,7 +47,7 @@ AC_CONFIG_FILES([
AC_OUTPUT
echo "
FreeIPA client $VERSION
IPA client $VERSION
========================
prefix: ${prefix}

View File

@@ -1,7 +1,7 @@
Name: freeipa-client
Version: 0.3.1
Name: ipa-client
Version: 0.5.0
Release: 1%{?dist}
Summary: FreeIPA client
Summary: IPA client
Group: System Environment/Base
License: GPL
@@ -9,10 +9,10 @@ URL: http://www.freeipa.org
Source0: %{name}-%{version}.tgz
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
Requires: python python-ldap python-krbV freeipa-python
Requires: python python-ldap python-krbV ipa-python
%description
FreeIPA is a server for identity, policy, and audit.
IPA is a server for identity, policy, and audit.
The client package provide install and configuration scripts for clients.
%prep
@@ -41,6 +41,9 @@ rm -rf %{buildroot}
%{_usr}/share/ipa/*
%changelog
* Wed Nov 21 2007 Karl MacMillan <kmacmill@redhat.com> - 0.5.0-1
- Version bump for release and rpm name change
* Thu Nov 1 2007 Karl MacMillan <kmacmill@redhat.com> - 0.3.1-1
- Version bump for release

View File

@@ -1,7 +1,7 @@
Name: freeipa-client
Name: ipa-client
Version: VERSION
Release: 1%{?dist}
Summary: FreeIPA client
Summary: IPA client
Group: System Environment/Base
License: GPL
@@ -9,10 +9,10 @@ URL: http://www.freeipa.org
Source0: %{name}-%{version}.tgz
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
Requires: python python-ldap python-krbV freeipa-python
Requires: python python-ldap python-krbV ipa-python
%description
FreeIPA is a server for identity, policy, and audit.
IPA is a server for identity, policy, and audit.
The client package provide install and configuration scripts for clients.
%prep
@@ -41,6 +41,9 @@ rm -rf %{buildroot}
%{_usr}/share/ipa/*
%changelog
* Wed Nov 21 2007 Karl MacMillan <kmacmill@redhat.com> - 0.5.0-1
- Version bump for release and rpm name change
* Thu Nov 1 2007 Karl MacMillan <kmacmill@redhat.com> - 0.3.1-1
- Version bump for release

View File

@@ -124,6 +124,21 @@ def main():
print "IPA Server: "+ds.getServerName()
print "BaseDN: "+ds.getBaseDN()
# Configure ipa.conf
ipaconf = ipaclient.ipachangeconf.IPAChangeConf("IPA Installer")
ipaconf.setOptionAssignment(" = ")
ipaconf.setSectionNameDelimiters(("[","]"))
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()})
opts.append({'name':'defaults', 'type':'section', 'value':defopts})
opts.append({'name':'empty', 'type':'empty'})
ipaconf.newConf("/etc/ipa/ipa.conf", opts)
# Configure ldap.conf
ldapconf = ipaclient.ipachangeconf.IPAChangeConf("IPA Installer")
ldapconf.setOptionAssignment(" ")

View File

@@ -37,6 +37,16 @@ class ACI:
if acistr is not None:
self.parse_acistr(acistr)
def __getitem__(self,key):
"""Fake getting attributes by key for sorting"""
if key == 0:
return self.name
if key == 1:
return self.source_group
if key == 2:
return self.dest_group
raise TypeError("Unknown key value %s" % key)
def export_to_string(self):
"""Converts the ACI to a string suitable for an LDAP aci attribute."""
attrs_str = ' || '.join(self.attrs)

View File

@@ -1,7 +1,7 @@
Name: freeipa-python
Version: 0.4.1
Name: ipa-python
Version: 0.5.0
Release: 1%{?dist}
Summary: FreeIPA authentication server
Summary: Ipa authentication server
Group: System Environment/Base
License: GPL
@@ -17,7 +17,7 @@ Requires: PyKerberos
%define pkgpythondir %{python_sitelib}/ipa
%description
FreeIPA is a server for identity, policy, and audit.
Ipa is a server for identity, policy, and audit.
%prep
%setup -q
@@ -38,6 +38,9 @@ rm -rf %{buildroot}
%config(noreplace) %{_sysconfdir}/ipa/ipa.conf
%changelog
* Wed Nov 21 2007 Karl MacMillan <kmacmill@redhat.com> - 0.5.0-1
- Version bump for release and rename of rpm
* Thu Nov 1 2007 Karl MacMillan <kmacmill@redhat.com> - 0.4.1-1
- Version bump for release

View File

@@ -1,7 +1,7 @@
Name: freeipa-python
Name: ipa-python
Version: VERSION
Release: 1%{?dist}
Summary: FreeIPA authentication server
Summary: Ipa authentication server
Group: System Environment/Base
License: GPL
@@ -17,7 +17,7 @@ Requires: PyKerberos
%define pkgpythondir %{python_sitelib}/ipa
%description
FreeIPA is a server for identity, policy, and audit.
Ipa is a server for identity, policy, and audit.
%prep
%setup -q
@@ -38,6 +38,9 @@ rm -rf %{buildroot}
%config(noreplace) %{_sysconfdir}/ipa/ipa.conf
%changelog
* Wed Nov 21 2007 Karl MacMillan <kmacmill@redhat.com> - 0.5.0-1
- Version bump for release and rename of rpm
* Thu Nov 1 2007 Karl MacMillan <kmacmill@redhat.com> - 0.4.1-1
- Version bump for release

View File

@@ -135,10 +135,14 @@ class IPAClient:
return all_users
def get_add_schema(self):
"""Prototype for the GUI. Specify in the directory fields to
be displayed and what data to get for new users."""
result = self.transport.get_add_schema()
def get_custom_fields(self):
"""Get custom user fields"""
result = self.transport.get_custom_fields()
return result
def set_custom_fields(self, schema):
"""Set custom user fields"""
result = self.transport.set_custom_fields(schema)
return result
def find_users(self, criteria, sattrs=None, searchlimit=0, timelimit=-1):
@@ -174,10 +178,16 @@ class IPAClient:
return result
def mark_user_deleted(self,uid):
def mark_user_active(self,uid):
"""Set a user as active by uid."""
result = self.transport.mark_user_active(uid)
return result
def mark_user_inactive(self,uid):
"""Set a user as inactive by uid."""
result = self.transport.mark_user_deleted(uid)
result = self.transport.mark_user_inactive(uid)
return result
# Groups support
@@ -332,6 +342,52 @@ class IPAClient:
entries.append(user.User(e))
return entries
def mark_group_active(self,cn):
"""Set a group as active by cn."""
result = self.transport.mark_group_active(cn)
return result
def mark_group_inactive(self,cn):
"""Set a group as inactive by cn."""
result = self.transport.mark_group_inactive(cn)
return result
# Configuration
def get_ipa_config(self):
"""Get the IPA configuration"""
result = self.transport.get_ipa_config()
return entity.Entity(result)
def update_ipa_config(self, config):
"""Updates the IPA configuration.
config is an Entity object.
"""
result = self.transport.update_ipa_config(config.origDataDict(), config.toDict())
return result
def get_password_policy(self):
"""Get the IPA password policy"""
result = self.transport.get_password_policy()
return entity.Entity(result)
def update_password_policy(self, policy):
"""Updates the IPA password policy.
policy is an Entity object.
"""
result = self.transport.update_password_policy(policy.origDataDict(), policy.toDict())
return result
def add_service_principal(self, princ_name):
return self.transport.add_service_principal(princ_name)
def get_keytab(self, princ_name):
return self.transport.get_keytab(princ_name)
# radius support
def get_radius_client_by_ip_addr(self, ip_addr, container=None, sattrs=None):

View File

@@ -28,6 +28,11 @@ class IPAError(exceptions.Exception):
error."""
self.code = code
self.message = message
# Fill this in as an empty LDAP error message so we don't have a lot
# of "if e.detail ..." everywhere
if detail is None:
detail = []
detail.append({'desc':'','info':''})
self.detail = detail
def __str__(self):
@@ -118,6 +123,11 @@ LDAP_EMPTY_MODLIST = gen_error_code(
0x0006,
"No modifications to be performed")
LDAP_NO_CONFIG = gen_error_code(
LDAP_CATEGORY,
0x0007,
"IPA configuration not found")
#
# Input errors (sample - replace me)
#
@@ -147,3 +157,8 @@ CONNECTION_GSSAPI_CREDENTIALS = gen_error_code(
CONNECTION_CATEGORY,
0x0003,
"GSSAPI Authorization error")
CONNECTION_UNWILLING = gen_error_code(
CONNECTION_CATEGORY,
0x0004,
"Account inactivated. Server is unwilling to perform.")

View File

@@ -23,8 +23,11 @@ import string
import tempfile
import logging
import subprocess
from random import Random
from time import gmtime
import os
import stat
import socket
import readline
import traceback
from types import *
@@ -39,7 +42,6 @@ def realm_to_suffix(realm_name):
terms = ["dc=" + x.lower() for x in s]
return ",".join(terms)
def template_str(txt, vars):
return string.Template(txt).substitute(vars)
@@ -334,6 +336,15 @@ def parse_generalized_time(timestr):
except ValueError:
return None
def ipa_generate_password():
rndpwd = ''
r = Random()
r.seed(gmtime())
for x in range(12):
# rndpwd += chr(r.randint(32,126))
rndpwd += chr(r.randint(65,90)) #stricter set for testing
return rndpwd
def format_list(items, quote=None, page_width=80):
'''Format a list of items formatting them so they wrap to fit the

View File

@@ -218,23 +218,32 @@ class RPCClient:
return ipautil.unwrap_binary_data(result)
def get_add_schema(self):
"""Get the list of attributes we need to ask when adding a new
user.
"""
def get_custom_fields(self):
"""Get custom user fields."""
server = self.setup_server()
# FIXME: Hardcoded and designed for the TurboGears GUI. Do we want
# this for the CLI as well?
try:
result = server.get_add_schema()
result = server.get_custom_fields()
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 set_custom_fields(self, schema):
"""Set custom user fields."""
server = self.setup_server()
try:
result = server.set_custom_fields(schema)
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_all_users (self):
"""Return a list containing a User object for each existing user."""
@@ -309,12 +318,12 @@ class RPCClient:
return result
def mark_user_deleted(self,uid):
"""Mark a user as deleted/inactive"""
def mark_user_active(self,uid):
"""Mark a user as active"""
server = self.setup_server()
try:
result = server.mark_user_deleted(uid)
result = server.mark_user_active(uid)
except xmlrpclib.Fault, fault:
raise ipaerror.gen_exception(fault.faultCode, fault.faultString)
except socket.error, (value, msg):
@@ -322,6 +331,20 @@ class RPCClient:
return ipautil.unwrap_binary_data(result)
def mark_user_inactive(self,uid):
"""Mark a user as inactive"""
server = self.setup_server()
try:
result = server.mark_user_inactive(uid)
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)
# Group support
def get_groups_by_member(self,member_dn,sattrs=None):
@@ -592,6 +615,106 @@ class RPCClient:
return ipautil.unwrap_binary_data(result)
def mark_group_active(self,cn):
"""Mark a group as active"""
server = self.setup_server()
try:
result = server.mark_group_active(cn)
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 mark_group_inactive(self,cn):
"""Mark a group as inactive"""
server = self.setup_server()
try:
result = server.mark_group_inactive(cn)
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)
# Configuration support
def get_ipa_config(self):
"""Get the IPA configuration"""
server = self.setup_server()
try:
result = server.get_ipa_config()
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 update_ipa_config(self, oldconfig, newconfig):
"""Update the IPA configuration"""
server = self.setup_server()
try:
result = server.update_ipa_config(oldconfig, newconfig)
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_password_policy(self):
"""Get the IPA password policy"""
server = self.setup_server()
try:
result = server.get_password_policy()
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 update_password_policy(self, oldpolicy, newpolicy):
"""Update the IPA password policy"""
server = self.setup_server()
try:
result = server.update_password_policy(oldpolicy, newpolicy)
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 add_service_principal(self, princ_name):
server = self.setup_server()
try:
result = server.add_service_principal(princ_name)
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()
try:
result = server.get_keytab(princ_name)
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)
# radius support
def get_radius_client_by_ip_addr(self, ip_addr, container, sattrs=None):

View File

@@ -35,7 +35,7 @@ def setup_package():
try:
setup(
name = "freeipa-python",
version = "0.4.1",
version = "0.5.0",
license = "GPL",
author = "Karl MacMillan, et.al.",
author_email = "kmacmillan@redhat.com",

View File

@@ -11,6 +11,7 @@ SUBDIRS = \
ipaserver \
ipa-slapi-plugins \
xmlrpc-server \
ipa-keytab-util \
$(NULL)
EXTRA_DIST = \

View File

@@ -1,6 +1,6 @@
AC_PREREQ(2.59c)
AC_INIT([freeipa-server],
[0.4],
AC_INIT([ipa-server],
[0.5],
[https://hosted.fedoraproject.org/projects/freeipa/newticket])
AC_CONFIG_SRCDIR([ipaserver/ipaldap.py])
@@ -229,12 +229,13 @@ AC_CONFIG_FILES([
ipa-slapi-plugins/ipa-pwd-extop/Makefile
xmlrpc-server/Makefile
xmlrpc-server/test/Makefile
ipa-keytab-util/Makefile
])
AC_OUTPUT
echo "
FreeIPA Server $VERSION
IPA Server $VERSION
========================
prefix: ${prefix}

View File

@@ -0,0 +1,27 @@
The way multi-valued fields work is this:
- A new widget is added to the form. I name it as the attribute + s.
For example, I use cns for the cn attribute.
- If you need a new validator use a ForEach() so that each value is
checked.
- This attribute is populated from the incoming attribute from the
user or group record. The widget can support multiple fields at once
but I'm using it for just one field. In fact, I don't know if it
will work with more the way I'm using it.
- In the GUI an operator can add/remove values to each multi-valued field.
- Naming is very important in the widget. TurboGears automatically
re-assembles the data into a list of dict entries if you name things
properly. For example, the cns (multiple CN entries) looks like:
cns-0.cn=Rob+Crittenden&cns-1.cn=Robert+Crittenden&cns-2.cn=rcrit
- This gets converted to:
[{'cn': u'Rob Crittenden'}, {'cn': u'Robert Crittenden'}, {'cn': u'rcrit'}]
- I take this list of dicts and pull out each value and append it to a new
list that represents the original multi-valued field
- Then the list/dict version is removed (in this case, kw['cns']).
When adding a new field you have to update:
1. The form to add the new ExpandingForm() field and perhaps a validator
2. The edit template to add the boilerplate to display the field
3. The show template to be able to display all the fields separately
4. The new template if you want to be able to enter these on new entries
5. The subcontroller so you can do the input and output conversions

View File

@@ -17,6 +17,8 @@ import ipa.ipaclient
from subcontrollers.user import UserController
from subcontrollers.group import GroupController
from subcontrollers.delegation import DelegationController
from subcontrollers.policy import PolicyController
from subcontrollers.ipapolicy import IPAPolicyController
ipa.config.init_config()
@@ -27,6 +29,8 @@ class Root(controllers.RootController):
user = UserController()
group = GroupController()
delegate = DelegationController()
policy = PolicyController()
ipapolicy = IPAPolicyController()
@expose(template="ipagui.templates.welcome")
@identity.require(identity.not_anonymous())

View File

@@ -4,8 +4,9 @@ appdir = $(IPA_DATA_DIR)/ipagui/forms
app_PYTHON = \
__init__.py \
group.py \
ipapolicy.py \
user.py \
delegate.py \
delegate.py \
$(NULL)
EXTRA_DIST = \

View File

@@ -44,7 +44,7 @@ aci_checkbox_attrs = [(field.name, field.label) for field in aci_attrs]
aci_name_to_label = dict(aci_checkbox_attrs)
class DelegateFields():
name = widgets.TextField(name="name", label="Name")
name = widgets.TextField(name="name", label="Delegation Name")
source_group_dn = widgets.HiddenField(name="source_group_dn")
dest_group_dn = widgets.HiddenField(name="dest_group_dn")

View File

@@ -1,14 +1,18 @@
import turbogears
from turbogears import validators, widgets
from tg_expanding_form_widget.tg_expanding_form_widget import ExpandingForm
class GroupFields():
cn = widgets.TextField(name="cn", label="Name")
gidnumber = widgets.TextField(name="gidnumber", label="GID")
description = widgets.TextField(name="description", label="Description")
cn_hidden = widgets.HiddenField(name="cn")
editprotected_hidden = widgets.HiddenField(name="editprotected")
nsAccountLock = widgets.SingleSelectField(name="nsAccountLock",
label="Group Status",
options = [("", "active"), ("true", "inactive")])
group_orig = widgets.HiddenField(name="group_orig")
member_data = widgets.HiddenField(name="member_data")
dn_to_info_json = widgets.HiddenField(name="dn_to_info_json")
@@ -37,6 +41,7 @@ class GroupNewForm(widgets.Form):
class GroupEditValidator(validators.Schema):
cn = validators.String(not_empty=True)
gidnumber = validators.Int(not_empty=False)
description = validators.String(not_empty=False)
@@ -48,7 +53,7 @@ class GroupEditForm(widgets.Form):
params = ['members', 'group_fields']
hidden_fields = [
GroupFields.cn_hidden, GroupFields.editprotected_hidden,
GroupFields.editprotected_hidden,
GroupFields.group_orig, GroupFields.member_data,
GroupFields.dn_to_info_json
]

View File

@@ -0,0 +1,59 @@
import turbogears
from turbogears import validators, widgets
class IPAPolicyFields():
# From cn=ipaConfig
ipausersearchfields = widgets.TextField(name="ipausersearchfields", label="User Search Fields")
ipagroupsearchfields = widgets.TextField(name="ipagroupsearchfields", label="Group Search Fields")
ipasearchtimelimit = widgets.TextField(name="ipasearchtimelimit", label="Search Time Limit (sec.)", attrs=dict(size=6,maxlength=6))
ipasearchrecordslimit = widgets.TextField(name="ipasearchrecordslimit", label="Search Records Limit", attrs=dict(size=6,maxlength=6))
ipahomesrootdir = widgets.TextField(name="ipahomesrootdir", label="Root for Home Directories")
ipadefaultloginshell = widgets.TextField(name="ipadefaultloginshell", label="Default shell")
ipadefaultprimarygroup = widgets.TextField(name="ipadefaultprimarygroup", label="Default Users group")
ipamaxusernamelength = widgets.TextField(name="ipamaxusernamelength", label="Max. Username Length", attrs=dict(size=3,maxlength=3))
ipapwdexpadvnotify = widgets.TextField(name="ipapwdexpadvnotify", label="Password Expiration Notification (days)", attrs=dict(size=3,maxlength=3))
ipapolicy_orig = widgets.HiddenField(name="ipapolicy_orig")
# From cn=accounts
krbmaxpwdlife = widgets.TextField(name="krbmaxpwdlife", label="Max. Password Lifetime", attrs=dict(size=3,maxlength=3))
krbminpwdlife = widgets.TextField(name="krbminpwdlife", label="Min. Password Lifetime", attrs=dict(size=3,maxlength=3))
krbpwdmindiffchars = widgets.TextField(name="krbpwdmindiffchars", label="Min. number of character classes", attrs=dict(size=3,maxlength=3))
krbpwdminlength = widgets.TextField(name="krbpwdminlength", label="Min. Length of password", attrs=dict(size=3,maxlength=3))
krbpwdhistorylength = widgets.TextField(name="krbpwdhistorylength", label="Password History size", attrs=dict(size=3,maxlength=3))
password_orig = widgets.HiddenField(name="password_orig")
class IPAPolicyValidator(validators.Schema):
ipausersearchfields = validators.String(not_empty=True)
ipagroupsearchfields = validators.String(not_empty=True)
ipasearchtimelimit = validators.Number(not_empty=True)
ipasearchrecordslimit = validators.Number(not_empty=True)
ipamaxusernamelength = validators.Number(not_empty=True)
ipapwdexpadvnotify = validators.Number(not_empty=True)
ipahomesrootdir = validators.String(not_empty=True)
ipadefaultloginshell = validators.String(not_empty=True)
ipadefaultprimarygroup = validators.String(not_empty=True)
krbmaxpwdlife = validators.Number(not_empty=True)
krbminpwdlife = validators.Number(not_empty=True)
krbpwdmindiffchars = validators.Number(not_empty=True)
krbpwdminlength = validators.Number(not_empty=True)
krbpwdhistorylength = validators.Number(not_empty=True)
class IPAPolicyForm(widgets.Form):
params = ['ipapolicy_fields']
hidden_fields = [
IPAPolicyFields.ipapolicy_orig, IPAPolicyFields.password_orig
]
validator = IPAPolicyValidator()
def __init__(self, *args, **kw):
super(IPAPolicyForm,self).__init__(*args, **kw)
(self.template_c, self.template) = widgets.meta.load_kid_template(
"ipagui.templates.ipapolicyeditform")
self.ipapolicy_fields = IPAPolicyFields
def update_params(self, params):
super(IPAPolicyForm,self).update_params(params)

View File

@@ -1,10 +1,12 @@
import turbogears
from turbogears import validators, widgets
from tg_expanding_form_widget.tg_expanding_form_widget import ExpandingForm
class UserFields():
givenname = widgets.TextField(name="givenname", label="Given Name")
sn = widgets.TextField(name="sn", label="Family Name")
cn = widgets.TextField(name="cn", label="Common Names")
cns = ExpandingForm(name="cns", label="Common Names", fields=[cn])
title = widgets.TextField(name="title", label="Title")
displayname = widgets.TextField(name="displayname", label="Display Name")
initials = widgets.TextField(name="initials", label="Initials")
@@ -21,11 +23,16 @@ class UserFields():
mail = widgets.TextField(name="mail", label="E-mail Address")
telephonenumber = widgets.TextField(name="telephonenumber", label="Work Number")
telephonenumbers = ExpandingForm(name="telephonenumbers", label="Work Numbers", fields=[telephonenumber])
facsimiletelephonenumber = widgets.TextField(name="facsimiletelephonenumber",
label="Fax Number")
facsimiletelephonenumbers = ExpandingForm(name="facsimiletelephonenumbers", label="Fax Numbers", fields=[facsimiletelephonenumber])
mobile = widgets.TextField(name="mobile", label="Cell Number")
mobiles = ExpandingForm(name="mobiles", label="Cell Numbers", fields=[mobile])
pager = widgets.TextField(name="pager", label="Pager Number")
pagers = ExpandingForm(name="pagers", label="Pager Numbers", fields=[pager])
homephone = widgets.TextField(name="homephone", label="Home Number")
homephones = ExpandingForm(name="homephones", label="Home Numbers", fields=[homephone])
street = widgets.TextField(name="street", label="Street Address")
l = widgets.TextField(name="l", label="City")
@@ -67,7 +74,8 @@ class UserNewValidator(validators.Schema):
userpassword_confirm = validators.String(not_empty=False)
givenname = validators.String(not_empty=True)
sn = validators.String(not_empty=True)
mail = validators.Email(not_empty=True)
cn = validators.ForEach(validators.String(not_empty=True))
mail = validators.Email(not_empty=False)
chained_validators = [
validators.FieldsMatch('userpassword', 'userpassword_confirm')
@@ -102,6 +110,7 @@ class UserEditValidator(validators.Schema):
userpassword_confirm = validators.String(not_empty=False)
givenname = validators.String(not_empty=True)
sn = validators.String(not_empty=True)
cn = validators.ForEach(validators.String(not_empty=True))
mail = validators.Email(not_empty=True)
uidnumber = validators.Int(not_empty=False)
gidnumber = validators.Int(not_empty=False)

View File

@@ -13,7 +13,7 @@ def password_expires_in(datestr):
if not expdate:
return sys.maxint
delta = expdate - datetime.datetime.now()
delta = expdate - datetime.datetime.now(ipautil.GeneralizedTimeZone())
return delta.days
def password_is_expired(days):

View File

@@ -2,6 +2,11 @@ from turbogears.identity.soprovider import *
from turbogears.identity.visitor import *
import logging
import os
import ipa.ipaclient
from ipaserver import funcs
import ipa.config
import ipa.group
import ipa.user
log = logging.getLogger("turbogears.identity")
@@ -15,7 +20,25 @@ class IPA_User(object):
(principal, realm) = user_name.split('@')
self.display_name = principal
self.permissions = None
self.groups = None
transport = funcs.IPAServer()
client = ipa.ipaclient.IPAClient(transport)
client.set_krbccache(os.environ["KRB5CCNAME"])
try:
user = client.get_user_by_principal(user_name, ['dn'])
self.groups = []
groups = client.get_groups_by_member(user.dn, ['dn', 'cn'])
if isinstance(groups, str):
groups = [groups]
for ginfo in groups:
# cn may be multi-valued, add them all just in case
cn = ginfo.getValue('cn')
if isinstance(cn, str):
cn = [cn]
for c in cn:
self.groups.append(c)
except:
raise
return
class ProxyIdentity(object):
@@ -57,7 +80,7 @@ class ProxyIdentity(object):
def _get_groups(self):
try:
return self._groups
return self._user.groups
except AttributeError:
# Groups haven't been computed yet
return None
@@ -87,10 +110,14 @@ class ProxyIdentityProvider(SqlObjectIdentityProvider):
pass
def validate_identity(self, user_name, password, visit_key):
user = IPA_User(user_name)
log.debug( "validate_identity %s" % user_name)
return ProxyIdentity(visit_key, user)
try:
user = IPA_User(user_name)
log.debug( "validate_identity %s" % user_name)
return ProxyIdentity(visit_key, user)
except:
# Something went wrong in fetching the user. Set to
# anonymous which will deny access.
return ProxyIdentity( None )
def validate_password(self, user, user_name, password):
'''Validation has already occurred in the proxy'''

View File

@@ -383,3 +383,22 @@ ul.checkboxlist li input {
#inactive {
background-color: silver;
}
/*
* * TableKit css
*
*/
.sortcol {
cursor: pointer;
padding-right: 20px !important;
background-repeat: no-repeat !important;
background-position: right center !important;
text-decoration: underline;
}
.sortasc {
background-image: url(/static/images/up.gif) !important;
}
.sortdesc {
background-image: url(/static/images/down.gif) !important;
}

View File

@@ -5,6 +5,8 @@ app_PYTHON = \
__init__.py \
group.py \
ipacontroller.py \
ipapolicy.py \
policy.py \
user.py \
delegation.py \
$(NULL)

View File

@@ -19,6 +19,7 @@ import ipagui.forms.delegate
import ipa.aci
import ldap.dn
import operator
log = logging.getLogger(__name__)
@@ -34,7 +35,7 @@ class DelegationController(IPAController):
raise turbogears.redirect("/delegate/list")
@expose("ipagui.templates.delegatenew")
@identity.require(identity.not_anonymous())
@identity.require(identity.in_group("admins"))
def new(self):
"""Display delegate page"""
client = self.get_ipaclient()
@@ -45,7 +46,7 @@ class DelegationController(IPAController):
return dict(form=delegate_form, delegate=delegate)
@expose()
@identity.require(identity.not_anonymous())
@identity.require(identity.in_group("admins"))
def create(self, **kw):
"""Creates a new delegation"""
self.restrict_post()
@@ -63,11 +64,34 @@ class DelegationController(IPAController):
tg_template='ipagui.templates.delegatenew')
try:
aci_entry = client.get_aci_entry(aci_fields)
new_aci = ipa.aci.ACI()
new_aci.name = kw.get('name')
new_aci.source_group = kw.get('source_group_dn')
new_aci.dest_group = kw.get('dest_group_dn')
new_aci.attrs = kw.get('attrs')
if (new_aci.attrs, str):
new_aci.attrs = [new_aci.attrs]
# Look for an existing ACI of the same name
aci_str_list = aci_entry.getValues('aci')
if aci_str_list is None:
aci_str_list = []
if not(isinstance(aci_str_list,list) or isinstance(aci_str_list,tuple)):
aci_str_list = [aci_str_list]
for aci_str in aci_str_list:
try:
old_aci = ipa.aci.ACI(aci_str)
if old_aci.name == new_aci.name:
turbogears.flash("Delgate add failed: a delegation of that name already exists")
return dict(form=delegate_form, delegate=kw,
tg_template='ipagui.templates.delegatenew')
except SyntaxError:
# ignore aci_str's that ACI can't parse
pass
# not pulling down existing aci attributes
aci_entry = client.get_aci_entry(['dn'])
@@ -75,7 +99,7 @@ class DelegationController(IPAController):
client.update_entry(aci_entry)
except ipaerror.IPAError, e:
turbogears.flash("Delgate add failed: " + str(e))
turbogears.flash("Delgate add failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
return dict(form=delegate_form, delegate=kw,
tg_template='ipagui.templates.delegatenew')
@@ -83,7 +107,7 @@ class DelegationController(IPAController):
raise turbogears.redirect('/delegate/list')
@expose("ipagui.templates.delegateedit")
@identity.require(identity.not_anonymous())
@identity.require(identity.in_group("admins"))
def edit(self, acistr, tg_errors=None):
"""Display delegate page"""
if tg_errors:
@@ -105,12 +129,12 @@ class DelegationController(IPAController):
return dict(form=delegate_form, delegate=delegate)
except (SyntaxError, ipaerror.IPAError), e:
turbogears.flash("Delegation edit failed: " + str(e))
turbogears.flash("Delegation edit failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
raise turbogears.redirect('/delegate/list')
@expose()
@identity.require(identity.not_anonymous())
@identity.require(identity.in_group("admins"))
def update(self, **kw):
"""Display delegate page"""
self.restrict_post()
@@ -162,7 +186,7 @@ class DelegationController(IPAController):
turbogears.flash("delegate updated")
raise turbogears.redirect('/delegate/list')
except (SyntaxError, ipaerror.IPAError), e:
turbogears.flash("Delegation update failed: " + str(e))
turbogears.flash("Delegation update failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
return dict(form=delegate_form, delegate=kw,
tg_template='ipagui.templates.delegateedit')
@@ -175,7 +199,7 @@ class DelegationController(IPAController):
try:
aci_entry = client.get_aci_entry(aci_fields)
except ipaerror.IPAError, e:
turbogears.flash("Delegation list failed: " + str(e))
turbogears.flash("Delegation list failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
raise turbogears.redirect('/')
aci_str_list = aci_entry.getValues('aci')
@@ -194,6 +218,7 @@ class DelegationController(IPAController):
pass
group_dn_to_cn = ipa.aci.extract_group_cns(aci_list, client)
aci_list = sorted(aci_list, key=operator.itemgetter(0))
# The list page needs to display field labels, not raw
# LDAP attributes
for aci in aci_list:
@@ -205,7 +230,7 @@ class DelegationController(IPAController):
fields=ipagui.forms.delegate.DelegateFields())
@expose()
@identity.require(identity.not_anonymous())
@identity.require(identity.in_group("admins"))
def delete(self, acistr):
"""Display delegate page"""
self.restrict_post()
@@ -237,7 +262,7 @@ class DelegationController(IPAController):
turbogears.flash("delegate deleted")
raise turbogears.redirect('/delegate/list')
except (SyntaxError, ipaerror.IPAError), e:
turbogears.flash("Delegation deletion failed: " + str(e))
turbogears.flash("Delegation deletion failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
raise turbogears.redirect('/delegate/list')
@expose("ipagui.templates.delegategroupsearch")

View File

@@ -22,7 +22,7 @@ log = logging.getLogger(__name__)
group_new_form = ipagui.forms.group.GroupNewForm()
group_edit_form = ipagui.forms.group.GroupEditForm()
group_fields = ['*']
group_fields = ['*', 'nsAccountLock']
class GroupController(IPAController):
@@ -37,7 +37,7 @@ class GroupController(IPAController):
raise turbogears.redirect("/group/list")
@expose("ipagui.templates.groupnew")
@identity.require(identity.not_anonymous())
@identity.require(identity.in_group("admins"))
def new(self, tg_errors=None):
"""Displays the new group form"""
if tg_errors:
@@ -49,7 +49,7 @@ class GroupController(IPAController):
return dict(form=group_new_form, group={})
@expose()
@identity.require(identity.not_anonymous())
@identity.require(identity.in_group("admins"))
def create(self, **kw):
"""Creates a new group"""
self.restrict_post()
@@ -75,13 +75,16 @@ class GroupController(IPAController):
new_group.setValue('description', kw.get('description'))
rv = client.add_group(new_group)
if kw.get('nsAccountLock'):
client.mark_group_inactive(kw.get('cn'))
except ipaerror.exception_for(ipaerror.LDAP_DUPLICATE):
turbogears.flash("Group with name '%s' already exists" %
kw.get('cn'))
return dict(form=group_new_form, group=kw,
tg_template='ipagui.templates.groupnew')
except ipaerror.IPAError, e:
turbogears.flash("Group add failed: " + str(e) + "<br/>" + str(e.detail))
turbogears.flash("Group add failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
return dict(form=group_new_form, group=kw,
tg_template='ipagui.templates.groupnew')
@@ -90,7 +93,11 @@ class GroupController(IPAController):
# on any error, we redirect to the _edit_ group page.
# this code does data setup, similar to groupedit()
#
group = client.get_entry_by_cn(kw['cn'], group_fields)
if isinstance(kw['cn'], list):
cn0 = kw['cn'][0]
else:
cn0 = kw['cn']
group = client.get_entry_by_cn(cn0, group_fields)
group_dict = group.toDict()
member_dicts = []
@@ -166,7 +173,7 @@ class GroupController(IPAController):
@expose("ipagui.templates.groupedit")
@identity.require(identity.not_anonymous())
@identity.require(identity.in_group("admins"))
def edit(self, cn, tg_errors=None):
"""Displays the edit group form"""
if tg_errors:
@@ -204,20 +211,31 @@ class GroupController(IPAController):
raise turbogears.redirect('/group/show', uid=cn)
@expose()
@identity.require(identity.not_anonymous())
@identity.require(identity.in_group("admins"))
def update(self, **kw):
"""Updates an existing group"""
self.restrict_post()
client = self.get_ipaclient()
if kw.get('submit') == 'Cancel Edit':
orig_group_dict = loads(b64decode(kw.get('group_orig')))
# if cancelling need to use the original group because the one
# in kw may not exist yet.
cn = orig_group_dict.get('cn')
if (isinstance(cn,str)):
cn = [cn]
turbogears.flash("Edit group cancelled")
raise turbogears.redirect('/group/show', cn=kw.get('cn'))
raise turbogears.redirect('/group/show', cn=cn[0])
if kw.get('editprotected') == '':
# if editprotected set these don't get sent in kw
orig_group_dict = loads(b64decode(kw.get('group_orig')))
kw['cn'] = orig_group_dict['cn']
kw['gidnumber'] = orig_group_dict['gidnumber']
# Decode the member data, in case we need to round trip
member_dicts = loads(b64decode(kw.get('member_data')))
tg_errors, kw = self.groupupdatevalidate(**kw)
if tg_errors:
turbogears.flash("There were validation errors.<br/>" +
@@ -242,6 +260,20 @@ class GroupController(IPAController):
if new_group.gidnumber != new_gid:
group_modified = True
new_group.setValue('gidnumber', new_gid)
else:
new_group.setValue('gidnumber', orig_group_dict.get('gidnumber'))
new_group.setValue('cn', orig_group_dict.get('cn'))
if new_group.cn != kw.get('cn'):
group_modified = True
new_group.setValue('cn', kw['cn'])
if group_modified:
rv = client.update_group(new_group)
#
# If the group update succeeds, but below operations fail, we
if new_group.cn != kw.get('cn'):
group_modified = True
new_group.setValue('cn', kw['cn'])
if group_modified:
rv = client.update_group(new_group)
@@ -252,10 +284,21 @@ class GroupController(IPAController):
#
kw['group_orig'] = b64encode(dumps(new_group.toDict()))
except ipaerror.IPAError, e:
turbogears.flash("Group update failed: " + str(e))
turbogears.flash("Group update failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
return dict(form=group_edit_form, group=kw, members=member_dicts,
tg_template='ipagui.templates.groupedit')
if kw.get('nsAccountLock') == '':
kw['nsAccountLock'] = "false"
modify_no_update = False
if kw.get('nsAccountLock') == "false" and new_group.getValues('nsaccountlock') == "true":
client.mark_group_active(kw.get('cn'))
modify_no_update = True
elif kw.get('nsAccountLock') == "true" and new_group.nsaccountlock != "true":
client.mark_group_inactive(kw.get('cn'))
modify_no_update = True
#
# Add members
#
@@ -268,8 +311,9 @@ class GroupController(IPAController):
failed_adds = client.add_members_to_group(
utf8_encode_values(dnadds), new_group.dn)
kw['dnadd'] = failed_adds
group_modified = True
except ipaerror.IPAError, e:
turbogears.flash("Group update failed: " + str(e))
turbogears.flash("Group update failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
return dict(form=group_edit_form, group=kw, members=member_dicts,
tg_template='ipagui.templates.groupedit')
@@ -285,8 +329,9 @@ class GroupController(IPAController):
failed_dels = client.remove_members_from_group(
utf8_encode_values(dndels), new_group.dn)
kw['dndel'] = failed_dels
group_modified = True
except ipaerror.IPAError, e:
turbogears.flash("Group update failed: " + str(e))
turbogears.flash("Group update failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
return dict(form=group_edit_form, group=kw, members=member_dicts,
tg_template='ipagui.templates.groupedit')
@@ -308,8 +353,15 @@ class GroupController(IPAController):
return dict(form=group_edit_form, group=kw, members=member_dicts,
tg_template='ipagui.templates.groupedit')
turbogears.flash("%s updated!" % kw['cn'])
raise turbogears.redirect('/group/show', cn=kw['cn'])
if isinstance(kw['cn'], list):
cn0 = kw['cn'][0]
else:
cn0 = kw['cn']
if group_modified == True or modify_no_update == True:
turbogears.flash("%s updated!" % cn0)
else:
turbogears.flash("No modifications requested.")
raise turbogears.redirect('/group/show', cn=cn0)
@expose("ipagui.templates.grouplist")
@@ -330,7 +382,7 @@ class GroupController(IPAController):
turbogears.flash("These results are truncated.<br />" +
"Please refine your search and try again.")
except ipaerror.IPAError, e:
turbogears.flash("Find groups failed: " + str(e))
turbogears.flash("Find groups failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
raise turbogears.redirect("/group/list")
return dict(groups=groups, criteria=criteria,
@@ -374,7 +426,7 @@ class GroupController(IPAController):
turbogears.flash("group deleted")
raise turbogears.redirect('/group/list')
except (SyntaxError, ipaerror.IPAError), e:
turbogears.flash("Group deletion failed: " + str(e) + "<br/>" + str(e.detail))
turbogears.flash("Group deletion failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
raise turbogears.redirect('/group/list')
@validate(form=group_new_form)

View File

@@ -0,0 +1,168 @@
import os
from pickle import dumps, loads
from base64 import b64encode, b64decode
import copy
import logging
import cherrypy
import turbogears
from turbogears import controllers, expose, flash
from turbogears import validators, validate
from turbogears import widgets, paginate
from turbogears import error_handler
from turbogears import identity
from ipacontroller import IPAController
from ipa.entity import utf8_encode_values
from ipa import ipaerror
import ipa.entity
import ipagui.forms.ipapolicy
import ldap.dn
log = logging.getLogger(__name__)
ipapolicy_edit_form = ipagui.forms.ipapolicy.IPAPolicyForm()
class IPAPolicyController(IPAController):
@expose()
@identity.require(identity.in_group("admins"))
def index(self):
raise turbogears.redirect("/ipapolicy/show")
@expose("ipagui.templates.ipapolicyshow")
@identity.require(identity.in_group("admins"))
def show(self, tg_errors=None):
"""Displays the one policy page"""
client = self.get_ipaclient()
config = client.get_ipa_config()
ipapolicy = config.toDict()
ppolicy = client.get_password_policy()
password = ppolicy.toDict()
return dict(ipapolicy=ipapolicy,password=password,fields=ipagui.forms.ipapolicy.IPAPolicyFields())
@expose("ipagui.templates.ipapolicyedit")
@identity.require(identity.in_group("admins"))
def edit(self, tg_errors=None):
"""Displays the edit IPA policy form"""
if tg_errors:
turbogears.flash("There were validation errors.<br/>" +
"Please see the messages below for details.")
try:
client = self.get_ipaclient()
config = client.get_ipa_config()
ipapolicy_dict = config.toDict()
ppolicy = client.get_password_policy()
password_dict = ppolicy.toDict()
# store a copy of the original policy for the update later
ipapolicy_data = b64encode(dumps(ipapolicy_dict))
ipapolicy_dict['ipapolicy_orig'] = ipapolicy_data
# store a copy of the original policy for the update later
password_data = b64encode(dumps(password_dict))
password_dict['password_orig'] = password_data
# Combine the 2 dicts to make the form easier
ipapolicy_dict.update(password_dict)
return dict(form=ipapolicy_edit_form, ipapolicy=ipapolicy_dict)
except ipaerror.IPAError, e:
turbogears.flash("IPA Policy edit failed: " + str(e) + "<br/>" + str(e.detail))
raise turbogears.redirect('/ipapolicy/show')
@expose()
@identity.require(identity.in_group("admins"))
def update(self, **kw):
"""Display delegate page"""
self.restrict_post()
client = self.get_ipaclient()
if kw.get('submit', '').startswith('Cancel'):
turbogears.flash("Edit policy cancelled")
raise turbogears.redirect('/ipapolicy/show')
tg_errors, kw = self.ipapolicyupdatevalidate(**kw)
if tg_errors:
turbogears.flash("There were validation errors.<br/>" +
"Please see the messages below for details.")
return dict(form=ipapolicy_edit_form, ipapolicy=kw,
tg_template='ipagui.templates.ipapolicyedit')
policy_modified = False
password_modified = False
try:
orig_ipapolicy_dict = loads(b64decode(kw.get('ipapolicy_orig')))
orig_password_dict = loads(b64decode(kw.get('password_orig')))
new_ipapolicy = ipa.entity.Entity(orig_ipapolicy_dict)
new_password = ipa.entity.Entity(orig_password_dict)
if str(new_ipapolicy.ipasearchtimelimit) != str(kw.get('ipasearchtimelimit')):
policy_modified = True
new_ipapolicy.setValue('ipasearchtimelimit', kw.get('ipasearchtimelimit'))
if str(new_ipapolicy.ipasearchrecordslimit) != str(kw.get('ipasearchrecordslimit')):
policy_modified = True
new_ipapolicy.setValue('ipasearchrecordslimit', kw.get('ipasearchrecordslimit'))
if new_ipapolicy.ipausersearchfields != kw.get('ipausersearchfields'):
policy_modified = True
new_ipapolicy.setValue('ipausersearchfields', kw.get('ipausersearchfields'))
if new_ipapolicy.ipagroupsearchfields != kw.get('ipagroupsearchfields'):
policy_modified = True
new_ipapolicy.setValue('ipagroupsearchfields', kw.get('ipagroupsearchfields'))
if str(new_ipapolicy.ipapwdexpadvnotify) != str(kw.get('ipapwdexpadvnotify')):
policy_modified = True
new_ipapolicy.setValue('ipapwdexpadvnotify', kw.get('ipapwdexpadvnotify'))
if str(new_ipapolicy.ipamaxusernamelength) != str(kw.get('ipamaxusernamelength')):
policy_modified = True
new_ipapolicy.setValue('ipamaxusernamelength', kw.get('ipamaxusernamelength'))
if new_ipapolicy.ipahomesrootdir != kw.get('ipahomesrootdir'):
policy_modified = True
new_ipapolicy.setValue('ipahomesrootdir', kw.get('ipahomesrootdir'))
if new_ipapolicy.ipadefaultloginshell != kw.get('ipadefaultloginshell'):
policy_modified = True
new_ipapolicy.setValue('ipadefaultloginshell', kw.get('ipadefaultloginshell'))
if new_ipapolicy.ipadefaultprimarygroup != kw.get('ipadefaultprimarygroup'):
policy_modified = True
new_ipapolicy.setValue('ipadefaultprimarygroup', kw.get('ipadefaultprimarygroup'))
if policy_modified:
rv = client.update_ipa_config(new_ipapolicy)
# Now check the password policy for updates
if str(new_password.krbmaxpwdlife) != str(kw.get('krbmaxpwdlife')):
password_modified = True
new_password.setValue('krbmaxpwdlife', str(kw.get('krbmaxpwdlife')))
if str(new_password.krbminpwdlife) != str(kw.get('krbminpwdlife')):
password_modified = True
new_password.setValue('krbminpwdlife', str(kw.get('krbminpwdlife')))
if str(new_password.krbpwdhistorylength) != str(kw.get('krbpwdhistorylength')):
password_modified = True
new_password.setValue('krbpwdhistorylength', str(kw.get('krbpwdhistorylength')))
if str(new_password.krbpwdmindiffchars) != str(kw.get('krbpwdmindiffchars')):
password_modified = True
new_password.setValue('krbpwdmindiffchars', str(kw.get('krbpwdmindiffchars')))
if str(new_password.krbpwdminlength) != str(kw.get('krbpwdminlength')):
password_modified = True
new_password.setValue('krbpwdminlength', str(kw.get('krbpwdminlength')))
if password_modified:
rv = client.update_password_policy(new_password)
turbogears.flash("IPA Policy updated")
raise turbogears.redirect('/ipapolicy/show')
except ipaerror.IPAError, e:
turbogears.flash("Policy update failed: " + str(e) + e.detail[0]['desc'])
return dict(form=ipapolicy_edit_form, ipapolicy=kw,
tg_template='ipagui.templates.ipapolicyedit')
@validate(form=ipapolicy_edit_form)
@identity.require(identity.not_anonymous())
def ipapolicyupdatevalidate(self, tg_errors=None, **kw):
return tg_errors, kw

View File

@@ -0,0 +1,32 @@
import os
from pickle import dumps, loads
from base64 import b64encode, b64decode
import copy
import logging
import cherrypy
import turbogears
from turbogears import controllers, expose, flash
from turbogears import validators, validate
from turbogears import widgets, paginate
from turbogears import error_handler
from turbogears import identity
from ipacontroller import IPAController
from ipa.entity import utf8_encode_values
from ipa import ipaerror
import ldap.dn
log = logging.getLogger(__name__)
class PolicyController(IPAController):
@expose("ipagui.templates.policyindex")
@identity.require(identity.in_group("admins"))
def index(self, tg_errors=None):
"""Displays the one policy page"""
# TODO: return a dict of the items and URLs to display on
# Manage Policy
return dict()

View File

@@ -34,26 +34,48 @@ class UserController(IPAController):
def __init__(self, *args, **kw):
super(UserController,self).__init__(*args, **kw)
self.load_custom_fields()
# self.load_custom_fields()
def load_custom_fields(self):
# client = self.get_ipaclient()
# schema = client.get_user_custom_schema()
schema = [
{ 'label': 'See Also',
'field': 'seeAlso',
'required': 'true', } ,
{ 'label': 'O O O',
'field': 'o',
'required': 'false', } ,
]
client = self.get_ipaclient()
schema = client.get_custom_fields()
# FIXME: Don't load from LDAP every single time it is called
# FIXME: Is removing the attributes on the fly thread-safe? Do we
# need to lock here?
for s in schema:
required=False
if (s['required'] == "true"):
if (s['required'].lower() == "true"):
required=True
field = widgets.TextField(name=s['field'],label=s['label'])
validator = validators.String(not_empty=required)
# Don't allow dupes on the new form
try:
for i in range(len(user_new_form.custom_fields)):
if user_new_form.custom_fields[i].name == s['field']:
user_new_form.custom_fields.pop(i)
except:
pass
# Don't allow dupes on the edit form
try:
for i in range(len(user_edit_form.custom_fields)):
if user_edit_form.custom_fields[i].name == s['field']:
user_edit_form.custom_fields.pop(i)
except:
pass
# Don't allow dupes in the list of user fields
try:
for i in range(len(ipagui.forms.user.UserFields.custom_fields)):
if ipagui.forms.user.UserFields.custom_fields[i].name == s['field']:
ipagui.forms.user.UserFields.custom_fields.pop(i)
except:
pass
ipagui.forms.user.UserFields.custom_fields.append(field)
user_new_form.custom_fields.append(field)
user_edit_form.custom_fields.append(field)
@@ -61,15 +83,45 @@ class UserController(IPAController):
user_new_form.validator.add_field(s['field'], validator)
user_edit_form.validator.add_field(s['field'], validator)
def setup_mv_fields(self, field, fieldname):
"""Given a field (must be a list) and field name, convert that
field into a list of dictionaries of the form:
[ { fieldname : v1}, { fieldname : v2 }, .. ]
This is how we pre-fill values for multi-valued fields.
"""
mvlist = []
if field is not None:
for v in field:
mvlist.append({ fieldname : v } )
else:
# We need to return an empty value so something can be
# displayed on the edit page. Otherwise only an Add link
# will show, not an empty field.
mvlist.append({ fieldname : '' } )
return mvlist
def fix_incoming_fields(self, fields, fieldname, multifieldname):
"""This is called by the update() function. It takes the incoming
list of dictionaries and converts it into back into the original
field, then removes the multiple field.
"""
fields[fieldname] = []
for i in range(len(fields[multifieldname])):
fields[fieldname].append(fields[multifieldname][i][fieldname])
del(fields[multifieldname])
return fields
@expose()
def index(self):
raise turbogears.redirect("/user/list")
@expose("ipagui.templates.usernew")
@identity.require(identity.not_anonymous())
@identity.require(identity.in_any_group("admins","editors"))
def new(self, tg_errors=None):
"""Displays the new user form"""
self.load_custom_fields()
if tg_errors:
turbogears.flash("There were validation errors.<br/>" +
"Please see the messages below for details.")
@@ -77,7 +129,7 @@ class UserController(IPAController):
return dict(form=user_new_form, user={})
@expose()
@identity.require(identity.not_anonymous())
@identity.require(identity.in_any_group("admins","editors"))
def create(self, **kw):
"""Creates a new user"""
self.restrict_post()
@@ -88,6 +140,15 @@ class UserController(IPAController):
raise turbogears.redirect('/user/list')
tg_errors, kw = self.usercreatevalidate(**kw)
# Fix incoming multi-valued fields we created for the form
kw = self.fix_incoming_fields(kw, 'cn', 'cns')
kw = self.fix_incoming_fields(kw, 'telephonenumber', 'telephonenumbers')
kw = self.fix_incoming_fields(kw, 'facsimiletelephonenumber', 'facsimiletelephonenumbers')
kw = self.fix_incoming_fields(kw, 'mobile', 'mobiles')
kw = self.fix_incoming_fields(kw, 'pager', 'pagers')
kw = self.fix_incoming_fields(kw, 'homephone', 'homephones')
if tg_errors:
turbogears.flash("There were validation errors.<br/>" +
"Please see the messages below for details.")
@@ -136,21 +197,21 @@ class UserController(IPAController):
new_user.setValue('carlicense', kw.get('carlicense'))
new_user.setValue('labeleduri', kw.get('labeleduri'))
if kw.get('nsAccountLock'):
new_user.setValue('nsAccountLock', 'true')
for custom_field in user_new_form.custom_fields:
new_user.setValue(custom_field.name,
kw.get(custom_field.name, ''))
rv = client.add_user(new_user)
if kw.get('nsAccountLock'):
client.mark_user_inactive(kw.get('uid'))
except ipaerror.exception_for(ipaerror.LDAP_DUPLICATE):
turbogears.flash("Person with login '%s' already exists" %
turbogears.flash("User with login '%s' already exists" %
kw.get('uid'))
return dict(form=user_new_form, user=kw,
tg_template='ipagui.templates.usernew')
except ipaerror.IPAError, e:
turbogears.flash("User add failed: " + str(e))
turbogears.flash("User add failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
return dict(form=user_new_form, user=kw,
tg_template='ipagui.templates.usernew')
@@ -181,7 +242,7 @@ class UserController(IPAController):
try:
client.modifyPassword(user_dict['krbprincipalname'], "", kw.get('userpassword'))
except ipaerror.IPAError, e:
message = "Person successfully created.<br />"
message = "User successfully created.<br />"
message += "There was an error setting the password.<br />"
turbogears.flash(message)
return dict(form=user_edit_form, user=user_dict,
@@ -204,7 +265,7 @@ class UserController(IPAController):
failed_adds = dnadds
if len(failed_adds) > 0:
message = "Person successfully created.<br />"
message = "User successfully created.<br />"
message += "There was an error adding groups.<br />"
message += "Failures have been preserved in the add/remove lists."
turbogears.flash(message)
@@ -243,6 +304,7 @@ class UserController(IPAController):
@identity.require(identity.not_anonymous())
def edit(self, uid=None, principal=None, tg_errors=None):
"""Displays the edit user form"""
self.load_custom_fields()
if tg_errors:
turbogears.flash("There were validation errors.<br/>" +
"Please see the messages below for details.")
@@ -259,6 +321,32 @@ class UserController(IPAController):
turbogears.flash("User edit failed: No uid or principal provided")
raise turbogears.redirect('/')
user_dict = user.toDict()
# Load potential multi-valued fields
if isinstance(user_dict['cn'], str):
user_dict['cn'] = [user_dict['cn']]
user_dict['cns'] = self.setup_mv_fields(user_dict['cn'], 'cn')
if isinstance(user_dict.get('telephonenumber',''), str):
user_dict['telephonenumber'] = [user_dict.get('telephonenumber'),'']
user_dict['telephonenumbers'] = self.setup_mv_fields(user_dict.get('telephonenumber'), 'telephonenumber')
if isinstance(user_dict.get('facsimiletelephonenumber',''), str):
user_dict['facsimiletelephonenumber'] = [user_dict.get('facsimiletelephonenumber'),'']
user_dict['facsimiletelephonenumbers'] = self.setup_mv_fields(user_dict.get('facsimiletelephonenumber'), 'facsimiletelephonenumber')
if isinstance(user_dict.get('mobile',''), str):
user_dict['mobile'] = [user_dict.get('mobile'),'']
user_dict['mobiles'] = self.setup_mv_fields(user_dict.get('mobile'), 'mobile')
if isinstance(user_dict.get('pager',''), str):
user_dict['pager'] = [user_dict.get('pager'),'']
user_dict['pagers'] = self.setup_mv_fields(user_dict.get('pager'), 'pager')
if isinstance(user_dict.get('homephone',''), str):
user_dict['homephone'] = [user_dict.get('homephone'),'']
user_dict['homephones'] = self.setup_mv_fields(user_dict.get('homephone'), 'homephone')
# Edit shouldn't fill in the password field.
if user_dict.has_key('userpassword'):
del(user_dict['userpassword'])
@@ -300,7 +388,7 @@ class UserController(IPAController):
except ipaerror.IPAError, e:
if uid is None:
uid = principal
turbogears.flash("User edit failed: " + str(e))
turbogears.flash("User edit failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
raise turbogears.redirect('/user/show', uid=uid)
@expose()
@@ -314,6 +402,23 @@ class UserController(IPAController):
turbogears.flash("Edit user cancelled")
raise turbogears.redirect('/user/show', uid=kw.get('uid'))
# Fix incoming multi-valued fields we created for the form
kw = self.fix_incoming_fields(kw, 'cn', 'cns')
kw = self.fix_incoming_fields(kw, 'telephonenumber', 'telephonenumbers')
kw = self.fix_incoming_fields(kw, 'facsimiletelephonenumber', 'facsimiletelephonenumbers')
kw = self.fix_incoming_fields(kw, 'mobile', 'mobiles')
kw = self.fix_incoming_fields(kw, 'pager', 'pagers')
kw = self.fix_incoming_fields(kw, 'homephone', 'homephones')
# admins and editors can update anybody. A user can only update
# themselves. We need this check because it is very easy to guess
# the edit URI.
if ((not 'admins' in turbogears.identity.current.groups and
not 'editors' in turbogears.identity.current.groups) and
(kw.get('uid') != turbogears.identity.current.display_name)):
turbogears.flash("You do not have permission to update this user.")
raise turbogears.redirect('/user/show', uid=kw.get('uid'))
# Decode the group data, in case we need to round trip
user_groups_dicts = loads(b64decode(kw.get('user_groups_data')))
@@ -334,6 +439,14 @@ class UserController(IPAController):
try:
orig_user_dict = loads(b64decode(kw.get('user_orig')))
# remove multi-valued fields we created for the form
del(orig_user_dict['cns'])
del(orig_user_dict['telephonenumbers'])
del(orig_user_dict['facsimiletelephonenumbers'])
del(orig_user_dict['mobiles'])
del(orig_user_dict['pagers'])
del(orig_user_dict['homephones'])
new_user = ipa.user.User(orig_user_dict)
new_user.setValue('title', kw.get('title'))
new_user.setValue('givenname', kw.get('givenname'))
@@ -369,12 +482,6 @@ class UserController(IPAController):
new_user.setValue('carlicense', kw.get('carlicense'))
new_user.setValue('labeleduri', kw.get('labeleduri'))
if kw.get('nsAccountLock'):
new_user.setValue('nsAccountLock', 'true')
else:
new_user.setValue('nsAccountLock', None)
if kw.get('editprotected') == 'true':
if kw.get('userpassword'):
password_change = True
@@ -400,7 +507,7 @@ class UserController(IPAController):
# too much work to figure out unless someone really screams
pass
except ipaerror.IPAError, e:
turbogears.flash("User update failed: " + str(e))
turbogears.flash("User update failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
return dict(form=user_edit_form, user=kw,
user_groups=user_groups_dicts,
tg_template='ipagui.templates.useredit')
@@ -412,7 +519,7 @@ class UserController(IPAController):
if password_change:
rv = client.modifyPassword(kw['krbprincipalname'], "", kw.get('userpassword'))
except ipaerror.IPAError, e:
turbogears.flash("User password change failed: " + str(e))
turbogears.flash("User password change failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
return dict(form=user_edit_form, user=kw,
user_groups=user_groups_dicts,
tg_template='ipagui.templates.useredit')
@@ -459,6 +566,20 @@ class UserController(IPAController):
user_groups=user_groups_dicts,
tg_template='ipagui.templates.useredit')
if kw.get('nsAccountLock') == '':
kw['nsAccountLock'] = "false"
try:
if kw.get('nsAccountLock') == "false" and new_user.getValues('nsaccountlock') == "true":
client.mark_user_active(kw.get('uid'))
elif kw.get('nsAccountLock') == "true" and new_user.nsaccountlock != "true":
client.mark_user_inactive(kw.get('uid'))
except ipaerror.IPAError, e:
turbogears.flash("User status change failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
return dict(form=user_edit_form, user=kw,
user_groups=user_groups_dicts,
tg_template='ipagui.templates.useredit')
turbogears.flash("%s updated!" % kw['uid'])
raise turbogears.redirect('/user/show', uid=kw['uid'])
@@ -481,7 +602,7 @@ class UserController(IPAController):
turbogears.flash("These results are truncated.<br />" +
"Please refine your search and try again.")
except ipaerror.IPAError, e:
turbogears.flash("User list failed: " + str(e))
turbogears.flash("User list failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
raise turbogears.redirect("/user/list")
return dict(users=users, uid=uid, fields=ipagui.forms.user.UserFields())
@@ -492,6 +613,7 @@ class UserController(IPAController):
def show(self, uid):
"""Retrieve a single user for display"""
client = self.get_ipaclient()
self.load_custom_fields()
try:
user = client.get_user_by_uid(uid, user_fields)
@@ -523,7 +645,7 @@ class UserController(IPAController):
user_groups=user_groups, user_reports=user_reports,
user_manager=user_manager, user_secretary=user_secretary)
except ipaerror.IPAError, e:
turbogears.flash("User show failed: " + str(e))
turbogears.flash("User show failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
raise turbogears.redirect("/")
@expose()
@@ -539,7 +661,7 @@ class UserController(IPAController):
turbogears.flash("user deleted")
raise turbogears.redirect('/user/list')
except (SyntaxError, ipaerror.IPAError), e:
turbogears.flash("User deletion failed: " + str(e))
turbogears.flash("User deletion failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
raise turbogears.redirect('/user/list')
@validate(form=user_new_form)
@@ -661,7 +783,7 @@ class UserController(IPAController):
users_counter = users[0]
users = users[1:]
except ipaerror.IPAError, e:
turbogears.flash("search failed: " + str(e))
turbogears.flash("search failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
return dict(users=users, criteria=criteria,
which_select=kw.get('which_select'),

View File

@@ -20,8 +20,13 @@ app_DATA = \
groupnewform.kid \
groupnew.kid \
groupshow.kid \
ipapolicyeditform.kid \
ipapolicyedit.kid \
ipapolicyshow.kid \
loginfailed.kid \
master.kid \
policyindex.kid \
policylayout.kid \
usereditform.kid \
useredit.kid \
userlayout.kid \

View File

@@ -25,17 +25,22 @@ from ipagui.helpers import ipahelper
<script type="text/javascript" charset="utf-8"
src="${tg.url('/static/javascript/dynamicedit.js')}"></script>
<script type="text/javascript" charset="utf-8"
src="${tg.url('/tg_widgets/tg_expanding_form_widget/javascript/expanding_form.js')}"></script>
<?python searchurl = tg.url('/group/edit_search') ?>
<script type="text/javascript">
function toggleProtectedFields(checkbox) {
var gidnumberField = $('form_gidnumber');
var cnField = $('form_cn');
if (checkbox.checked) {
gidnumberField.disabled = false;
cnField.disabled = false;
$('form_editprotected').value = 'true';
} else {
gidnumberField.disabled = true;
cnField.disabled = true;
$('form_editprotected').value = '';
}
}
@@ -70,11 +75,9 @@ from ipagui.helpers import ipahelper
py:content="group_fields.cn.label" />:
</th>
<td>
<!-- <span py:replace="group_fields.cn.display(value_for(group_fields.cn))" />
<span py:replace="group_fields.cn.display(value_for(group_fields.cn))" />
<span py:if="tg.errors.get('cn')" class="fielderror"
py:content="tg.errors.get('cn')" /> -->
${value_for(group_fields.cn)}
py:content="tg.errors.get('cn')" />
</td>
</tr>
@@ -88,6 +91,9 @@ from ipagui.helpers import ipahelper
<span py:if="tg.errors.get('description')" class="fielderror"
py:content="tg.errors.get('description')" />
<script type="text/javascript">
document.getElementById('form_cn').disabled = true;
</script>
</td>
</tr>
@@ -106,6 +112,16 @@ from ipagui.helpers import ipahelper
</script>
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" for="${group_fields.nsAccountLock.field_id}" py:content="group_fields.nsAccountLock.label" />:
</th>
<td>
<span py:replace="group_fields.nsAccountLock.display(value_for(group_fields.nsAccountLock))" />
<span py:if="tg.errors.get('nsAccountLock')" class="fielderror"
py:content="tg.errors.get('nsAccountLock')" />
</td>
</tr>
</table>
<div>
@@ -160,6 +176,7 @@ from ipagui.helpers import ipahelper
div_counter = div_counter + 1
?>
</div>
&nbsp; <!-- a space here to prevent an empty div -->
</div>
</div>

View File

@@ -20,7 +20,7 @@
</div>
<div py:if='(groups != None) and (len(groups) > 0)'>
<h2>${len(groups)} results returned:</h2>
<table id="resultstable" class="details sortable resizable">
<table id="resultstable" class="details sortable resizable" cellspacing="0">
<thead>
<tr>
<th>
@@ -32,7 +32,15 @@
</tr>
</thead>
<tbody>
<tr py:for="group in groups">
<tr py:for="group in groups" py:if="group.nsAccountLock != 'true'">
<td>
<a href="${tg.url('/group/show',cn=group.cn)}">${group.cn}</a>
</td>
<td>
${group.description}
</td>
</tr>
<tr id="inactive" py:for="group in groups" py:if="group.nsAccountLock == 'true'">
<td>
<a href="${tg.url('/group/show',cn=group.cn)}">${group.cn}</a>
</td>

View File

@@ -7,12 +7,17 @@
</head>
<body>
<?python
edit_url = tg.url('/group/edit', cn=group.get('cn'))
cn = group.get('cn')
if isinstance(cn, list):
cn = cn[0]
edit_url = tg.url('/group/edit', cn=cn)
from ipagui.helpers import userhelper
?>
<div id="details">
<h1>View Group</h1>
<input class="submitbutton" type="button"
<input py:if="'editors' in tg.identity.groups or 'admins' in tg.identity.groups"
class="submitbutton" type="button"
onclick="document.location.href='${edit_url}'"
value="Edit Group" />
@@ -38,6 +43,12 @@ edit_url = tg.url('/group/edit', cn=group.get('cn'))
</th>
<td>${group.get("gidnumber")}</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="fields.nsAccountLock.label" />:
</th>
<td>${userhelper.account_status_display(group.get("nsAccountLock"))}</td>
</tr>
</table>
<h2 class="formsection">Group Members</h2>
@@ -51,7 +62,10 @@ edit_url = tg.url('/group/edit', cn=group.get('cn'))
member_type = "user"
view_url = tg.url('/user/show', uid=member_uid)
else:
member_cn = "%s" % member.get('cn')
mem = member.get('cn')
if isinstance(mem, list):
mem = mem[0]
member_cn = "%s" % mem
member_desc = "[group]"
member_type = "group"
view_url = tg.url('/group/show', cn=member_cn)
@@ -70,7 +84,8 @@ edit_url = tg.url('/group/edit', cn=group.get('cn'))
<br/>
<hr />
<input class="submitbutton" type="button"
<input py:if="'editors' in tg.identity.groups or 'admins' in tg.identity.groups"
class="submitbutton" type="button"
onclick="document.location.href='${edit_url}'"
value="Edit Group" />
</div>

View File

@@ -0,0 +1,15 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#"
py:extends="'policylayout.kid'">
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/>
<title>Edit IPA Policy</title>
</head>
<body>
<div>
<h1>Edit IPA Policy</h1>
${form.display(action=tg.url('/ipapolicy/update'), value=ipapolicy)}
</div>
</body>
</html>

View File

@@ -0,0 +1,176 @@
<div xmlns:py="http://purl.org/kid/ns#"
class="simpleroster">
<form action="${action}" name="${name}" method="${method}" class="tableform"
onsubmit="preSubmit()" >
<input type="submit" class="submitbutton" name="submit"
value="Update Policy"/>
<input type="submit" class="submitbutton" name="submit"
value="Cancel Edit" />
<?python
from ipagui.helpers import ipahelper
?>
<script type="text/javascript" charset="utf-8"
src="${tg.url('/static/javascript/dynamicedit.js')}"></script>
<div py:for="field in hidden_fields"
py:replace="field.display(value_for(field), **params_for(field))"
/>
<h2 class="formsection">Search</h2>
<table class="formtable" cellpadding="2" cellspacing="0" border="0">
<tr>
<th>
<label class="fieldlabel" py:content="ipapolicy_fields.ipasearchtimelimit.label" />:
</th>
<td>
<span py:replace="ipapolicy_fields.ipasearchtimelimit.display(value_for(ipapolicy_fields.ipasearchtimelimit))" />
<span py:if="tg.errors.get('ipasearchtimelimit')" class="fielderror"
py:content="tg.errors.get('ipasearchtimelimit')" />
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="ipapolicy_fields.ipasearchrecordslimit.label" />:
</th>
<td>
<span py:replace="ipapolicy_fields.ipasearchrecordslimit.display(value_for(ipapolicy_fields.ipasearchrecordslimit))" />
<span py:if="tg.errors.get('ipasearchrecordslimit')" class="fielderror"
py:content="tg.errors.get('ipasearchrecordslimit')" />
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="ipapolicy_fields.ipausersearchfields.label" />:
</th>
<td>
<span py:replace="ipapolicy_fields.ipausersearchfields.display(value_for(ipapolicy_fields.ipausersearchfields))" />
<span py:if="tg.errors.get('ipausersearchfields')" class="fielderror"
py:content="tg.errors.get('ipausersearchfields')" />
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="ipapolicy_fields.ipagroupsearchfields.label" />:
</th>
<td>
<span py:replace="ipapolicy_fields.ipagroupsearchfields.display(value_for(ipapolicy_fields.ipagroupsearchfields))" />
<span py:if="tg.errors.get('ipagroupsearchfields')" class="fielderror"
py:content="tg.errors.get('ipagroupsearchfields')" />
</td>
</tr>
</table>
<h2 class="formsection">Password Policy</h2>
<table class="formtable" cellpadding="2" cellspacing="0" border="0">
<tr>
<th>
<label class="fieldlabel" py:content="ipapolicy_fields.ipapwdexpadvnotify.label" />:
</th>
<td>
<span py:replace="ipapolicy_fields.ipapwdexpadvnotify.display(value_for(ipapolicy_fields.ipapwdexpadvnotify))" />
<span py:if="tg.errors.get('ipapwdexpadvnotify')" class="fielderror"
py:content="tg.errors.get('ipapwdexpadvnotify')" />
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="ipapolicy_fields.krbminpwdlife.label" />:
</th>
<td>
<span py:replace="ipapolicy_fields.krbminpwdlife.display(value_for(ipapolicy_fields.krbminpwdlife))" />
<span py:if="tg.errors.get('krbminpwdlife')" class="fielderror"
py:content="tg.errors.get('krbminpwdlife')" />
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="ipapolicy_fields.krbmaxpwdlife.label" />:
</th>
<td>
<span py:replace="ipapolicy_fields.krbmaxpwdlife.display(value_for(ipapolicy_fields.krbmaxpwdlife))" />
<span py:if="tg.errors.get('krbmaxpwdlife')" class="fielderror"
py:content="tg.errors.get('krbmaxpwdlife')" />
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="ipapolicy_fields.krbpwdmindiffchars.label" />:
</th>
<td>
<span py:replace="ipapolicy_fields.krbpwdmindiffchars.display(value_for(ipapolicy_fields.krbpwdmindiffchars))" />
<span py:if="tg.errors.get('krbpwdmindiffchars')" class="fielderror"
py:content="tg.errors.get('krbpwdmindiffchars')" />
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="ipapolicy_fields.krbpwdminlength.label" />:
</th>
<td>
<span py:replace="ipapolicy_fields.krbpwdminlength.display(value_for(ipapolicy_fields.krbpwdminlength))" />
<span py:if="tg.errors.get('krbpwdminlength')" class="fielderror"
py:content="tg.errors.get('krbpwdminlength')" />
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="ipapolicy_fields.krbpwdhistorylength.label" />:
</th>
<td>
<span py:replace="ipapolicy_fields.krbpwdhistorylength.display(value_for(ipapolicy_fields.krbpwdhistorylength))" />
<span py:if="tg.errors.get('krbpwdhistorylength')" class="fielderror"
py:content="tg.errors.get('krbpwdhistorylength')" />
</td>
</tr>
</table>
<h2 class="formsection">User Settings</h2>
<table class="formtable" cellpadding="2" cellspacing="0" border="0">
<tr>
<th>
<label class="fieldlabel" py:content="ipapolicy_fields.ipamaxusernamelength.label" />:
</th>
<td>
<span py:replace="ipapolicy_fields.ipamaxusernamelength.display(value_for(ipapolicy_fields.ipamaxusernamelength))" />
<span py:if="tg.errors.get('ipamaxusernamelength')" class="fielderror"
py:content="tg.errors.get('ipamaxusernamelength')" />
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="ipapolicy_fields.ipahomesrootdir.label" />:
</th>
<td>
<span py:replace="ipapolicy_fields.ipahomesrootdir.display(value_for(ipapolicy_fields.ipahomesrootdir))" />
<span py:if="tg.errors.get('ipahomesrootdir')" class="fielderror"
py:content="tg.errors.get('ipahomesrootdir')" />
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="ipapolicy_fields.ipadefaultloginshell.label" />:
</th>
<td>
<span py:replace="ipapolicy_fields.ipadefaultloginshell.display(value_for(ipapolicy_fields.ipadefaultloginshell))" />
<span py:if="tg.errors.get('ipadefaultloginshell')" class="fielderror"
py:content="tg.errors.get('ipadefaultloginshell')" />
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="ipapolicy_fields.ipadefaultprimarygroup.label" />:
</th>
<td>
<span py:replace="ipapolicy_fields.ipadefaultprimarygroup.display(value_for(ipapolicy_fields.ipadefaultprimarygroup))" />
<span py:if="tg.errors.get('ipadefaultprimarygroup')" class="fielderror"
py:content="tg.errors.get('ipadefaultprimarygroup')" />
</td>
</tr>
</table>
</form>
</div>

View File

@@ -0,0 +1,120 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#"
py:extends="'policylayout.kid'">
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/>
<title>Manage IPA Policy</title>
</head>
<body>
<?python
from ipagui.helpers import ipahelper
edit_url = tg.url('/ipapolicy/edit')
?>
<script type="text/javascript" charset="utf-8" src="${tg.url('/static/javascript/tablekit.js')}"></script>
<h1>Manage IPA Policy</h1>
<h2 class="formsection">Search</h2>
<table class="formtable" cellpadding="2" cellspacing="0" border="0">
<tr>
<th>
<label class="fieldlabel" py:content="fields.ipasearchtimelimit.label" />:
</th>
<td>${ipapolicy.get("ipasearchtimelimit")}</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="fields.ipasearchrecordslimit.label" />:
</th>
<td>${ipapolicy.get("ipasearchrecordslimit")}</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="fields.ipausersearchfields.label" />:
</th>
<td>${ipapolicy.get("ipausersearchfields")}</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="fields.ipagroupsearchfields.label" />:
</th>
<td>${ipapolicy.get("ipagroupsearchfields")}</td>
</tr>
</table>
<h2 class="formsection">Password Policy</h2>
<table class="formtable" cellpadding="2" cellspacing="0" border="0">
<tr>
<th>
<label class="fieldlabel" py:content="fields.ipapwdexpadvnotify.label" />:
</th>
<td>${ipapolicy.get("ipapwdexpadvnotify")}</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="fields.krbminpwdlife.label" />:
</th>
<td>${password.get("krbminpwdlife")}</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="fields.krbmaxpwdlife.label" />:
</th>
<td>${password.get("krbmaxpwdlife")}</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="fields.krbpwdmindiffchars.label" />:
</th>
<td>${password.get("krbpwdmindiffchars")}</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="fields.krbpwdminlength.label" />:
</th>
<td>${password.get("krbpwdminlength")}</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="fields.krbpwdhistorylength.label" />:
</th>
<td>${password.get("krbpwdhistorylength")}</td>
</tr>
</table>
<h2 class="formsection">User Settings</h2>
<table class="formtable" cellpadding="2" cellspacing="0" border="0">
<tr>
<th>
<label class="fieldlabel" py:content="fields.ipamaxusernamelength.label" />:
</th>
<td>${ipapolicy.get("ipamaxusernamelength")}</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="fields.ipahomesrootdir.label" />:
</th>
<td>${ipapolicy.get("ipahomesrootdir")}</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="fields.ipadefaultloginshell.label" />:
</th>
<td>${ipapolicy.get("ipadefaultloginshell")}</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="fields.ipadefaultprimarygroup.label" />:
</th>
<td>${ipapolicy.get("ipadefaultprimarygroup")}</td>
</tr>
</table>
<hr />
<input class="submitbutton" type="button"
onclick="document.location.href='${edit_url}'"
value="Edit Policy" />
</body>
</html>

View File

@@ -1,35 +1,24 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:py="http://purl.org/kid/ns#">
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#"
py:extends="'master.kid'">
<head>
<meta content="text/html; charset=UTF-8"
http-equiv="content-type" py:replace="''"/>
<title>Login Failure</title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/>
<title>Permission Denied</title>
</head>
<body>
<div id="header">
<div id="logo">
<a href="${tg.url('/')}"><img
src="${tg.url('/static/images/logo.png')}"
border="0" alt="homepage"
/></a>
</div>
<div id="headerinfo">
<div id="login">
<div py:if="tg.config('identity.on') and not defined('logging_in')" id="page
Login">
<span py:if="tg.identity.anonymous">
Kerberos login failed.
</span>
<span py:if="not tg.identity.anonymous">
Logged in as: ${tg.identity.user.display_name}
</span>
<div id="main_content">
<div id="details">
<div id="alertbox" py:if="value_of('tg_flash', None)">
<p py:content="XML(tg_flash)"></p></div>
<h1>Permission Denied</h1>
<div class="instructions">
<p>
You do not have permission to access this page.
</p>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@@ -70,18 +70,20 @@
<div id="sidebar">
<h2>Tasks</h2>
<ul>
<li><a href="${tg.url('/user/new')}">Add Person</a></li>
<li><a href="${tg.url('/user/list')}">Find People</a></li>
<li py:if="'admins' in tg.identity.groups"><a href="${tg.url('/user/new')}">Add User</a></li>
<li><a href="${tg.url('/user/list')}">Find Users</a></li>
</ul>
<ul>
<li><a href="${tg.url('/group/new')}">Add Group</a></li>
<li py:if="'admins' in tg.identity.groups"><a href="${tg.url('/group/new')}">Add Group</a></li>
<li><a href="${tg.url('/group/list')}">Find Groups</a></li>
</ul>
<ul>
<li><a href="${tg.url('/')}">Manage Policy</a></li>
<li><a href="${tg.url('/user/edit/', principal=tg.identity.user.display_name)}">Self Service</a></li>
<ul 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>
</ul>
<ul py:if="'admins' in tg.identity.groups">
<li><a href="${tg.url('/delegate/list')}">Delegations</a></li>
</ul>
</div>

View File

@@ -0,0 +1,31 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#"
py:extends="'policylayout.kid'">
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/>
<title>Manage Policy</title>
</head>
<body>
<?python
from ipagui.helpers import ipahelper
?>
<script type="text/javascript" charset="utf-8" src="${tg.url('/static/javascript/tablekit.js')}"></script>
<h1>Manage Policy</h1>
<table>
<tbody>
<tr>
<td>
<a href="${tg.url('/ipapolicy/show')}"
>IPA Policy</a>
</td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@@ -0,0 +1,17 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#"
py:extends="'master.kid'">
<head>
</head>
<body py:match="item.tag=='{http://www.w3.org/1999/xhtml}body'" py:attrs="item.items()">
<div id="main_content">
<div id="details">
<div id="alertbox" py:if="value_of('tg_flash', None)"><p py:content="XML(tg_flash)"></p></div>
<div py:replace="[item.text]+item[:]"></div>
</div>
</div>
</body>
</html>

View File

@@ -3,7 +3,7 @@
py:extends="'userlayout.kid'">
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/>
<title>Edit Person</title>
<title>Edit User</title>
</head>
<body>
@@ -14,7 +14,7 @@
<span class="small">edit protected fields</span>
</input>
</div>
<h1>Edit Person</h1>
<h1>Edit User</h1>
</div>
<?python

View File

@@ -10,11 +10,11 @@
onsubmit="preSubmit()">
<input type="submit" class="submitbutton" name="submit"
value="Update Person"/>
value="Update User"/>
<input type="submit" class="submitbutton" name="submit"
value="Cancel Edit" />
<input type="button" class="submitbutton"
value="Delete Person"
value="Delete User"
onclick="return confirmDelete();"
/>
@@ -26,6 +26,8 @@ from ipagui.helpers import ipahelper
src="${tg.url('/static/javascript/dynamicedit.js')}"></script>
<script type="text/javascript" charset="utf-8"
src="${tg.url('/static/javascript/dynamicselect.js')}"></script>
<script type="text/javascript" charset="utf-8"
src="${tg.url('/tg_widgets/tg_expanding_form_widget/javascript/expanding_form.js')}"></script>
<?python
searchurl = tg.url('/user/edit_search')
@@ -141,14 +143,35 @@ from ipagui.helpers import ipahelper
<tr>
<th>
<label class="fieldlabel" for="${user_fields.cn.field_id}"
py:content="user_fields.cn.label" />:
<label class="fieldlabel" for="${user_fields.cns.field_id}"
py:content="user_fields.cns.label" />:
</th>
<td>
<span py:replace="user_fields.cn.display(value_for(user_fields.cn))" />
<span py:if="tg.errors.get('cn')" class="fielderror"
py:content="tg.errors.get('cn')" />
<td colspan="3">
<table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${user_fields.cns.field_id}">
<tbody>
<?python repetition = 0
cn_index = 0
cn_error = tg.errors.get('cn')
?>
<tr py:for="cn in value_for(user_fields.cn)"
id="${user_fields.cns.field_id}_${repetition}"
class="${user_fields.cns.field_class}">
<td py:for="field in user_fields.cns.fields">
<span><input class="textfield" type="text" id="${user_fields.cns.field_id}_${repetition}_cn" name="cns-${repetition}.cn" value="${cn}"/></span>
<span py:if="cn_error and cn_error[cn_index]" class="fielderror"
py:content="tg.errors.get('cn')" />
</td>
<?python cn_index = cn_index + 1 ?>
<td>
<a
href="javascript:ExpandingForm.removeItem('${user_fields.cns.field_id}_${repetition}')">Remove</a>
</td>
<?python repetition = repetition + 1?>
</tr>
</tbody>
</table>
<a id="${user_fields.cns.field_id}_doclink" href="javascript:ExpandingForm.addItem('${user_fields.cns.field_id}');">Add Common Name</a>
</td>
</tr>
@@ -364,61 +387,170 @@ from ipagui.helpers import ipahelper
<tr>
<th>
<label class="fieldlabel" for="${user_fields.telephonenumber.field_id}"
py:content="user_fields.telephonenumber.label" />:
<label class="fieldlabel" for="${user_fields.telephonenumbers.field_id}"
py:content="user_fields.telephonenumbers.label" />:
</th>
<td>
<span py:replace="user_fields.telephonenumber.display(value_for(user_fields.telephonenumber))" />
<span py:if="tg.errors.get('telephonenumber')" class="fielderror"
py:content="tg.errors.get('telephonenumber')" />
<td colspan="3">
<table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${user_fields.telephonenumbers.field_id}">
<tbody>
<?python repetition = 0
tele_index = 0
tele_error = tg.errors.get('telephonenumber')
?>
<tr py:for="tele in value_for(user_fields.telephonenumber)"
id="${user_fields.telephonenumbers.field_id}_${repetition}"
class="${user_fields.telephonenumbers.field_class}">
<td py:for="field in user_fields.telephonenumbers.fields">
<span><input class="textfield" type="text" id="${user_fields.telephonenumbers.field_id}_${repetition}_telephonenumber" name="telephonenumbers-${repetition}.telephonenumber" value="${tele}"/></span>
<span py:if="tele_error and tele_error[tele_index]" class="fielderror"
py:content="tg.errors.get('telephonenumber')" />
</td>
<?python tele_index = tele_index + 1 ?>
<td>
<a
href="javascript:ExpandingForm.removeItem('${user_fields.telephonenumbers.field_id}_${repetition}')">Remove</a>
</td>
<?python repetition = repetition + 1?>
</tr>
</tbody>
</table>
<a id="${user_fields.telephonenumbers.field_id}_doclink" href="javascript:ExpandingForm.addItem('${user_fields.telephonenumbers.field_id}');">Add Work Number</a>
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" for="${user_fields.facsimiletelephonenumbers.field_id}"
py:content="user_fields.facsimiletelephonenumbers.label" />:
</th>
<td colspan="3">
<table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${user_fields.facsimiletelephonenumbers.field_id}">
<tbody>
<?python repetition = 0
fax_index = 0
fax_error = tg.errors.get('facsimiletelephonenumber')
?>
<tr py:for="fax in value_for(user_fields.facsimiletelephonenumber)"
id="${user_fields.facsimiletelephonenumbers.field_id}_${repetition}"
class="${user_fields.facsimiletelephonenumbers.field_class}">
<td py:for="field in user_fields.facsimiletelephonenumbers.fields">
<span><input class="textfield" type="text" id="${user_fields.facsimiletelephonenumbers.field_id}_${repetition}_facsimiletelephonenumber" name="facsimiletelephonenumbers-${repetition}.facsimiletelephonenumber" value="${fax}"/></span>
<span py:if="fax_error and fax_error[fax_index]" class="fielderror"
py:content="tg.errors.get('facsimiletelephonenumber')" />
</td>
<?python fax_index = fax_index + 1 ?>
<td>
<a
href="javascript:ExpandingForm.removeItem('${user_fields.facsimiletelephonenumbers.field_id}_${repetition}')">Remove</a>
</td>
<?python repetition = repetition + 1?>
</tr>
</tbody>
</table>
<a id="${user_fields.facsimiletelephonenumbers.field_id}_doclink" href="javascript:ExpandingForm.addItem('${user_fields.facsimiletelephonenumbers.field_id}');">Add Fax Number</a>
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" for="${user_fields.facsimiletelephonenumber.field_id}"
py:content="user_fields.facsimiletelephonenumber.label" />:
<label class="fieldlabel" for="${user_fields.mobiles.field_id}"
py:content="user_fields.mobiles.label" />:
</th>
<td>
<span py:replace="user_fields.facsimiletelephonenumber.display(value_for(user_fields.facsimiletelephonenumber))" />
<span py:if="tg.errors.get('facsimiletelephonenumber')" class="fielderror"
py:content="tg.errors.get('facsimiletelephonenumber')" />
<td colspan="3">
<table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${user_fields.mobiles.field_id}">
<tbody>
<?python repetition = 0
mobile_index = 0
mobile_error = tg.errors.get('mobile')
?>
<tr py:for="mobile in value_for(user_fields.mobile)"
id="${user_fields.mobiles.field_id}_${repetition}"
class="${user_fields.mobiles.field_class}">
<td py:for="field in user_fields.mobiles.fields">
<span><input class="textfield" type="text" id="${user_fields.mobiles.field_id}_${repetition}_mobile" name="mobiles-${repetition}.mobile" value="${mobile}"/></span>
<span py:if="mobile_error and mobile_error[mobile_index]" class="fielderror"
py:content="tg.errors.get('mobile')" />
</td>
<?python mobile_index = mobile_index + 1 ?>
<td>
<a
href="javascript:ExpandingForm.removeItem('${user_fields.mobiles.field_id}_${repetition}')">Remove</a>
</td>
<?python repetition = repetition + 1?>
</tr>
</tbody>
</table>
<a id="${user_fields.mobiles.field_id}_doclink" href="javascript:ExpandingForm.addItem('${user_fields.mobiles.field_id}');">Add Cell Number</a>
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" for="${user_fields.mobile.field_id}"
py:content="user_fields.mobile.label" />:
<label class="fieldlabel" for="${user_fields.pagers.field_id}"
py:content="user_fields.pagers.label" />:
</th>
<td>
<span py:replace="user_fields.mobile.display(value_for(user_fields.mobile))" />
<span py:if="tg.errors.get('mobile')" class="fielderror"
py:content="tg.errors.get('mobile')" />
<td colspan="3">
<table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${user_fields.pagers.field_id}">
<tbody>
<?python repetition = 0
pager_index = 0
pager_error = tg.errors.get('pager')
?>
<tr py:for="pager in value_for(user_fields.pager)"
id="${user_fields.pagers.field_id}_${repetition}"
class="${user_fields.pagers.field_class}">
<td py:for="field in user_fields.pagers.fields">
<span><input class="textfield" type="text" id="${user_fields.pagers.field_id}_${repetition}_pager" name="pagers-${repetition}.pager" value="${pager}"/></span>
<span py:if="pager_error and pager_error[pager_index]" class="fielderror"
py:content="tg.errors.get('pager')" />
</td>
<?python pager_index = pager_index + 1 ?>
<td>
<a
href="javascript:ExpandingForm.removeItem('${user_fields.pagers.field_id}_${repetition}')">Remove</a>
</td>
<?python repetition = repetition + 1?>
</tr>
</tbody>
</table>
<a id="${user_fields.pagers.field_id}_doclink" href="javascript:ExpandingForm.addItem('${user_fields.pagers.field_id}');">Add Pager Number</a>
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" for="${user_fields.pager.field_id}"
py:content="user_fields.pager.label" />:
<label class="fieldlabel" for="${user_fields.homephones.field_id}"
py:content="user_fields.homephones.label" />:
</th>
<td>
<span py:replace="user_fields.pager.display(value_for(user_fields.pager))" />
<span py:if="tg.errors.get('pager')" class="fielderror"
py:content="tg.errors.get('pager')" />
</td>
</tr>
<td colspan="3">
<table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${user_fields.homephones.field_id}">
<tbody>
<?python repetition = 0
homephone_index = 0
homephone_error = tg.errors.get('homephone')
?>
<tr py:for="homephone in value_for(user_fields.homephone)"
id="${user_fields.homephones.field_id}_${repetition}"
class="${user_fields.homephones.field_class}">
<tr>
<th>
<label class="fieldlabel" for="${user_fields.homephone.field_id}"
py:content="user_fields.homephone.label" />:
</th>
<td>
<span py:replace="user_fields.homephone.display(value_for(user_fields.homephone))" />
<span py:if="tg.errors.get('homephone')" class="fielderror"
py:content="tg.errors.get('homephone')" />
<td py:for="field in user_fields.homephones.fields">
<span><input class="textfield" type="text" id="${user_fields.homephones.field_id}_${repetition}_homephone" name="homephones-${repetition}.homephone" value="${homephone}"/></span>
<span py:if="homephone_error and homephone_error[homephone_index]" class="fielderror"
py:content="tg.errors.get('homephone')" />
</td>
<?python homephone_index = homephone_index + 1 ?>
<td>
<a
href="javascript:ExpandingForm.removeItem('${user_fields.homephones.field_id}_${repetition}')">Remove</a>
</td>
<?python repetition = repetition + 1?>
</tr>
</tbody>
</table>
<a id="${user_fields.homephones.field_id}_doclink" href="javascript:ExpandingForm.addItem('${user_fields.homephones.field_id}');">Add Home Phone</a>
</td>
</tr>
</table>
@@ -685,6 +817,7 @@ from ipagui.helpers import ipahelper
div_counter = div_counter + 1
?>
</div>
&nbsp; <!-- a space here to prevent an empty div -->
</div>
</div>
@@ -714,11 +847,11 @@ from ipagui.helpers import ipahelper
<hr/>
<input type="submit" class="submitbutton" name="submit"
value="Update Person"/>
value="Update User"/>
<input type="submit" class="submitbutton" name="submit"
value="Cancel Edit" />
<input type="button" class="submitbutton"
value="Delete Person"
value="Delete User"
onclick="return confirmDelete();"
/>

View File

@@ -15,8 +15,8 @@
<!-- <div id="sidebar">
<h2>Tools</h2>
<a href="${tg.url('/user/new')}">Add Person</a><br/>
<a href="${tg.url('/user/list')}">Find People</a><br/>
<a href="${tg.url('/user/new')}">Add User</a><br/>
<a href="${tg.url('/user/list')}">Find Users</a><br/>
</div> -->
</div>
</body>

View File

@@ -3,15 +3,15 @@
py:extends="'userlayout.kid'">
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/>
<title>Find People</title>
<title>Find Users</title>
</head>
<body>
<h1>Find People</h1>
<h1>Find Users</h1>
<script type="text/javascript" charset="utf-8" src="${tg.url('/static/javascript/tablekit.js')}"></script>
<div id="search">
<form action="${tg.url('/user/list')}" method="get">
<input id="uid" type="text" name="uid" value="${uid}" />
<input class="searchbutton" type="submit" value="Find People"/>
<input class="searchbutton" type="submit" value="Find Users"/>
</form>
<script type="text/javascript">
document.getElementById("uid").focus();
@@ -23,7 +23,7 @@
<thead>
<tr>
<th>
Person
User
</th>
<th>
Phone

View File

@@ -3,10 +3,10 @@
py:extends="'userlayout.kid'">
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/>
<title>Add Person</title>
<title>Add User</title>
</head>
<body>
<h1>Add Person</h1>
<h1>Add User</h1>
${form.display(action=tg.url("/user/create"), value=user)}
</body>

View File

@@ -2,8 +2,8 @@
class="simpleroster">
<form action="${action}" name="${name}" method="${method}" class="tableform"
onsubmit="preSubmit()">
<input type="submit" class="submitbutton" name="submit" value="Add Person"/>
<input type="submit" class="submitbutton" name="submit" value="Add User"/>
<?python
from ipagui.helpers import ipahelper
@@ -13,6 +13,8 @@ from ipagui.helpers import ipahelper
src="${tg.url('/static/javascript/dynamicedit.js')}"></script>
<script type="text/javascript" charset="utf-8"
src="${tg.url('/static/javascript/dynamicselect.js')}"></script>
<script type="text/javascript" charset="utf-8"
src="${tg.url('/tg_widgets/tg_expanding_form_widget/javascript/expanding_form.js')}"></script>
<?python
searchurl = tg.url('/user/edit_search')
@@ -51,7 +53,7 @@ from ipagui.helpers import ipahelper
</script>
<div py:for="field in hidden_fields"
py:replace="field.display(value_for(field), **params_for(field))"
py:replace="field.display(value_for(field), **params_for(field))"
/>
<h2 class="formsection">Identity Details</h2>
@@ -107,7 +109,7 @@ from ipagui.helpers import ipahelper
var uid = $('form_uid');
var mail = $('form_mail');
var cn = $('form_cn');
var cn = $('form_cns_0_cn');
var displayname = $('form_displayname');
var initials = $('form_initials');
@@ -164,14 +166,38 @@ from ipagui.helpers import ipahelper
<tr>
<th>
<label class="fieldlabel" for="${user_fields.cn.field_id}"
py:content="user_fields.cn.label" />:
<label class="fieldlabel" for="${user_fields.cns.field_id}"
py:content="user_fields.cns.label" />:
</th>
<td>
<span py:replace="user_fields.cn.display(value_for(user_fields.cn))" />
<span py:if="tg.errors.get('cn')" class="fielderror"
py:content="tg.errors.get('cn')" />
<td colspan="3">
<table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${user_fields.cns.field_id}">
<tbody>
<?python repetition = 0
cn_index = 0
cn_error = tg.errors.get('cn')
values = value_for(user_fields.cn)
if values is None:
values=['']
?>
<tr py:for="cn in values"
id="${user_fields.cns.field_id}_${repetition}"
class="${user_fields.cns.field_class}">
<td py:for="field in user_fields.cns.fields">
<span><input class="textfield" type="text" id="${user_fields.cns.field_id}_${repetition}_cn" name="cns-${repetition}.cn" value="${cn}"/></span>
<span py:if="cn_error and cn_error[cn_index]" class="fielderror"
py:content="tg.errors.get('cn')" />
</td>
<?python cn_index = cn_index + 1 ?>
<td>
<a
href="javascript:ExpandingForm.removeItem('${user_fields.cns.field_id}_${repetition}')">Remove</a>
</td>
<?python repetition = repetition + 1?>
</tr>
</tbody>
</table>
<a id="${user_fields.cns.field_id}_doclink" href="javascript:ExpandingForm.addItem('${user_fields.cns.field_id}');">Add Common Name</a>
</td>
</tr>
@@ -337,63 +363,188 @@ from ipagui.helpers import ipahelper
<tr>
<th>
<label class="fieldlabel" for="${user_fields.telephonenumber.field_id}"
py:content="user_fields.telephonenumber.label" />:
<label class="fieldlabel" for="${user_fields.telephonenumbers.field_id}"
py:content="user_fields.telephonenumbers.label" />:
</th>
<td>
<span py:replace="user_fields.telephonenumber.display(value_for(user_fields.telephonenumber))" />
<span py:if="tg.errors.get('telephonenumber')" class="fielderror"
py:content="tg.errors.get('telephonenumber')" />
<td colspan="3">
<table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${user_fields.telephonenumbers.field_id}">
<tbody>
<?python repetition = 0
tele_index = 0
tele_error = tg.errors.get('telephonenumber')
values = value_for(user_fields.telephonenumber)
if values is None:
values=['']
?>
<tr py:for="tele in values"
id="${user_fields.telephonenumbers.field_id}_${repetition}"
class="${user_fields.telephonenumbers.field_class}">
<td py:if="user_fields.telephonenumbers.fields is not None" py:for="field in user_fields.telephonenumbers.fields">
<span><input class="textfield" type="text" id="${user_fields.telephonenumbers.field_id}_${repetition}_telephonenumber" name="telephonenumbers-${repetition}.telephonenumber" value="${tele}"/></span>
<span py:if="tele_error and tele_error[tele_index]" class="fielderror"
py:content="tg.errors.get('telephonenumber')" />
</td>
<?python tele_index = tele_index + 1 ?>
<td>
<a
href="javascript:ExpandingForm.removeItem('${user_fields.telephonenumbers.field_id}_${repetition}')">Remove</a>
</td>
<?python repetition = repetition + 1?>
</tr>
</tbody>
</table>
<a id="${user_fields.telephonenumbers.field_id}_doclink" href="javascript:ExpandingForm.addItem('${user_fields.telephonenumbers.field_id}');">Add Work Number</a>
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" for="${user_fields.facsimiletelephonenumbers.field_id}"
py:content="user_fields.facsimiletelephonenumbers.label" />:
</th>
<td colspan="3">
<table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${user_fields.facsimiletelephonenumbers.field_id}">
<tbody>
<?python repetition = 0
fax_index = 0
fax_error = tg.errors.get('facsimiletelephonenumber')
values = value_for(user_fields.facsimiletelephonenumber)
if values is None:
values=['']
?>
<tr py:for="fax in values"
id="${user_fields.facsimiletelephonenumbers.field_id}_${repetition}"
class="${user_fields.facsimiletelephonenumbers.field_class}">
<td py:for="field in user_fields.facsimiletelephonenumbers.fields">
<span><input class="textfield" type="text" id="${user_fields.facsimiletelephonenumbers.field_id}_${repetition}_facsimiletelephonenumber" name="facsimiletelephonenumbers-${repetition}.facsimiletelephonenumber" value="${fax}"/></span>
<span py:if="fax_error and fax_error[fax_index]" class="fielderror"
py:content="tg.errors.get('facsimiletelephonenumber')" />
</td>
<?python fax_index = fax_index + 1 ?>
<td>
<a
href="javascript:ExpandingForm.removeItem('${user_fields.facsimiletelephonenumbers.field_id}_${repetition}')">Remove</a>
</td>
<?python repetition = repetition + 1?>
</tr>
</tbody>
</table>
<a id="${user_fields.facsimiletelephonenumbers.field_id}_doclink" href="javascript:ExpandingForm.addItem('${user_fields.facsimiletelephonenumbers.field_id}');">Add Fax Number</a>
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" for="${user_fields.facsimiletelephonenumber.field_id}"
py:content="user_fields.facsimiletelephonenumber.label" />:
<label class="fieldlabel" for="${user_fields.mobiles.field_id}"
py:content="user_fields.mobiles.label" />:
</th>
<td>
<span py:replace="user_fields.facsimiletelephonenumber.display(value_for(user_fields.facsimiletelephonenumber))" />
<span py:if="tg.errors.get('facsimiletelephonenumber')" class="fielderror"
py:content="tg.errors.get('facsimiletelephonenumber')" />
<td colspan="3">
<table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${user_fields.mobiles.field_id}">
<tbody>
<?python repetition = 0
mobile_index = 0
mobile_error = tg.errors.get('mobile')
values = value_for(user_fields.mobile)
if values is None:
values=['']
?>
<tr py:for="mobile in values"
id="${user_fields.mobiles.field_id}_${repetition}"
class="${user_fields.mobiles.field_class}">
<td py:for="field in user_fields.mobiles.fields">
<span><input class="textfield" type="text" id="${user_fields.mobiles.field_id}_${repetition}_mobile" name="mobiles-${repetition}.mobile" value="${mobile}"/></span>
<span py:if="mobile_error and mobile_error[mobile_index]" class="fielderror"
py:content="tg.errors.get('mobile')" />
</td>
<?python mobile_index = mobile_index + 1 ?>
<td>
<a
href="javascript:ExpandingForm.removeItem('${user_fields.mobiles.field_id}_${repetition}')">Remove</a>
</td>
<?python repetition = repetition + 1?>
</tr>
</tbody>
</table>
<a id="${user_fields.mobiles.field_id}_doclink" href="javascript:ExpandingForm.addItem('${user_fields.mobiles.field_id}');">Add Cell Number</a>
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" for="${user_fields.mobile.field_id}"
py:content="user_fields.mobile.label" />:
<label class="fieldlabel" for="${user_fields.pagers.field_id}"
py:content="user_fields.pagers.label" />:
</th>
<td>
<span py:replace="user_fields.mobile.display(value_for(user_fields.mobile))" />
<span py:if="tg.errors.get('mobile')" class="fielderror"
py:content="tg.errors.get('mobile')" />
<td colspan="3">
<table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${user_fields.pagers.field_id}">
<tbody>
<?python repetition = 0
pager_index = 0
pager_error = tg.errors.get('pager')
values = value_for(user_fields.pager)
if values is None:
values=['']
?>
<tr py:for="pager in values"
id="${user_fields.pagers.field_id}_${repetition}"
class="${user_fields.pagers.field_class}">
<td py:for="field in user_fields.pagers.fields">
<span><input class="textfield" type="text" id="${user_fields.pagers.field_id}_${repetition}_pager" name="pagers-${repetition}.pager" value="${pager}"/></span>
<span py:if="pager_error and pager_error[pager_index]" class="fielderror"
py:content="tg.errors.get('pager')" />
</td>
<?python pager_index = pager_index + 1 ?>
<td>
<a
href="javascript:ExpandingForm.removeItem('${user_fields.pagers.field_id}_${repetition}')">Remove</a>
</td>
<?python repetition = repetition + 1?>
</tr>
</tbody>
</table>
<a id="${user_fields.pagers.field_id}_doclink" href="javascript:ExpandingForm.addItem('${user_fields.pagers.field_id}');">Add Pager Number</a>
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" for="${user_fields.pager.field_id}"
py:content="user_fields.pager.label" />:
<label class="fieldlabel" for="${user_fields.homephones.field_id}"
py:content="user_fields.homephones.label" />:
</th>
<td>
<span py:replace="user_fields.pager.display(value_for(user_fields.pager))" />
<span py:if="tg.errors.get('pager')" class="fielderror"
py:content="tg.errors.get('pager')" />
<td colspan="3">
<table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${user_fields.homephones.field_id}">
<tbody>
<?python repetition = 0
homephone_index = 0
homephone_error = tg.errors.get('homephone')
values = value_for(user_fields.homephone)
if values is None:
values=['']
?>
<tr py:for="homephone in values"
id="${user_fields.homephones.field_id}_${repetition}"
class="${user_fields.homephones.field_class}">
<td py:for="field in user_fields.homephones.fields">
<span><input class="textfield" type="text" id="${user_fields.homephones.field_id}_${repetition}_homephone" name="homephones-${repetition}.homephone" value="${homephone}"/></span>
<span py:if="homephone_error and homephone_error[homephone_index]" class="fielderror"
py:content="tg.errors.get('homephone')" />
</td>
<?python homephone_index = homephone_index + 1 ?>
<td>
<a
href="javascript:ExpandingForm.removeItem('${user_fields.homephones.field_id}_${repetition}')">Remove</a>
</td>
<?python repetition = repetition + 1?>
</tr>
</tbody>
</table>
<a id="${user_fields.homephones.field_id}_doclink" href="javascript:ExpandingForm.addItem('${user_fields.homephones.field_id}');">Add Home Phone</a>
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" for="${user_fields.homephone.field_id}"
py:content="user_fields.homephone.label" />:
</th>
<td>
<span py:replace="user_fields.homephone.display(value_for(user_fields.homephone))" />
<span py:if="tg.errors.get('homephone')" class="fielderror"
py:content="tg.errors.get('homephone')" />
</td>
</tr>
</table>
<h2 class="formsection">Mailing Address</h2>
@@ -635,7 +786,7 @@ from ipagui.helpers import ipahelper
</div>
<hr />
<input type="submit" class="submitbutton" name="submit" value="Add Person"/>
<input type="submit" class="submitbutton" name="submit" value="Add User"/>
</form>

View File

@@ -3,17 +3,18 @@
py:extends="'userlayout.kid'">
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/>
<title>View Person</title>
<title>View User</title>
</head>
<body>
<?python
edit_url = tg.url('/user/edit', uid=user.get('uid'))
?>
<h1>View Person</h1>
<h1>View User</h1>
<input class="submitbutton" type="button"
<input py:if="'editors' in tg.identity.groups or 'admins' in tg.identity.groups"
class="submitbutton" type="button"
onclick="document.location.href='${edit_url}'"
value="Edit Person" />
value="Edit User" />
<?python
from ipagui.helpers import userhelper
@@ -57,7 +58,21 @@ else:
<th>
<label class="fieldlabel" py:content="fields.cn.label" />:
</th>
<td>${user.get("cn")}</td>
<td>
<table cellpadding="2" cellspacing="0" border="0">
<tbody>
<?python
index = 0
values = user.get("cn")
if isinstance(values, str):
values = [values]
?>
<tr py:for="index in range(len(values))">
<td>${values[index]}</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<th>
@@ -132,31 +147,101 @@ else:
<th>
<label class="fieldlabel" py:content="fields.telephonenumber.label" />:
</th>
<td>${user.get("telephonenumber")}</td>
<td>
<table cellpadding="2" cellspacing="0" border="0">
<tbody>
<?python
index = 0
values = user.get("telephonenumber", '')
if isinstance(values, str):
values = [values]
?>
<tr py:for="index in range(len(values))">
<td>${values[index]}</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="fields.facsimiletelephonenumber.label" />:
</th>
<td>${user.get("facsimiletelephonenumber")}</td>
<td>
<table cellpadding="2" cellspacing="0" border="0">
<tbody>
<?python
index = 0
values = user.get("facsimiletelephonenumber", '')
if isinstance(values, str):
values = [values]
?>
<tr py:for="index in range(len(values))">
<td>${values[index]}</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="fields.mobile.label" />:
</th>
<td>${user.get("mobile")}</td>
<td>
<table cellpadding="2" cellspacing="0" border="0">
<tbody>
<?python
index = 0
values = user.get("mobile", '')
if isinstance(values, str):
values = [values]
?>
<tr py:for="index in range(len(values))">
<td>${values[index]}</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="fields.pager.label" />:
</th>
<td>${user.get("pager")}</td>
<td>
<table cellpadding="2" cellspacing="0" border="0">
<tbody>
<?python
index = 0
values = user.get("pager", '')
if isinstance(values, str):
values = [values]
?>
<tr py:for="index in range(len(values))">
<td>${values[index]}</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="fields.homephone.label" />:
</th>
<td>${user.get("homephone")}</td>
<td>
<table cellpadding="2" cellspacing="0" border="0">
<tbody>
<?python
index = 0
values = user.get("homephone", '')
if isinstance(values, str):
values = [values]
?>
<tr py:for="index in range(len(values))">
<td>${values[index]}</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
@@ -260,7 +345,7 @@ else:
</table>
<div py:if='len(fields.custom_fields) &gt; 0'>
<div class="formsection" >Custom Fields</div>
<h2 class="formsection">Custom Fields</h2>
<table class="formtable" cellpadding="2" cellspacing="0" border="0">
<tr py:for='custom_field in fields.custom_fields'>
<th>
@@ -289,8 +374,9 @@ else:
<br/>
<hr />
<input class="submitbutton" type="button"
<input py:if="'editors' in tg.identity.groups or 'admins' in tg.identity.groups"
class="submitbutton" type="button"
onclick="document.location.href='${edit_url}'"
value="Edit Person" />
value="Edit User" />
</body>
</html>

View File

@@ -6,6 +6,8 @@ SUBDIRS = \
sbin_SCRIPTS = \
ipa-server-install \
ipa-replica-install \
ipa-replica-prepare \
$(NULL)
appdir = $(IPA_DATA_DIR)

View File

@@ -0,0 +1,142 @@
#! /usr/bin/python -E
# Authors: Karl MacMillan <kmacmillan@mentalrootkit.com>
#
# Copyright (C) 2007 Red Hat
# see file 'COPYING' for use and warranty information
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; version 2 or later
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
import sys
sys.path.append("/usr/share/ipa")
import tempfile
from ConfigParser import SafeConfigParser
from ipa import ipautil
from ipaserver import dsinstance, replication, installutils, krbinstance, service
from ipaserver import httpinstance, webguiinstance, radiusinstance, ntpinstance
class ReplicaConfig:
def __init__(self):
self.realm_name = ""
self.master_host_name = ""
self.dirman_password = ""
self.ds_user = ""
self.host_name = ""
self.repl_password = ""
self.dir = ""
def parse_options():
from optparse import OptionParser
parser = OptionParser()
parser.add_option("-r", "--read-only", dest="master", action="store_false",
default=True, help="create read-only replica - default is master")
options, args = parser.parse_args()
if len(args) != 1:
parser.error("you must provide a file generated by ipa-replica-prepare")
return options, args[0]
def get_dirman_password():
return installutils.read_password("Directory Manager (existing master)")
def expand_info(filename):
top_dir = tempfile.mkdtemp("ipa")
dir = top_dir + "/realm_info"
ipautil.run(["tar", "xfz", filename, "-C", top_dir])
return top_dir, dir
def read_info(dir, rconfig):
filename = dir + "/realm_info"
fd = open(filename)
config = SafeConfigParser()
config.readfp(fd)
rconfig.realm_name = config.get("realm", "realm_name")
rconfig.master_host_name = config.get("realm", "master_host_name")
rconfig.ds_user = config.get("realm", "ds_user")
def get_host_name():
hostname = installutils.get_fqdn()
try:
installutils.verify_fqdn(hostname)
except RuntimeError, e:
logging.error(str(e))
sys.exit(1)
return hostname
def install_ds(config):
dsinstance.check_existing_installation()
dsinstance.check_ports()
ds = dsinstance.DsInstance()
ds.create_instance(config.ds_user, config.realm_name, config.host_name, config.dirman_password)
def install_krb(config):
krb = krbinstance.KrbInstance()
ldappwd_filename = config.dir + "/ldappwd"
krb.create_replica(config.ds_user, config.realm_name, config.host_name,
config.dirman_password, ldappwd_filename)
def install_http(config):
http = httpinstance.HTTPInstance()
http.create_instance(config.realm_name, config.host_name)
def main():
options, filename = parse_options()
top_dir, dir = expand_info(filename)
config = ReplicaConfig()
read_info(dir, config)
config.host_name = get_host_name()
config.repl_password = "box"
config.dir = dir
# get the directory manager password
config.dirman_password = get_dirman_password()
install_ds(config)
repl = replication.ReplicationManager(config.host_name, config.dirman_password)
repl.setup_replication(config.master_host_name, config.realm_name, options.master)
install_krb(config)
install_http(config)
# Create a Web Gui instance
webgui = webguiinstance.WebGuiInstance()
webgui.create_instance()
# Create a radius instance
radius = radiusinstance.RadiusInstance()
# FIXME: ldap_server should be derived, not hardcoded to localhost, also should it be a URL?
radius.create_instance(config.realm_name, config.host_name, 'localhost')
# Configure ntpd
ntp = ntpinstance.NTPInstance()
ntp.create_instance()
service.restart("dirsrv")
service.restart("krb5kdc")
main()

View File

@@ -0,0 +1,114 @@
#! /usr/bin/python -E
# Authors: Karl MacMillan <kmacmillan@mentalrootkit.com>
#
# Copyright (C) 2007 Red Hat
# see file 'COPYING' for use and warranty information
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; version 2 or later
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
import sys
sys.path.append("/usr/share/ipa")
import logging, tempfile, shutil, os, pwd
from ConfigParser import SafeConfigParser
import krbV
from ipa import ipautil
from ipaserver import dsinstance
from ipaserver import installutils
certutil = "/usr/bin/certutil"
def get_host_name():
hostname = installutils.get_fqdn()
try:
installutils.verify_fqdn(hostname)
except RuntimeError, e:
logging.error(str(e))
sys.exit(1)
return hostname
def get_realm_name():
c = krbV.default_context()
return c.default_realm
def check_ipa_configuration(realm_name):
config_dir = dsinstance.config_dirname(realm_name)
if not ipautil.dir_exists(config_dir):
logging.error("could not find directory instance: %s" % config_dir)
sys.exit(1)
def create_certdb(ds_dir, dir):
# copy the passwd, noise, and pin files
shutil.copyfile(ds_dir + "/pwdfile.txt", dir + "/pwdfile.txt")
shutil.copyfile(ds_dir + "/noise.txt", dir + "/noise.txt")
shutil.copyfile(ds_dir + "/pin.txt", dir + "/pin.txt")
# create a new cert db
ipautil.run([certutil, "-N", "-d", dir, "-f", dir + "/pwdfile.txt"])
# Add the CA cert
ipautil.run([certutil, "-A", "-d", dir, "-n", "CA certificate", "-t", "CT,CT", "-a", "-i",
ds_dir + "/cacert.asc"])
def get_ds_user(ds_dir):
uid = os.stat(ds_dir).st_uid
user = pwd.getpwuid(uid)[0]
return user
def copy_files(realm_name, dir):
shutil.copy("/var/kerberos/krb5kdc/ldappwd", dir + "/ldappwd")
def save_config(dir, realm_name, host_name, ds_user):
config = SafeConfigParser()
config.add_section("realm")
config.set("realm", "realm_name", realm_name)
config.set("realm", "master_host_name", host_name)
config.set("realm", "ds_user", ds_user)
fd = open(dir + "/realm_info", "w")
config.write(fd)
def main():
realm_name = get_realm_name()
host_name = get_host_name()
ds_dir = dsinstance.config_dirname(realm_name)
ds_user = get_ds_user(ds_dir)
check_ipa_configuration(realm_name)
top_dir = tempfile.mkdtemp("ipa")
dir = top_dir + "/realm_info"
os.mkdir(dir, 0700)
create_certdb(ds_dir, dir)
copy_files(realm_name, dir)
save_config(dir, realm_name, host_name, ds_user)
ipautil.run(["/bin/tar", "cfz", "replica-info-" + realm_name, "-C", top_dir, "realm_info"])
shutil.rmtree(dir)
main()

View File

@@ -34,7 +34,6 @@ import socket
import errno
import logging
import pwd
import getpass
import subprocess
import signal
import shutil
@@ -51,8 +50,9 @@ import ipaserver.radiusinstance
import ipaserver.webguiinstance
from ipaserver import service
from ipaserver.installutils import *
from ipa.ipautil import run
from ipa.ipautil import *
def parse_options():
parser = OptionParser(version=VERSION)
@@ -86,39 +86,6 @@ def parse_options():
return options
def logging_setup(options):
# Always log everything (i.e., DEBUG) to the log
# file.
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(levelname)s %(message)s',
filename='ipaserver-install.log',
filemode='w')
console = logging.StreamHandler()
# If the debug option is set, also log debug messages to the console
if options.debug:
console.setLevel(logging.DEBUG)
else:
# Otherwise, log critical and error messages
console.setLevel(logging.ERROR)
formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
console.setFormatter(formatter)
logging.getLogger('').addHandler(console)
def erase_ds_instance_data(serverid):
try:
shutil.rmtree("/etc/dirsrv/slapd-%s" % serverid)
except:
pass
try:
shutil.rmtree("/var/lib/dirsrv/slapd-%s" % serverid)
except:
pass
try:
shutil.rmtree("/var/lock/dirsrv/slapd-%s" % serverid)
except:
pass
def signal_handler(signum, frame):
global ds
print "\nCleaning up..."
@@ -126,59 +93,9 @@ def signal_handler(signum, frame):
print "Removing configuration for %s instance" % ds.serverid
ds.stop()
if ds.serverid:
erase_ds_instance_data (ds.serverid)
ipaserver.dsinstance.erase_ds_instance_data (ds.serverid)
sys.exit(1)
def check_existing_installation():
dirs = glob.glob("/etc/dirsrv/slapd-*")
if not dirs:
return
print ""
print "An existing Directory Server has been detected."
yesno = raw_input("Do you wish to remove it and create a new one? [no]: ")
if not yesno or yesno.lower()[0] != "y":
sys.exit(1)
try:
run(["/sbin/service", "dirsrv", "stop"])
except:
pass
for d in dirs:
serverid = os.path.basename(d).split("slapd-", 1)[1]
if serverid:
erase_ds_instance_data(serverid)
def check_ports():
ds_unsecure = port_available(389)
ds_secure = port_available(636)
if not ds_unsecure or not ds_secure:
print "IPA requires ports 389 and 636 for the Directory Server."
print "These are currently in use:"
if not ds_unsecure:
print "\t389"
if not ds_secure:
print "\t636"
sys.exit(1)
def get_fqdn():
fqdn = ""
try:
fqdn = socket.getfqdn()
except:
try:
fqdn = socket.gethostname()
except:
fqdn = ""
return fqdn
def verify_fqdn(host_name):
is_ok = True
if len(host_name.split(".")) < 2 or host_name == "localhost.localdomain":
print "Invalid hostname: " + host_name
print "This host name can't be used as a hostname for an IPA Server"
is_ok = False
return is_ok
def read_host_name(host_default):
host_ok = False
host_name = ""
@@ -198,7 +115,9 @@ def read_host_name(host_default):
host_name = host_default
else:
host_name = host_input
if not verify_fqdn(host_name):
try:
verify_fqdn(host_name)
except:
host_name = ""
continue
else:
@@ -256,36 +175,6 @@ def read_ip_address(host_name):
return ip
def port_available(port):
"""Try to bind to a port on the wildcard host
Return 1 if the port is available
Return 0 if the port is in use
"""
rv = 1
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('', port))
s.shutdown(0)
s.close()
except socket.error, e:
if e[0] == errno.EADDRINUSE:
rv = 0
if rv:
try:
s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('', port))
s.shutdown(0)
s.close()
except socket.error, e:
if e[0] == errno.EADDRINUSE:
rv = 0
return rv
def read_ds_user():
print "The server must run as a specific user in a specific group."
print "It is strongly recommended that this user should have no privileges"
@@ -333,23 +222,6 @@ def read_realm_name(domain_name):
realm_name = upper_dom
return realm_name
def read_password(user):
correct = False
pwd = ""
while not correct:
pwd = getpass.getpass(user + " password: ")
if not pwd:
continue
pwd_confirm = getpass.getpass("Password (confirm): ")
if pwd != pwd_confirm:
print "Password mismatch!"
print ""
else:
correct = True
#TODO: check validity/length
print ""
return pwd
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"
@@ -360,17 +232,6 @@ def read_dm_password():
dm_password = read_password("Directory Manager")
return dm_password
def read_master_password():
print "The Kerberos database is usually encrypted using a master password."
print "Please store this password offline in a secure place."
print "It may be necessary in a recovery situation or to install a replica."
print "Without the master password the encrypted material can't be used by the KDC."
print "If the master password is lost all kerberos related secrets will also be lost."
print ""
#TODO: provide the option of generating a random password
master_password = read_password("Kerberos master")
return master_password
def read_admin_password():
print "The IPA server requires an administrative user, named 'admin'."
print "This user is a regular system account used for IPA server administration."
@@ -392,6 +253,8 @@ def main():
global ds
ds = None
options = parse_options()
if os.getegid() != 0:
print "Must be root to setup server"
return
@@ -399,17 +262,17 @@ def main():
signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGINT, signal_handler)
standard_logging_setup("ipaserver-install.log", options.debug)
print "=============================================================================="
print "This program will setup the FreeIPA Server."
print ""
print "To accept the default shown in brackets, press the Enter key."
print ""
check_existing_installation()
check_ports()
ipaserver.dsinstance.check_existing_installation()
ipaserver.dsinstance.check_ports()
options = parse_options()
logging_setup(options)
ds_user = ""
realm_name = ""
@@ -439,10 +302,13 @@ def main():
host_default = get_fqdn()
if options.unattended:
if not verify_fqdn(host_default):
try:
verify_fqdn(host_default)
except RuntimeError, e:
logging.error(str(e) + "\n")
return "-Fatal Error-"
else:
host_name = host_default
host_name = host_default
else:
host_name = read_host_name(host_default)
@@ -504,7 +370,7 @@ def main():
dm_password = options.dm_password
if not options.master_password:
master_password = read_master_password()
master_password = ipa_generate_password()
else:
master_password = options.master_password

View File

@@ -0,0 +1,37 @@
## schema file for ipa configuration
##
## IPA Base OID: 2.16.840.1.113730.3.8
##
## Attributes: 2.16.840.1.113730.3.8.1
## ObjectClasses: 2.16.840.1.113730.3.8.2
dn: cn=schema
###############################################
##
## Attributes
##
## ipaUserSearchFields - attribute names to search against when looking for users
attributetypes: ( 2.16.840.1.113730.3.8.1.1 NAME 'ipaUserSearchFields' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26)
## ipaGroupSearchFields - attribute names to search against when looking for groups
attributetypes: ( 2.16.840.1.113730.3.8.1.2 NAME 'ipaGroupSearchFields' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26)
## ipaSearchTimeLimit - search time limit in seconds
attributetypes: ( 2.16.840.1.113730.3.8.1.3 NAME 'ipaSearchTimeLimit' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE)
## ipaSearchRecordsLimit - maximum number of records to return
attributetypes: ( 2.16.840.1.113730.3.8.1.4 NAME 'ipaSearchRecordsLimit' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE)
## ipaCustomFields - custom fields to show in the UI in addition to pre-defined ones
attributetypes: ( 2.16.840.1.113730.3.8.1.5 NAME 'ipaCustomFields' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15)
## ipaHomesRootDir - default posix home directory root dir to use when creating new accounts
attributetypes: ( 2.16.840.1.113730.3.8.1.6 NAME 'ipaHomesRootDir' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE)
## ipaDefaultLoginShell - default posix login shell to use when creating new accounts
attributetypes: ( 2.16.840.1.113730.3.8.1.7 NAME 'ipaDefaultLoginShell' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE)
## ipaDefaultPrimaryGroup - default posix primary group to assign when creating new accounts
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
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)
###############################################
##
## 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 ) )

View File

@@ -5,6 +5,7 @@ app_DATA = \
60kerberos.ldif \
60samba.ldif \
60radius.ldif \
60ipaconfig.ldif \
bootstrap-template.ldif \
default-aci.ldif \
kerberos.ldif \
@@ -22,6 +23,7 @@ app_DATA = \
referint-conf.ldif \
dna-posix.ldif \
master-entry.ldif \
memberof-task.ldif \
$(NULL)
EXTRA_DIST = \

View File

@@ -8,7 +8,13 @@ dn: cn=accounts,$SUFFIX
changetype: add
objectClass: top
objectClass: nsContainer
objectClass: krbPwdPolicy
cn: accounts
krbMinPwdLife: 3600
krbPwdMinDiffChars: 0
krbPwdMinLength: 8
krbPwdHistoryLength: 0
krbMaxPwdLife: 864000
dn: cn=users,cn=accounts,$SUFFIX
changetype: add
@@ -22,10 +28,11 @@ objectClass: top
objectClass: nsContainer
cn: groups
#dn: cn=computers,cn=accounts,$SUFFIX
#objectClass: top
#objectClass: nsContainer
#cn: computers
dn: cn=services,cn=accounts,$SUFFIX
changetype: add
objectClass: top
objectClass: nsContainer
cn: services
dn: cn=etc,$SUFFIX
changetype: add
@@ -101,17 +108,80 @@ uid: ipa_default
dn: cn=admins,cn=groups,cn=accounts,$SUFFIX
changetype: add
objectClass: top
objectClass: groupofuniquenames
objectClass: groupofnames
objectClass: posixGroup
cn: admins
description: Account administrators group
gidNumber: 1001
uniqueMember: uid=admin,cn=sysaccounts,cn=etc,$SUFFIX
member: uid=admin,cn=sysaccounts,cn=etc,$SUFFIX
dn: cn=ipausers,cn=groups,cn=accounts,$SUFFIX
changetype: add
objectClass: top
objectClass: groupofuniquenames
objectClass: groupofnames
objectClass: posixGroup
gidNumber: 1002
description: Default group for all users
cn: ipausers
dn: cn=editors,cn=groups,cn=accounts,$SUFFIX
changetype: add
objectClass: top
objectClass: groupofnames
objectClass: posixGroup
gidNumber: 1003
description: Limited admins who can edit other users
cn: editors
dn: cn=ipaConfig,cn=etc,$SUFFIX
changetype: add
objectClass: nsContainer
objectClass: top
objectClass: ipaGuiConfig
ipaUserSearchFields: uid,givenName,sn,telephoneNumber,ou,title
ipaGroupSearchFields: cn,description
ipaSearchTimeLimit: 2
ipaSearchRecordsLimit: 0
ipaHomesRootDir: /home
ipaDefaultLoginShell: /bin/sh
ipaDefaultPrimaryGroup: ipausers
ipaMaxUsernameLength: 8
ipaPwdExpAdvNotify: 4
dn: cn=account inactivation,cn=accounts,$SUFFIX
description: Lock accounts based on group membership
objectClass: top
objectClass: ldapsubentry
objectClass: cosSuperDefinition
objectClass: cosClassicDefinition
cosTemplateDn: cn=cosTemplates,cn=accounts,$SUFFIX
cosAttribute: nsAccountLock operational
cosSpecifier: memberOf
cn: Account Inactivation
dn: cn=cosTemplates,cn=accounts,$SUFFIX
objectclass: top
objectclass: nsContainer
cn: cosTemplates
dn: cn="cn=inactivated,cn=account inactivation,cn=accounts,$SUFFIX", cn=cosTemplates,cn=accounts,$SUFFIX
objectClass: top
objectClass: cosTemplate
objectClass: extensibleobject
nsAccountLock: true
cosPriority: 1
dn: cn=inactivated,cn=account inactivation,cn=accounts,$SUFFIX
objectclass: top
objectclass: groupofnames
dn: cn="cn=activated,cn=account inactivation,cn=accounts,$SUFFIX", cn=cosTemplates,cn=accounts,$SUFFIX
objectClass: top
objectClass: cosTemplate
objectClass: extensibleobject
nsAccountLock: false
cosPriority: 0
dn: cn=Activated,cn=Account Inactivation,cn=accounts,$SUFFIX
objectclass: top
objectclass: groupofnames

View File

@@ -4,9 +4,24 @@ changetype: modify
replace: aci
aci: (targetattr!="userPassword || krbPrincipalKey ||sambaLMPassword || sambaNTPassword")(version 3.0; acl "Enable anonymous access"; allow (read, search, compare) userdn="ldap:///anyone";)
aci: (targetattr=*)(version 3.0; acl "Admin can manage any entry"; allow (all) userdn="ldap:///uid=admin,cn=sysaccounts,cn=etc,$SUFFIX";)
aci: (targetattr="krbPrincipalName || krbUPEnabled || krbPrincipalKey || krbTicketPolicyReference || krbPrincipalExpiration || krbPasswordExpiration || krbPwdPolicyReference || krbPrincipalType || krbPwdHistory || krbLastPwdChange || krbPrincipalAliases || krbExtraData")(version 3.0; acl "KDC System Account"; allow (read, search, compare) userdn="ldap:///uid=kdc,cn=sysaccounts,cn=etc,$SUFFIX";)
aci: (targetattr="krbPrincipalName || krbUPEnabled || krbPrincipalKey || krbMKey || krbTicketPolicyReference || krbPrincipalExpiration || krbPasswordExpiration || krbPwdPolicyReference || krbPrincipalType || krbPwdHistory || krbLastPwdChange || krbPrincipalAliases || krbExtraData")(version 3.0; acl "KDC System Account"; allow (read, search, compare) userdn="ldap:///uid=kdc,cn=sysaccounts,cn=etc,$SUFFIX";)
aci: (targetattr="krbLastSuccessfulAuth || krbLastFailedAuth || krbLoginFailedCount")(version 3.0; acl "KDC System Account"; allow (read, search, compare, write) userdn="ldap:///uid=kdc,cn=sysaccounts,cn=etc,$SUFFIX";)
aci: (targetattr="userPassword || krbPrincipalKey ||sambaLMPassword || sambaNTPassword || krbPasswordExpiration || krbPwdHistory || krbLastPwdChange")(version 3.0; acl "Kpasswd access to passowrd hashes for passowrd changes"; allow (read, write) userdn="ldap:///krbprincipalname=kadmin/changepw@$REALM,cn=$REALM,cn=kerberos,$SUFFIX";)
aci: (targetfilter="(|(objectClass=person)(objectClass=krbPrincipalAux)(objectClass=posixAccount)(objectClass=groupOfUniqueNames)(objectClass=posixGroup)(objectClass=radiusprofile))")(targetattr="*")(version 3.0; acl "Account Admins can manage Users and Groups"; allow (add,delete,read,write) groupdn="ldap:///cn=admins,cn=groups,cn=accounts,$SUFFIX";)
aci: (targetfilter="(|(objectClass=person)(objectClass=krbPrincipalAux)(objectClass=posixAccount)(objectClass=groupOfNames)(objectClass=posixGroup)(objectClass=radiusprofile))")(targetattr="*")(version 3.0; acl "Account Admins can manage Users and Groups"; allow (add,delete,read,write) groupdn="ldap:///cn=admins,cn=groups,cn=accounts,$SUFFIX";)
aci: (targetattr = "givenName || sn || cn || displayName || initials || loginShell || homePhone || mobile || pager || facsimileTelephoneNumber || telephoneNumber || street || roomNumber || l || st || postalCode || manager || description || carLicense || labeledURI || inetUserHTTPURL || seeAlso || userPassword")(version 3.0;acl "Self service";allow (write) userdn="ldap:///self";)
aci: (target="ldap:///cn=radius,cn=services,cn=etc,$SUFFIX")(version 3.0; acl "Only radius and admin can access radius service data"; deny (all) userdn!="ldap:///uid=admin,cn=sysaccounts,cn=etc,$SUFFIX || ldap:///krbprincipalname=radius/$FQDN@$REALM,cn=$REALM,cn=kerberos,$SUFFIX";)
dn: cn=ipaConfig,cn=etc,$SUFFIX
changetype: modify
add: aci
aci: (targetattr = "ipaUserSearchFields || ipaGroupSearchFields || ipaSearchTimeLimit || ipaSearchRecordsLimit || ipaCustomFields || ipaHomesRootDir || ipaDefaultLoginShell || ipaDefaultPrimaryGroup || ipaMaxUsernameLength || ipaPwdExpAdvNotify")(version 3.0;acl "Admins can write IPA policy"; allow (write) groupdn="ldap:///cn=admins,cn=groups,cn=accounts,$SUFFIX";)
dn: cn=accounts,$SUFFIX
changetype: modify
add: aci
aci: (targetattr = "krbMaxPwdLife || krbMinPwdLife || krbPwdMinDiffChars || krbPwdMinLength || krbPwdHistoryLength")(version 3.0;acl "Admins can write password policy"; allow (write) groupdn="ldap:///cn=admins,cn=groups,cn=accounts,$SUFFIX";)
dn: cn=services,cn=accounts,$SUFFIX
changetype: modify
add: aci
aci: (targetattr="krbPrincipalName || krbUPEnabled || krbPrincipalKey || krbMKey || krbTicketPolicyReference || krbPrincipalExpiration || krbPasswordExpiration || krbPwdPolicyReference || krbPrincipalType || krbPwdHistory || krbLastPwdChange || krbPrincipalAliases || krbExtraData")(version 3.0; acl "KDC System Account"; allow (read, search, compare,write) userdn="ldap:///uid=kdc,cn=sysaccounts,cn=etc,$SUFFIX";)

View File

@@ -42,6 +42,14 @@ cn:manager
nsSystemIndex:false
nsIndexType:eq
dn: cn=secretary,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
changetype: add
objectClass:top
objectClass:nsIndex
cn:secretary
nsSystemIndex:false
nsIndexType:eq
dn: cn=displayname,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
changetype: add
objectClass:top

View File

@@ -14,22 +14,4 @@ objectClass: top
cn: kerberos
aci: (targetattr="*")(version 3.0; acl "KDC System Account"; allow (all) userdn= "ldap:///uid=kdc,cn=sysaccounts,cn=etc,$SUFFIX";)
#sasl mapping
dn: cn=Full Principal,cn=mapping,cn=sasl,cn=config
changetype: add
objectclass: top
objectclass: nsSaslMapping
cn: Full Principal
nsSaslMapRegexString: \(.*\)@\(.*\)
nsSaslMapBaseDNTemplate: $SUFFIX
nsSaslMapFilterTemplate: (krbPrincipalName=\1@\2)
dn: cn=Name Only,cn=mapping,cn=sasl,cn=config
changetype: add
objectclass: top
objectclass: nsSaslMapping
cn: Name Only
nsSaslMapRegexString: \(.*\)
nsSaslMapBaseDNTemplate: $SUFFIX
nsSaslMapFilterTemplate: (krbPrincipalName=\1@$REALM)

View File

@@ -0,0 +1,7 @@
dn: cn=IPA install, cn=memberof task, cn=tasks, cn=config
changetype: add
objectClass: top
objectClass: extensibleObject
cn: IPA install
basedn: $SUFFIX
filter: (objectclass=*)

View File

@@ -0,0 +1,22 @@
NULL =
sbin_PROGRAMS = \
ipa-keytab-util \
$(NULL)
ipa_keytab_util_SOURCES = \
ipa-keytab-util.c \
$(NULL)
ipa_keytab_util_LDADD = \
-lcap \
$(NULL)
MAINTAINERCLEANFILES = \
*~ \
Makefile.in
install-exec-hook:
-chown root:apache $(DESTDIR)$(sbindir)/ipa-keytab-util
-chmod o-rwxs $(DESTDIR)$(sbindir)/ipa-keytab-util
-chmod ug+s $(DESTDIR)$(sbindir)/ipa-keytab-util

View File

@@ -0,0 +1,304 @@
/*
* Authors:
* Karl MacMillan <kmacmill@redhat.com>
*
* Copyright (C) 2007 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#define _GNU_SOURCE /* for asprintf */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/capability.h>
#include <sys/prctl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#define KADMIN_PATH "/usr/kerberos/sbin/kadmin.local"
struct options
{
char *princ_name;
char *realm;
int kstdin, kstdout, kstderr;
};
void *xmalloc(size_t size)
{
void *foo = malloc(size);
if (!foo) {
fprintf(stderr, "malloc error of size %jd\n", size);
exit(1);
}
memset(foo, 0, size);
return foo;
}
void usage(void)
{
printf("ipa-keytab-util princ-name realm-name\n");
}
struct options *process_args(int argc, char **argv)
{
struct options* opts;
opts = xmalloc(sizeof(struct options));
if (argc != 3) {
usage();
exit(1);
}
opts->princ_name = argv[1];
opts->realm = argv[2];
return opts;
}
void drop_caps(void)
{
cap_t caps;
int ret;
if (geteuid() != 0)
return;
if (getuid() != 0)
return;
caps = cap_init();
if (!caps) {
perror("error initializing caps");
exit(1);
}
ret = cap_clear(caps);
if (ret != 0) {
perror("could not clear capps");
exit(1);
}
ret = cap_set_proc(caps);
if (ret != 0) {
perror("could not drop caps");
exit(1);
}
cap_free(caps);
}
pid_t exec_kadmin_local(struct options *opts)
{
int stdin_pipes[2];
int stdout_pipes[2];
int stderr_pipes[2];
int ret;
pid_t chpid;
char *princ;
/* create a pair of pipes for stdin / stdout
of the child process.
*/
if (pipe(stdin_pipes) == -1) {
perror("creating stdin");
exit(1);
}
if (pipe(stdout_pipes) == -1) {
perror("creating stdin");
exit(1);
}
if (pipe(stderr_pipes) == -1) {
perror("creating stdin");
exit(1);
}
chpid = fork();
if (chpid == -1) {
perror("fork");
exit(1);
}
/* CHILD */
if (chpid == 0) {
/* stdin */
close(stdin_pipes[1]);
dup2(stdin_pipes[0], 0);
/* stdout */
close(stdout_pipes[0]);
dup2(stdout_pipes[1], 1);
/* stderr */
close(stderr_pipes[0]);
dup2(stdout_pipes[1], 2);
/* now exec kadmin.local */
ret = asprintf(&princ, "admin@%s", opts->realm);
if (!princ) {
perror("creating bind princ");
exit(1);
}
ret = execl(KADMIN_PATH, "kadmin.local", "-p", princ, NULL);
free(princ);
if (ret == -1) {
perror("exec");
exit(1);
}
} else {
close(stdin_pipes[0]);
close(stdout_pipes[1]);
close(stderr_pipes[1]);
opts->kstdin = stdin_pipes[1];
opts->kstdout = stdout_pipes[0];
opts->kstderr = stdout_pipes[0];
}
return chpid;
}
void write_to_kadmin(struct options *opts, char *buf, int len)
{
int ret;
ret = write(opts->kstdin, buf, len);
if (ret != len) {
perror("write");
fprintf(stderr, "write is short %d:%d\n", len, ret);
exit(1);
}
fsync(opts->kstdin);
}
char *get_temp_filename(void)
{
char *fname;
/* ok - we have to use mktemp here even w/ the race
* because kadmin.local barfs if the file exists. The
* risk is pretty low and we will try to protect the files
* with selinux.
*
* TODO: generate these files in a safer place than /tmp
*/
fname = strdup("/tmp/ipa-keytab-util-XXXXXX");
if (!fname) {
fprintf(stderr, "could not allocate temporary file name");
exit(1);
}
fname = mktemp(fname);
return fname;
}
char *create_keytab(struct options *opts)
{
char *buf, *fname;
int ret;
fname = get_temp_filename();
ret = asprintf(&buf, "ktadd -k %s %s\n", fname, opts->princ_name);
if (ret == -1) {
perror("asprintf");
exit(1);
}
write_to_kadmin(opts, buf, ret);
free(buf);
write_to_kadmin(opts, "quit\n", sizeof("quit\n"));
return fname;
}
void read_keytab(char *fname)
{
FILE *fd;
char *data;
long flen, ret;
fd = fopen(fname, "r");
if (!fd) {
fprintf(stderr, "could not open file %s: ", fname);
perror(NULL);
exit(1);
}
fseek(fd, 0, SEEK_END);
flen = ftell(fd);
rewind(fd);
data = xmalloc(flen);
/* TODO: handle short reads */
ret = fread(data, 1, flen, fd);
if (ret != flen) {
fprintf(stderr, "short read");
exit(1);
}
fclose(fd);
/* write to stdout */
ret = fwrite(data, 1, flen, stdout);
if (ret != flen) {
fprintf(stderr, "short write");
exit(1);
}
}
void remove_keytab(char *filename)
{
unlink(filename);
}
/* TODO: add significantly better authorization */
int main(int argc, char **argv)
{
struct options *opts;
pid_t chpid;
int status, ret;
char *fname;
opts = process_args(argc, argv);
/* must really be root */
setuid(0);
drop_caps();
chpid = exec_kadmin_local(opts);
fname = create_keytab(opts);
ret = waitpid(-1, &status, 0);
if (WEXITSTATUS(status)) {
fprintf(stderr, "error creating keytab\n");
exit(1);
}
read_keytab(fname);
remove_keytab(fname);
return 0;
}

View File

@@ -28,26 +28,54 @@
#define TMP_TEMPLATE "/tmp/kpasswd.XXXXXX"
#define KPASSWD_PORT 464
/* blacklist entries are released only BLCAKLIST_TIMEOUT seconds
* after the children performing the noperation has finished.
* this is to avoid races */
#define BLACKLIST_TIMEOUT 5
struct blacklist {
struct blacklist *next;
char *address;
pid_t pid;
time_t expire;
};
static struct blacklist *global_blacklist = NULL;
int check_blacklist(char *address)
{
struct blacklist *bl;
struct blacklist *bl, *prev_bl;
time_t now = time(NULL);
if (!global_blacklist) {
return 0;
}
for (bl = global_blacklist; bl; bl = bl->next) {
prev_bl = NULL;
bl = global_blacklist;
while (bl) {
if (bl->expire && (bl->expire < now)) {
if (prev_bl) {
prev_bl->next = bl->next;
free(bl->address);
free(bl);
bl = prev_bl->next;
} else {
global_blacklist = bl->next;
free(bl->address);
free(bl);
bl = global_blacklist;
}
continue;
}
if (strcmp(address, bl->address) == 0) {
return 1;
}
prev_bl = bl;
bl = bl->next;
}
return 0;
@@ -62,6 +90,7 @@ int add_blacklist(pid_t pid, char *address)
bl->next = NULL;
bl->pid = pid;
bl->expire = 0;
bl->address = strdup(address);
if (!bl->address) {
free(bl);
@@ -83,32 +112,24 @@ int add_blacklist(pid_t pid, char *address)
int remove_blacklist(pid_t pid)
{
struct blacklist *bl, *pbl;
struct blacklist *bl;
if (!global_blacklist) {
return -1;
}
pbl = NULL;
bl = global_blacklist;
while (bl) {
if (pid == bl->pid) {
if (pbl == NULL) {
global_blacklist = bl->next;
} else {
pbl->next = bl->next;
}
free(bl->address);
free(bl);
bl->expire = time(NULL) + BLACKLIST_TIMEOUT;
return 0;
}
pbl = bl;
bl = bl->next;
}
return -1;
}
int debug = 1;
int debug = 0;
char *srv_pri_name = "kadmin/changepw";
char *keytab_name = NULL;
@@ -255,12 +276,19 @@ int ldap_sasl_interact(LDAP *ld, unsigned flags, void *priv_data, void *sit)
return ret;
}
int ldap_pwd_change(char *client_name, char *realm_name, krb5_data pwd)
/* from DS ldaprot.h */
#define LDAP_TAG_PWP_WARNING 0xA0 /* context specific + constructed + 0 */
#define LDAP_TAG_PWP_SECSLEFT 0x80L /* context specific + primitive */
#define LDAP_TAG_PWP_GRCLOGINS 0x81L /* context specific + primitive + 1 */
#define LDAP_TAG_PWP_ERROR 0x81L /* context specific + primitive + 1 */
int ldap_pwd_change(char *client_name, char *realm_name, krb5_data pwd, char **errstr)
{
char *tmp_file = NULL;
int version;
LDAP *ld = NULL;
BerElement *ctrl = NULL;
BerElement *sctrl = NULL;
struct berval control;
struct berval newpw;
char hostname[1024];
@@ -275,7 +303,12 @@ int ldap_pwd_change(char *client_name, char *realm_name, krb5_data pwd)
struct berval *retdata = NULL;
struct timeval tv;
LDAPMessage *entry, *res = NULL;
int ret;
LDAPControl **srvctrl = NULL;
char *exterr1 = NULL;
char *exterr2 = NULL;
char *err;
int msgid;
int ret, rc;
tmp_file = strdup(TMP_TEMPLATE);
if (!tmp_file) {
@@ -399,7 +432,12 @@ int ldap_pwd_change(char *client_name, char *realm_name, krb5_data pwd)
if (ret != LDAP_SUCCESS) {
syslog(LOG_ERR, "Search for %s failed with error %d",
filter, ret);
ret = KRB5_KPASSWD_HARDERROR;
if (ret == LDAP_CONSTRAINT_VIOLATION) {
*errstr = strdup("Password Change Failed");
ret = KRB5_KPASSWD_SOFTERROR;
} else {
ret = KRB5_KPASSWD_HARDERROR;
}
goto done;
}
free(filter);
@@ -409,6 +447,7 @@ int ldap_pwd_change(char *client_name, char *realm_name, krb5_data pwd)
userdn = ldap_get_dn(ld, entry);
ldap_msgfree(res);
res = NULL;
if (!userdn) {
syslog(LOG_ERR, "No userdn, can't change password!");
@@ -430,25 +469,164 @@ int ldap_pwd_change(char *client_name, char *realm_name, krb5_data pwd)
ret = ber_flatten2(ctrl, &control, 0);
if (ret < 0) {
syslog(LOG_ERR, "ber flattening failed!");
ret = -1;
goto done;
}
/* perform password change */
ret = ldap_extended_operation_s(ld, LDAP_EXOP_MODIFY_PASSWD, &control,
NULL, NULL, &retoid, &retdata);
if (ret != LDAP_SUCCESS) {
syslog(LOG_ERR, "password change failed!");
ret = KRB5_KPASSWD_HARDERROR;
goto done;
}
/* TODO: interpret retdata so that we can give back meaningful errors */
/* perform password change */
ret = ldap_extended_operation(ld,
LDAP_EXOP_MODIFY_PASSWD,
&control, NULL, NULL,
&msgid);
if (ret != LDAP_SUCCESS) {
syslog(LOG_ERR, "ldap_extended_operation() failed. (%d)", ret);
ret = KRB5_KPASSWD_HARDERROR;
goto done;
}
tv.tv_sec = 10;
tv.tv_usec = 0;
ret = ldap_result(ld, msgid, 1, &tv, &res);
if (ret == -1) {
ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &rc);
syslog(LOG_ERR, "ldap_result() failed. (%d)", rc);
ret = KRB5_KPASSWD_HARDERROR;
goto done;
}
ret = ldap_parse_extended_result(ld, res, &retoid, &retdata, 0);
if(ret != LDAP_SUCCESS) {
syslog(LOG_ERR, "ldap_parse_extended_result() failed.");
ldap_msgfree(res);
ret = KRB5_KPASSWD_HARDERROR;
goto done;
}
if (retoid || retdata) {
syslog(LOG_ERR, "ldap_parse_extended_result() returned data, but we don't handle it yet.");
}
ret = ldap_parse_result(ld, res, &rc, NULL, &err, NULL, &srvctrl, 0);
if(ret != LDAP_SUCCESS) {
syslog(LOG_ERR, "ldap_parse_result() failed.");
ret = KRB5_KPASSWD_HARDERROR;
goto done;
}
if (rc != LDAP_SUCCESS) {
ret = KRB5_KPASSWD_SOFTERROR;
if (rc != LDAP_CONSTRAINT_VIOLATION) {
ret = KRB5_KPASSWD_HARDERROR;
}
}
if (err) {
syslog(LOG_ERR, "ldap_parse_result(): [%s]", err);
ldap_memfree(err);
}
if (srvctrl) {
LDAPControl *pprc = NULL;
int i;
for (i = 0; srvctrl[i]; i++) {
if (0 == strcmp(srvctrl[i]->ldctl_oid, LDAP_CONTROL_PASSWORDPOLICYRESPONSE)) {
pprc = srvctrl[i];
}
}
if (pprc) {
sctrl = ber_init(&pprc->ldctl_value);
}
if (sctrl) {
/*
* PasswordPolicyResponseValue ::= SEQUENCE {
* warning [0] CHOICE OPTIONAL {
* timeBeforeExpiration [0] INTEGER (0 .. maxInt),
* graceLoginsRemaining [1] INTEGER (0 .. maxInt) }
* error [1] ENUMERATED OPTIONAL {
* passwordExpired (0),
* accountLocked (1),
* changeAfterReset (2),
* passwordModNotAllowed (3),
* mustSupplyOldPassword (4),
* invalidPasswordSyntax (5),
* passwordTooShort (6),
* passwordTooYoung (7),
* passwordInHistory (8) } }
*/
ber_tag_t rtag, btag;
ber_int_t bint;
rtag = ber_scanf(sctrl, "{t", &btag);
if (btag == LDAP_TAG_PWP_WARNING) {
rtag = ber_scanf(sctrl, "{ti}", &btag, &bint);
if (btag == LDAP_TAG_PWP_SECSLEFT) {
asprintf(&exterr2, " (%d seconds left before password expires)", bint);
} else {
asprintf(&exterr2, " (%d grace logins remaining)", bint);
}
if (!exterr2) {
syslog(LOG_ERR, "exterr2: OOM?");
}
rtag = ber_scanf(sctrl, "t", &btag);
}
if (btag == LDAP_TAG_PWP_ERROR) {
rtag = ber_scanf(sctrl, "e", &bint);
switch(bint) {
case 0:
asprintf(&exterr1, " Err%d: Password Expired.", bint);
break;
case 1:
asprintf(&exterr1, " Err%d: Account locked.", bint);
break;
case 2:
asprintf(&exterr1, " Err%d: Password changed after reset.", bint);
break;
case 3:
asprintf(&exterr1, " Err%d: Password change not allowed.", bint);
break;
case 4:
asprintf(&exterr1, " Err%d: [Shouldn't happen].", bint);
break;
case 5:
asprintf(&exterr1, " Err%d: Password too simple.", bint);
break;
case 6:
asprintf(&exterr1, " Err%d: Password too short.", bint);
break;
case 7:
asprintf(&exterr1, " Err%d: Too soon to change password.", bint);
break;
case 8:
asprintf(&exterr1, " Err%d: Password reuse not permitted.", bint);
break;
default:
asprintf(&exterr1, " Err%d: Unknown Errorcode.", bint);
break;
}
if (!exterr1) {
syslog(LOG_ERR, "exterr1: OOM?");
}
}
}
}
if (ret != LDAP_SUCCESS) {
syslog(LOG_ERR, "password change failed!");
asprintf(errstr, "Password change, Failed.%s%s", exterr1?exterr1:"", exterr2?exterr2:"");
} else {
syslog(LOG_ERR, "password change succeeded!");
asprintf(errstr, "Password change, Succeeded.%s%s", exterr1?exterr1:"", exterr2?exterr2:"");
}
done:
if (userdn) free(userdn);
if (ctrl) ber_free(ctrl, 1);
if (sctrl) ber_free(sctrl, 1);
if (srvctrl) ldap_controls_free(srvctrl);
if (res) ldap_msgfree(res);
if (exterr1) free(exterr1);
if (exterr2) free(exterr2);
if (userdn) free(userdn);
if (ld) ldap_unbind_ext_s(ld, NULL, NULL);
if (ldap_uri) free(ldap_uri);
if (tmp_file) {
@@ -482,6 +660,7 @@ void handle_krb_packets(uint8_t *buf, ssize_t buflen,
*replen = 0;
result_string = NULL;
auth_context = NULL;
krep.length = 0;
krep.data = NULL;
@@ -508,14 +687,14 @@ void handle_krb_packets(uint8_t *buf, ssize_t buflen,
}
break;
default:
result_string = "Invalid remopte IP address";
result_string = strdup("Invalid remopte IP address");
result_err = KRB5_KPASSWD_MALFORMED;
syslog(LOG_ERR, "%s", result_string);
goto done;
}
if (buflen < 4) {
result_string = "Request truncated";
result_string = strdup("Request truncated");
result_err = KRB5_KPASSWD_MALFORMED;
syslog(LOG_ERR, "%s", result_string);
goto done;
@@ -524,7 +703,7 @@ void handle_krb_packets(uint8_t *buf, ssize_t buflen,
reqlen = (buf[0] << 8) + buf[1];
if (reqlen != buflen) {
result_string = "Unmatching request length";
result_string = strdup("Unmatching request length");
result_err = KRB5_KPASSWD_MALFORMED;
syslog(LOG_ERR, "%s", result_string);
goto done;
@@ -533,7 +712,7 @@ void handle_krb_packets(uint8_t *buf, ssize_t buflen,
verno = (buf[2] << 8) + buf[3];
if (verno != 1) {
result_string = "Unsupported version";
result_string = strdup("Unsupported version");
result_err = KRB5_KPASSWD_BAD_VERSION;
syslog(LOG_ERR, "%s", result_string);
goto done;
@@ -541,7 +720,7 @@ void handle_krb_packets(uint8_t *buf, ssize_t buflen,
kreq.length = (buf[4] << 8) + buf[5];
if (kreq.length > (buflen - 6)) {
result_string = "Request truncated";
result_string = strdup("Request truncated");
result_err = KRB5_KPASSWD_MALFORMED;
syslog(LOG_ERR, "%s", result_string);
goto done;
@@ -550,7 +729,7 @@ void handle_krb_packets(uint8_t *buf, ssize_t buflen,
krberr = krb5_init_context(&context);
if (krberr) {
result_string = "Failed to init kerberos context";
result_string = strdup("Failed to init kerberos context");
result_err = KRB5_KPASSWD_HARDERROR;
syslog(LOG_ERR, "%s", result_string);
goto done;
@@ -558,7 +737,7 @@ void handle_krb_packets(uint8_t *buf, ssize_t buflen,
krberr = krb5_get_default_realm(context, &realm_name);
if (krberr) {
result_string = "Failed to get default realm name";
result_string = strdup("Failed to get default realm name");
result_err = KRB5_KPASSWD_HARDERROR;
syslog(LOG_ERR, "%s", result_string);
goto done;
@@ -566,7 +745,7 @@ void handle_krb_packets(uint8_t *buf, ssize_t buflen,
krberr = krb5_auth_con_init(context, &auth_context);
if (krberr) {
result_string = "Unable to init auth context";
result_string = strdup("Unable to init auth context");
result_err = KRB5_KPASSWD_HARDERROR;
syslog(LOG_ERR, "%s: %s", result_string,
krb5_get_error_message(context, krberr));
@@ -576,7 +755,7 @@ void handle_krb_packets(uint8_t *buf, ssize_t buflen,
krberr = krb5_auth_con_setflags(context, auth_context,
KRB5_AUTH_CONTEXT_DO_SEQUENCE);
if (krberr) {
result_string = "Unable to init auth context";
result_string = strdup("Unable to init auth context");
result_err = KRB5_KPASSWD_HARDERROR;
syslog(LOG_ERR, "%s: %s", result_string,
krb5_get_error_message(context, krberr));
@@ -587,7 +766,7 @@ void handle_krb_packets(uint8_t *buf, ssize_t buflen,
strlen(realm_name), realm_name,
"kadmin", "changepw", NULL);
if (krberr) {
result_string = "Unable to build principal";
result_string = strdup("Unable to build principal");
result_err = KRB5_KPASSWD_HARDERROR;
syslog(LOG_ERR, "%s: %s", result_string,
krb5_get_error_message(context, krberr));
@@ -596,7 +775,7 @@ void handle_krb_packets(uint8_t *buf, ssize_t buflen,
krberr = krb5_kt_resolve(context, keytab_name, &keytab);
if (krberr) {
result_string = "Unable to retrieve keytab";
result_string = strdup("Unable to retrieve keytab");
result_err = KRB5_KPASSWD_HARDERROR;
syslog(LOG_ERR, "%s: %s", result_string,
krb5_get_error_message(context, krberr));
@@ -606,7 +785,7 @@ void handle_krb_packets(uint8_t *buf, ssize_t buflen,
krberr = krb5_rd_req(context, &auth_context, &kreq,
kprincpw, keytab, NULL, &ticket);
if (krberr) {
result_string = "Unable to read request";
result_string = strdup("Unable to read request");
result_err = KRB5_KPASSWD_AUTHERROR;
syslog(LOG_ERR, "%s: %s", result_string,
krb5_get_error_message(context, krberr));
@@ -618,7 +797,7 @@ void handle_krb_packets(uint8_t *buf, ssize_t buflen,
* the password have been successfully changed */
krberr = krb5_mk_rep(context, auth_context, &krep);
if (krberr) {
result_string = "Failed to to build reply";
result_string = strdup("Failed to to build reply");
result_err = KRB5_KPASSWD_HARDERROR;
syslog(LOG_ERR, "%s: %s", result_string,
krb5_get_error_message(context, krberr));
@@ -627,7 +806,7 @@ void handle_krb_packets(uint8_t *buf, ssize_t buflen,
/* verify that this is an AS_REQ ticket */
if (!(ticket->enc_part2->flags & TKT_FLG_INITIAL)) {
result_string = "Ticket must be derived from a password";
result_string = strdup("Ticket must be derived from a password");
result_err = KRB5_KPASSWD_AUTHERROR;
syslog(LOG_ERR, "%s", result_string);
goto kpreply;
@@ -636,7 +815,7 @@ void handle_krb_packets(uint8_t *buf, ssize_t buflen,
krberr = krb5_unparse_name(context, ticket->enc_part2->client,
&client_name);
if (krberr) {
result_string = "Unable to parse client name";
result_string = strdup("Unable to parse client name");
result_err = KRB5_KPASSWD_HARDERROR;
syslog(LOG_ERR, "%s", result_string);
goto kpreply;
@@ -644,7 +823,7 @@ void handle_krb_packets(uint8_t *buf, ssize_t buflen,
krberr = krb5_auth_con_setaddrs(context, auth_context, NULL, &rkaddr);
if (krberr) {
result_string = "Failed to set client address";
result_string = strdup("Failed to set client address");
result_err = KRB5_KPASSWD_HARDERROR;
syslog(LOG_ERR, "%s: %s", result_string,
krb5_get_error_message(context, krberr));
@@ -659,24 +838,22 @@ void handle_krb_packets(uint8_t *buf, ssize_t buflen,
* requires the local address (from kadmin code) */
krberr = krb5_rd_priv(context, auth_context, &kenc, &kdec, &replay);
if (krberr) {
result_string = "Failed to decrypt password";
result_string = strdup("Failed to decrypt password");
result_err = KRB5_KPASSWD_HARDERROR;
syslog(LOG_ERR, "%s: %s", result_string,
krb5_get_error_message(context, krberr));
goto kpreply;
}
if (debug > 0) {
if (debug > 100) {
syslog(LOG_ERR, "Client %s trying to set password [%*s]",
client_name, kdec.length, kdec.data);
}
/* Actually try to change the password */
result_err = ldap_pwd_change(client_name, realm_name, kdec);
if (result_err != KRB5_KPASSWD_SUCCESS) {
result_string = "Generic error occurred while changing password";
} else {
result_string = "";
result_err = ldap_pwd_change(client_name, realm_name, kdec, &result_string);
if (result_string == NULL) {
result_string = strdup("Server Error");
}
/* make sure password is cleared off before we free the memory */
@@ -700,7 +877,7 @@ kpreply:
/* we listen on ANYADDR, use this retrieve the right address */
krberr = krb5_os_localaddr(context, &lkaddr);
if (krberr) {
result_string = "Failed to retrieve local address";
result_string = strdup("Failed to retrieve local address");
syslog(LOG_ERR, "%s: %s", result_string,
krb5_get_error_message(context, krberr));
goto done;
@@ -708,7 +885,7 @@ kpreply:
krberr = krb5_auth_con_setaddrs(context, auth_context, lkaddr[0], NULL);
if (krberr) {
result_string = "Failed to set local address";
result_string = strdup("Failed to set local address");
syslog(LOG_ERR, "%s: %s", result_string,
krb5_get_error_message(context, krberr));
goto done;
@@ -716,7 +893,7 @@ kpreply:
krberr = krb5_mk_priv(context, auth_context, &kdec, &kenc, &replay);
if (krberr) {
result_string = "Failed to encrypt reply message";
result_string = strdup("Failed to encrypt reply message");
syslog(LOG_ERR, "%s: %s", result_string,
krb5_get_error_message(context, krberr));
/* encryption was unsuccessful, let's return a krb error */
@@ -734,7 +911,7 @@ kpreply:
krb5err.susec = 0;
krberr = krb5_timeofday(context, &krb5err.stime);
if (krberr) {
result_string = "Failed to set time of day";
result_string = strdup("Failed to set time of day");
syslog(LOG_ERR, "%s: %s", result_string,
krb5_get_error_message(context, krberr));
goto done;
@@ -744,7 +921,7 @@ kpreply:
krb5err.e_data = kdec;
krberr = krb5_mk_error(context, &krb5err, &kenc);
if (krberr) {
result_string = "Failed to build error message";
result_string = strdup("Failed to build error message");
syslog(LOG_ERR, "%s: %s", result_string,
krb5_get_error_message(context, krberr));
goto done;
@@ -774,6 +951,7 @@ kpreply:
*replen = replylen;
done:
if (result_string) free(result_string);
if (auth_context) krb5_auth_con_free(context, auth_context);
if (kprincpw) krb5_free_principal(context, kprincpw);
if (krep.length) free(krep.data);
@@ -787,6 +965,7 @@ pid_t handle_conn(int fd, int type)
{
int mfd, tcp;
pid_t pid;
char addrto6[INET6_ADDRSTRLEN+1];
char address[INET6_ADDRSTRLEN+1];
uint8_t request[1500];
ssize_t reqlen;
@@ -809,17 +988,38 @@ pid_t handle_conn(int fd, int type)
return -1;
}
} else {
mfd = fd;
/* read first to empty the buffer on udp connections */
reqlen = recvfrom(fd, request, sizeof(request), 0,
(struct sockaddr *)&from, &fromlen);
if (reqlen <= 0) {
syslog(LOG_ERR, "Error receiving request (%d) %s",
errno, strerror(errno));
return -1;
}
}
(void) getnameinfo((struct sockaddr *)&from, fromlen,
address, INET6_ADDRSTRLEN+1,
addrto6, INET6_ADDRSTRLEN+1,
NULL, 0, NI_NUMERICHOST);
if (debug > 0) {
syslog(LOG_ERR, "Connection from %s", address);
syslog(LOG_ERR, "Connection from %s", addrto6);
}
if (strchr(addrto6, ':') == NULL) {
char *prefix6 = "::ffff:";
/* this is an IPv4 formatted addr
* convert to IPv6 mapped addr */
memcpy(address, prefix6, 7);
memcpy(&address[7], addrto6, INET6_ADDRSTRLEN-7);
} else {
/* regular IPv6 address, copy as is */
memcpy(address, addrto6, INET6_ADDRSTRLEN);
}
/* make sure we have termination */
address[INET6_ADDRSTRLEN] = '\0';
/* Check blacklist for requests from the same IP until operations
* are finished on the active client.
* the password change may be slow and pam_krb5 sends up to 3 UDP
@@ -834,15 +1034,17 @@ pid_t handle_conn(int fd, int type)
return 0;
}
reqlen = recvfrom(mfd, request, sizeof(request), 0,
(struct sockaddr *)&from, &fromlen);
if (reqlen <= 0) {
syslog(LOG_ERR, "Error receiving request (%d) %s",
errno, strerror(errno));
if (tcp) close(mfd);
return -1;
/* now read data if it was a TCP connection */
if (tcp) {
reqlen = recvfrom(mfd, request, sizeof(request), 0,
(struct sockaddr *)&from, &fromlen);
if (reqlen <= 0) {
syslog(LOG_ERR, "Error receiving request (%d) %s",
errno, strerror(errno));
close(mfd);
return -1;
}
}
#if 1
/* handle kerberos and ldap operations in childrens */
pid = fork();
@@ -860,6 +1062,7 @@ pid_t handle_conn(int fd, int type)
#endif
/* children */
if (debug > 0) syslog(LOG_ERR, "Servicing %s", address);
/* TCP packets prepend the lenght as a 32bit network order field,
* this information seem to be just redundant, so let's simply
@@ -874,13 +1077,13 @@ pid_t handle_conn(int fd, int type)
if (tcp) {
sendret = sendto(mfd, reply, replen, 0, NULL, 0);
} else {
sendret = sendto(mfd, reply, replen, 0, (struct sockaddr *)&from, fromlen);
sendret = sendto(fd, reply, replen, 0, (struct sockaddr *)&from, fromlen);
}
if (sendret == -1) {
syslog(LOG_ERR, "Error sending reply (%d)", errno);
}
}
close(mfd);
if (tcp) close(mfd);
exit(0);
}
@@ -895,7 +1098,7 @@ int main(int argc, char *argv[])
int pfdtype[4];
int nfds;
int ret;
char *key;
char *env;
/* log to syslog */
openlog("kpasswd", LOG_PID, LOG_DAEMON);
@@ -935,15 +1138,21 @@ int main(int argc, char *argv[])
exit(0);
}
key = getenv("KRB5_KTNAME");
if (!key) {
key = DEFAULT_KEYTAB;
/* source evn vars */
env = getenv("KRB5_KTNAME");
if (!env) {
env = DEFAULT_KEYTAB;
}
keytab_name = strdup(key);
keytab_name = strdup(env);
if (!keytab_name) {
syslog(LOG_ERR, "Out of memory!");
}
env = getenv("IPA_KPASSWD_DEBUG");
if (env) {
debug = strtol(env, NULL, 0);
}
/* set hints */
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
@@ -1026,6 +1235,8 @@ int main(int argc, char *argv[])
/* check for children exiting */
cid = waitpid(-1, &cstatus, WNOHANG);
if (cid != -1 && cid != 0) {
if (debug > 0)
syslog(LOG_ERR, "pid %d completed operations!\n", cid);
remove_blacklist(cid);
}
}

View File

@@ -1,7 +1,7 @@
Name: freeipa-server
Version: 0.4.1
Name: ipa-server
Version: 0.5.0
Release: 1%{?dist}
Summary: FreeIPA authentication server
Summary: Ipa authentication server
Group: System Environment/Base
License: GPL
@@ -9,17 +9,41 @@ URL: http://www.freeipa.org
Source0: %{name}-%{version}.tgz
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
BuildRequires: fedora-ds-base-devel openldap-devel krb5-devel nss-devel mozldap-devel openssl-devel
BuildRequires: fedora-ds-base-devel >= 1.1
BuildRequires: mozldap-devel
BuildRequires: openssl-devel
BuildRequires: openldap-devel
BuildRequires: krb5-devel
BuildRequires: nss-devel
Requires: python fedora-ds-base krb5-server krb5-server-ldap nss-tools openldap-clients httpd mod_python mod_auth_kerb python-ldap freeipa-python ntp cyrus-sasl-gssapi nss TurboGears python-krbV acl freeipa-admintools
Requires: ipa-python
Requires: ipa-admintools
Requires: fedora-ds-base >= 1.1
Requires: openldap-clients
Requires: nss
Requires: nss-tools
Requires: krb5-server
Requires: krb5-server-ldap
Requires: cyrus-sasl-gssapi
Requires: ntp
Requires: httpd
Requires: mod_python
Requires: mod_auth_kerb
Requires: mod_nss >= 1.0.7-2
Requires: freeradius >= 1.1.7
Requires: python-ldap
Requires: python
Requires: python-krbV
Requires: TurboGears
Requires: python-tgexpandingformwidget
Requires: acl
Requires: freeradius
Requires: pyasn1
%define httpd_conf /etc/httpd/conf.d
%define plugin_dir %{_libdir}/dirsrv/plugins
%description
FreeIPA is a server for identity, policy, and audit.
Ipa is a server for identity, policy, and audit.
%prep
%setup -q
@@ -47,8 +71,11 @@ rm -rf %{buildroot}
%files
%defattr(-,root,root,-)
%{_sbindir}/ipa-server-install
%{_sbindir}/ipa-replica-install
%{_sbindir}/ipa-replica-prepare
%{_sbindir}/ipa_kpasswd
%{_sbindir}/ipa-webgui
%attr(4750,root,apache) %{_sbindir}/ipa-keytab-util
%attr(755,root,root) %{_initrddir}/ipa-kpasswd
%attr(755,root,root) %{_initrddir}/ipa-webgui
@@ -61,7 +88,17 @@ rm -rf %{buildroot}
%changelog
* Thu Nov 1 2007 Karl MacMillan <kmacmill@redhat.com> - 0.4.1-1
* Wed Nov 21 2007 Karl MacMillan <kmacmill@mentalrootkit.com> - 0.5.0-1
- Preverse mode on ipa-keytab-util
- Version bump for relase and rpm name change
* Thu Nov 15 2007 Rob Crittenden <rcritten@redhat.com> - 0.4.1-2
- Broke invididual Requires and BuildRequires onto separate lines and
reordered them
- Added python-tgexpandingformwidget as a dependency
- Require at least fedora-ds-base 1.1
* Thu Nov 1 2007 Karl MacMillan <kmacmill@redhat.com> - 0.4.1-1
- Version bump for release
* Wed Oct 31 2007 Karl MacMillan <kmacmill@redhat.com> - 0.4.0-6
@@ -98,5 +135,3 @@ rm -rf %{buildroot}
* Fri Jul 27 2007 Karl MacMillan <kmacmill@redhat.com> - 0.1.0-1
- Initial rpm version

View File

@@ -1,7 +1,7 @@
Name: freeipa-server
Name: ipa-server
Version: VERSION
Release: 1%{?dist}
Summary: FreeIPA authentication server
Summary: Ipa authentication server
Group: System Environment/Base
License: GPL
@@ -9,17 +9,41 @@ URL: http://www.freeipa.org
Source0: %{name}-%{version}.tgz
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
BuildRequires: fedora-ds-base-devel openldap-devel krb5-devel nss-devel mozldap-devel openssl-devel
BuildRequires: fedora-ds-base-devel >= 1.1
BuildRequires: mozldap-devel
BuildRequires: openssl-devel
BuildRequires: openldap-devel
BuildRequires: krb5-devel
BuildRequires: nss-devel
Requires: python fedora-ds-base krb5-server krb5-server-ldap nss-tools openldap-clients httpd mod_python mod_auth_kerb python-ldap freeipa-python ntp cyrus-sasl-gssapi nss TurboGears python-krbV acl freeipa-admintools rpm
Requires: ipa-python
Requires: ipa-admintools
Requires: fedora-ds-base >= 1.1
Requires: openldap-clients
Requires: nss
Requires: nss-tools
Requires: krb5-server
Requires: krb5-server-ldap
Requires: cyrus-sasl-gssapi
Requires: ntp
Requires: httpd
Requires: mod_python
Requires: mod_auth_kerb
Requires: mod_nss >= 1.0.7-2
Requires: freeradius >= 1.1.7
Requires: python-ldap
Requires: python
Requires: python-krbV
Requires: TurboGears
Requires: python-tgexpandingformwidget
Requires: acl
Requires: freeradius
Requires: pyasn1
%define httpd_conf /etc/httpd/conf.d
%define plugin_dir %{_libdir}/dirsrv/plugins
%description
FreeIPA is a server for identity, policy, and audit.
Ipa is a server for identity, policy, and audit.
%prep
%setup -q
@@ -47,8 +71,11 @@ rm -rf %{buildroot}
%files
%defattr(-,root,root,-)
%{_sbindir}/ipa-server-install
%{_sbindir}/ipa-replica-install
%{_sbindir}/ipa-replica-prepare
%{_sbindir}/ipa_kpasswd
%{_sbindir}/ipa-webgui
%attr(4750,root,apache) %{_sbindir}/ipa-keytab-util
%attr(755,root,root) %{_initrddir}/ipa-kpasswd
%attr(755,root,root) %{_initrddir}/ipa-webgui
@@ -61,7 +88,17 @@ rm -rf %{buildroot}
%changelog
* Thu Nov 1 2007 Karl MacMillan <kmacmill@redhat.com> - 0.4.1-1
* Wed Nov 21 2007 Karl MacMillan <kmacmill@mentalrootkit.com> - 0.5.0-1
- Preverse mode on ipa-keytab-util
- Version bump for relase and rpm name change
* Thu Nov 15 2007 Rob Crittenden <rcritten@redhat.com> - 0.4.1-2
- Broke invididual Requires and BuildRequires onto separate lines and
reordered them
- Added python-tgexpandingformwidget as a dependency
- Require at least fedora-ds-base 1.1
* Thu Nov 1 2007 Karl MacMillan <kmacmill@redhat.com> - 0.4.1-1
- Version bump for release
* Wed Oct 31 2007 Karl MacMillan <kmacmill@redhat.com> - 0.4.0-6
@@ -98,5 +135,3 @@ rm -rf %{buildroot}
* Fri Jul 27 2007 Karl MacMillan <kmacmill@redhat.com> - 0.1.0-1
- Initial rpm version

View File

@@ -70,7 +70,7 @@
#include "string.h"
#include "nspr.h"
#define IPA_GROUP_ATTR "uniquemember"
#define IPA_GROUP_ATTR "member"
#define IPA_MEMBEROF_ATTR "memberof"
#define IPA_GROUP_ATTR_IS_DN 1
#define IPA_GROUP_ATTR_TYPE "uid"

File diff suppressed because it is too large Load Diff

View File

@@ -12,4 +12,5 @@ nsslapd-pluginid: Multi-hash Change Password Extended Operation
nsslapd-pluginversion: 1.0
nsslapd-pluginvendor: RedHat
nsslapd-plugindescription: Support saving passwords in multiple formats for different consumers (krb5, samba, freeradius, etc.)
nsslapd-pluginarg0: /var/kerberos/krb5kdc/.k5.$REALM
nsslapd-plugin-depends-on-type: database
nsslapd-realmTree: $SUFFIX

View File

@@ -12,6 +12,8 @@ app_PYTHON = \
radiusinstance.py \
webguiinstance.py \
service.py \
installutils.py \
replication.py \
$(NULL)
EXTRA_DIST = \

View File

@@ -24,10 +24,14 @@ import tempfile
import shutil
import logging
import pwd
import glob
import sys
from ipa.ipautil import *
import service
import installutils
SERVER_ROOT_64 = "/usr/lib64/dirsrv"
SERVER_ROOT_32 = "/usr/lib/dirsrv"
@@ -46,6 +50,61 @@ def find_server_root():
else:
return SERVER_ROOT_32
def realm_to_serverid(realm_name):
return "-".join(realm_name.split("."))
def config_dirname(realm_name):
return "/etc/dirsrv/slapd-" + realm_to_serverid(realm_name) + "/"
def schema_dirname(realm_name):
return config_dirname(realm_name) + "/schema/"
def erase_ds_instance_data(serverid):
try:
shutil.rmtree("/etc/dirsrv/slapd-%s" % serverid)
except:
pass
try:
shutil.rmtree("/var/lib/dirsrv/slapd-%s" % serverid)
except:
pass
try:
shutil.rmtree("/var/lock/dirsrv/slapd-%s" % serverid)
except:
pass
def check_existing_installation():
dirs = glob.glob("/etc/dirsrv/slapd-*")
if not dirs:
return
print ""
print "An existing Directory Server has been detected."
yesno = raw_input("Do you wish to remove it and create a new one? [no]: ")
if not yesno or yesno.lower()[0] != "y":
sys.exit(1)
try:
run(["/sbin/service", "dirsrv", "stop"])
except:
pass
for d in dirs:
serverid = os.path.basename(d).split("slapd-", 1)[1]
if serverid:
erase_ds_instance_data(serverid)
def check_ports():
ds_unsecure = installutils.port_available(389)
ds_secure = installutils.port_available(636)
if not ds_unsecure or not ds_secure:
print "IPA requires ports 389 and 636 for the Directory Server."
print "These are currently in use:"
if not ds_unsecure:
print "\t389"
if not ds_secure:
print "\t636"
sys.exit(1)
INF_TEMPLATE = """
[General]
FullMachineName= $FQHN
@@ -69,20 +128,25 @@ class DsInstance(service.Service):
self.dm_password = None
self.sub_dict = None
def create_instance(self, ds_user, realm_name, host_name, dm_password):
def create_instance(self, ds_user, realm_name, host_name, dm_password, ro_replica=False):
self.ds_user = ds_user
self.realm_name = realm_name.upper()
self.serverid = "-".join(self.realm_name.split("."))
self.serverid = realm_to_serverid(self.realm_name)
self.suffix = realm_to_suffix(self.realm_name)
self.host_name = host_name
self.dm_password = dm_password
self.__setup_sub_dict()
if ro_replica:
self.start_creation(15, "Configuring directory server:")
else:
self.start_creation(15, "Configuring directory server:")
self.start_creation(14, "Configuring directory server:")
self.__create_ds_user()
self.__create_instance()
self.__add_default_schemas()
self.__add_memberof_module()
if not ro_replica:
self.__add_memberof_module()
self.__add_referint_module()
self.__add_dna_module()
self.__create_indeces()
@@ -94,9 +158,11 @@ class DsInstance(service.Service):
except:
# TODO: roll back here?
logging.critical("Failed to restart the ds instance")
self.__config_uidgid_gen_first_master()
self.__add_default_layout()
self.__add_master_entry_first_master()
if not ro_replica:
self.__config_uidgid_gen_first_master()
self.__add_master_entry_first_master()
self.__init_memberof()
self.step("configuring directoy to start on boot")
@@ -104,18 +170,10 @@ class DsInstance(service.Service):
self.done_creation()
def config_dirname(self):
if not self.serverid:
raise RuntimeError("serverid not set")
return "/etc/dirsrv/slapd-" + self.serverid + "/"
def schema_dirname(self):
return self.config_dirname() + "/schema/"
def __setup_sub_dict(self):
server_root = find_server_root()
self.sub_dict = dict(FQHN=self.host_name, SERVERID=self.serverid,
PASSWORD=self.dm_password, SUFFIX=self.suffix,
PASSWORD=self.dm_password, SUFFIX=self.suffix.lower(),
REALM=self.realm_name, USER=self.ds_user,
SERVER_ROOT=server_root)
@@ -161,11 +219,13 @@ class DsInstance(service.Service):
def __add_default_schemas(self):
self.step("adding default schema")
shutil.copyfile(SHARE_DIR + "60kerberos.ldif",
self.schema_dirname() + "60kerberos.ldif")
schema_dirname(self.realm_name) + "60kerberos.ldif")
shutil.copyfile(SHARE_DIR + "60samba.ldif",
self.schema_dirname() + "60samba.ldif")
schema_dirname(self.realm_name) + "60samba.ldif")
shutil.copyfile(SHARE_DIR + "60radius.ldif",
self.schema_dirname() + "60radius.ldif")
schema_dirname(self.realm_name) + "60radius.ldif")
shutil.copyfile(SHARE_DIR + "60ipaconfig.ldif",
schema_dirname(self.realm_name) + "60ipaconfig.ldif")
def __add_memberof_module(self):
self.step("enabling memboerof plugin")
@@ -177,6 +237,16 @@ class DsInstance(service.Service):
logging.critical("Failed to load memberof-conf.ldif: %s" % str(e))
memberof_fd.close()
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)
try:
ldap_mod(memberof_fd, "cn=Directory Manager", self.dm_password)
except subprocess.CalledProcessError, e:
logging.critical("Failed to load memberof-conf.ldif: %s" % str(e))
memberof_fd.close()
def __add_referint_module(self):
self.step("enabling referential integrity plugin")
referint_txt = template_file(SHARE_DIR + "referint-conf.ldif", self.sub_dict)
@@ -219,7 +289,7 @@ class DsInstance(service.Service):
def __enable_ssl(self):
self.step("configuring ssl for ds instance")
dirname = self.config_dirname()
dirname = config_dirname(self.realm_name)
args = ["/usr/share/ipa/ipa-server-setupssl", self.dm_password,
dirname, self.host_name]
try:
@@ -257,7 +327,7 @@ class DsInstance(service.Service):
def __certmap_conf(self):
self.step("configuring certmap.conf")
dirname = self.config_dirname()
dirname = config_dirname(self.realm_name)
certmap_conf = template_file(SHARE_DIR+"certmap.conf.template", self.sub_dict)
certmap_fd = open(dirname+"certmap.conf", "w+")
certmap_fd.write(certmap_conf)
@@ -265,7 +335,7 @@ class DsInstance(service.Service):
def change_admin_password(self, password):
logging.debug("Changing admin password")
dirname = self.config_dirname()
dirname = config_dirname(self.realm_name)
if dir_exists("/usr/lib64/mozldap"):
app = "/usr/lib64/mozldap/ldappasswd"
else:

View File

@@ -50,13 +50,17 @@ def update_file(filename, orig, subst):
else:
sys.stdout.write(p.sub(subst, line))
fileinput.close()
return 0
else:
print "File %s doesn't exist." % filename
return 1
class HTTPInstance(service.Service):
def __init__(self):
service.Service.__init__(self, "httpd")
def create_instance(self, realm, fqdn):
self.sub_dict = { "REALM" : realm }
self.sub_dict = { "REALM" : realm, "FQDN": fqdn }
self.fqdn = fqdn
self.realm = realm
@@ -137,4 +141,5 @@ class HTTPInstance(service.Service):
def __set_mod_nss_port(self):
self.step("Setting mod_nss port to 443")
update_file(NSS_CONF, '8443', '443')
if update_file(NSS_CONF, '8443', '443') != 0:
print "Updating %s failed." % NSS_CONF

View File

@@ -0,0 +1,108 @@
# Authors: Simo Sorce <ssorce@redhat.com>
#
# Copyright (C) 2007 Red Hat
# see file 'COPYING' for use and warranty information
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; version 2 or later
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
import logging
import socket
import errno
import getpass
def get_fqdn():
fqdn = ""
try:
fqdn = socket.getfqdn()
except:
try:
fqdn = socket.gethostname()
except:
fqdn = ""
return fqdn
def verify_fqdn(host_name):
if len(host_name.split(".")) < 2 or host_name == "localhost.localdomain":
raise RuntimeError("Invalid hostname: " + host_name)
def port_available(port):
"""Try to bind to a port on the wildcard host
Return 1 if the port is available
Return 0 if the port is in use
"""
rv = 1
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('', port))
s.shutdown(0)
s.close()
except socket.error, e:
if e[0] == errno.EADDRINUSE:
rv = 0
if rv:
try:
s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('', port))
s.shutdown(0)
s.close()
except socket.error, e:
if e[0] == errno.EADDRINUSE:
rv = 0
return rv
def standard_logging_setup(log_filename, debug=False):
# Always log everything (i.e., DEBUG) to the log
# file.
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(levelname)s %(message)s',
filename=log_filename,
filemode='w')
console = logging.StreamHandler()
# If the debug option is set, also log debug messages to the console
if debug:
console.setLevel(logging.DEBUG)
else:
# Otherwise, log critical and error messages
console.setLevel(logging.ERROR)
formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
console.setFormatter(formatter)
logging.getLogger('').addHandler(console)
def read_password(user):
correct = False
pwd = ""
while not correct:
pwd = getpass.getpass(user + " password: ")
if not pwd:
continue
if len(pwd) < 8:
print "Password must be at least 8 characters long"
continue
pwd_confirm = getpass.getpass("Password (confirm): ")
if pwd != pwd_confirm:
print "Password mismatch!"
print ""
else:
correct = True
print ""
return pwd

View File

@@ -176,25 +176,90 @@ def wrapper(f,name):
return f(*args, **kargs)
return inner
class LDIFConn(ldif.LDIFParser):
def __init__(
self,
input_file,
ignored_attr_types=None,max_entries=0,process_url_schemes=None
):
"""
See LDIFParser.__init__()
Additional Parameters:
all_records
List instance for storing parsed records
"""
self.dndict = {} # maps dn to Entry
self.dnlist = [] # contains entries in order read
myfile = input_file
if isinstance(input_file,str) or isinstance(input_file,unicode):
myfile = open(input_file, "r")
ldif.LDIFParser.__init__(self,myfile,ignored_attr_types,max_entries,process_url_schemes)
self.parse()
if isinstance(input_file,str) or isinstance(input_file,unicode):
myfile.close()
def handle(self,dn,entry):
"""
Append single record to dictionary of all records.
"""
if not dn:
dn = ''
newentry = Entry((dn, entry))
self.dndict[IPAdmin.normalizeDN(dn)] = newentry
self.dnlist.append(newentry)
def get(self,dn):
ndn = IPAdmin.normalizeDN(dn)
return self.dndict.get(ndn, Entry(None))
class IPAdmin(SimpleLDAPObject):
CFGSUFFIX = "o=NetscapeRoot"
DEFAULT_USER_ID = "nobody"
def getDseAttr(self,attrname):
conffile = self.confdir + '/dse.ldif'
dseldif = LDIFConn(conffile)
cnconfig = dseldif.get("cn=config")
if cnconfig:
return cnconfig.getValue(attrname)
return None
def __initPart2(self):
if self.binddn and len(self.binddn) and not hasattr(self,'sroot'):
try:
ent = self.getEntry('cn=config', ldap.SCOPE_BASE, '(objectclass=*)',
[ 'nsslapd-instancedir', 'nsslapd-errorlog' ])
instdir = ent.getValue('nsslapd-instancedir')
self.sroot, self.inst = re.match(r'(.*)[\/]slapd-(\w+)$', instdir).groups()
[ 'nsslapd-instancedir', 'nsslapd-errorlog',
'nsslapd-certdir', 'nsslapd-schemadir' ])
self.errlog = ent.getValue('nsslapd-errorlog')
except (ldap.INSUFFICIENT_ACCESS, ldap.CONNECT_ERROR,
ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND)):
self.confdir = None
if self.isLocal:
self.confdir = ent.getValue('nsslapd-certdir')
if not self.confdir or not os.access(self.confdir + '/dse.ldif', os.R_OK):
self.confdir = ent.getValue('nsslapd-schemadir')
if self.confdir:
self.confdir = os.path.dirname(self.confdir)
instdir = ent.getValue('nsslapd-instancedir')
if not instdir:
# get instance name from errorlog
self.inst = re.match(r'(.*)[\/]slapd-([\w-]+)/errors', self.errlog).group(2)
if self.confdir:
instdir = self.getDseAttr('nsslapd-instancedir')
else:
if self.isLocal:
print instdir
self.sroot, self.inst = re.match(r'(.*)[\/]slapd-([\w-]+)$', instdir).groups()
instdir = re.match(r'(.*/slapd-.*)/errors', self.errlog).group(1)
#self.sroot, self.inst = re.match(r'(.*)[\/]slapd-([\w-]+)$', instdir).groups()
ent = self.getEntry('cn=config,cn=ldbm database,cn=plugins,cn=config',
ldap.SCOPE_BASE, '(objectclass=*)',
[ 'nsslapd-directory' ])
self.dbdir = os.path.dirname(ent.getValue('nsslapd-directory'))
except (ldap.INSUFFICIENT_ACCESS, ldap.CONNECT_ERROR):
pass # usually means
# print "ignored exception"
except ldap.LDAPError, e:
print "caught exception ", e
raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e)
raise
def __localinit__(self):
"""If a CA certificate is provided then it is assumed that we are
@@ -209,7 +274,7 @@ class IPAdmin(SimpleLDAPObject):
else:
SimpleLDAPObject.__init__(self,'ldap://%s:%d' % (self.host,self.port))
def __init__(self,host,port,cacert,bindcert,bindkey,proxydn=None,debug=None):
def __init__(self,host,port=389,cacert=None,bindcert=None,bindkey=None,proxydn=None,debug=None):
"""We just set our instance variables and wrap the methods - the real
work is done in __localinit__ and __initPart2 - these are separated
out this way so that we can call them from places other than
@@ -223,7 +288,7 @@ class IPAdmin(SimpleLDAPObject):
ldap.set_option(ldap.OPT_X_TLS_KEYFILE,bindkey)
self.__wrapmethods()
self.port = port or 389
self.port = port
self.host = host
self.cacert = cacert
self.bindcert = bindcert
@@ -272,6 +337,12 @@ class IPAdmin(SimpleLDAPObject):
self.principal = principal
self.proxydn = None
def do_simple_bind(self, binddn="cn=directory manager", bindpw=""):
self.binddn = binddn
self.bindpwd = bindpw
self.simple_bind_s(binddn, bindpw)
self.__initPart2()
def getEntry(self,*args):
"""This wraps the search function. It is common to just get one entry"""
@@ -283,8 +354,9 @@ class IPAdmin(SimpleLDAPObject):
try:
res = self.search(*args)
type, obj = self.result(res)
# res = self.search_ext(args[0], args[1], filterstr=args[2], attrlist=args[3], serverctrls=sctrl)
except ldap.NO_SUCH_OBJECT:
raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND,
"no such entry for " + str(args))
except ldap.LDAPError, e:
raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e)
@@ -377,6 +449,23 @@ class IPAdmin(SimpleLDAPObject):
raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e)
return "Success"
def updateRDN(self, dn, newrdn):
"""Wrap the modrdn function."""
sctrl = self.__get_server_controls__()
if dn == newrdn:
# no need to report an error
return "Success"
try:
if sctrl is not None:
self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
self.modrdn_s(dn, newrdn, delold=1)
except ldap.LDAPError, e:
raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e)
return "Success"
def updateEntry(self,dn,olduser,newuser):
"""This wraps the mod function. It assumes that the entry is already
populated with all of the desired objectclasses and attributes"""
@@ -521,7 +610,7 @@ class IPAdmin(SimpleLDAPObject):
print "Export task %s for file %s completed successfully" % (cn,file)
return rc
def waitForEntry(self, dn, timeout=7200, attr='', quiet=False):
def waitForEntry(self, dn, timeout=7200, attr='', quiet=True):
scope = ldap.SCOPE_BASE
filter = "(objectclass=*)"
attrlist = []
@@ -543,7 +632,8 @@ class IPAdmin(SimpleLDAPObject):
entry = self.getEntry(dn, scope, filter, attrlist)
except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
pass # found entry, but no attr
except ldap.NO_SUCH_OBJECT: pass # no entry yet
except ldap.NO_SUCH_OBJECT:
pass # no entry yet
except ldap.LDAPError, e: # badness
print "\nError reading entry", dn, e
break
@@ -557,7 +647,7 @@ class IPAdmin(SimpleLDAPObject):
print "\nwaitForEntry timeout for %s for %s" % (self,dn)
elif entry and not quiet:
print "\nThe waited for entry is:", entry
else:
elif not entry:
print "\nError: could not read entry %s from %s" % (dn,self)
return entry

View File

@@ -26,29 +26,32 @@ import logging
import fileinput
import re
import sys
from random import Random
from time import gmtime
import os
import pwd
import socket
import time
import shutil
import service
from ipa.ipautil import *
from ipa import ipaerror
import ipaldap
import ldap
from ldap import LDAPError
from ldap import ldapobject
from pyasn1.type import univ, namedtype
import pyasn1.codec.ber.encoder
import pyasn1.codec.ber.decoder
import struct
import base64
def host_to_domain(fqdn):
s = fqdn.split(".")
return ".".join(s[1:])
def generate_kdc_password():
rndpwd = ''
r = Random()
r.seed(gmtime())
for x in range(12):
# rndpwd += chr(r.randint(32,126))
rndpwd += chr(r.randint(65,90)) #stricter set for testing
return rndpwd
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)
@@ -79,18 +82,26 @@ class KrbInstance(service.Service):
self.kdc_password = None
self.sub_dict = None
def create_instance(self, ds_user, realm_name, host_name, admin_password, master_password):
def __common_setup(self, ds_user, realm_name, host_name, admin_password):
self.ds_user = ds_user
self.fqdn = host_name
self.ip = socket.gethostbyname(host_name)
self.fqdn = host_name
self.realm = realm_name.upper()
self.host = host_name.split(".")[0]
self.domain = host_to_domain(host_name)
self.admin_password = admin_password
self.master_password = master_password
self.ip = socket.gethostbyname(host_name)
self.domain = host_to_domain(host_name)
self.suffix = realm_to_suffix(self.realm)
self.kdc_password = generate_kdc_password()
self.kdc_password = ipa_generate_password()
self.admin_password = admin_password
self.__setup_sub_dict()
# get a connection to the DS
try:
self.conn = ipaldap.IPAdmin(self.fqdn)
self.conn.do_simple_bind(bindpw=self.admin_password)
except ipaerror.exception_for(ipaerror.LDAP_DATABASE_ERROR), e:
logging.critical("Could not connect to DS")
raise e
try:
self.stop()
@@ -98,22 +109,7 @@ class KrbInstance(service.Service):
# It could have been not running
pass
self.start_creation(10, "Configuring Kerberos KDC")
self.__configure_kdc_account_password()
self.__setup_sub_dict()
self.__configure_ldap()
self.__create_instance()
self.__create_ds_keytab()
self.__export_kadmin_changepw_keytab()
self.__add_pwd_extop_module()
def __common_post_setup(self):
try:
self.step("starting the KDC")
self.start()
@@ -129,8 +125,49 @@ class KrbInstance(service.Service):
self.step("starting ipa-kpasswd")
service.start("ipa-kpasswd")
def create_instance(self, ds_user, realm_name, host_name, admin_password, master_password):
self.master_password = master_password
self.__common_setup(ds_user, realm_name, host_name, admin_password)
self.start_creation(11, "Configuring Kerberos KDC")
self.__configure_kdc_account_password()
self.__configure_sasl_mappings()
self.__add_krb_entries()
self.__create_instance()
self.__create_ds_keytab()
self.__export_kadmin_changepw_keytab()
self.__add_pwd_extop_module()
self.__common_post_setup()
self.done_creation()
def create_replica(self, ds_user, realm_name, host_name, admin_password, ldap_passwd_filename):
self.__common_setup(ds_user, realm_name, host_name, admin_password)
self.start_creation(9, "Configuring Kerberos KDC")
self.__copy_ldap_passwd(ldap_passwd_filename)
self.__configure_sasl_mappings()
self.__write_stash_from_ds()
self.__create_instance(replica=True)
self.__create_ds_keytab()
self.__export_kadmin_changepw_keytab()
self.__common_post_setup()
self.done_creation()
def __copy_ldap_passwd(self, filename):
shutil.copy(filename, "/var/kerberos/krb5kdc/ldappwd")
os.chmod("/var/kerberos/krb5kdc/ldappwd", 0600)
def __configure_kdc_account_password(self):
self.step("setting KDC account password")
hexpwd = ''
@@ -139,6 +176,7 @@ class KrbInstance(service.Service):
pwd_fd = open("/var/kerberos/krb5kdc/ldappwd", "w")
pwd_fd.write("uid=kdc,cn=sysaccounts,cn=etc,"+self.suffix+"#{HEX}"+hexpwd+"\n")
pwd_fd.close()
os.chmod("/var/kerberos/krb5kdc/ldappwd", 0600)
def __setup_sub_dict(self):
self.sub_dict = dict(FQDN=self.fqdn,
@@ -149,9 +187,60 @@ class KrbInstance(service.Service):
HOST=self.host,
REALM=self.realm)
def __configure_ldap(self):
self.step("adding kerberos configuration to the directory")
#TODO: test that the ldif is ok with any random charcter we may use in the password
def __configure_sasl_mappings(self):
self.step("adding sasl mappings to the directory")
# we need to remove any existing SASL mappings in the directory as otherwise they
# they may conflict. There is no way to define the order they are used in atm.
# FIXME: for some reason IPAdmin dies here, so we switch
# it out for a regular ldapobject.
conn = self.conn
self.conn = ldapobject.SimpleLDAPObject("ldap://127.0.0.1/")
self.conn.bind("cn=directory manager", self.admin_password)
try:
msgid = self.conn.search("cn=mapping,cn=sasl,cn=config", ldap.SCOPE_ONELEVEL, "(objectclass=nsSaslMapping)")
res = self.conn.result(msgid)
for r in res[1]:
mid = self.conn.delete_s(r[0])
#except LDAPError, e:
# logging.critical("Error during SASL mapping removal: %s" % str(e))
except Exception, e:
print type(e)
print dir(e)
raise e
self.conn = conn
entry = ipaldap.Entry("cn=Full Principal,cn=mapping,cn=sasl,cn=config")
entry.setValues("objectclass", "top", "nsSaslMapping")
entry.setValues("cn", "Full Principal")
entry.setValues("nsSaslMapRegexString", '\(.*\)@\(.*\)')
entry.setValues("nsSaslMapBaseDNTemplate", self.suffix)
entry.setValues("nsSaslMapFilterTemplate", '(krbPrincipalName=\\1@\\2)')
try:
self.conn.add_s(entry)
except ldap.ALREADY_EXISTS:
logging.critical("failed to add Full Principal Sasl mapping")
raise e
entry = ipaldap.Entry("cn=Name Only,cn=mapping,cn=sasl,cn=config")
entry.setValues("objectclass", "top", "nsSaslMapping")
entry.setValues("cn", "Name Only")
entry.setValues("nsSaslMapRegexString", '\(.*\)')
entry.setValues("nsSaslMapBaseDNTemplate", self.suffix)
entry.setValues("nsSaslMapFilterTemplate", '(krbPrincipalName=\\1@%s)' % self.realm)
try:
self.conn.add_s(entry)
except ldap.ALREADY_EXISTS:
logging.critical("failed to add Name Only Sasl mapping")
raise e
def __add_krb_entries(self):
self.step("adding kerberos entries to the DS")
#TODO: test that the ldif is ok with any random charcter we may use in the password
kerberos_txt = template_file(SHARE_DIR + "kerberos.ldif", self.sub_dict)
kerberos_fd = write_tmp_file(kerberos_txt)
try:
@@ -169,7 +258,7 @@ class KrbInstance(service.Service):
logging.critical("Failed to load default-aci.ldif: %s" % str(e))
aci_fd.close()
def __create_instance(self):
def __create_instance(self, replica=False):
self.step("configuring KDC")
kdc_conf = template_file(SHARE_DIR+"kdc.conf.template", self.sub_dict)
kdc_fd = open("/var/kerberos/krb5kdc/kdc.conf", "w+")
@@ -197,12 +286,34 @@ class KrbInstance(service.Service):
krb_fd.write(krb_realm)
krb_fd.close()
#populate the directory with the realm structure
args = ["/usr/kerberos/sbin/kdb5_ldap_util", "-D", "uid=kdc,cn=sysaccounts,cn=etc,"+self.suffix, "-w", self.kdc_password, "create", "-s", "-P", self.master_password, "-r", self.realm, "-subtrees", self.suffix, "-sscope", "sub"]
if not replica:
#populate the directory with the realm structure
args = ["/usr/kerberos/sbin/kdb5_ldap_util", "-D", "uid=kdc,cn=sysaccounts,cn=etc,"+self.suffix, "-w", self.kdc_password, "create", "-s", "-P", self.master_password, "-r", self.realm, "-subtrees", self.suffix, "-sscope", "sub"]
try:
run(args)
except subprocess.CalledProcessError, e:
print "Failed to populate the realm structure in kerberos", e
def __write_stash_from_ds(self):
self.step("writing stash file from DS")
try:
run(args)
except subprocess.CalledProcessError, e:
print "Failed to populate the realm structure in kerberos", e
entry = self.conn.getEntry("cn=%s, cn=kerberos, %s" % (self.realm, self.suffix), ldap.SCOPE_SUBTREE)
except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND), e:
logging.critical("Could not find master key in DS")
raise e
krbMKey = pyasn1.codec.ber.decoder.decode(entry.krbmkey)
keytype = int(krbMKey[0][1][0])
keydata = str(krbMKey[0][1][1])
format = '=hi%ss' % len(keydata)
s = struct.pack(format, keytype, len(keydata), keydata)
try:
fd = open("/var/kerberos/krb5kdc/.k5."+self.realm, "w")
fd.write(s)
except os.error, e:
logging.critical("failed to write stash file")
raise e
#add the password extop module
def __add_pwd_extop_module(self):
@@ -215,12 +326,31 @@ class KrbInstance(service.Service):
logging.critical("Failed to load pwd-extop-conf.ldif: %s" % str(e))
extop_fd.close()
#add an ACL to let the DS user read the master key
args = ["/usr/bin/setfacl", "-m", "u:"+self.ds_user+":r", "/var/kerberos/krb5kdc/.k5."+self.realm]
#get the Master Key from the stash file
try:
run(args)
except subprocess.CalledProcessError, e:
logging.critical("Failed to set the ACL on the master key: %s" % str(e))
stash = open("/var/kerberos/krb5kdc/.k5."+self.realm, "r")
keytype = struct.unpack('h', stash.read(2))[0]
keylen = struct.unpack('i', stash.read(4))[0]
keydata = stash.read(keylen)
except os.error:
logging.critical("Failed to retrieve Master Key from Stash file: %s")
#encode it in the asn.1 attribute
MasterKey = univ.Sequence()
MasterKey.setComponentByPosition(0, univ.Integer(keytype))
MasterKey.setComponentByPosition(1, univ.OctetString(keydata))
krbMKey = univ.Sequence()
krbMKey.setComponentByPosition(0, univ.Integer(0)) #we have no kvno
krbMKey.setComponentByPosition(1, MasterKey)
asn1key = pyasn1.codec.ber.encoder.encode(krbMKey)
entry = ipaldap.Entry("cn="+self.realm+",cn=kerberos,"+self.suffix)
dn = "cn="+self.realm+",cn=kerberos,"+self.suffix
mod = [(ldap.MOD_ADD, 'krbMKey', str(asn1key))]
try:
self.conn.modify_s(dn, mod)
except ldap.TYPE_OR_VALUE_EXISTS, e:
logging.critical("failed to add master key to kerberos database\n")
raise e
def __create_ds_keytab(self):
self.step("creating a keytab for the directory")

View File

@@ -26,6 +26,7 @@ import shutil
import logging
import pwd
import time
import sys
from ipa.ipautil import *
from ipa import radius_util
@@ -147,8 +148,7 @@ class RadiusInstance(service.Service):
retry += 1
if retry > 15:
print "Error timed out waiting for kadmin to finish operations\n"
sys.exit()
sys.exit(1)
try:
pent = pwd.getpwnam(radius_util.RADIUS_USER)
os.chown(radius_util.RADIUS_IPA_KEYTAB_FILEPATH, pent.pw_uid, pent.pw_gid)

View File

@@ -0,0 +1,316 @@
# Authors: Karl MacMillan <kmacmillan@mentalrootkit.com>
#
# Copyright (C) 2007 Red Hat
# see file 'COPYING' for use and warranty information
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; version 2 or later
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
import time, logging
import ipaldap, ldap, dsinstance
from ipa import ipaerror
DIRMAN_CN = "cn=directory manager"
PORT = 389
TIMEOUT = 120
class ReplicationManager:
"""Manage replicatin agreements between DS servers"""
def __init__(self, hostname, dirman_passwd):
self.hostname = hostname
self.dirman_passwd = dirman_passwd
self.conn = ipaldap.IPAdmin(hostname)
self.conn.do_simple_bind(bindpw=dirman_passwd)
self.repl_man_passwd = dirman_passwd
# these are likely constant, but you could change them
# at runtime if you really want
self.repl_man_dn = "cn=replication manager,cn=config"
self.repl_man_cn = "replication manager"
self.suffix = ""
def find_replication_dns(self, conn):
filt = "(objectlcass=nsds5ReplicationAgreement)"
try:
ents = conn.search_s("cn=mapping tree,cn-config", ldap.SCOPE_SUBTREE, filt, ["cn"])
except ldap.NO_SUCH_OBJECT:
return []
return [ent.dn for ent in ents]
def add_replication_manager(self, conn, passwd=None):
"""
Create a pseudo user to use for replication. If no password
is provided the directory manager password will be used.
"""
if passwd:
self.repl_man_passwd = passwd
ent = ipaldap.Entry(self.repl_man_dn)
ent.setValues("objectclass", "top", "person")
ent.setValues("cn", self.repl_man_cn)
ent.setValues("userpassword", self.repl_man_passwd)
ent.setValues("sn", "replication manager pseudo user")
try:
conn.add_s(ent)
except ldap.ALREADY_EXISTS:
# should we set the password here?
pass
def delete_replication_manager(self, conn, dn="cn=replication manager,cn=config"):
try:
conn.delete_s(dn)
except ldap.NO_SUCH_OBJECT:
pass
def get_replica_type(self, master):
if master:
return "3"
else:
return "2"
def replica_dn(self):
return 'cn=replica, cn="%s", cn=mapping tree, cn=config' % self.suffix
def local_replica_config(self, conn, master, replica_id):
dn = self.replica_dn()
try:
conn.getEntry(dn, ldap.SCOPE_BASE)
# replication is already configured
return
except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
pass
replica_type = self.get_replica_type(master)
entry = ipaldap.Entry(dn)
entry.setValues('objectclass', "top", "nsds5replica", "extensibleobject")
entry.setValues('cn', "replica")
entry.setValues('nsds5replicaroot', self.suffix)
entry.setValues('nsds5replicaid', str(replica_id))
entry.setValues('nsds5replicatype', replica_type)
entry.setValues('nsds5flags', "1")
entry.setValues('nsds5replicabinddn', [self.repl_man_dn])
entry.setValues('nsds5replicalegacyconsumer', "off")
conn.add_s(entry)
def setup_changelog(self, conn):
dn = "cn=changelog5, cn=config"
dirpath = conn.dbdir + "/cldb"
entry = ipaldap.Entry(dn)
entry.setValues('objectclass', "top", "extensibleobject")
entry.setValues('cn', "changelog5")
entry.setValues('nsslapd-changelogdir', dirpath)
try:
conn.add_s(entry)
except ldap.ALREADY_EXISTS:
return
def setup_chaining_backend(self, conn):
chaindn = "cn=chaining database, cn=plugins, cn=config"
benamebase = "chaindb"
urls = [self.to_ldap_url(conn)]
cn = ""
benum = 1
done = False
while not done:
try:
cn = benamebase + str(benum) # e.g. localdb1
dn = "cn=" + cn + ", " + chaindn
entry = ipaldap.Entry(dn)
entry.setValues('objectclass', 'top', 'extensibleObject', 'nsBackendInstance')
entry.setValues('cn', cn)
entry.setValues('nsslapd-suffix', self.suffix)
entry.setValues('nsfarmserverurl', urls)
entry.setValues('nsmultiplexorbinddn', self.repl_man_dn)
entry.setValues('nsmultiplexorcredentials', self.repl_man_passwd)
self.conn.add_s(entry)
done = True
except ldap.ALREADY_EXISTS:
benum += 1
except ldap.LDAPError, e:
print "Could not add backend entry " + dn, e
raise
return cn
def to_ldap_url(self, conn):
return "ldap://%s:%d/" % (conn.host, conn.port)
def setup_chaining_farm(self, conn):
try:
conn.modify_s(self.suffix, [(ldap.MOD_ADD, 'aci',
[ "(targetattr = \"*\")(version 3.0; acl \"Proxied authorization for database links\"; allow (proxy) userdn = \"ldap:///%s\";)" % self.repl_man_dn ])])
except ldap.TYPE_OR_VALUE_EXISTS:
logging.debug("proxy aci already exists in suffix %s on %s" % (self.suffix, conn.host))
def get_mapping_tree_entry(self):
try:
entry = self.conn.getEntry("cn=mapping tree,cn=config", ldap.SCOPE_ONELEVEL,
"(cn=\"%s\")" % (self.suffix))
except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND), e:
logging.debug("failed to find mappting tree entry for %s" % self.suffix)
raise e
return entry
def enable_chain_on_update(self, bename):
mtent = self.get_mapping_tree_entry()
dn = mtent.dn
plgent = self.conn.getEntry("cn=Multimaster Replication Plugin,cn=plugins,cn=config",
ldap.SCOPE_BASE, "(objectclass=*)", ['nsslapd-pluginPath'])
path = plgent.getValue('nsslapd-pluginPath')
mod = [(ldap.MOD_REPLACE, 'nsslapd-state', 'backend'),
(ldap.MOD_ADD, 'nsslapd-backend', bename),
(ldap.MOD_ADD, 'nsslapd-distribution-plugin', path),
(ldap.MOD_ADD, 'nsslapd-distribution-funct', 'repl_chain_on_update')]
try:
self.conn.modify_s(dn, mod)
except ldap.TYPE_OR_VALUE_EXISTS:
logging.debug("chainOnUpdate already enabled for %s" % self.suffix)
def setup_chain_on_update(self, other_conn):
chainbe = self.setup_chaining_backend(other_conn)
self.enable_chain_on_update(chainbe)
def agreement_dn(self, conn):
cn = "meTo%s%d" % (conn.host, PORT)
dn = "cn=%s, %s" % (cn, self.replica_dn())
return (cn, dn)
def setup_agreement(self, a, b):
cn, dn = self.agreement_dn(b)
try:
a.getEntry(dn, ldap.SCOPE_BASE)
return
except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
pass
entry = ipaldap.Entry(dn)
entry.setValues('objectclass', "top", "nsds5replicationagreement")
entry.setValues('cn', cn)
entry.setValues('nsds5replicahost', b.host)
entry.setValues('nsds5replicaport', str(PORT))
entry.setValues('nsds5replicatimeout', str(TIMEOUT))
entry.setValues('nsds5replicabinddn', self.repl_man_dn)
entry.setValues('nsds5replicacredentials', self.repl_man_passwd)
entry.setValues('nsds5replicabindmethod', 'simple')
entry.setValues('nsds5replicaroot', self.suffix)
entry.setValues('nsds5replicaupdateschedule', '0000-2359 0123456')
entry.setValues('description', "me to %s%d" % (b.host, PORT))
a.add_s(entry)
entry = a.waitForEntry(entry)
def check_repl_init(self, conn, agmtdn):
done = False
hasError = 0
attrlist = ['cn', 'nsds5BeginReplicaRefresh', 'nsds5replicaUpdateInProgress',
'nsds5ReplicaLastInitStatus', 'nsds5ReplicaLastInitStart',
'nsds5ReplicaLastInitEnd']
entry = conn.getEntry(agmtdn, ldap.SCOPE_BASE, "(objectclass=*)", attrlist)
if not entry:
print "Error reading status from agreement", agmtdn
hasError = 1
else:
refresh = entry.nsds5BeginReplicaRefresh
inprogress = entry.nsds5replicaUpdateInProgress
status = entry.nsds5ReplicaLastInitStatus
if not refresh: # done - check status
if not status:
print "No status yet"
elif status.find("replica busy") > -1:
print "Update failed - replica busy - status", status
done = True
hasError = 2
elif status.find("Total update succeeded") > -1:
print "Update succeeded"
done = True
elif inprogress.lower() == 'true':
print "Update in progress yet not in progress"
else:
print "Update failed: status", status
hasError = 1
done = True
else:
print "Update in progress"
return done, hasError
def wait_for_repl_init(self, conn, agmtdn):
done = False
haserror = 0
while not done and not haserror:
time.sleep(1) # give it a few seconds to get going
done, haserror = self.check_repl_init(conn, agmtdn)
return haserror
def start_replication(self, other_conn):
print "starting replication"
cn, dn = self.agreement_dn(self.conn)
mod = [(ldap.MOD_ADD, 'nsds5BeginReplicaRefresh', 'start')]
other_conn.modify_s(dn, mod)
return self.wait_for_repl_init(other_conn, dn)
def basic_replication_setup(self, conn, master, replica_id):
self.add_replication_manager(conn)
self.local_replica_config(conn, master, replica_id)
if master:
self.setup_changelog(conn)
def setup_replication(self, other_hostname, realm_name, master=True):
"""
NOTES:
- the directory manager password needs to be the same on
both directories.
"""
other_conn = ipaldap.IPAdmin(other_hostname)
other_conn.do_simple_bind(bindpw=self.dirman_passwd)
self.suffix = ipaldap.IPAdmin.normalizeDN(dsinstance.realm_to_suffix(realm_name))
self.basic_replication_setup(self.conn, master, 1)
self.basic_replication_setup(other_conn, True, 2)
self.setup_agreement(other_conn, self.conn)
if master:
self.setup_agreement(self.conn, other_conn)
else:
self.setup_chaining_farm(other_conn)
self.setup_chain_on_update(other_conn)
return self.start_replication(other_conn)

View File

@@ -30,12 +30,15 @@ import xmlrpclib
import copy
import attrs
from ipa import ipaerror
from urllib import quote,unquote
from ipa import radius_util
import string
from types import *
import os
import re
import logging
import subprocess
try:
from threading import Lock
@@ -48,6 +51,12 @@ _LDAPPool = None
ACIContainer = "cn=accounts"
DefaultUserContainer = "cn=users,cn=accounts"
DefaultGroupContainer = "cn=groups,cn=accounts"
DefaultServiceContainer = "cn=services,cn=accounts"
# FIXME: need to check the ipadebug option in ipa.conf
#logging.basicConfig(level=logging.DEBUG,
# format='%(asctime)s %(levelname)s %(message)s',
# stream=sys.stderr)
#
# Apache runs in multi-process mode so each process will have its own
@@ -78,7 +87,10 @@ class IPAConnPool:
conn = ipaserver.ipaldap.IPAdmin(host,port,None,None,None,debug)
# This will bind the connection
conn.set_krbccache(krbccache, cprinc.name)
try:
conn.set_krbccache(krbccache, cprinc.name)
except ldap.UNWILLING_TO_PERFORM, e:
raise ipaerror.gen_exception(ipaerror.CONNECTION_UNWILLING)
return conn
@@ -418,17 +430,30 @@ class IPAServer:
# FIXME: This should be dynamic and can include just about anything
# Get our configuration
config = self.get_ipa_config(opts)
# Let us add in some missing attributes
if user.get('homedirectory') is None:
user['homedirectory'] = '/home/%s' % user.get('uid')
user['homedirectory'] = '%s/%s' % (config.get('ipahomesrootdir'), user.get('uid'))
user['homedirectory'] = user['homedirectory'].replace('//', '/')
user['homedirectory'] = user['homedirectory'].rstrip('/')
if user.get('loginshell') is None:
user['loginshell'] = config.get('ipadefaultloginshell')
if user.get('gecos') is None:
user['gecos'] = user['uid']
# FIXME: This can be removed once the DS plugin is installed
user['uidnumber'] = '501'
# If uidnumber is blank the the FDS dna_plugin will automatically
# assign the next value. So we don't have to do anything with it.
# FIXME: What is the default group for users?
user['gidnumber'] = '501'
group_dn="cn=%s,%s,%s" % (config.get('ipadefaultprimarygroup'), DefaultGroupContainer, self.basedn)
try:
default_group = self.get_entry_by_dn(group_dn, ['dn','gidNumber'], opts)
if default_group:
user['gidnumber'] = default_group.get('gidnumber')
except ipaerror.exception_for(ipaerror.LDAP_DATABASE_ERROR):
# Fake an LDAP error so we can return something useful to the user
raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND, "No default group for new users can be found.")
if user.get('krbprincipalname') is None:
user['krbprincipalname'] = "%s@%s" % (user.get('uid'), self.realm)
@@ -453,10 +478,40 @@ class IPAServer:
conn = self.getConnection(opts)
try:
res = conn.addEntry(entry)
self.add_user_to_group(user.get('uid'), group_dn, opts)
finally:
self.releaseConnection(conn)
return res
def get_custom_fields (self, opts=None):
"""Get the list of custom user fields.
A schema is a list of dict's of the form:
label: The label dispayed to the user
field: the attribute name
required: true/false
It is displayed to the user in the order of the list.
"""
config = self.get_ipa_config(opts)
fields = config.get('ipacustomfields')
if fields is None or fields == '':
return []
fl = fields.split('$')
schema = []
for x in range(len(fl)):
vals = fl[x].split(',')
if len(vals) != 3:
# Raise?
print "Invalid field, skipping"
d = dict(label=unquote(vals[0]), field=unquote(vals[1]), required=unquote(vals[2]))
schema.append(d)
return schema
# radius support
# clients
@@ -696,6 +751,37 @@ class IPAServer:
return fields
def set_custom_fields (self, schema, opts=None):
"""Set the list of custom user fields.
A schema is a list of dict's of the form:
label: The label dispayed to the user
field: the attribute name
required: true/false
It is displayed to the user in the order of the list.
"""
config = self.get_ipa_config(opts)
# The schema is stored as:
# label,field,required$label,field,required$...
# quote() from urilib is used to ensure that it is easy to unparse
stored_schema = ""
for i in range(len(schema)):
entry = schema[i]
entry = quote(entry.get('label')) + "," + quote(entry.get('field')) + "," + quote(entry.get('required'))
if stored_schema != "":
stored_schema = stored_schema + "$" + entry
else:
stored_schema = entry
new_config = copy.deepcopy(config)
new_config['ipacustomfields'] = stored_schema
return self.update_entry(config, new_config, opts)
def get_all_users (self, args=None, opts=None):
"""Return a list containing a User object for each
existing user.
@@ -714,18 +800,21 @@ class IPAServer:
return users
def find_users (self, criteria, sattrs=None, searchlimit=0, timelimit=-1,
def find_users (self, criteria, sattrs=None, 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."""
# TODO - retrieve from config
timelimit = 2
config = self.get_ipa_config(opts)
if timelimit < 0:
timelimit = float(config.get('ipasearchtimelimit'))
if searchlimit < 0:
searchlimit = float(config.get('ipasearchrecordslimit'))
# Assume the list of fields to search will come from a central
# configuration repository. A good format for that would be
# a comma-separated list of fields
search_fields_conf_str = "uid,givenName,sn,telephoneNumber,ou,title"
search_fields_conf_str = config.get('ipausersearchfields')
search_fields = string.split(search_fields_conf_str, ",")
criteria = self.__safe_filter(criteria)
@@ -797,29 +886,115 @@ class IPAServer:
return new_dict
def update_user (self, oldentry, newentry, opts=None):
"""Thin wrapper around update_entry"""
return self.update_entry(oldentry, newentry, opts)
"""Wrapper around update_entry with user-specific handling.
def mark_user_deleted (self, uid, opts=None):
"""Mark a user as inactive in LDAP. We aren't actually deleting
users here, just making it so they can't log in, etc."""
user = self.get_user_by_uid(uid, ['dn', 'uid', 'nsAccountlock'], opts)
If you want to change the RDN of a user you must use
this function. update_entry will fail.
"""
# Are we doing an add or replace operation?
if user.has_key('nsaccountlock'):
if user['nsaccountlock'] == "true":
return "already marked as deleted"
has_key = True
else:
has_key = False
newrdn = 0
if oldentry.get('uid') != newentry.get('uid'):
# RDN change
conn = self.getConnection(opts)
try:
res = conn.updateRDN(oldentry.get('dn'), "uid=" + newentry.get('uid'))
newdn = oldentry.get('dn')
newdn = newdn.replace("uid=%s" % oldentry.get('uid'), "uid=%s" % newentry.get('uid'))
# Now fix up the dns and uids so they aren't seen as having
# changed.
oldentry['dn'] = newdn
newentry['dn'] = newdn
oldentry['uid'] = newentry['uid']
newrdn = 1
finally:
self.releaseConnection(conn)
conn = self.getConnection(opts)
try:
res = conn.inactivateEntry(user['dn'], has_key)
finally:
self.releaseConnection(conn)
rv = self.update_entry(oldentry, newentry, opts)
return rv
except ipaerror.exception_for(ipaerror.LDAP_EMPTY_MODLIST):
# This means that there was just an rdn change, nothing else.
if newrdn == 1:
return "Success"
else:
raise
def mark_entry_active (self, dn, opts=None):
"""Mark an entry as active in LDAP."""
# This can be tricky. The entry itself can be marked inactive
# by being in the inactivated group. It can also be inactivated by
# being the member of an inactive group.
#
# First we try to remove the entry from the inactivated group. Then
# if it is still inactive we have to add it to the activated group
# which will override the group membership.
logging.debug("IPA: activating entry %s" % dn)
res = ""
# First, check the entry status
entry = self.get_entry_by_dn(dn, ['dn', 'nsAccountlock'], opts)
if entry.get('nsaccountlock', 'false') == "false":
logging.debug("IPA: already active")
raise ipaerror.gen_exception(ipaerror.LDAP_EMPTY_MODLIST)
group = self.get_entry_by_cn("inactivated", None, opts)
res = self.remove_member_from_group(entry.get('dn'), group.get('dn'), opts)
# Now they aren't a member of inactivated directly, what is the status
# now?
entry = self.get_entry_by_dn(dn, ['dn', 'nsAccountlock'], opts)
if entry.get('nsaccountlock', 'false') == "false":
# great, we're done
logging.debug("IPA: removing from inactivated did it.")
return res
# So still inactive, add them to activated
group = self.get_entry_by_cn("activated", None, opts)
res = self.add_member_to_group(dn, group.get('dn'), opts)
logging.debug("IPA: added to activated.")
return res
def mark_entry_inactive (self, dn, opts=None):
"""Mark an entry as inactive in LDAP."""
logging.debug("IPA: inactivating entry %s" % dn)
entry = self.get_entry_by_dn(dn, ['dn', 'nsAccountlock', 'memberOf'], opts)
if entry.get('nsaccountlock', 'false') == "true":
logging.debug("IPA: already marked as inactive")
raise ipaerror.gen_exception(ipaerror.LDAP_EMPTY_MODLIST)
# First see if they are in the activated group as this will override
# the our inactivation.
group = self.get_entry_by_cn("activated", None, opts)
self.remove_member_from_group(dn, group.get('dn'), opts)
# Now add them to inactivated
group = self.get_entry_by_cn("inactivated", None, opts)
res = self.add_member_to_group(dn, group.get('dn'), opts)
return res
def mark_user_active(self, uid, opts=None):
"""Mark a user as active"""
user = self.get_user_by_uid(uid, ['dn', 'uid'], opts)
return self.mark_entry_active(user.get('dn'))
def mark_user_inactive(self, uid, opts=None):
"""Mark a user as inactive"""
user = self.get_user_by_uid(uid, ['dn', 'uid'], opts)
return self.mark_entry_inactive(user.get('dn'))
def delete_user (self, uid, opts=None):
"""Delete a user. Not to be confused with inactivate_user. This
makes the entry go away completely.
@@ -877,7 +1052,7 @@ class IPAServer:
"""
member_dn = self.__safe_filter(member_dn)
filter = "(&(objectClass=posixGroup)(uniqueMember=%s))" % member_dn
filter = "(&(objectClass=posixGroup)(member=%s))" % member_dn
try:
return self.__get_list(self.basedn, filter, sattrs, opts)
@@ -900,12 +1075,11 @@ class IPAServer:
entry = ipaserver.ipaldap.Entry(dn)
# some required objectclasses
entry.setValues('objectClass', 'top', 'groupofuniquenames', 'posixGroup',
entry.setValues('objectClass', 'top', 'groupofnames', 'posixGroup',
'inetUser')
# FIXME, need a gidNumber generator
if group.get('gidnumber') is None:
entry.setValues('gidNumber', '501')
# No need to explicitly set gidNumber. The dna_plugin will do this
# for us if the value isn't provided by the user.
# fill in our new entry with everything sent by the user
for g in group:
@@ -917,16 +1091,22 @@ class IPAServer:
finally:
self.releaseConnection(conn)
def find_groups (self, criteria, sattrs=None, searchlimit=0, timelimit=-1,
def find_groups (self, criteria, sattrs=None, searchlimit=-1, timelimit=-1,
opts=None):
"""Return a list containing a User object for each
existing group that matches the criteria.
"""
config = self.get_ipa_config(opts)
if timelimit < 0:
timelimit = float(config.get('ipasearchtimelimit'))
if searchlimit < 0:
searchlimit = float(config.get('ipasearchrecordslimit'))
# Assume the list of fields to search will come from a central
# configuration repository. A good format for that would be
# a comma-separated list of fields
search_fields_conf_str = "cn,description"
search_fields_conf_str = config.get('ipagroupsearchfields')
search_fields = string.split(search_fields_conf_str, ",")
criteria = self.__safe_filter(criteria)
@@ -1001,12 +1181,12 @@ class IPAServer:
# check to make sure member_dn exists
member_entry = self.__get_base_entry(member_dn, "(objectClass=*)", ['dn','uid'], opts)
if new_group.get('uniquemember') is not None:
if ((isinstance(new_group.get('uniquemember'), str)) or (isinstance(new_group.get('uniquemember'), unicode))):
new_group['uniquemember'] = [new_group['uniquemember']]
new_group['uniquemember'].append(member_dn)
if new_group.get('member') is not None:
if ((isinstance(new_group.get('member'), str)) or (isinstance(new_group.get('member'), unicode))):
new_group['member'] = [new_group['member']]
new_group['member'].append(member_dn)
else:
new_group['uniquemember'] = member_dn
new_group['member'] = member_dn
try:
ret = self.__update_entry(old_group, new_group, opts)
@@ -1045,11 +1225,11 @@ class IPAServer:
raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND)
new_group = copy.deepcopy(old_group)
if new_group.get('uniquemember') is not None:
if ((isinstance(new_group.get('uniquemember'), str)) or (isinstance(new_group.get('uniquemember'), unicode))):
new_group['uniquemember'] = [new_group['uniquemember']]
if new_group.get('member') is not None:
if ((isinstance(new_group.get('member'), str)) or (isinstance(new_group.get('member'), unicode))):
new_group['member'] = [new_group['member']]
try:
new_group['uniquemember'].remove(member_dn)
new_group['member'].remove(member_dn)
except ValueError:
# member is not in the group
# FIXME: raise more specific error?
@@ -1198,8 +1378,56 @@ class IPAServer:
return failed
def update_group (self, oldentry, newentry, opts=None):
"""Thin wrapper around update_entry"""
return self.update_entry(oldentry, newentry, opts)
"""Wrapper around update_entry with group-specific handling.
If you want to change the RDN of a group you must use
this function. update_entry will fail.
"""
newrdn = 0
oldcn=oldentry.get('cn')
newcn=newentry.get('cn')
if isinstance(oldcn, str):
oldcn = [oldcn]
if isinstance(newcn, str):
newcn = [newcn]
oldcn.sort()
newcn.sort()
if oldcn != newcn:
# RDN change
conn = self.getConnection(opts)
try:
res = conn.updateRDN(oldentry.get('dn'), "cn=" + newcn[0])
newdn = oldentry.get('dn')
# 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])
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']
newrdn = 1
finally:
self.releaseConnection(conn)
try:
rv = self.update_entry(oldentry, newentry, opts)
return rv
except ipaerror.exception_for(ipaerror.LDAP_EMPTY_MODLIST):
if newrdn == 1:
# This means that there was just the rdn change, no other
# attributes
return "Success"
else:
raise
def delete_group (self, group_dn, opts=None):
"""Delete a group
@@ -1234,12 +1462,12 @@ class IPAServer:
if group_dn is None:
raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND)
if new_group.get('uniquemember') is not None:
if ((isinstance(new_group.get('uniquemember'), str)) or (isinstance(new_group.get('uniquemember'), unicode))):
new_group['uniquemember'] = [new_group['uniquemember']]
new_group['uniquemember'].append(group_dn['dn'])
if new_group.get('member') is not None:
if ((isinstance(new_group.get('member'), str)) or (isinstance(new_group.get('member'), unicode))):
new_group['member'] = [new_group['member']]
new_group['member'].append(group_dn['dn'])
else:
new_group['uniquemember'] = group_dn['dn']
new_group['member'] = group_dn['dn']
try:
ret = self.__update_entry(old_group, new_group, opts)
@@ -1261,10 +1489,10 @@ class IPAServer:
"""Do a memberOf search of groupdn and return the attributes in
attr_list (an empty list returns everything)."""
# TODO - retrieve from config
timelimit = 2
config = self.get_ipa_config(opts)
timelimit = float(config.get('ipasearchtimelimit'))
searchlimit = 0
searchlimit = float(config.get('ipasearchrecordslimit'))
groupdn = self.__safe_filter(groupdn)
filter = "(memberOf=%s)" % groupdn
@@ -1288,6 +1516,134 @@ class IPAServer:
return entries
def mark_group_active(self, cn, opts=None):
"""Mark a group as active"""
group = self.get_entry_by_cn(cn, ['dn', 'cn'], opts)
return self.mark_entry_active(group.get('dn'))
def mark_group_inactive(self, cn, opts=None):
"""Mark a group as inactive"""
group = self.get_entry_by_cn(cn, ['dn', 'uid'], opts)
return self.mark_entry_inactive(group.get('dn'))
def __is_service_unique(self, name, opts):
"""Return 1 if the uid is unique in the tree, 0 otherwise."""
name = self.__safe_filter(name)
filter = "(&(krbprincipalname=%s)(objectclass=krbPrincipal))" % name
try:
entry = self.__get_sub_entry(self.basedn, filter, ['dn','krbprincipalname'], opts)
return 0
except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
return 1
def add_service_principal(self, name, opts=None):
service_container = DefaultServiceContainer
princ_name = name + "@" + self.realm
conn = self.getConnection(opts)
if self.__is_service_unique(name, opts) == 0:
raise ipaerror.gen_exception(ipaerror.LDAP_DUPLICATE)
dn = "krbprincipalname=%s,%s,%s" % (ldap.dn.escape_dn_chars(princ_name),
service_container,self.basedn)
entry = ipaserver.ipaldap.Entry(dn)
entry.setValues('objectclass', 'krbPrincipal', 'krbPrincipalAux', 'krbTicketPolicyAux')
entry.setValues('krbprincipalname', princ_name)
try:
res = conn.addEntry(entry)
finally:
self.releaseConnection(conn)
return res
def get_keytab(self, name, opts=None):
"""get a keytab"""
princ_name = name + "@" + self.realm
conn = self.getConnection(opts)
if conn.principal != "admin@" + self.realm:
raise ipaerror.gen_exception(ipaerror.CONNECTION_GSSAPI_CREDENTIALS)
try:
try:
princs = conn.getList(self.basedn, self.scope, "krbprincipalname=" + princ_name, None)
except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
return None
finally:
self.releaseConnection(conn)
# This is ugly - call out to a C wrapper around kadmin.local
p = subprocess.Popen(["/usr/sbin/ipa-keytab-util", princ_name, self.realm],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout,stderr = p.communicate()
if p.returncode != 0:
return None
return stdout
# Configuration support
def get_ipa_config(self, opts=None):
"""Retrieve the IPA configuration"""
try:
config = self.get_entry_by_cn("ipaconfig", None, opts)
except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
raise ipaerror.gen_exception(ipaerror.LDAP_NO_CONFIG)
return config
def update_ipa_config(self, oldconfig, newconfig, opts=None):
"""Update the IPA configuration"""
# The LDAP routines want strings, not ints, so convert a few
# things. Otherwise it sees a string -> int conversion as a change.
try:
newconfig['krbmaxpwdlife'] = str(newconfig.get('krbmaxpwdlife'))
newconfig['krbminpwdlife'] = str(newconfig.get('krbminpwdlife'))
newconfig['krbpwdmindiffchars'] = str(newconfig.get('krbpwdmindiffchars'))
newconfig['krbpwdminlength'] = str(newconfig.get('krbpwdminlength'))
newconfig['krbpwdhistorylength'] = str(newconfig.get('krbpwdhistorylength'))
except KeyError:
# These should all be there but if not, let things proceed
pass
return self.update_entry(oldconfig, newconfig, opts)
def get_password_policy(self, opts=None):
"""Retrieve the IPA password policy"""
try:
policy = self.get_entry_by_cn("accounts", None, opts)
except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
raise ipaerror.gen_exception(ipaerror.LDAP_NO_CONFIG)
return policy
def update_password_policy(self, oldpolicy, newpolicy, opts=None):
"""Update the IPA configuration"""
# 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'))
except KeyError:
# These should all be there but if not, let things proceed
pass
return self.update_entry(oldpolicy, newpolicy, opts)
def ldap_search_escape(match):
"""Escapes out nasty characters from the ldap search.

View File

@@ -2,12 +2,18 @@
ProxyRequests Off
# Make all requests use SSL except for Kerberos authentication errors
RewriteEngine on
# Redirect to the fully-qualified hostname. Not redirecting to secure
# port so configuration files can be retrieved without requiring SSL.
RewriteCond %{HTTP_HOST} !^$FQDN$$ [NC]
RewriteRule ^/(.*) http://$FQDN/$$1 [L,R=301]
# Redirect to the secure port if not displaying an error or retrieving
# configuration.
RewriteCond %{SERVER_PORT} !^443$$
RewriteCond %{REQUEST_URI} !^/(errors|config)/
RewriteRule ^/(.*) https://%{SERVER_NAME}/$$1 [L,R,NC]
RewriteRule ^/(.*) https://$FQDN/$$1 [L,R=301,NC]
<Proxy *>
AuthType Kerberos

View File

@@ -141,8 +141,8 @@ class ModXMLRPCRequestHandler(object):
if req.subprocess_env.get("KRB5CCNAME") is not None:
opts['krbccache'] = req.subprocess_env.get("KRB5CCNAME")
else:
sys.stderr.write("IPA: did not receive a Kerberos credentials cache. Expect problems")
sys.stderr.flush()
response = dumps(Fault(5, "Did not receive Kerberos credentials."))
return response
if pythonopts.get("IPADebug"):
opts['ipadebug'] = pythonopts.get("IPADebug")
@@ -277,17 +277,17 @@ class ModXMLRPCRequestHandler(object):
def handle_request(self,req):
"""Handle a single XML-RPC request"""
# The LDAP connection pool is not thread-safe. Avoid problems and
# force the forked model for now.
if not apache.mpm_query(apache.AP_MPMQ_IS_FORKED):
raise Fault(3, "Apache must use the forked model")
# XMLRPC uses POST only. Reject anything else
if req.method != 'POST':
req.allow_methods(['POST'],1)
raise apache.SERVER_RETURN, apache.HTTP_METHOD_NOT_ALLOWED
response = self._marshaled_dispatch(req.read(), req)
# The LDAP connection pool is not thread-safe. Avoid problems and
# force the forked model for now.
if apache.mpm_query(apache.AP_MPMQ_IS_THREADED):
response = dumps(Fault(3, "Apache must use the forked model"))
else:
response = self._marshaled_dispatch(req.read(), req)
req.content_type = "text/xml"
req.set_content_length(len(response))
@@ -326,12 +326,16 @@ def handler(req, profiling=False):
h.register_function(f.get_user_by_email)
h.register_function(f.get_users_by_manager)
h.register_function(f.add_user)
h.register_function(f.get_add_schema)
h.register_function(f.get_custom_fields)
h.register_function(f.set_custom_fields)
h.register_function(f.get_all_users)
h.register_function(f.find_users)
h.register_function(f.update_user)
h.register_function(f.delete_user)
h.register_function(f.mark_user_deleted)
h.register_function(f.mark_user_active)
h.register_function(f.mark_user_inactive)
h.register_function(f.mark_group_active)
h.register_function(f.mark_group_inactive)
h.register_function(f.modifyPassword)
h.register_function(f.get_groups_by_member)
h.register_function(f.add_group)
@@ -351,6 +355,12 @@ def handler(req, profiling=False):
h.register_function(f.delete_group)
h.register_function(f.attrs_to_labels)
h.register_function(f.group_members)
h.register_function(f.get_ipa_config)
h.register_function(f.update_ipa_config)
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.get_keytab)
h.register_function(f.get_radius_client_by_ip_addr)
h.register_function(f.add_radius_client)
h.register_function(f.update_radius_client)

View File

@@ -7,7 +7,7 @@ Unable to verify your Kerberos credentials. Please make sure
that you have valid Kerberos tickets (obtainable via kinit), and that you
have <a href="/errors/ssbrowser.html">configured your
browser correctly</a>. If you are still unable to access
the idm wiki, please contact the helpdesk on for additional assistance.
the IPA Web interface, please contact the helpdesk on for additional assistance.
</p>
</ul>
</body>