summaryrefslogtreecommitdiffstats
path: root/ipa-python
diff options
context:
space:
mode:
authorSimo Sorce <ssorce@redhat.com>2007-08-09 14:52:54 -0400
committerSimo Sorce <ssorce@redhat.com>2007-08-09 14:52:54 -0400
commita59c36b394dba5f8b371c1a6cdab0317a5507486 (patch)
tree91cbc96560da1e0aebe3b0dda64c8b3191495a0b /ipa-python
parent950bddf2a32b765ee97df5442a1ec9c0a358ccb3 (diff)
parentcf93b1bc642a843242cc9aaf585c08c5475ad632 (diff)
downloadfreeipa.git-a59c36b394dba5f8b371c1a6cdab0317a5507486.tar.gz
freeipa.git-a59c36b394dba5f8b371c1a6cdab0317a5507486.tar.xz
freeipa.git-a59c36b394dba5f8b371c1a6cdab0317a5507486.zip
merge in changes from upstream
Diffstat (limited to 'ipa-python')
-rw-r--r--ipa-python/Makefile8
-rw-r--r--ipa-python/config.py106
-rwxr-xr-xipa-python/freeipa-python.spec12
-rwxr-xr-xipa-python/freeipa-python.spec.in12
-rw-r--r--ipa-python/ipa.conf3
-rw-r--r--ipa-python/ipaclient.py87
-rw-r--r--ipa-python/krbtransport.py55
-rw-r--r--ipa-python/rpcclient.py146
-rw-r--r--ipa-python/user.py112
9 files changed, 474 insertions, 67 deletions
diff --git a/ipa-python/Makefile b/ipa-python/Makefile
index bc6554be..b2e4660f 100644
--- a/ipa-python/Makefile
+++ b/ipa-python/Makefile
@@ -1,11 +1,17 @@
PYTHONLIBDIR ?= $(shell python -c "from distutils.sysconfig import *; print get_python_lib(1)")
PACKAGEDIR ?= $(DESTDIR)/$(PYTHONLIBDIR)/ipa
+CONFIGDIR ?= $(DESTDIR)/etc/ipa
all: ;
install:
-mkdir -p $(PACKAGEDIR)
install -m 644 *.py $(PACKAGEDIR)
+ -mkdir -p $(CONFIGDIR)
+ if ! [ -e $(CONFIGDIR)/ipa.conf ]; then \
+ install -m 644 ipa.conf $(CONFIGDIR); \
+ fi
clean:
- rm -f *~ *.pyc \ No newline at end of file
+ rm -f *~ *.pyc
+
diff --git a/ipa-python/config.py b/ipa-python/config.py
new file mode 100644
index 00000000..a17e585b
--- /dev/null
+++ b/ipa-python/config.py
@@ -0,0 +1,106 @@
+# 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 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 ConfigParser
+from optparse import OptionParser
+
+class IPAConfigError(Exception):
+ def __init__(self, msg=''):
+ self.msg = msg
+ Exception.__init__(self, msg)
+
+ def __repr__(self):
+ return self.msg
+
+ __str__ = __repr__
+
+class IPAConfig:
+ def __init__(self):
+ self.default_realm = None
+ self.default_server = None
+
+ def get_realm(self):
+ if self.default_realm:
+ return self.default_realm
+ else:
+ raise IPAConfigError("no default realm")
+
+ def get_server(self):
+ if self.default_server:
+ return self.default_server
+ else:
+ raise IPAConfigError("no default server")
+
+# Global library config
+config = IPAConfig()
+
+def __parse_config():
+ p = ConfigParser.SafeConfigParser()
+ p.read("/etc/ipa/ipa.conf")
+
+ try:
+ config.default_realm = p.get("defaults", "realm")
+ config.default_server = p.get("defaults", "server")
+ except:
+ pass
+
+def usage():
+ return """ --realm\tset the IPA realm
+ --server\tset the IPA server
+"""
+
+def __parse_args(args):
+ # Can't use option parser because it doesn't easily leave
+ # unknown arguments - creating our own seems simpler.
+ #
+ # should make this more robust and handle --realm=foo syntax
+ out_args = []
+ i = 0
+ while i < len(args):
+ if args[i] == "--realm":
+ if i == len(args) - 1:
+ raise IPAConfigError("missing argument to --realm")
+ config.default_realm = args[i + 1]
+ i = i + 2
+ continue
+ if args[i] == "--server":
+ if i == len(args) - 1:
+ raise IPAConfigError("missing argument to --server")
+ config.default_server = args[i + 1]
+ i = i + 2
+ continue
+ out_args.append(args[i])
+ i = i + 1
+
+ return out_args
+
+
+def init_config(args=None):
+ __parse_config()
+ out_args = None
+ if args:
+ out_args = __parse_args(args)
+
+ if not config.default_realm:
+ raise IPAConfigError("realm not specified in config file or on command line")
+ if not config.default_server:
+ raise IPAConfigError("server not specified in config file or on command line")
+
+ if out_args:
+ return out_args
diff --git a/ipa-python/freeipa-python.spec b/ipa-python/freeipa-python.spec
index 61be3a5d..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
@@ -15,6 +15,7 @@ Requires: python
%{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}
%define pkgpythondir %{python_sitelib}/ipa
+%define configdir /etc/ipa
%description
FreeIPA is a server for identity, policy, and audit.
@@ -25,6 +26,7 @@ FreeIPA is a server for identity, policy, and audit.
%install
rm -rf %{buildroot}
mkdir -p %{buildroot}%{pkgpythondir}
+mkdir -p %{buildroot}%{configdir}
make install DESTDIR=%{buildroot}
@@ -36,9 +38,17 @@ rm -rf %{buildroot}
%files
%defattr(-,root,root,-)
%{pkgpythondir}/*
+%config(noreplace) %{configdir}/ipa.conf
%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 90a135b4..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
@@ -15,6 +15,7 @@ Requires: python
%{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}
%define pkgpythondir %{python_sitelib}/ipa
+%define configdir /etc/ipa
%description
FreeIPA is a server for identity, policy, and audit.
@@ -25,6 +26,7 @@ FreeIPA is a server for identity, policy, and audit.
%install
rm -rf %{buildroot}
mkdir -p %{buildroot}%{pkgpythondir}
+mkdir -p %{buildroot}%{configdir}
make install DESTDIR=%{buildroot}
@@ -36,9 +38,17 @@ rm -rf %{buildroot}
%files
%defattr(-,root,root,-)
%{pkgpythondir}/*
+%config(noreplace) %{configdir}/ipa.conf
%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/ipa.conf b/ipa-python/ipa.conf
new file mode 100644
index 00000000..5243cf22
--- /dev/null
+++ b/ipa-python/ipa.conf
@@ -0,0 +1,3 @@
+[defaults]
+realm = foo.bar
+server = realm.foo.bar
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 41602662..d4e645e1 100644
--- a/ipa-python/rpcclient.py
+++ b/ipa-python/rpcclient.py
@@ -20,83 +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
+
+class RPCClient:
+
+ def __init__(self):
+ ipa.config.init_config()
-# FIXME: do we want this set somewhere else?
-server = xmlrpclib.ServerProxy("http://localhost:80/ipa")
-
-def get_user(username):
- """Get a specific user"""
+ def server_url(self):
+ return "http://" + config.config.get_server() + "/ipa"
- 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 setup_server(self):
+ return xmlrpclib.ServerProxy(self.server_url(), KerbTransport())
- return myuser
+ 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]
-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'
+ 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['gn'], user['sn'])
- if user.get('gn'):
- del user['gn']
+ 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.
- """
+ 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,faultCode, 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()