- 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:
1 parent
66ab69d0b2
commit
993f76fe60
26 files changed
+877
-322
No files matched your search
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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()
|
||||
Reference in new issue
Block a user