From 7d95cd612c1b340add692038c835e7cd8d8ad18b Mon Sep 17 00:00:00 2001 From: Karl MacMillan Date: Tue, 31 Jul 2007 12:09:38 -0400 Subject: Final reorginzation to reflect packaging. --- Makefile | 18 +- ipa-admintools/Makefile | 8 +- ipa-admintools/ipa-adduser | 80 +++++++ ipa-admintools/ipa-finduser | 58 +++++ ipa-admintools/src/Makefile | 10 - ipa-admintools/src/ipa-adduser | 80 ------- ipa-admintools/src/ipa-finduser | 58 ----- ipa-python/Makefile | 11 + ipa-python/README | 0 ipa-python/rpcclient.py | 102 +++++++++ ipa-server/Makefile | 2 +- ipa-server/ipa-web/Makefile | 9 - ipa-server/ipa-web/README | 0 ipa-server/ipa-web/api/Makefile | 12 - ipa-server/ipa-web/api/README | 0 ipa-server/ipa-web/api/funcs.py | 168 -------------- ipa-server/ipa-web/api/ipa.conf | 24 -- ipa-server/ipa-web/api/ipaxmlrpc.py | 288 ------------------------ ipa-server/ipa-web/client/Makefile | 11 - ipa-server/ipa-web/client/README | 0 ipa-server/ipa-web/client/ipaldap.py | 395 --------------------------------- ipa-server/ipa-web/client/rpcclient.py | 102 --------- ipa-server/ipa-web/gui/README | 0 ipa-server/ipaserver/ipaldap.py | 395 +++++++++++++++++++++++++++++++++ ipa-server/xmlrpc-server/Makefile | 12 + ipa-server/xmlrpc-server/README | 0 ipa-server/xmlrpc-server/funcs.py | 170 ++++++++++++++ ipa-server/xmlrpc-server/ipa.conf | 24 ++ ipa-server/xmlrpc-server/ipaxmlrpc.py | 274 +++++++++++++++++++++++ 29 files changed, 1147 insertions(+), 1164 deletions(-) create mode 100644 ipa-admintools/ipa-adduser create mode 100644 ipa-admintools/ipa-finduser delete mode 100644 ipa-admintools/src/Makefile delete mode 100644 ipa-admintools/src/ipa-adduser delete mode 100644 ipa-admintools/src/ipa-finduser create mode 100644 ipa-python/Makefile create mode 100644 ipa-python/README create mode 100644 ipa-python/rpcclient.py delete mode 100644 ipa-server/ipa-web/Makefile delete mode 100644 ipa-server/ipa-web/README delete mode 100644 ipa-server/ipa-web/api/Makefile delete mode 100644 ipa-server/ipa-web/api/README delete mode 100644 ipa-server/ipa-web/api/funcs.py delete mode 100644 ipa-server/ipa-web/api/ipa.conf delete mode 100644 ipa-server/ipa-web/api/ipaxmlrpc.py delete mode 100644 ipa-server/ipa-web/client/Makefile delete mode 100644 ipa-server/ipa-web/client/README delete mode 100644 ipa-server/ipa-web/client/ipaldap.py delete mode 100644 ipa-server/ipa-web/client/rpcclient.py delete mode 100644 ipa-server/ipa-web/gui/README create mode 100644 ipa-server/ipaserver/ipaldap.py create mode 100644 ipa-server/xmlrpc-server/Makefile create mode 100644 ipa-server/xmlrpc-server/README create mode 100644 ipa-server/xmlrpc-server/funcs.py create mode 100644 ipa-server/xmlrpc-server/ipa.conf create mode 100644 ipa-server/xmlrpc-server/ipaxmlrpc.py diff --git a/Makefile b/Makefile index 6b9283cbf..4031978b5 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -SUBDIRS=ipa-server ipa-admintools +SUBDIRS=ipa-server ipa-admintools ipa-python PRJ_PREFIX=freeipa @@ -9,7 +9,6 @@ SERV_MAJOR=0 SERV_MINOR=1 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 @@ -17,10 +16,16 @@ ADMIN_MAJOR=0 ADMIN_MINOR=1 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=1 +PYTHON_RELEASE=0 +PYTHON_VERSION=$(PYTHON_MAJOR).$(PYTHON_MINOR).$(PYTHON_RELEASE) +PYTHON_TARBALL_PREFIX=$(PRJ_PREFIX)-admintools-$(PYTHON_VERSION) +PYTHON_TARBALL=$(PYTHON_TARBALL_PREFIX).tgz + all: @for subdir in $(SUBDIRS); do \ (cd $$subdir && $(MAKE) $@) || exit 1; \ @@ -35,6 +40,7 @@ clean: @for subdir in $(SUBDIRS); do \ (cd $$subdir && $(MAKE) $@) || exit 1; \ done + rm -f *~ version-update: sed s/VERSION/$(SERV_VERSION)/ ipa-server/freeipa-server.spec.in \ @@ -55,6 +61,12 @@ tarballs: cd dist; tar cfz $(ADMIN_TARBALL) $(ADMIN_TARBALL_PREFIX) rm -fr dist/$(ADMIN_TARBALL_PREFIX) + # ipa-python + mv dist/freeipa/ipa-python dist/$(PYTHON_TARBALL_PREFIX) + rm -f dist/$(PYTHON_TARBALL) + cd dist; tar cfz $(PYTHON_TARBALL) $(PYTHON_TARBALL_PREFIX) + rm -fr dist/$(PYTHON_TARBALL_PREFIX) + # cleanup rm -fr dist/freeipa diff --git a/ipa-admintools/Makefile b/ipa-admintools/Makefile index a7b276f2b..e0fd405ab 100644 --- a/ipa-admintools/Makefile +++ b/ipa-admintools/Makefile @@ -1,8 +1,10 @@ +SBINDIR = $(DESTDIR)/usr/sbin + all: ; install: - $(MAKE) -C src $@ + install -m 755 ipa-adduser $(SBINDIR) + install -m 755 ipa-finduser $(SBINDIR) clean: - $(MAKE) -C src $@ - rm -f *~ + rm -f *~ *.pyc diff --git a/ipa-admintools/ipa-adduser b/ipa-admintools/ipa-adduser new file mode 100644 index 000000000..94a19dba4 --- /dev/null +++ b/ipa-admintools/ipa-adduser @@ -0,0 +1,80 @@ +#! /usr/bin/python -E +# Authors: Rob Crittenden +# +# 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.rpcclient +import xmlrpclib + +def usage(): + print "ipa-adduser [-c|--gecos STRING] [-d|--directory STRING] [-f|--firstname STRING] [-l|--lastname STRING] user" + sys.exit(1) + +def parse_options(): + parser = OptionParser() + parser.add_option("-c", "--gecos", dest="gecos", + help="Set the GECOS field") + parser.add_option("-d", "--directory", dest="directory", + help="Set the User's home directory") + parser.add_option("-f", "--firstname", dest="gn", + help="User's first name") + parser.add_option("-l", "--lastname", dest="sn", + help="User's last name") + parser.add_option("-s", "--shell", dest="shell", + help="Set user's login shell to shell") + parser.add_option("--usage", action="store_true", + help="Program usage") + + (options, args) = parser.parse_args() + + if not options.gn or not options.sn: + usage() + + return options, args + +def main(): + user={} + (options, args) = parse_options() + + if len(args) != 1: + usage() + + user['gn'] = options.gn + user['sn'] = options.sn + user['uid'] = args[0] + if options.gecos: + user['gecos'] = options.gecos + if options.directory: + user['homedirectory'] = options.directory + if options.shell: + user['loginshell'] = options.shell + else + user['loginshell'] = "/bin/bash" + + try: + ipa.rpcclient.add_user(user) + print args[0] "successfully added" + except xmlrpclib.Fault, f: + print f.faultString + + return 0 + +main() \ No newline at end of file diff --git a/ipa-admintools/ipa-finduser b/ipa-admintools/ipa-finduser new file mode 100644 index 000000000..928eff753 --- /dev/null +++ b/ipa-admintools/ipa-finduser @@ -0,0 +1,58 @@ +#! /usr/bin/python -E +# Authors: Rob Crittenden +# +# 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 +# + +from optparse import OptionParser +import ipa +import ipa.rpcclient +import ipa.ipaldap +import base64 +import sys +import xmlrpclib + +def usage(): + print "ipa-finduser " + sys.exit() + +def parse_options(): + parser = OptionParser() + + (options, args) = parser.parse_args() + + return options, args + +def main(): + user={} + (options, args) = parse_options() + + if len(args) != 1: + usage() + + try: + ent = ipa.rpcclient.get_user(args[0]) + entry = ipa.ipaldap.Entry(ent['dn']) + for e in ent: + entry.setValues(e, ent[e]) + print entry + except xmlrpclib.Fault, fault: + print fault.faultString + + return 0 + +main() \ No newline at end of file diff --git a/ipa-admintools/src/Makefile b/ipa-admintools/src/Makefile deleted file mode 100644 index e0fd405ab..000000000 --- a/ipa-admintools/src/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -SBINDIR = $(DESTDIR)/usr/sbin - -all: ; - -install: - install -m 755 ipa-adduser $(SBINDIR) - install -m 755 ipa-finduser $(SBINDIR) - -clean: - rm -f *~ *.pyc diff --git a/ipa-admintools/src/ipa-adduser b/ipa-admintools/src/ipa-adduser deleted file mode 100644 index 94a19dba4..000000000 --- a/ipa-admintools/src/ipa-adduser +++ /dev/null @@ -1,80 +0,0 @@ -#! /usr/bin/python -E -# Authors: Rob Crittenden -# -# 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.rpcclient -import xmlrpclib - -def usage(): - print "ipa-adduser [-c|--gecos STRING] [-d|--directory STRING] [-f|--firstname STRING] [-l|--lastname STRING] user" - sys.exit(1) - -def parse_options(): - parser = OptionParser() - parser.add_option("-c", "--gecos", dest="gecos", - help="Set the GECOS field") - parser.add_option("-d", "--directory", dest="directory", - help="Set the User's home directory") - parser.add_option("-f", "--firstname", dest="gn", - help="User's first name") - parser.add_option("-l", "--lastname", dest="sn", - help="User's last name") - parser.add_option("-s", "--shell", dest="shell", - help="Set user's login shell to shell") - parser.add_option("--usage", action="store_true", - help="Program usage") - - (options, args) = parser.parse_args() - - if not options.gn or not options.sn: - usage() - - return options, args - -def main(): - user={} - (options, args) = parse_options() - - if len(args) != 1: - usage() - - user['gn'] = options.gn - user['sn'] = options.sn - user['uid'] = args[0] - if options.gecos: - user['gecos'] = options.gecos - if options.directory: - user['homedirectory'] = options.directory - if options.shell: - user['loginshell'] = options.shell - else - user['loginshell'] = "/bin/bash" - - try: - ipa.rpcclient.add_user(user) - print args[0] "successfully added" - except xmlrpclib.Fault, f: - print f.faultString - - return 0 - -main() \ No newline at end of file diff --git a/ipa-admintools/src/ipa-finduser b/ipa-admintools/src/ipa-finduser deleted file mode 100644 index 928eff753..000000000 --- a/ipa-admintools/src/ipa-finduser +++ /dev/null @@ -1,58 +0,0 @@ -#! /usr/bin/python -E -# Authors: Rob Crittenden -# -# 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 -# - -from optparse import OptionParser -import ipa -import ipa.rpcclient -import ipa.ipaldap -import base64 -import sys -import xmlrpclib - -def usage(): - print "ipa-finduser " - sys.exit() - -def parse_options(): - parser = OptionParser() - - (options, args) = parser.parse_args() - - return options, args - -def main(): - user={} - (options, args) = parse_options() - - if len(args) != 1: - usage() - - try: - ent = ipa.rpcclient.get_user(args[0]) - entry = ipa.ipaldap.Entry(ent['dn']) - for e in ent: - entry.setValues(e, ent[e]) - print entry - except xmlrpclib.Fault, fault: - print fault.faultString - - return 0 - -main() \ No newline at end of file diff --git a/ipa-python/Makefile b/ipa-python/Makefile new file mode 100644 index 000000000..bc6554be4 --- /dev/null +++ b/ipa-python/Makefile @@ -0,0 +1,11 @@ +PYTHONLIBDIR ?= $(shell python -c "from distutils.sysconfig import *; print get_python_lib(1)") +PACKAGEDIR ?= $(DESTDIR)/$(PYTHONLIBDIR)/ipa + +all: ; + +install: + -mkdir -p $(PACKAGEDIR) + install -m 644 *.py $(PACKAGEDIR) + +clean: + rm -f *~ *.pyc \ No newline at end of file diff --git a/ipa-python/README b/ipa-python/README new file mode 100644 index 000000000..e69de29bb diff --git a/ipa-python/rpcclient.py b/ipa-python/rpcclient.py new file mode 100644 index 000000000..416026628 --- /dev/null +++ b/ipa-python/rpcclient.py @@ -0,0 +1,102 @@ +#! /usr/bin/python -E +# Authors: Rob Crittenden +# +# 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 +# + +#!/usr/bin/python + +try: + import krbV +except ImportError: + pass +import xmlrpclib +import socket +import os +import base64 + +# Some errors to catch +# http://cvs.fedora.redhat.com/viewcvs/ldapserver/ldap/servers/plugins/pam_passthru/README?root=dirsec&rev=1.6&view=auto + +# FIXME: do we want this set somewhere else? +server = xmlrpclib.ServerProxy("http://localhost:80/ipa") + +def get_user(username): + """Get a specific user""" + + try: + result = server.get_user(username) + myuser = result + except xmlrpclib.Fault, fault: + raise xmlrpclib.Fault(fault.faultCode, fault.faultString) + return None + except socket.error, (value, msg): + raise xmlrpclib.Fault(value, msg) + return None + + return myuser + +def add_user(user): + """Add a new user""" + + # FIXME: Get the realm from somewhere + realm="GREYOAK.COM" + + # FIXME: This should be dynamic and can include just about anything + # Let us add in some missing attributes + if user.get('homeDirectory') is None: + user['homeDirectory'] ='/home/%s' % user['uid'] + if user.get('gecos') is None: + user['gecos'] = user['uid'] + + # FIXME: This can be removed once the DS plugin is installed + user['uidNumber'] ='501' + + # FIXME: What is the default group for users? + user['gidNumber'] ='501' + user['krbPrincipalName'] = "%s@%s" % (user['uid'], realm) + user['cn'] = "%s %s" % (user['gn'], user['sn']) + if user.get('gn'): + del user['gn'] + + try: + result = server.add_user(user) + return result + except xmlrpclib.Fault, fault: + raise xmlrpclib.Fault(fault.faultCode, fault.faultString) + return None + except socket.error, (value, msg): + raise xmlrpclib.Fault(value, msg) + return None + +def get_add_schema(): + """Get the list of attributes we need to ask when adding a new + user. + """ + + # FIXME: Hardcoded and designed for the TurboGears GUI. Do we want + # this for the CLI as well? + try: + result = server.get_add_schema() + except xmlrpclib.Fault, fault: + raise xmlrpclib.Fault(fault,faultCode, fault.faultString) + return None + except socket.error, (value, msg): + raise xmlrpclib.Fault(value, msg) + return None + + return result diff --git a/ipa-server/Makefile b/ipa-server/Makefile index 0976df430..dd3fa71ef 100644 --- a/ipa-server/Makefile +++ b/ipa-server/Makefile @@ -1,4 +1,4 @@ -SUBDIRS=ipa-install +SUBDIRS=ipa-install xmlrpc-server PYTHONDIR=$(DESTDIR)/usr/share/ipa/ipaserver all: diff --git a/ipa-server/ipa-web/Makefile b/ipa-server/ipa-web/Makefile deleted file mode 100644 index 055ee9f44..000000000 --- a/ipa-server/ipa-web/Makefile +++ /dev/null @@ -1,9 +0,0 @@ -all: ; - -install: - $(MAKE) -C api $@ - $(MAKE) -C client $@ - -clean: - $(MAKE) -C api $@ - rm -f *~ diff --git a/ipa-server/ipa-web/README b/ipa-server/ipa-web/README deleted file mode 100644 index e69de29bb..000000000 diff --git a/ipa-server/ipa-web/api/Makefile b/ipa-server/ipa-web/api/Makefile deleted file mode 100644 index 6af262ee9..000000000 --- a/ipa-server/ipa-web/api/Makefile +++ /dev/null @@ -1,12 +0,0 @@ -SHAREDIR = $(DESTDIR)/usr/share/ipa -HTTPDIR = $(DESTDIR)/etc/httpd/conf.d/ - -all: ; - -install: - -mkdir -p $(SHAREDIR) - install -m 644 *.py $(SHAREDIR) - install -m 644 ipa.conf $(HTTPDIR) - -clean: - rm -f *~ *.pyc diff --git a/ipa-server/ipa-web/api/README b/ipa-server/ipa-web/api/README deleted file mode 100644 index e69de29bb..000000000 diff --git a/ipa-server/ipa-web/api/funcs.py b/ipa-server/ipa-web/api/funcs.py deleted file mode 100644 index b23a40c9b..000000000 --- a/ipa-server/ipa-web/api/funcs.py +++ /dev/null @@ -1,168 +0,0 @@ -# Authors: Rob Crittenden -# -# 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 ldap -import ipa -import ipa.dsinstance -import ipa.ipaldap -import pdb -import string -from types import * -import xmlrpclib - -# FIXME, this needs to be auto-discovered -host = 'localhost' -port = 389 -binddn = "cn=directory manager" -bindpw = "freeipa" - -basedn = "dc=greyoak,dc=com" -scope = ldap.SCOPE_SUBTREE - -def get_user (username): - """Get a specific user's entry. Return as a dict of values. - Multi-valued fields are represented as lists. - """ - ent="" - - # FIXME: Is this the filter we want or should it be more specific? - filter = "(uid=" username ")" - try: - m1 = ipa.ipaldap.IPAdmin(host,port,binddn,bindpw) - ent = m1.getEntry(basedn, scope, filter, None) - except ldap.LDAPError, e: - raise xmlrpclib.Fault(1, e) - except ipa.ipaldap.NoSuchEntryError: - raise xmlrpclib.Fault(2, "No such user") - - # Convert to LDIF - entry = str(ent) - - # Strip off any junk - entry = entry.strip() - - # Don't need to identify binary fields and this breaks the parser so - # remove double colons - entry = entry.replace('::', ':') - specs = [spec.split(':') for spec in entry.split('\n')] - - # Convert into a dict. We need to handle multi-valued attributes as well - # so we'll convert those into lists. - user={} - for (k,v) in specs: - k = k.lower() - if user.get(k) is not None: - if isinstance(user[k],list): - user[k].append(v.strip()) - else: - first = user[k] - user[k] = [] - user[k].append(first) - user[k].append(v.strip()) - else: - user[k] = v.strip() - - return user -# return str(ent) # return as LDIF - -def add_user (user): - """Add a user in LDAP""" - dn="uid=%s,ou=users,ou=default,dc=greyoak,dc=com" % user['uid'] - entry = ipa.ipaldap.Entry(dn) - - # some required objectclasses - entry.setValues('objectClass', 'top', 'posixAccount', 'shadowAccount', 'account', 'person', 'inetOrgPerson', 'organizationalPerson', 'krbPrincipalAux', 'krbTicketPolicyAux') - - # Fill in shadow fields - entry.setValue('shadowMin', '0') - entry.setValue('shadowMax', '99999') - entry.setValue('shadowWarning', '7') - entry.setValue('shadowExpire', '-1') - entry.setValue('shadowInactive', '-1') - entry.setValue('shadowFlag', '-1') - - # FIXME: calculate shadowLastChange - - # fill in our new entry with everything sent by the user - for u in user: - entry.setValues(u, user[u]) - - try: - m1 = ipa.ipaldap.IPAdmin(host,port,binddn,bindpw) - res = m1.addEntry(entry) - return res - except ldap.ALREADY_EXISTS: - raise xmlrpclib.Fault(3, "User already exists") - return None - except ldap.LDAPError, e: - raise xmlrpclib.Fault(1, str(e)) - return None - -def get_add_schema (): - """Get the list of fields to be used when adding users in the GUI.""" - - # FIXME: this needs to be pulled from LDAP - fields = [] - - field1 = { - "name": "uid" , - "label": "Login:", - "type": "text", - "validator": "text", - "required": "true" - } - fields.append(field1) - - field1 = { - "name": "userPassword" , - "label": "Password:", - "type": "password", - "validator": "String", - "required": "true" - } - fields.append(field1) - - field1 = { - "name": "gn" , - "label": "First name:", - "type": "text", - "validator": "string", - "required": "true" - } - fields.append(field1) - - field1 = { - "name": "sn" , - "label": "Last name:", - "type": "text", - "validator": "string", - "required": "true" - } - fields.append(field1) - - field1 = { - "name": "mail" , - "label": "E-mail address:", - "type": "text", - "validator": "email", - "required": "true" - } - fields.append(field1) - - return fields diff --git a/ipa-server/ipa-web/api/ipa.conf b/ipa-server/ipa-web/api/ipa.conf deleted file mode 100644 index 44c4d25ec..000000000 --- a/ipa-server/ipa-web/api/ipa.conf +++ /dev/null @@ -1,24 +0,0 @@ -# LoadModule auth_kerb_module modules/mod_auth_kerb.so - -Alias /ipa "/usr/share/ipa/XMLRPC" - - -# AuthType Kerberos -# AuthName "Kerberos Login" -# KrbMethodNegotiate on -# KrbMethodK5Passwd off -# KrbServiceName HTTP -# KrbAuthRealms GREYOAK.COM -# Krb5KeyTab /etc/httpd/conf/ipa.keytab -# KrbSaveCredentials on -# Require valid-user - ErrorDocument 401 /errors/unauthorized.html - - SetHandler mod_python - PythonHandler ipaxmlrpc - - PythonDebug Off - - # this is pointless to use since it would just reload ipaxmlrpc.py - PythonAutoReload Off - diff --git a/ipa-server/ipa-web/api/ipaxmlrpc.py b/ipa-server/ipa-web/api/ipaxmlrpc.py deleted file mode 100644 index 26cac39ae..000000000 --- a/ipa-server/ipa-web/api/ipaxmlrpc.py +++ /dev/null @@ -1,288 +0,0 @@ -# mod_python script - -# ipaxmlrpc - an XMLRPC interface for ipa. -# Copyright (c) 2007 Red Hat -# -# IPA 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; -# version 2.1 of the License. -# -# This software 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 software; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -# -# Based on kojixmlrpc - an XMLRPC interface for koji by -# Mike McLean -# -# Authors: -# Rob Crittenden - -import sys -import time -import traceback -import pprint -from xmlrpclib import Marshaller,loads,dumps,Fault -from mod_python import apache - -import ipa -import funcs -import string -import base64 - -# -# An override so we can base64 encode all outgoing values. -# This is set by calling: Marshaller._Marshaller__dump = xmlrpclib_dump -# -# Not currently used. -# -def xmlrpclib_escape(s, replace = string.replace): - """ - xmlrpclib only handles certain characters. Lets encode the whole - blob - """ - - return base64.encodestring(s) - -def xmlrpclib_dump(self, value, write): - """ - xmlrpclib cannot marshal instances of subclasses of built-in - types. This function overrides xmlrpclib.Marshaller.__dump so that - any value that is an instance of one of its acceptable types is - marshalled as that type. - - xmlrpclib also cannot handle invalid 7-bit control characters. See - above. - """ - - # Use our escape function - args = [self, value, write] - if isinstance(value, (str, unicode)): - args.append(xmlrpclib_escape) - - try: - # Try for an exact match first - f = self.dispatch[type(value)] - except KeyError: - # Try for an isinstance() match - for Type, f in self.dispatch.iteritems(): - if isinstance(value, Type): - f(*args) - return - raise TypeError, "cannot marshal %s objects" % type(value) - else: - f(*args) - - -class ModXMLRPCRequestHandler(object): - """Simple XML-RPC handler for mod_python environment""" - - def __init__(self): - self.funcs = {} - self.traceback = False - #introspection functions - self.register_function(self.list_api, name="_listapi") - self.register_function(self.system_listMethods, name="system.listMethods") - self.register_function(self.system_methodSignature, name="system.methodSignature") - self.register_function(self.system_methodHelp, name="system.methodHelp") - self.register_function(self.multiCall) - - def register_function(self, function, name = None): - if name is None: - name = function.__name__ - self.funcs[name] = function - - def register_module(self, instance, prefix=None): - """Register all the public functions in an instance with prefix prepended - - For example - h.register_module(exports,"pub.sys") - will register the methods of exports with names like - pub.sys.method1 - pub.sys.method2 - ...etc - """ - for name in dir(instance): - if name.startswith('_'): - continue - function = getattr(instance, name) - if not callable(function): - continue - if prefix is not None: - name = "%s.%s" %(prefix,name) - self.register_function(function, name=name) - - def register_instance(self,instance): - self.register_module(instance) - - def _marshaled_dispatch(self, data): - """Dispatches an XML-RPC method from marshalled (XML) data.""" - - params, method = loads(data) - - # special case -# if method == "get_user": -# Marshaller._Marshaller__dump = xmlrpclib_dump - - start = time.time() - # generate response - try: - response = self._dispatch(method, params) - # wrap response in a singleton tuple - response = (response,) - response = dumps(response, methodresponse=1, allow_none=1) - except Fault, fault: - self.traceback = True - response = dumps(fault) - except: - self.traceback = True - # report exception back to server - e_class, e = sys.exc_info()[:2] - faultCode = getattr(e_class,'faultCode',1) - tb_str = ''.join(traceback.format_exception(*sys.exc_info())) - faultString = tb_str - response = dumps(Fault(faultCode, faultString)) - - return response - - def _dispatch(self,method,params): - func = self.funcs.get(method,None) - if func is None: - raise Fault(1, "Invalid method: %s" % method) - params,opts = ipa.decode_args(*params) - - ret = func(*params,**opts) - - return ret - - def multiCall(self, calls): - """Execute a multicall. Execute each method call in the calls list, collecting - results and errors, and return those as a list.""" - results = [] - for call in calls: - try: - result = self._dispatch(call['methodName'], call['params']) - except Fault, fault: - results.append({'faultCode': fault.faultCode, 'faultString': fault.faultString}) - except: - # transform unknown exceptions into XML-RPC Faults - # don't create a reference to full traceback since this creates - # a circular reference. - exc_type, exc_value = sys.exc_info()[:2] - faultCode = getattr(exc_type, 'faultCode', 1) - faultString = ', '.join(exc_value.args) - trace = traceback.format_exception(*sys.exc_info()) - # traceback is not part of the multicall spec, but we include it for debugging purposes - results.append({'faultCode': faultCode, 'faultString': faultString, 'traceback': trace}) - else: - results.append([result]) - - return results - - def list_api(self): - funcs = [] - for name,func in self.funcs.items(): - #the keys in self.funcs determine the name of the method as seen over xmlrpc - #func.__name__ might differ (e.g. for dotted method names) - args = self._getFuncArgs(func) - funcs.append({'name': name, - 'doc': func.__doc__, - 'args': args}) - return funcs - - def _getFuncArgs(self, func): - args = [] - for x in range(0, func.func_code.co_argcount): - if x == 0 and func.func_code.co_varnames[x] == "self": - continue - if func.func_defaults and func.func_code.co_argcount - x <= len(func.func_defaults): - args.append((func.func_code.co_varnames[x], func.func_defaults[x - func.func_code.co_argcount len(func.func_defaults)])) - else: - args.append(func.func_code.co_varnames[x]) - return args - - def system_listMethods(self): - return self.funcs.keys() - - def system_methodSignature(self, method): - #it is not possible to autogenerate this data - return 'signatures not supported' - - def system_methodHelp(self, method): - func = self.funcs.get(method) - if func is None: - return "" - arglist = [] - for arg in self._getFuncArgs(func): - if isinstance(arg,str): - arglist.append(arg) - else: - arglist.append('%s=%s' % (arg[0], arg[1])) - ret = '%s(%s)' % (method, ", ".join(arglist)) - if func.__doc__: - ret = "\ndescription: %s" % func.__doc__ - return ret - - def handle_request(self,req): - """Handle a single XML-RPC request""" - - # 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.content_type = "text/xml" - req.set_content_length(len(response)) - req.write(response) - - -# -# mod_python handler -# - -def handler(req, profiling=False): - if profiling: - import profile, pstats, StringIO, tempfile - global _profiling_req - _profiling_req = req - temp = tempfile.NamedTemporaryFile() - profile.run("import ipxmlrpc; ipaxmlrpc.handler(ipaxmlrpc._profiling_req, False)", temp.name) - stats = pstats.Stats(temp.name) - strstream = StringIO.StringIO() - sys.stdout = strstream - stats.sort_stats("time") - stats.print_stats() - req.write("
"  strstream.getvalue()  "
") - _profiling_req = None - else: - opts = req.get_options() - try: - h = ModXMLRPCRequestHandler() - h.register_function(funcs.get_user) - h.register_function(funcs.add_user) - h.register_function(funcs.get_add_schema) - h.handle_request(req) - finally: - pass - return apache.OK -diff -r 0afcf345979d ipa-server/ipa-web/client/Makefile ---- a/dev/null Thu Jan 01 00:00:00 1970 0000 - b/ipa-server/ipa-web/client/Makefile Wed Jul 19 20:17:24 2007 -0400 -PYTHONLIBDIR ?= $(shell python -c "from distutils.sysconfig import *; print get_python_lib(1)") -PACKAGEDIR ?= $(DESTDIR)/$(PYTHONLIBDIR)/ipa - -all: ; - -install: - -mkdir -p $(PACKAGEDIR) - install -m 644 *.py $(PACKAGEDIR) - -clean: - rm -f *~ *.pyc diff --git a/ipa-server/ipa-web/client/Makefile b/ipa-server/ipa-web/client/Makefile deleted file mode 100644 index bc6554be4..000000000 --- a/ipa-server/ipa-web/client/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -PYTHONLIBDIR ?= $(shell python -c "from distutils.sysconfig import *; print get_python_lib(1)") -PACKAGEDIR ?= $(DESTDIR)/$(PYTHONLIBDIR)/ipa - -all: ; - -install: - -mkdir -p $(PACKAGEDIR) - install -m 644 *.py $(PACKAGEDIR) - -clean: - rm -f *~ *.pyc \ No newline at end of file diff --git a/ipa-server/ipa-web/client/README b/ipa-server/ipa-web/client/README deleted file mode 100644 index e69de29bb..000000000 diff --git a/ipa-server/ipa-web/client/ipaldap.py b/ipa-server/ipa-web/client/ipaldap.py deleted file mode 100644 index 50f88520c..000000000 --- a/ipa-server/ipa-web/client/ipaldap.py +++ /dev/null @@ -1,395 +0,0 @@ -#! /usr/bin/python -E -# Authors: Rich Megginson -# Rob Crittenden 0 - - def hasAttr(self,name): - """Return True if this entry has an attribute named name, False otherwise""" - return self.data and self.data.has_key(name) - - def __getattr__(self,name): - """If name is the name of an LDAP attribute, return the first value for that - attribute - equivalent to getValue - this allows the use of - entry.cn - instead of - entry.getValue('cn') - This also allows us to return None if an attribute is not found rather than - throwing an exception""" - return self.getValue(name) - - def getValues(self,name): - """Get the list (array) of values for the attribute named name""" - return self.data.get(name) - - def getValue(self,name): - """Get the first value for the attribute named name""" - return self.data.get(name,[None])[0] - - def setValue(self,name,*value): - """Value passed in may be a single value, several values, or a single sequence. - For example: - ent.setValue('name', 'value') - ent.setValue('name', 'value1', 'value2', ..., 'valueN') - ent.setValue('name', ['value1', 'value2', ..., 'valueN']) - ent.setValue('name', ('value1', 'value2', ..., 'valueN')) - Since *value is a tuple, we may have to extract a list or tuple from that - tuple as in the last two examples above""" - if isinstance(value[0],list) or isinstance(value[0],tuple): - self.data[name] = value[0] - else: - self.data[name] = value - - setValues = setValue - - def toTupleList(self): - """Convert the attrs and values to a list of 2-tuples. The first element - of the tuple is the attribute name. The second element is either a - single value or a list of values.""" - return self.data.items() - - def __str__(self): - """Convert the Entry to its LDIF representation""" - return self.__repr__() - - # the ldif class base64 encodes some attrs which I would rather see in raw form - to - # encode specific attrs as base64, add them to the list below - ldif.safe_string_re = re.compile('^$') - base64_attrs = ['nsstate', 'krbprincipalkey', 'krbExtraData'] - - def __repr__(self): - """Convert the Entry to its LDIF representation""" - sio = cStringIO.StringIO() - # what's all this then? the unparse method will currently only accept - # a list or a dict, not a class derived from them. self.data is a - # cidict, so unparse barfs on it. I've filed a bug against python-ldap, - # but in the meantime, we have to convert to a plain old dict for printing - # I also don't want to see wrapping, so set the line width really high (1000) - newdata = {} - newdata.update(self.data) - ldif.LDIFWriter(sio,Entry.base64_attrs,1000).unparse(self.dn,newdata) - return sio.getvalue() - -def wrapper(f,name): - """This is the method that wraps all of the methods of the superclass. This seems - to need to be an unbound method, that's why it's outside of IPAdmin. Perhaps there - is some way to do this with the new classmethod or staticmethod of 2.4. - Basically, we replace every call to a method in SimpleLDAPObject (the superclass - of IPAdmin) with a call to inner. The f argument to wrapper is the bound method - of IPAdmin (which is inherited from the superclass). Bound means that it will implicitly - be called with the self argument, it is not in the args list. name is the name of - the method to call. If name is a method that returns entry objects (e.g. result), - we wrap the data returned by an Entry class. If name is a method that takes an entry - argument, we extract the raw data from the entry object to pass in.""" - def inner(*args, **kargs): - if name == 'result': - type, data = f(*args, **kargs) - # data is either a 2-tuple or a list of 2-tuples - # print data - if data: - if isinstance(data,tuple): - return type, Entry(data) - elif isinstance(data,list): - return type, [Entry(x) for x in data] - else: - raise TypeError, "unknown data type %s returned by result" % type(data) - else: - return type, data - elif name.startswith('add'): - # the first arg is self - # the second and third arg are the dn and the data to send - # We need to convert the Entry into the format used by - # python-ldap - ent = args[0] - if isinstance(ent,Entry): - return f(ent.dn, ent.toTupleList(), *args[2:]) - else: - return f(*args, **kargs) - else: - return f(*args, **kargs) - return inner - -class IPAdmin(SimpleLDAPObject): - CFGSUFFIX = "o=NetscapeRoot" - DEFAULT_USER_ID = "nobody" - - 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() - self.errlog = ent.getValue('nsslapd-errorlog') - except (ldap.INSUFFICIENT_ACCESS, ldap.CONNECT_ERROR, NoSuchEntryError): - pass # usually means -# print "ignored exception" - except ldap.LDAPError, e: - print "caught exception ", e - raise - - def __localinit__(self): - SimpleLDAPObject.__init__(self,'ldap://%s:%d' % (self.host,self.port)) - # see if binddn is a dn or a uid that we need to lookup - if self.binddn and not IPAdmin.is_a_dn(self.binddn): - self.simple_bind("","") # anon - ent = self.getEntry(IPAdmin.CFGSUFFIX, ldap.SCOPE_SUBTREE, - "(uid=%s)" % self.binddn, - ['uid']) - if ent: - self.binddn = ent.dn - else: - print "Error: could not find %s under %s" % (self.binddn, IPAdmin.CFGSUFFIX) - self.simple_bind(self.binddn,self.bindpw) -# self.__initPart2() - - def __init__(self,host,port,binddn,bindpw): - """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 instance creation e.g. when - using the start command, we just need to reconnect, not create a new instance""" - self.__wrapmethods() - self.port = port or 389 - self.sslport = 0 - self.host = host - self.binddn = binddn - self.bindpw = bindpw - # see if is local or not - host1 = IPAdmin.getfqdn(host) - host2 = IPAdmin.getfqdn() - self.isLocal = (host1 == host2) - self.suffixes = {} - self.__localinit__() - - def __str__(self): - return self.host ":" str(self.port) - - def toLDAPURL(self): - return "ldap://%s:%d/" % (self.host,self.port) - - def getEntry(self,*args): - """This wraps the search function. It is common to just get one entry""" - res = self.search(*args) - type, obj = self.result(res) - if not obj: - raise NoSuchEntryError("no such entry for " str(args)) - elif isinstance(obj,Entry): - return obj - else: # assume list/tuple - return obj[0] - - def addEntry(self,*args): - """This wraps the add function. It assumes that the entry is already - populated with all of the desired objectclasses and attributes""" - try: - self.add_s(*args) - except ldap.ALREADY_EXISTS: - raise ldap.ALREADY_EXISTS - except ldap.LDAPError, e: - raise e - return "Success" - - def __wrapmethods(self): - """This wraps all methods of SimpleLDAPObject, so that we can intercept - the methods that deal with entries. Instead of using a raw list of tuples - of lists of hashes of arrays as the entry object, we want to wrap entries - in an Entry class that provides some useful methods""" - for name in dir(self.__class__.__bases__[0]): - attr = getattr(self, name) - if callable(attr): - setattr(self, name, wrapper(attr, name)) - - def exportLDIF(self, file, suffix, forrepl=False, verbose=False): - cn = "export" str(int(time.time())) - dn = "cn=%s, cn=export, cn=tasks, cn=config" % cn - entry = Entry(dn) - entry.setValues('objectclass', 'top', 'extensibleObject') - entry.setValues('cn', cn) - entry.setValues('nsFilename', file) - entry.setValues('nsIncludeSuffix', suffix) - if forrepl: - entry.setValues('nsExportReplica', 'true') - - rc = self.startTaskAndWait(entry, verbose) - - if rc: - if verbose: - print "Error: export task %s for file %s exited with %d" % (cn,file,rc) - else: - if verbose: - print "Export task %s for file %s completed successfully" % (cn,file) - return rc - - def waitForEntry(self, dn, timeout=7200, attr='', quiet=False): - scope = ldap.SCOPE_BASE - filter = "(objectclass=*)" - attrlist = [] - if attr: - filter = "(%s=*)" % attr - attrlist.append(attr) - timeout = int(time.time()) - - if isinstance(dn,Entry): - dn = dn.dn - - # wait for entry and/or attr to show up - if not quiet: - sys.stdout.write("Waiting for %s %s:%s " % (self,dn,attr)) - sys.stdout.flush() - entry = None - while not entry and int(time.time()) < timeout: - try: - entry = self.getEntry(dn, scope, filter, attrlist) - except NoSuchEntryError: pass # found entry, but no attr - except ldap.NO_SUCH_OBJECT: pass # no entry yet - except ldap.LDAPError, e: # badness - print "\nError reading entry", dn, e - break - if not entry: - if not quiet: - sys.stdout.write(".") - sys.stdout.flush() - time.sleep(1) - - if not entry and int(time.time()) > timeout: - print "\nwaitForEntry timeout for %s for %s" % (self,dn) - elif entry and not quiet: - print "\nThe waited for entry is:", entry - else: - print "\nError: could not read entry %s from %s" % (dn,self) - - return entry - - def addSchema(self, attr, val): - dn = "cn=schema" - self.modify_s(dn, [(ldap.MOD_ADD, attr, val)]) - - def addAttr(self, *args): - return self.addSchema('attributeTypes', args) - - def addObjClass(self, *args): - return self.addSchema('objectClasses', args) - - ########################### - # Static methods start here - ########################### - def normalizeDN(dn): - # not great, but will do until we use a newer version of python-ldap - # that has DN utilities - ary = ldap.explode_dn(dn.lower()) - return ",".join(ary) - normalizeDN = staticmethod(normalizeDN) - - def getfqdn(name=''): - return socket.getfqdn(name) - getfqdn = staticmethod(getfqdn) - - def getdomainname(name=''): - fqdn = IPAdmin.getfqdn(name) - index = fqdn.find('.') - if index >= 0: - return fqdn[index1:] - else: - return fqdn - getdomainname = staticmethod(getdomainname) - - def getdefaultsuffix(name=''): - dm = IPAdmin.getdomainname(name) - if dm: - return "dc=" dm.replace('.', ', dc=') - else: - return 'dc=localdomain' - getdefaultsuffix = staticmethod(getdefaultsuffix) - - def getnewhost(args): - """One of the arguments to createInstance is newhost. If this is specified, we need - to convert it to the fqdn. If not given, we need to figure out what the fqdn of the - local host is. This method sets newhost in args to the appropriate value and - returns True if newhost is the localhost, False otherwise""" - isLocal = False - if args.has_key('newhost'): - args['newhost'] = IPAdmin.getfqdn(args['newhost']) - myhost = IPAdmin.getfqdn() - if myhost == args['newhost']: - isLocal = True - elif args['newhost'] == 'localhost' or \ - args['newhost'] == 'localhost.localdomain': - isLocal = True - else: - isLocal = True - args['newhost'] = IPAdmin.getfqdn() - return isLocal - getnewhost = staticmethod(getnewhost) - - def is_a_dn(dn): - """Returns True if the given string is a DN, False otherwise.""" - return (dn.find("=") > 0) - is_a_dn = staticmethod(is_a_dn) diff --git a/ipa-server/ipa-web/client/rpcclient.py b/ipa-server/ipa-web/client/rpcclient.py deleted file mode 100644 index 416026628..000000000 --- a/ipa-server/ipa-web/client/rpcclient.py +++ /dev/null @@ -1,102 +0,0 @@ -#! /usr/bin/python -E -# Authors: Rob Crittenden -# -# 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 -# - -#!/usr/bin/python - -try: - import krbV -except ImportError: - pass -import xmlrpclib -import socket -import os -import base64 - -# Some errors to catch -# http://cvs.fedora.redhat.com/viewcvs/ldapserver/ldap/servers/plugins/pam_passthru/README?root=dirsec&rev=1.6&view=auto - -# FIXME: do we want this set somewhere else? -server = xmlrpclib.ServerProxy("http://localhost:80/ipa") - -def get_user(username): - """Get a specific user""" - - try: - result = server.get_user(username) - myuser = result - except xmlrpclib.Fault, fault: - raise xmlrpclib.Fault(fault.faultCode, fault.faultString) - return None - except socket.error, (value, msg): - raise xmlrpclib.Fault(value, msg) - return None - - return myuser - -def add_user(user): - """Add a new user""" - - # FIXME: Get the realm from somewhere - realm="GREYOAK.COM" - - # FIXME: This should be dynamic and can include just about anything - # Let us add in some missing attributes - if user.get('homeDirectory') is None: - user['homeDirectory'] ='/home/%s' % user['uid'] - if user.get('gecos') is None: - user['gecos'] = user['uid'] - - # FIXME: This can be removed once the DS plugin is installed - user['uidNumber'] ='501' - - # FIXME: What is the default group for users? - user['gidNumber'] ='501' - user['krbPrincipalName'] = "%s@%s" % (user['uid'], realm) - user['cn'] = "%s %s" % (user['gn'], user['sn']) - if user.get('gn'): - del user['gn'] - - try: - result = server.add_user(user) - return result - except xmlrpclib.Fault, fault: - raise xmlrpclib.Fault(fault.faultCode, fault.faultString) - return None - except socket.error, (value, msg): - raise xmlrpclib.Fault(value, msg) - return None - -def get_add_schema(): - """Get the list of attributes we need to ask when adding a new - user. - """ - - # FIXME: Hardcoded and designed for the TurboGears GUI. Do we want - # this for the CLI as well? - try: - result = server.get_add_schema() - except xmlrpclib.Fault, fault: - raise xmlrpclib.Fault(fault,faultCode, fault.faultString) - return None - except socket.error, (value, msg): - raise xmlrpclib.Fault(value, msg) - return None - - return result diff --git a/ipa-server/ipa-web/gui/README b/ipa-server/ipa-web/gui/README deleted file mode 100644 index e69de29bb..000000000 diff --git a/ipa-server/ipaserver/ipaldap.py b/ipa-server/ipaserver/ipaldap.py new file mode 100644 index 000000000..50f88520c --- /dev/null +++ b/ipa-server/ipaserver/ipaldap.py @@ -0,0 +1,395 @@ +#! /usr/bin/python -E +# Authors: Rich Megginson +# Rob Crittenden 0 + + def hasAttr(self,name): + """Return True if this entry has an attribute named name, False otherwise""" + return self.data and self.data.has_key(name) + + def __getattr__(self,name): + """If name is the name of an LDAP attribute, return the first value for that + attribute - equivalent to getValue - this allows the use of + entry.cn + instead of + entry.getValue('cn') + This also allows us to return None if an attribute is not found rather than + throwing an exception""" + return self.getValue(name) + + def getValues(self,name): + """Get the list (array) of values for the attribute named name""" + return self.data.get(name) + + def getValue(self,name): + """Get the first value for the attribute named name""" + return self.data.get(name,[None])[0] + + def setValue(self,name,*value): + """Value passed in may be a single value, several values, or a single sequence. + For example: + ent.setValue('name', 'value') + ent.setValue('name', 'value1', 'value2', ..., 'valueN') + ent.setValue('name', ['value1', 'value2', ..., 'valueN']) + ent.setValue('name', ('value1', 'value2', ..., 'valueN')) + Since *value is a tuple, we may have to extract a list or tuple from that + tuple as in the last two examples above""" + if isinstance(value[0],list) or isinstance(value[0],tuple): + self.data[name] = value[0] + else: + self.data[name] = value + + setValues = setValue + + def toTupleList(self): + """Convert the attrs and values to a list of 2-tuples. The first element + of the tuple is the attribute name. The second element is either a + single value or a list of values.""" + return self.data.items() + + def __str__(self): + """Convert the Entry to its LDIF representation""" + return self.__repr__() + + # the ldif class base64 encodes some attrs which I would rather see in raw form - to + # encode specific attrs as base64, add them to the list below + ldif.safe_string_re = re.compile('^$') + base64_attrs = ['nsstate', 'krbprincipalkey', 'krbExtraData'] + + def __repr__(self): + """Convert the Entry to its LDIF representation""" + sio = cStringIO.StringIO() + # what's all this then? the unparse method will currently only accept + # a list or a dict, not a class derived from them. self.data is a + # cidict, so unparse barfs on it. I've filed a bug against python-ldap, + # but in the meantime, we have to convert to a plain old dict for printing + # I also don't want to see wrapping, so set the line width really high (1000) + newdata = {} + newdata.update(self.data) + ldif.LDIFWriter(sio,Entry.base64_attrs,1000).unparse(self.dn,newdata) + return sio.getvalue() + +def wrapper(f,name): + """This is the method that wraps all of the methods of the superclass. This seems + to need to be an unbound method, that's why it's outside of IPAdmin. Perhaps there + is some way to do this with the new classmethod or staticmethod of 2.4. + Basically, we replace every call to a method in SimpleLDAPObject (the superclass + of IPAdmin) with a call to inner. The f argument to wrapper is the bound method + of IPAdmin (which is inherited from the superclass). Bound means that it will implicitly + be called with the self argument, it is not in the args list. name is the name of + the method to call. If name is a method that returns entry objects (e.g. result), + we wrap the data returned by an Entry class. If name is a method that takes an entry + argument, we extract the raw data from the entry object to pass in.""" + def inner(*args, **kargs): + if name == 'result': + type, data = f(*args, **kargs) + # data is either a 2-tuple or a list of 2-tuples + # print data + if data: + if isinstance(data,tuple): + return type, Entry(data) + elif isinstance(data,list): + return type, [Entry(x) for x in data] + else: + raise TypeError, "unknown data type %s returned by result" % type(data) + else: + return type, data + elif name.startswith('add'): + # the first arg is self + # the second and third arg are the dn and the data to send + # We need to convert the Entry into the format used by + # python-ldap + ent = args[0] + if isinstance(ent,Entry): + return f(ent.dn, ent.toTupleList(), *args[2:]) + else: + return f(*args, **kargs) + else: + return f(*args, **kargs) + return inner + +class IPAdmin(SimpleLDAPObject): + CFGSUFFIX = "o=NetscapeRoot" + DEFAULT_USER_ID = "nobody" + + 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() + self.errlog = ent.getValue('nsslapd-errorlog') + except (ldap.INSUFFICIENT_ACCESS, ldap.CONNECT_ERROR, NoSuchEntryError): + pass # usually means +# print "ignored exception" + except ldap.LDAPError, e: + print "caught exception ", e + raise + + def __localinit__(self): + SimpleLDAPObject.__init__(self,'ldap://%s:%d' % (self.host,self.port)) + # see if binddn is a dn or a uid that we need to lookup + if self.binddn and not IPAdmin.is_a_dn(self.binddn): + self.simple_bind("","") # anon + ent = self.getEntry(IPAdmin.CFGSUFFIX, ldap.SCOPE_SUBTREE, + "(uid=%s)" % self.binddn, + ['uid']) + if ent: + self.binddn = ent.dn + else: + print "Error: could not find %s under %s" % (self.binddn, IPAdmin.CFGSUFFIX) + self.simple_bind(self.binddn,self.bindpw) +# self.__initPart2() + + def __init__(self,host,port,binddn,bindpw): + """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 instance creation e.g. when + using the start command, we just need to reconnect, not create a new instance""" + self.__wrapmethods() + self.port = port or 389 + self.sslport = 0 + self.host = host + self.binddn = binddn + self.bindpw = bindpw + # see if is local or not + host1 = IPAdmin.getfqdn(host) + host2 = IPAdmin.getfqdn() + self.isLocal = (host1 == host2) + self.suffixes = {} + self.__localinit__() + + def __str__(self): + return self.host ":" str(self.port) + + def toLDAPURL(self): + return "ldap://%s:%d/" % (self.host,self.port) + + def getEntry(self,*args): + """This wraps the search function. It is common to just get one entry""" + res = self.search(*args) + type, obj = self.result(res) + if not obj: + raise NoSuchEntryError("no such entry for " str(args)) + elif isinstance(obj,Entry): + return obj + else: # assume list/tuple + return obj[0] + + def addEntry(self,*args): + """This wraps the add function. It assumes that the entry is already + populated with all of the desired objectclasses and attributes""" + try: + self.add_s(*args) + except ldap.ALREADY_EXISTS: + raise ldap.ALREADY_EXISTS + except ldap.LDAPError, e: + raise e + return "Success" + + def __wrapmethods(self): + """This wraps all methods of SimpleLDAPObject, so that we can intercept + the methods that deal with entries. Instead of using a raw list of tuples + of lists of hashes of arrays as the entry object, we want to wrap entries + in an Entry class that provides some useful methods""" + for name in dir(self.__class__.__bases__[0]): + attr = getattr(self, name) + if callable(attr): + setattr(self, name, wrapper(attr, name)) + + def exportLDIF(self, file, suffix, forrepl=False, verbose=False): + cn = "export" str(int(time.time())) + dn = "cn=%s, cn=export, cn=tasks, cn=config" % cn + entry = Entry(dn) + entry.setValues('objectclass', 'top', 'extensibleObject') + entry.setValues('cn', cn) + entry.setValues('nsFilename', file) + entry.setValues('nsIncludeSuffix', suffix) + if forrepl: + entry.setValues('nsExportReplica', 'true') + + rc = self.startTaskAndWait(entry, verbose) + + if rc: + if verbose: + print "Error: export task %s for file %s exited with %d" % (cn,file,rc) + else: + if verbose: + print "Export task %s for file %s completed successfully" % (cn,file) + return rc + + def waitForEntry(self, dn, timeout=7200, attr='', quiet=False): + scope = ldap.SCOPE_BASE + filter = "(objectclass=*)" + attrlist = [] + if attr: + filter = "(%s=*)" % attr + attrlist.append(attr) + timeout = int(time.time()) + + if isinstance(dn,Entry): + dn = dn.dn + + # wait for entry and/or attr to show up + if not quiet: + sys.stdout.write("Waiting for %s %s:%s " % (self,dn,attr)) + sys.stdout.flush() + entry = None + while not entry and int(time.time()) < timeout: + try: + entry = self.getEntry(dn, scope, filter, attrlist) + except NoSuchEntryError: pass # found entry, but no attr + except ldap.NO_SUCH_OBJECT: pass # no entry yet + except ldap.LDAPError, e: # badness + print "\nError reading entry", dn, e + break + if not entry: + if not quiet: + sys.stdout.write(".") + sys.stdout.flush() + time.sleep(1) + + if not entry and int(time.time()) > timeout: + print "\nwaitForEntry timeout for %s for %s" % (self,dn) + elif entry and not quiet: + print "\nThe waited for entry is:", entry + else: + print "\nError: could not read entry %s from %s" % (dn,self) + + return entry + + def addSchema(self, attr, val): + dn = "cn=schema" + self.modify_s(dn, [(ldap.MOD_ADD, attr, val)]) + + def addAttr(self, *args): + return self.addSchema('attributeTypes', args) + + def addObjClass(self, *args): + return self.addSchema('objectClasses', args) + + ########################### + # Static methods start here + ########################### + def normalizeDN(dn): + # not great, but will do until we use a newer version of python-ldap + # that has DN utilities + ary = ldap.explode_dn(dn.lower()) + return ",".join(ary) + normalizeDN = staticmethod(normalizeDN) + + def getfqdn(name=''): + return socket.getfqdn(name) + getfqdn = staticmethod(getfqdn) + + def getdomainname(name=''): + fqdn = IPAdmin.getfqdn(name) + index = fqdn.find('.') + if index >= 0: + return fqdn[index1:] + else: + return fqdn + getdomainname = staticmethod(getdomainname) + + def getdefaultsuffix(name=''): + dm = IPAdmin.getdomainname(name) + if dm: + return "dc=" dm.replace('.', ', dc=') + else: + return 'dc=localdomain' + getdefaultsuffix = staticmethod(getdefaultsuffix) + + def getnewhost(args): + """One of the arguments to createInstance is newhost. If this is specified, we need + to convert it to the fqdn. If not given, we need to figure out what the fqdn of the + local host is. This method sets newhost in args to the appropriate value and + returns True if newhost is the localhost, False otherwise""" + isLocal = False + if args.has_key('newhost'): + args['newhost'] = IPAdmin.getfqdn(args['newhost']) + myhost = IPAdmin.getfqdn() + if myhost == args['newhost']: + isLocal = True + elif args['newhost'] == 'localhost' or \ + args['newhost'] == 'localhost.localdomain': + isLocal = True + else: + isLocal = True + args['newhost'] = IPAdmin.getfqdn() + return isLocal + getnewhost = staticmethod(getnewhost) + + def is_a_dn(dn): + """Returns True if the given string is a DN, False otherwise.""" + return (dn.find("=") > 0) + is_a_dn = staticmethod(is_a_dn) diff --git a/ipa-server/xmlrpc-server/Makefile b/ipa-server/xmlrpc-server/Makefile new file mode 100644 index 000000000..10b796ea6 --- /dev/null +++ b/ipa-server/xmlrpc-server/Makefile @@ -0,0 +1,12 @@ +SHAREDIR = $(DESTDIR)/usr/share/ipa/ipaserver +HTTPDIR = $(DESTDIR)/etc/httpd/conf.d/ + +all: ; + +install: + -mkdir -p $(SHAREDIR) + install -m 644 *.py $(SHAREDIR) + install -m 644 ipa.conf $(HTTPDIR) + +clean: + rm -f *~ *.pyc diff --git a/ipa-server/xmlrpc-server/README b/ipa-server/xmlrpc-server/README new file mode 100644 index 000000000..e69de29bb diff --git a/ipa-server/xmlrpc-server/funcs.py b/ipa-server/xmlrpc-server/funcs.py new file mode 100644 index 000000000..78180a491 --- /dev/null +++ b/ipa-server/xmlrpc-server/funcs.py @@ -0,0 +1,170 @@ +# Authors: Rob Crittenden +# +# 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 +sys.path.append("/usr/share/ipa") + +import ldap +import ipaserver.dsinstance +import ipaserver.ipaldap +import pdb +import string +from types import * +import xmlrpclib + +# FIXME, this needs to be auto-discovered +host = 'localhost' +port = 389 +binddn = "cn=directory manager" +bindpw = "freeipa" + +basedn = "dc=greyoak,dc=com" +scope = ldap.SCOPE_SUBTREE + +def get_user (username): + """Get a specific user's entry. Return as a dict of values. + Multi-valued fields are represented as lists. + """ + ent="" + + # FIXME: Is this the filter we want or should it be more specific? + filter = "(uid=" username ")" + try: + m1 = ipaserver.ipaldap.IPAdmin(host,port,binddn,bindpw) + ent = m1.getEntry(basedn, scope, filter, None) + except ldap.LDAPError, e: + raise xmlrpclib.Fault(1, e) + except ipaserver.ipaldap.NoSuchEntryError: + raise xmlrpclib.Fault(2, "No such user") + + # Convert to LDIF + entry = str(ent) + + # Strip off any junk + entry = entry.strip() + + # Don't need to identify binary fields and this breaks the parser so + # remove double colons + entry = entry.replace('::', ':') + specs = [spec.split(':') for spec in entry.split('\n')] + + # Convert into a dict. We need to handle multi-valued attributes as well + # so we'll convert those into lists. + user={} + for (k,v) in specs: + k = k.lower() + if user.get(k) is not None: + if isinstance(user[k],list): + user[k].append(v.strip()) + else: + first = user[k] + user[k] = [] + user[k].append(first) + user[k].append(v.strip()) + else: + user[k] = v.strip() + + return user +# return str(ent) # return as LDIF + +def add_user (user): + """Add a user in LDAP""" + dn="uid=%s,ou=users,ou=default,dc=greyoak,dc=com" % user['uid'] + entry = ipaserver.ipaldap.Entry(dn) + + # some required objectclasses + entry.setValues('objectClass', 'top', 'posixAccount', 'shadowAccount', 'account', 'person', 'inetOrgPerson', 'organizationalPerson', 'krbPrincipalAux', 'krbTicketPolicyAux') + + # Fill in shadow fields + entry.setValue('shadowMin', '0') + entry.setValue('shadowMax', '99999') + entry.setValue('shadowWarning', '7') + entry.setValue('shadowExpire', '-1') + entry.setValue('shadowInactive', '-1') + entry.setValue('shadowFlag', '-1') + + # FIXME: calculate shadowLastChange + + # fill in our new entry with everything sent by the user + for u in user: + entry.setValues(u, user[u]) + + try: + m1 = ipaserver.ipaldap.IPAdmin(host,port,binddn,bindpw) + res = m1.addEntry(entry) + return res + except ldap.ALREADY_EXISTS: + raise xmlrpclib.Fault(3, "User already exists") + return None + except ldap.LDAPError, e: + raise xmlrpclib.Fault(1, str(e)) + return None + +def get_add_schema (): + """Get the list of fields to be used when adding users in the GUI.""" + + # FIXME: this needs to be pulled from LDAP + fields = [] + + field1 = { + "name": "uid" , + "label": "Login:", + "type": "text", + "validator": "text", + "required": "true" + } + fields.append(field1) + + field1 = { + "name": "userPassword" , + "label": "Password:", + "type": "password", + "validator": "String", + "required": "true" + } + fields.append(field1) + + field1 = { + "name": "gn" , + "label": "First name:", + "type": "text", + "validator": "string", + "required": "true" + } + fields.append(field1) + + field1 = { + "name": "sn" , + "label": "Last name:", + "type": "text", + "validator": "string", + "required": "true" + } + fields.append(field1) + + field1 = { + "name": "mail" , + "label": "E-mail address:", + "type": "text", + "validator": "email", + "required": "true" + } + fields.append(field1) + + return fields diff --git a/ipa-server/xmlrpc-server/ipa.conf b/ipa-server/xmlrpc-server/ipa.conf new file mode 100644 index 000000000..5a1304188 --- /dev/null +++ b/ipa-server/xmlrpc-server/ipa.conf @@ -0,0 +1,24 @@ +# LoadModule auth_kerb_module modules/mod_auth_kerb.so + +Alias /ipa "/usr/share/ipa/ipaserver/XMLRPC" + + +# AuthType Kerberos +# AuthName "Kerberos Login" +# KrbMethodNegotiate on +# KrbMethodK5Passwd off +# KrbServiceName HTTP +# KrbAuthRealms GREYOAK.COM +# Krb5KeyTab /etc/httpd/conf/ipa.keytab +# KrbSaveCredentials on +# Require valid-user + ErrorDocument 401 /errors/unauthorized.html + + SetHandler mod_python + PythonHandler ipaxmlrpc + + PythonDebug Off + + # this is pointless to use since it would just reload ipaxmlrpc.py + PythonAutoReload Off + diff --git a/ipa-server/xmlrpc-server/ipaxmlrpc.py b/ipa-server/xmlrpc-server/ipaxmlrpc.py new file mode 100644 index 000000000..1dc15956a --- /dev/null +++ b/ipa-server/xmlrpc-server/ipaxmlrpc.py @@ -0,0 +1,274 @@ +# mod_python script + +# ipaxmlrpc - an XMLRPC interface for ipa. +# Copyright (c) 2007 Red Hat +# +# IPA 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; +# version 2.1 of the License. +# +# This software 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 software; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Based on kojixmlrpc - an XMLRPC interface for koji by +# Mike McLean +# +# Authors: +# Rob Crittenden + +import sys +import time +import traceback +import pprint +from xmlrpclib import Marshaller,loads,dumps,Fault +from mod_python import apache + +import ipaserver +import funcs +import string +import base64 + +# +# An override so we can base64 encode all outgoing values. +# This is set by calling: Marshaller._Marshaller__dump = xmlrpclib_dump +# +# Not currently used. +# +def xmlrpclib_escape(s, replace = string.replace): + """ + xmlrpclib only handles certain characters. Lets encode the whole + blob + """ + + return base64.encodestring(s) + +def xmlrpclib_dump(self, value, write): + """ + xmlrpclib cannot marshal instances of subclasses of built-in + types. This function overrides xmlrpclib.Marshaller.__dump so that + any value that is an instance of one of its acceptable types is + marshalled as that type. + + xmlrpclib also cannot handle invalid 7-bit control characters. See + above. + """ + + # Use our escape function + args = [self, value, write] + if isinstance(value, (str, unicode)): + args.append(xmlrpclib_escape) + + try: + # Try for an exact match first + f = self.dispatch[type(value)] + except KeyError: + # Try for an isinstance() match + for Type, f in self.dispatch.iteritems(): + if isinstance(value, Type): + f(*args) + return + raise TypeError, "cannot marshal %s objects" % type(value) + else: + f(*args) + + +class ModXMLRPCRequestHandler(object): + """Simple XML-RPC handler for mod_python environment""" + + def __init__(self): + self.funcs = {} + self.traceback = False + #introspection functions + self.register_function(self.list_api, name="_listapi") + self.register_function(self.system_listMethods, name="system.listMethods") + self.register_function(self.system_methodSignature, name="system.methodSignature") + self.register_function(self.system_methodHelp, name="system.methodHelp") + self.register_function(self.multiCall) + + def register_function(self, function, name = None): + if name is None: + name = function.__name__ + self.funcs[name] = function + + def register_module(self, instance, prefix=None): + """Register all the public functions in an instance with prefix prepended + + For example + h.register_module(exports,"pub.sys") + will register the methods of exports with names like + pub.sys.method1 + pub.sys.method2 + ...etc + """ + for name in dir(instance): + if name.startswith('_'): + continue + function = getattr(instance, name) + if not callable(function): + continue + if prefix is not None: + name = "%s.%s" %(prefix,name) + self.register_function(function, name=name) + + def register_instance(self,instance): + self.register_module(instance) + + def _marshaled_dispatch(self, data): + """Dispatches an XML-RPC method from marshalled (XML) data.""" + + params, method = loads(data) + + # special case +# if method == "get_user": +# Marshaller._Marshaller__dump = xmlrpclib_dump + + start = time.time() + # generate response + try: + response = self._dispatch(method, params) + # wrap response in a singleton tuple + response = (response,) + response = dumps(response, methodresponse=1, allow_none=1) + except Fault, fault: + self.traceback = True + response = dumps(fault) + except: + self.traceback = True + # report exception back to server + e_class, e = sys.exc_info()[:2] + faultCode = getattr(e_class,'faultCode',1) + tb_str = ''.join(traceback.format_exception(*sys.exc_info())) + faultString = tb_str + response = dumps(Fault(faultCode, faultString)) + + return response + + def _dispatch(self,method,params): + func = self.funcs.get(method,None) + if func is None: + raise Fault(1, "Invalid method: %s" % method) + params,opts = ipaserver.decode_args(*params) + + ret = func(*params,**opts) + + return ret + + def multiCall(self, calls): + """Execute a multicall. Execute each method call in the calls list, collecting + results and errors, and return those as a list.""" + results = [] + for call in calls: + try: + result = self._dispatch(call['methodName'], call['params']) + except Fault, fault: + results.append({'faultCode': fault.faultCode, 'faultString': fault.faultString}) + except: + # transform unknown exceptions into XML-RPC Faults + # don't create a reference to full traceback since this creates + # a circular reference. + exc_type, exc_value = sys.exc_info()[:2] + faultCode = getattr(exc_type, 'faultCode', 1) + faultString = ', '.join(exc_value.args) + trace = traceback.format_exception(*sys.exc_info()) + # traceback is not part of the multicall spec, but we include it for debugging purposes + results.append({'faultCode': faultCode, 'faultString': faultString, 'traceback': trace}) + else: + results.append([result]) + + return results + + def list_api(self): + funcs = [] + for name,func in self.funcs.items(): + #the keys in self.funcs determine the name of the method as seen over xmlrpc + #func.__name__ might differ (e.g. for dotted method names) + args = self._getFuncArgs(func) + funcs.append({'name': name, + 'doc': func.__doc__, + 'args': args}) + return funcs + + def _getFuncArgs(self, func): + args = [] + for x in range(0, func.func_code.co_argcount): + if x == 0 and func.func_code.co_varnames[x] == "self": + continue + if func.func_defaults and func.func_code.co_argcount - x <= len(func.func_defaults): + args.append((func.func_code.co_varnames[x], func.func_defaults[x - func.func_code.co_argcount len(func.func_defaults)])) + else: + args.append(func.func_code.co_varnames[x]) + return args + + def system_listMethods(self): + return self.funcs.keys() + + def system_methodSignature(self, method): + #it is not possible to autogenerate this data + return 'signatures not supported' + + def system_methodHelp(self, method): + func = self.funcs.get(method) + if func is None: + return "" + arglist = [] + for arg in self._getFuncArgs(func): + if isinstance(arg,str): + arglist.append(arg) + else: + arglist.append('%s=%s' % (arg[0], arg[1])) + ret = '%s(%s)' % (method, ", ".join(arglist)) + if func.__doc__: + ret = "\ndescription: %s" % func.__doc__ + return ret + + def handle_request(self,req): + """Handle a single XML-RPC request""" + + # 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.content_type = "text/xml" + req.set_content_length(len(response)) + req.write(response) + + +# +# mod_python handler +# + +def handler(req, profiling=False): + if profiling: + import profile, pstats, StringIO, tempfile + global _profiling_req + _profiling_req = req + temp = tempfile.NamedTemporaryFile() + profile.run("import ipxmlrpc; ipaxmlrpc.handler(ipaxmlrpc._profiling_req, False)", temp.name) + stats = pstats.Stats(temp.name) + strstream = StringIO.StringIO() + sys.stdout = strstream + stats.sort_stats("time") + stats.print_stats() + req.write("
"  strstream.getvalue()  "
") + _profiling_req = None + else: + opts = req.get_options() + try: + h = ModXMLRPCRequestHandler() + h.register_function(funcs.get_user) + h.register_function(funcs.add_user) + h.register_function(funcs.get_add_schema) + h.handle_request(req) + finally: + pass + return apache.OK -- cgit