diff options
Diffstat (limited to 'ipa-python')
-rwxr-xr-x | ipa-python/freeipa-python.spec | 9 | ||||
-rwxr-xr-x | ipa-python/freeipa-python.spec.in | 9 | ||||
-rw-r--r-- | ipa-python/ipaclient.py | 87 | ||||
-rw-r--r-- | ipa-python/krbtransport.py | 55 | ||||
-rw-r--r-- | ipa-python/rpcclient.py | 148 | ||||
-rw-r--r-- | ipa-python/user.py | 112 |
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() |