summaryrefslogtreecommitdiffstats
path: root/ipa-python
diff options
context:
space:
mode:
authorrcritten@redhat.com <rcritten@redhat.com>2007-08-06 10:05:53 -0400
committerrcritten@redhat.com <rcritten@redhat.com>2007-08-06 10:05:53 -0400
commit993f76fe6035cf59cceb88f3611fc53680738007 (patch)
tree17bb5afed002709bd322f5fe7e99e473adc1d018 /ipa-python
parent66ab69d0b23da46b21dbb4bf165011f318ec2da8 (diff)
downloadfreeipa-993f76fe6035cf59cceb88f3611fc53680738007.tar.gz
freeipa-993f76fe6035cf59cceb88f3611fc53680738007.tar.xz
freeipa-993f76fe6035cf59cceb88f3611fc53680738007.zip
- Abstracted client class to work directly or over RPC
- Add mod_auth_kerb and cyrus-sasl-gssapi to Requires - Remove references to admin server in ipa-server-setupssl - Generate a client certificate for the XML-RPC server to connect to LDAP with - Create a keytab for Apache - Create an ldif with a test user - Provide a certmap.conf for doing SSL client authentication - Update tools to use kerberos - Add User class
Diffstat (limited to 'ipa-python')
-rwxr-xr-xipa-python/freeipa-python.spec9
-rwxr-xr-xipa-python/freeipa-python.spec.in9
-rw-r--r--ipa-python/ipaclient.py87
-rw-r--r--ipa-python/krbtransport.py55
-rw-r--r--ipa-python/rpcclient.py148
-rw-r--r--ipa-python/user.py112
6 files changed, 352 insertions, 68 deletions
diff --git a/ipa-python/freeipa-python.spec b/ipa-python/freeipa-python.spec
index 8339cf74..e9b1e708 100755
--- a/ipa-python/freeipa-python.spec
+++ b/ipa-python/freeipa-python.spec
@@ -1,6 +1,6 @@
Name: freeipa-python
Version: 0.1.0
-Release: 1%{?dist}
+Release: 3%{?dist}
Summary: FreeIPA authentication server
Group: System Environment/Base
@@ -42,6 +42,13 @@ rm -rf %{buildroot}
%changelog
+* Mon Aug 5 2007 Rob Crittenden <rcritten@redhat.com> - 0.1.0-3
+- Abstracted client class to work directly or over RPC
+
+* Wed Aug 1 2007 Rob Crittenden <rcritten@redhat.com> - 0.1.0-2
+- Add User class
+- Add kerberos authentication to the XML-RPC request made from tools.
+
* Fri Jul 27 2007 Karl MacMillan <kmacmill@localhost.localdomain> - 0.1.0-1
- Initial rpm version
diff --git a/ipa-python/freeipa-python.spec.in b/ipa-python/freeipa-python.spec.in
index bec58819..b0a37308 100755
--- a/ipa-python/freeipa-python.spec.in
+++ b/ipa-python/freeipa-python.spec.in
@@ -1,6 +1,6 @@
Name: freeipa-python
Version: VERSION
-Release: 1%{?dist}
+Release: 3%{?dist}
Summary: FreeIPA authentication server
Group: System Environment/Base
@@ -42,6 +42,13 @@ rm -rf %{buildroot}
%changelog
+* Mon Aug 5 2007 Rob Crittenden <rcritten@redhat.com> - 0.1.0-3
+- Abstracted client class to work directly or over RPC
+
+* Wed Aug 1 2007 Rob Crittenden <rcritten@redhat.com> - 0.1.0-2
+- Add User class
+- Add kerberos authentication to the XML-RPC request made from tools.
+
* Fri Jul 27 2007 Karl MacMillan <kmacmill@localhost.localdomain> - 0.1.0-1
- Initial rpm version
diff --git a/ipa-python/ipaclient.py b/ipa-python/ipaclient.py
new file mode 100644
index 00000000..c75b5bc9
--- /dev/null
+++ b/ipa-python/ipaclient.py
@@ -0,0 +1,87 @@
+#! /usr/bin/python -E
+# Authors: Rob Crittenden <rcritten@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
+#
+
+#!/usr/bin/python
+
+import sys
+sys.path.append("/usr/share/ipa")
+
+from ipaserver import funcs
+import ipa.rpcclient as rpcclient
+import user
+import ipa
+import config
+
+class IPAClient:
+
+ def __init__(self,local=None):
+ self.local = local
+ ipa.config.init_config()
+ if local:
+ self.transport = funcs.IPAServer()
+ # client needs to call set_principal(user@REALM)
+ else:
+ self.transport = rpcclient.RPCClient()
+
+ def set_principal(self,princ):
+ if self.local:
+ self.transport.set_principal(princ)
+
+ def get_user(self,uid):
+ result = self.transport.get_user(uid)
+ return user.User(result)
+
+ def add_user(self,user):
+
+ realm = config.config.get_realm()
+
+ # 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['givenName'], user['sn'])
+ if user.get('gn'):
+ del user['gn']
+
+ result = self.transport.add_user(user)
+ return result
+
+ def get_all_users(self):
+ result = self.transport.get_all_users()
+
+ all_users = []
+ for (attrs) in result:
+ if attrs is not None:
+ all_users.append(user.User(attrs))
+
+ return all_users
+
+ def get_add_schema(self):
+ result = self.transport.get_add_schema()
+ return result
diff --git a/ipa-python/krbtransport.py b/ipa-python/krbtransport.py
new file mode 100644
index 00000000..dbb8ec34
--- /dev/null
+++ b/ipa-python/krbtransport.py
@@ -0,0 +1,55 @@
+#! /usr/bin/python -E
+# Authors: Rob Crittenden <rcritten@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
+#
+
+#!/usr/bin/python
+
+import httplib
+import xmlrpclib
+import kerberos
+from kerberos import GSSError
+
+class KerbTransport(xmlrpclib.Transport):
+ """Handles Kerberos Negotiation authentication to an XML-RPC server."""
+
+ def get_host_info(self, host):
+
+ host, extra_headers, x509 = xmlrpclib.Transport.get_host_info(self, host)
+
+ # Set the remote host principal
+ h = host
+ hostinfo = h.split(':')
+ service = "HTTP@" + hostinfo[0]
+
+ try:
+ rc, vc = kerberos.authGSSClientInit(service);
+ except kerberos.GSSError, e:
+ raise GSSError(e)
+
+ try:
+ kerberos.authGSSClientStep(vc, "");
+ except kerberos.GSSError, e:
+ raise GSSError(e)
+
+ extra_headers = [
+ ("Authorization", "negotiate %s" % kerberos.authGSSClientResponse(vc) )
+ ]
+
+ return host, extra_headers, x509
+
diff --git a/ipa-python/rpcclient.py b/ipa-python/rpcclient.py
index 8834a82f..d4e645e1 100644
--- a/ipa-python/rpcclient.py
+++ b/ipa-python/rpcclient.py
@@ -20,85 +20,101 @@
#!/usr/bin/python
-try:
- import krbV
-except ImportError:
- pass
import xmlrpclib
import socket
import config
+from krbtransport import KerbTransport
+from kerberos import GSSError
+import os
+import base64
+import user
+import ipa
# Some errors to catch
# http://cvs.fedora.redhat.com/viewcvs/ldapserver/ldap/servers/plugins/pam_passthru/README?root=dirsec&rev=1.6&view=auto
-def server_url():
- return "http://" + config.config.get_server() + "/ipa"
+class RPCClient:
-def setup_server():
- return xmlrpclib.ServerProxy(server_url())
+ def __init__(self):
+ ipa.config.init_config()
-def get_user(username):
- """Get a specific user"""
- server = setup_server()
- 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
+ def server_url(self):
+ return "http://" + config.config.get_server() + "/ipa"
- return myuser
+ def setup_server(self):
+ return xmlrpclib.ServerProxy(self.server_url(), KerbTransport())
-def add_user(user):
- """Add a new user"""
- server = setup_server()
-
- # FIXME: Get the realm from somewhere
- realm = config.config.get_realm()
-
- # 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'
+ def convert_entry(self,ent):
+ # Convert into a dict. We need to handle multi-valued attributes as well
+ # so we'll convert those into lists.
+ user={}
+ for (k) in ent:
+ k = k.lower()
+ if user.get(k) is not None:
+ if isinstance(user[k],list):
+ user[k].append(ent[k].strip())
+ else:
+ first = user[k]
+ user[k] = ()
+ user[k].append(first)
+ user[k].append(ent[k].strip())
+ else:
+ user[k] = ent[k]
+
+ return user
+
+ def get_user(self,username):
+ """Get a specific user"""
+ server = self.setup_server()
+ try:
+ result = server.get_user(username)
+ except xmlrpclib.Fault, fault:
+ raise xmlrpclib.Fault(fault.faultCode, fault.faultString)
+ except socket.error, (value, msg):
+ raise xmlrpclib.Fault(value, msg)
- # FIXME: What is the default group for users?
- user['gidNumber'] ='501'
- user['krbPrincipalName'] = "%s@%s" % (user['uid'], realm)
- user['cn'] = "%s %s" % (user['givenName'], user['sn'])
+ return result
+
+
+ def add_user(self,user):
+ """Add a new user"""
+ server = self.setup_server()
+
+ try:
+ result = server.add_user(user)
+ except xmlrpclib.Fault, fault:
+ raise xmlrpclib.Fault(fault.faultCode, fault.faultString)
+ except socket.error, (value, msg):
+ raise xmlrpclib.Fault(value, msg)
- 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(self):
+ """Get the list of attributes we need to ask when adding a new
+ user.
+ """
+ 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()
+ except xmlrpclib.Fault, fault:
+ raise xmlrpclib.Fault(fault.faultCode, fault.faultString)
+ except socket.error, (value, msg):
+ raise xmlrpclib.Fault(value, msg)
+
+ return result
-def get_add_schema():
- """Get the list of attributes we need to ask when adding a new
- user.
- """
- server = setup_server()
+ def get_all_users (self):
+ """Return a list containing a User object for each existing 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, fault.faultString)
- return None
- except socket.error, (value, msg):
- raise xmlrpclib.Fault(value, msg)
- return None
-
- return result
+ server = self.setup_server()
+ try:
+ result = server.get_all_users()
+ except xmlrpclib.Fault, fault:
+ raise xmlrpclib.Fault(fault.faultCode, fault.faultString)
+ except socket.error, (value, msg):
+ raise xmlrpclib.Fault(value, msg)
+
+ return result
diff --git a/ipa-python/user.py b/ipa-python/user.py
new file mode 100644
index 00000000..6162354b
--- /dev/null
+++ b/ipa-python/user.py
@@ -0,0 +1,112 @@
+import ldap
+import ldif
+import re
+import cStringIO
+
+class User:
+ """This class represents an IPA user. An LDAP entry consists of a DN
+ and a list of attributes. Each attribute consists of a name and a list of
+ values. For the time being I will maintain this.
+
+ In python-ldap, entries are returned as a list of 2-tuples.
+ Instance variables:
+ dn - string - the string DN of the entry
+ data - cidict - case insensitive dict of the attributes and values"""
+
+ def __init__(self,entrydata):
+ """data is the raw data returned from the python-ldap result method,
+ which is a search result entry or a reference or None.
+ If creating a new empty entry, data is the string DN."""
+ if entrydata:
+ if isinstance(entrydata,tuple):
+ self.dn = entrydata[0]
+ self.data = ldap.cidict.cidict(entrydata[1])
+ elif isinstance(entrydata,str):
+ self.dn = entrydata
+ self.data = ldap.cidict.cidict()
+ elif isinstance(entrydata,dict):
+ self.dn = entrydata['dn']
+ del entrydata['dn']
+ self.data = ldap.cidict.cidict(entrydata)
+ else:
+ self.dn = ''
+ self.data = ldap.cidict.cidict()
+
+ def __nonzero__(self):
+ """This allows us to do tests like if entry: returns false if there is no data,
+ true otherwise"""
+ return self.data != None and len(self.data) > 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"""
+ value = self.data.get(name,[None])
+ if isinstance(value[0],list) or isinstance(value[0],tuple):
+ return value[0]
+ else:
+ return value
+
+ 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 attrList(self):
+ """Return a list of all attributes in the entry"""
+ return self.data.keys()
+
+# 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,User.base64_attrs,1000).unparse(self.dn,newdata)
+# return sio.getvalue()