- 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
This commit is contained in:
rcritten@redhat.com committed 2007-08-06 10:05:53 -04:00
1 parent 66ab69d0b2
commit 993f76fe60
26 files changed
+877 -322

No files matched your search

+8 -1
View File
@@ -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
+8 -1
View File
@@ -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
+87
View File
@@ -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
+55
View File
@@ -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
+83 -67
View File
@@ -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()
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: 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'
# 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'])
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 add_user(self,user):
"""Add a new user"""
server = self.setup_server()
def get_add_schema():
"""Get the list of attributes we need to ask when adding a new
user.
"""
server = 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)
return result
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
# 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
def get_all_users (self):
"""Return a list containing a User object for each existing user."""
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
+112
View File
@@ -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()