mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
merged radius work with latest mainline tip
This commit is contained in:
68
Makefile
68
Makefile
@@ -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/
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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])
|
||||
|
||||
83
ipa-admintools/ipa-getkeytab
Normal file
83
ipa-admintools/ipa-getkeytab
Normal 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())
|
||||
@@ -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])
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(" ")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -11,6 +11,7 @@ SUBDIRS = \
|
||||
ipaserver \
|
||||
ipa-slapi-plugins \
|
||||
xmlrpc-server \
|
||||
ipa-keytab-util \
|
||||
$(NULL)
|
||||
|
||||
EXTRA_DIST = \
|
||||
|
||||
@@ -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}
|
||||
|
||||
27
ipa-server/ipa-gui/README.multivalue
Normal file
27
ipa-server/ipa-gui/README.multivalue
Normal 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
|
||||
@@ -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())
|
||||
|
||||
@@ -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 = \
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
]
|
||||
|
||||
59
ipa-server/ipa-gui/ipagui/forms/ipapolicy.py
Normal file
59
ipa-server/ipa-gui/ipagui/forms/ipapolicy.py
Normal 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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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'''
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ app_PYTHON = \
|
||||
__init__.py \
|
||||
group.py \
|
||||
ipacontroller.py \
|
||||
ipapolicy.py \
|
||||
policy.py \
|
||||
user.py \
|
||||
delegation.py \
|
||||
$(NULL)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
168
ipa-server/ipa-gui/ipagui/subcontrollers/ipapolicy.py
Normal file
168
ipa-server/ipa-gui/ipagui/subcontrollers/ipapolicy.py
Normal 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
|
||||
32
ipa-server/ipa-gui/ipagui/subcontrollers/policy.py
Normal file
32
ipa-server/ipa-gui/ipagui/subcontrollers/policy.py
Normal 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()
|
||||
@@ -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'),
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -25,17 +25,22 @@ from ipagui.helpers import ipahelper
|
||||
|
||||
<script type="text/javascript" charset="utf-8"
|
||||
src="${tg.url('/static/javascript/dynamicedit.js')}"></script>
|
||||
<script type="text/javascript" charset="utf-8"
|
||||
src="${tg.url('/tg_widgets/tg_expanding_form_widget/javascript/expanding_form.js')}"></script>
|
||||
|
||||
<?python searchurl = tg.url('/group/edit_search') ?>
|
||||
|
||||
<script type="text/javascript">
|
||||
function toggleProtectedFields(checkbox) {
|
||||
var gidnumberField = $('form_gidnumber');
|
||||
var cnField = $('form_cn');
|
||||
if (checkbox.checked) {
|
||||
gidnumberField.disabled = false;
|
||||
cnField.disabled = false;
|
||||
$('form_editprotected').value = 'true';
|
||||
} else {
|
||||
gidnumberField.disabled = true;
|
||||
cnField.disabled = true;
|
||||
$('form_editprotected').value = '';
|
||||
}
|
||||
}
|
||||
@@ -70,11 +75,9 @@ from ipagui.helpers import ipahelper
|
||||
py:content="group_fields.cn.label" />:
|
||||
</th>
|
||||
<td>
|
||||
<!-- <span py:replace="group_fields.cn.display(value_for(group_fields.cn))" />
|
||||
<span py:replace="group_fields.cn.display(value_for(group_fields.cn))" />
|
||||
<span py:if="tg.errors.get('cn')" class="fielderror"
|
||||
py:content="tg.errors.get('cn')" /> -->
|
||||
${value_for(group_fields.cn)}
|
||||
|
||||
py:content="tg.errors.get('cn')" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -88,6 +91,9 @@ from ipagui.helpers import ipahelper
|
||||
<span py:if="tg.errors.get('description')" class="fielderror"
|
||||
py:content="tg.errors.get('description')" />
|
||||
|
||||
<script type="text/javascript">
|
||||
document.getElementById('form_cn').disabled = true;
|
||||
</script>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -106,6 +112,16 @@ from ipagui.helpers import ipahelper
|
||||
</script>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
<label class="fieldlabel" for="${group_fields.nsAccountLock.field_id}" py:content="group_fields.nsAccountLock.label" />:
|
||||
</th>
|
||||
<td>
|
||||
<span py:replace="group_fields.nsAccountLock.display(value_for(group_fields.nsAccountLock))" />
|
||||
<span py:if="tg.errors.get('nsAccountLock')" class="fielderror"
|
||||
py:content="tg.errors.get('nsAccountLock')" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div>
|
||||
@@ -160,6 +176,7 @@ from ipagui.helpers import ipahelper
|
||||
div_counter = div_counter + 1
|
||||
?>
|
||||
</div>
|
||||
<!-- a space here to prevent an empty div -->
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
15
ipa-server/ipa-gui/ipagui/templates/ipapolicyedit.kid
Normal file
15
ipa-server/ipa-gui/ipagui/templates/ipapolicyedit.kid
Normal 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>
|
||||
176
ipa-server/ipa-gui/ipagui/templates/ipapolicyeditform.kid
Normal file
176
ipa-server/ipa-gui/ipagui/templates/ipapolicyeditform.kid
Normal 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>
|
||||
120
ipa-server/ipa-gui/ipagui/templates/ipapolicyshow.kid
Normal file
120
ipa-server/ipa-gui/ipagui/templates/ipapolicyshow.kid
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
31
ipa-server/ipa-gui/ipagui/templates/policyindex.kid
Normal file
31
ipa-server/ipa-gui/ipagui/templates/policyindex.kid
Normal 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>
|
||||
17
ipa-server/ipa-gui/ipagui/templates/policylayout.kid
Normal file
17
ipa-server/ipa-gui/ipagui/templates/policylayout.kid
Normal 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>
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
<!-- 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();"
|
||||
/>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -3,17 +3,18 @@
|
||||
py:extends="'userlayout.kid'">
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/>
|
||||
<title>View Person</title>
|
||||
<title>View User</title>
|
||||
</head>
|
||||
<body>
|
||||
<?python
|
||||
edit_url = tg.url('/user/edit', uid=user.get('uid'))
|
||||
?>
|
||||
<h1>View Person</h1>
|
||||
<h1>View User</h1>
|
||||
|
||||
<input class="submitbutton" type="button"
|
||||
<input py:if="'editors' in tg.identity.groups or 'admins' in tg.identity.groups"
|
||||
class="submitbutton" type="button"
|
||||
onclick="document.location.href='${edit_url}'"
|
||||
value="Edit Person" />
|
||||
value="Edit User" />
|
||||
|
||||
<?python
|
||||
from ipagui.helpers import userhelper
|
||||
@@ -57,7 +58,21 @@ else:
|
||||
<th>
|
||||
<label class="fieldlabel" py:content="fields.cn.label" />:
|
||||
</th>
|
||||
<td>${user.get("cn")}</td>
|
||||
<td>
|
||||
<table cellpadding="2" cellspacing="0" border="0">
|
||||
<tbody>
|
||||
<?python
|
||||
index = 0
|
||||
values = user.get("cn")
|
||||
if isinstance(values, str):
|
||||
values = [values]
|
||||
?>
|
||||
<tr py:for="index in range(len(values))">
|
||||
<td>${values[index]}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
@@ -132,31 +147,101 @@ else:
|
||||
<th>
|
||||
<label class="fieldlabel" py:content="fields.telephonenumber.label" />:
|
||||
</th>
|
||||
<td>${user.get("telephonenumber")}</td>
|
||||
<td>
|
||||
<table cellpadding="2" cellspacing="0" border="0">
|
||||
<tbody>
|
||||
<?python
|
||||
index = 0
|
||||
values = user.get("telephonenumber", '')
|
||||
if isinstance(values, str):
|
||||
values = [values]
|
||||
?>
|
||||
<tr py:for="index in range(len(values))">
|
||||
<td>${values[index]}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
<label class="fieldlabel" py:content="fields.facsimiletelephonenumber.label" />:
|
||||
</th>
|
||||
<td>${user.get("facsimiletelephonenumber")}</td>
|
||||
<td>
|
||||
<table cellpadding="2" cellspacing="0" border="0">
|
||||
<tbody>
|
||||
<?python
|
||||
index = 0
|
||||
values = user.get("facsimiletelephonenumber", '')
|
||||
if isinstance(values, str):
|
||||
values = [values]
|
||||
?>
|
||||
<tr py:for="index in range(len(values))">
|
||||
<td>${values[index]}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
<label class="fieldlabel" py:content="fields.mobile.label" />:
|
||||
</th>
|
||||
<td>${user.get("mobile")}</td>
|
||||
<td>
|
||||
<table cellpadding="2" cellspacing="0" border="0">
|
||||
<tbody>
|
||||
<?python
|
||||
index = 0
|
||||
values = user.get("mobile", '')
|
||||
if isinstance(values, str):
|
||||
values = [values]
|
||||
?>
|
||||
<tr py:for="index in range(len(values))">
|
||||
<td>${values[index]}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
<label class="fieldlabel" py:content="fields.pager.label" />:
|
||||
</th>
|
||||
<td>${user.get("pager")}</td>
|
||||
<td>
|
||||
<table cellpadding="2" cellspacing="0" border="0">
|
||||
<tbody>
|
||||
<?python
|
||||
index = 0
|
||||
values = user.get("pager", '')
|
||||
if isinstance(values, str):
|
||||
values = [values]
|
||||
?>
|
||||
<tr py:for="index in range(len(values))">
|
||||
<td>${values[index]}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
<label class="fieldlabel" py:content="fields.homephone.label" />:
|
||||
</th>
|
||||
<td>${user.get("homephone")}</td>
|
||||
<td>
|
||||
<table cellpadding="2" cellspacing="0" border="0">
|
||||
<tbody>
|
||||
<?python
|
||||
index = 0
|
||||
values = user.get("homephone", '')
|
||||
if isinstance(values, str):
|
||||
values = [values]
|
||||
?>
|
||||
<tr py:for="index in range(len(values))">
|
||||
<td>${values[index]}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -260,7 +345,7 @@ else:
|
||||
</table>
|
||||
|
||||
<div py:if='len(fields.custom_fields) > 0'>
|
||||
<div class="formsection" >Custom Fields</div>
|
||||
<h2 class="formsection">Custom Fields</h2>
|
||||
<table class="formtable" cellpadding="2" cellspacing="0" border="0">
|
||||
<tr py:for='custom_field in fields.custom_fields'>
|
||||
<th>
|
||||
@@ -289,8 +374,9 @@ else:
|
||||
|
||||
<br/>
|
||||
<hr />
|
||||
<input class="submitbutton" type="button"
|
||||
<input py:if="'editors' in tg.identity.groups or 'admins' in tg.identity.groups"
|
||||
class="submitbutton" type="button"
|
||||
onclick="document.location.href='${edit_url}'"
|
||||
value="Edit Person" />
|
||||
value="Edit User" />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -6,6 +6,8 @@ SUBDIRS = \
|
||||
|
||||
sbin_SCRIPTS = \
|
||||
ipa-server-install \
|
||||
ipa-replica-install \
|
||||
ipa-replica-prepare \
|
||||
$(NULL)
|
||||
|
||||
appdir = $(IPA_DATA_DIR)
|
||||
|
||||
142
ipa-server/ipa-install/ipa-replica-install
Normal file
142
ipa-server/ipa-install/ipa-replica-install
Normal 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()
|
||||
|
||||
|
||||
114
ipa-server/ipa-install/ipa-replica-prepare
Normal file
114
ipa-server/ipa-install/ipa-replica-prepare
Normal 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()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
37
ipa-server/ipa-install/share/60ipaconfig.ldif
Normal file
37
ipa-server/ipa-install/share/60ipaconfig.ldif
Normal 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 ) )
|
||||
@@ -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 = \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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";)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
7
ipa-server/ipa-install/share/memberof-task.ldif
Normal file
7
ipa-server/ipa-install/share/memberof-task.ldif
Normal 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=*)
|
||||
22
ipa-server/ipa-keytab-util/Makefile.am
Normal file
22
ipa-server/ipa-keytab-util/Makefile.am
Normal 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
|
||||
304
ipa-server/ipa-keytab-util/ipa-keytab-util.c
Normal file
304
ipa-server/ipa-keytab-util/ipa-keytab-util.c
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -12,6 +12,8 @@ app_PYTHON = \
|
||||
radiusinstance.py \
|
||||
webguiinstance.py \
|
||||
service.py \
|
||||
installutils.py \
|
||||
replication.py \
|
||||
$(NULL)
|
||||
|
||||
EXTRA_DIST = \
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
108
ipa-server/ipaserver/installutils.py
Normal file
108
ipa-server/ipaserver/installutils.py
Normal 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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
316
ipa-server/ipaserver/replication.py
Normal file
316
ipa-server/ipaserver/replication.py
Normal 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)
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user