From 3afd023c3aad0ade7ce391ba18dcd2c9c8e59426 Mon Sep 17 00:00:00 2001 From: Kevin McCarthy Date: Tue, 4 Sep 2007 13:44:59 -0700 Subject: Generalized Time parser and tests, for use in krbPasswordExpiration --- ipa-python/ipautil.py | 98 +++++++++++++++++++++++++++++++++++++++++ ipa-python/test/test_ipautil.py | 97 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 195 insertions(+) diff --git a/ipa-python/ipautil.py b/ipa-python/ipautil.py index f6d62f7a3..2989b4211 100644 --- a/ipa-python/ipautil.py +++ b/ipa-python/ipautil.py @@ -30,6 +30,7 @@ import stat from string import lower import re import xmlrpclib +import datetime def realm_to_suffix(realm_name): s = realm_name.split(".") @@ -233,3 +234,100 @@ def unwrap_binary_data(data): else: return data +class GeneralizedTimeZone(datetime.tzinfo): + """This class is a basic timezone wrapper for the offset specified + in a Generalized Time. It is dst-ignorant.""" + def __init__(self,offsetstr="Z"): + super(GeneralizedTimeZone, self).__init__() + + self.name = offsetstr + self.houroffset = 0 + self.minoffset = 0 + + if offsetstr == "Z": + self.houroffset = 0 + self.minoffset = 0 + else: + if (len(offsetstr) >= 3) and re.match(r'[-+]\d\d', offsetstr): + self.houroffset = int(offsetstr[0:3]) + offsetstr = offsetstr[3:] + if (len(offsetstr) >= 2) and re.match(r'\d\d', offsetstr): + self.minoffset = int(offsetstr[0:2]) + offsetstr = offsetstr[2:] + if len(offsetstr) > 0: + raise ValueError() + if self.houroffset < 0: + self.minoffset *= -1 + + def utcoffset(self, dt): + return datetime.timedelta(hours=self.houroffset, minutes=self.minoffset) + + def dst(self, dt): + return datetime.timedelta(0) + + def tzname(self, dt): + return self.name + + +def parse_generalized_time(timestr): + """Parses are Generalized Time string (as specified in X.680), + returning a datetime object. Generalized Times are stored inside + the krbPasswordExpiration attribute in LDAP. + + This method doesn't attempt to be perfect wrt timezones. If python + can't be bothered to implement them, how can we...""" + + if len(timestr) < 8: + return None + try: + date = timestr[:8] + time = timestr[8:] + + year = int(date[:4]) + month = int(date[4:6]) + day = int(date[6:8]) + + hour = min = sec = msec = 0 + tzone = None + + if (len(time) >= 2) and re.match(r'\d', time[0]): + hour = int(time[:2]) + time = time[2:] + if len(time) >= 2 and (time[0] == "," or time[0] == "."): + hour_fraction = "." + time = time[1:] + while (len(time) > 0) and re.match(r'\d', time[0]): + hour_fraction += time[0] + time = time[1:] + total_secs = int(float(hour_fraction) * 3600) + min, sec = divmod(total_secs, 60) + + if (len(time) >= 2) and re.match(r'\d', time[0]): + min = int(time[:2]) + time = time[2:] + if len(time) >= 2 and (time[0] == "," or time[0] == "."): + min_fraction = "." + time = time[1:] + while (len(time) > 0) and re.match(r'\d', time[0]): + min_fraction += time[0] + time = time[1:] + sec = int(float(min_fraction) * 60) + + if (len(time) >= 2) and re.match(r'\d', time[0]): + sec = int(time[:2]) + time = time[2:] + if len(time) >= 2 and (time[0] == "," or time[0] == "."): + sec_fraction = "." + time = time[1:] + while (len(time) > 0) and re.match(r'\d', time[0]): + sec_fraction += time[0] + time = time[1:] + msec = int(float(sec_fraction) * 1000000) + + if (len(time) > 0): + tzone = GeneralizedTimeZone(time) + + return datetime.datetime(year, month, day, hour, min, sec, msec, tzone) + + except ValueError: + return None diff --git a/ipa-python/test/test_ipautil.py b/ipa-python/test/test_ipautil.py index 54ff1dc26..2755f71ea 100644 --- a/ipa-python/test/test_ipautil.py +++ b/ipa-python/test/test_ipautil.py @@ -21,6 +21,7 @@ import sys sys.path.insert(0, ".") import unittest +import datetime import ipautil @@ -207,6 +208,102 @@ class TestCIDict(unittest.TestCase): self.assert_(item in items) items.discard(item) +class TestTimeParser(unittest.TestCase): + def setUp(self): + pass + + def tearDown(self): + pass + + def testSimple(self): + timestr = "20070803" + + time = ipautil.parse_generalized_time(timestr) + self.assertEqual(2007, time.year) + self.assertEqual(8, time.month) + self.assertEqual(3, time.day) + self.assertEqual(0, time.hour) + self.assertEqual(0, time.minute) + self.assertEqual(0, time.second) + + def testHourMinSec(self): + timestr = "20051213141205" + + time = ipautil.parse_generalized_time(timestr) + self.assertEqual(2005, time.year) + self.assertEqual(12, time.month) + self.assertEqual(13, time.day) + self.assertEqual(14, time.hour) + self.assertEqual(12, time.minute) + self.assertEqual(5, time.second) + + def testFractions(self): + timestr = "2003092208.5" + + time = ipautil.parse_generalized_time(timestr) + self.assertEqual(2003, time.year) + self.assertEqual(9, time.month) + self.assertEqual(22, time.day) + self.assertEqual(8, time.hour) + self.assertEqual(30, time.minute) + self.assertEqual(0, time.second) + + timestr = "199203301544,25" + + time = ipautil.parse_generalized_time(timestr) + self.assertEqual(1992, time.year) + self.assertEqual(3, time.month) + self.assertEqual(30, time.day) + self.assertEqual(15, time.hour) + self.assertEqual(44, time.minute) + self.assertEqual(15, time.second) + + timestr = "20060401185912,8" + + time = ipautil.parse_generalized_time(timestr) + self.assertEqual(2006, time.year) + self.assertEqual(4, time.month) + self.assertEqual(1, time.day) + self.assertEqual(18, time.hour) + self.assertEqual(59, time.minute) + self.assertEqual(12, time.second) + self.assertEqual(800000, time.microsecond) + + def testTimeZones(self): + timestr = "20051213141205Z" + + time = ipautil.parse_generalized_time(timestr) + self.assertEqual(0, time.tzinfo.houroffset) + self.assertEqual(0, time.tzinfo.minoffset) + offset = time.tzinfo.utcoffset(None) + self.assertEqual(0, offset.seconds) + + timestr = "20051213141205+0500" + + time = ipautil.parse_generalized_time(timestr) + self.assertEqual(5, time.tzinfo.houroffset) + self.assertEqual(0, time.tzinfo.minoffset) + offset = time.tzinfo.utcoffset(None) + self.assertEqual(5 * 60 * 60, offset.seconds) + + timestr = "20051213141205-0500" + + time = ipautil.parse_generalized_time(timestr) + self.assertEqual(-5, time.tzinfo.houroffset) + self.assertEqual(0, time.tzinfo.minoffset) + # NOTE - the offset is always positive - it's minutes + # _east_ of UTC + offset = time.tzinfo.utcoffset(None) + self.assertEqual((24 - 5) * 60 * 60, offset.seconds) + + timestr = "20051213141205-0930" + + time = ipautil.parse_generalized_time(timestr) + self.assertEqual(-9, time.tzinfo.houroffset) + self.assertEqual(-30, time.tzinfo.minoffset) + offset = time.tzinfo.utcoffset(None) + self.assertEqual(((24 - 9) * 60 * 60) - (30 * 60), offset.seconds) + if __name__ == '__main__': unittest.main() -- cgit From 82943c31de32b0388a7c23e6e6a18cc9c99ac502 Mon Sep 17 00:00:00 2001 From: Pete Rowley Date: Tue, 4 Sep 2007 15:39:53 -0700 Subject: Initial commit of dna plugin - origin: FDS with fix ups --- ipa-server/ipa-slapi-plugins/Makefile | 2 +- ipa-server/ipa-slapi-plugins/dna/Makefile | 34 + ipa-server/ipa-slapi-plugins/dna/dna-conf.ldif | 13 + ipa-server/ipa-slapi-plugins/dna/dna.c | 1174 ++++++++++++++++++++++++ 4 files changed, 1222 insertions(+), 1 deletion(-) create mode 100644 ipa-server/ipa-slapi-plugins/dna/Makefile create mode 100644 ipa-server/ipa-slapi-plugins/dna/dna-conf.ldif create mode 100644 ipa-server/ipa-slapi-plugins/dna/dna.c diff --git a/ipa-server/ipa-slapi-plugins/Makefile b/ipa-server/ipa-slapi-plugins/Makefile index a5d1c1913..23bcd94bd 100644 --- a/ipa-server/ipa-slapi-plugins/Makefile +++ b/ipa-server/ipa-slapi-plugins/Makefile @@ -1,4 +1,4 @@ -SUBDIRS=ipa-pwd-extop ipa-memberof +SUBDIRS=ipa-pwd-extop ipa-memberof dna all: @for subdir in $(SUBDIRS); do \ diff --git a/ipa-server/ipa-slapi-plugins/dna/Makefile b/ipa-server/ipa-slapi-plugins/dna/Makefile new file mode 100644 index 000000000..1bc0d23f8 --- /dev/null +++ b/ipa-server/ipa-slapi-plugins/dna/Makefile @@ -0,0 +1,34 @@ +DIRSRV ?= fedora-ds +PREFIX ?= $(DESTDIR)/usr +LIBDIR ?= $(PREFIX)/lib/$(DIRSRV)/plugins +LIB64DIR ?= $(PREFIX)/lib64/$(DIRSRV)/plugins +SHAREDIR = $(DESTDIR)/usr/share/ipa + +SONAME = libipa-dna-plugin.so +LDFLAGS += -llber +CFLAGS ?= -g -Wall -Wshadow +CFLAGS += -I/usr/include/$(DIRSRV) -I/usr/include/nss3 -I/usr/include/mozldap -I/usr/include/nspr4 -fPIC -DPIC + +OBJS = $(patsubst %.c,%.o,$(wildcard *.c)) + +all: $(OBJS) + $(CC) $(LDFLAGS) $(OBJS) -Wl,-soname -Wl,$(SONAME) -shared -o $(SONAME) + +%.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + +install: + -mkdir -p $(LIBDIR) + if [ -e $(PREFIX)/lib/$(DIRSRV) ]; then \ + install -m 644 $(SONAME) $(LIBDIR); \ + else \ + install -m 644 $(SONAME) $(LIB64DIR); \ + fi + install -m 644 *.ldif $(SHAREDIR) + +clean: + rm -f *.o + rm -f $(SONAME) + rm -f *~ + + diff --git a/ipa-server/ipa-slapi-plugins/dna/dna-conf.ldif b/ipa-server/ipa-slapi-plugins/dna/dna-conf.ldif new file mode 100644 index 000000000..a133fcf46 --- /dev/null +++ b/ipa-server/ipa-slapi-plugins/dna/dna-conf.ldif @@ -0,0 +1,13 @@ +dn: cn=ipa-dna,cn=plugins,cn=config +objectclass: top +objectclass: nsSlapdPlugin +objectclass: extensibleObject +cn: ipa-dna +nsslapd-pluginpath: libipa-dna-plugin +nsslapd-plugininitfunc: dna_init +nsslapd-plugintype: postoperation +nsslapd-pluginenabled: on +nsslapd-pluginid: ipa-dna +nsslapd-pluginversion: 1.0 +nsslapd-pluginvendor: Red Hat +nsslapd-plugindescription: Distributed numeric assignment plugin diff --git a/ipa-server/ipa-slapi-plugins/dna/dna.c b/ipa-server/ipa-slapi-plugins/dna/dna.c new file mode 100644 index 000000000..399f89f00 --- /dev/null +++ b/ipa-server/ipa-slapi-plugins/dna/dna.c @@ -0,0 +1,1174 @@ +/** BEGIN COPYRIGHT BLOCK + * 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 of the License. + * + * 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. + * + * In addition, as a special exception, Red Hat, Inc. gives You the additional + * right to link the code of this Program with code not covered under the GNU + * General Public License ("Non-GPL Code") and to distribute linked combinations + * including the two, subject to the limitations in this paragraph. Non-GPL Code + * permitted under this exception must only link to the code of this Program + * through those well defined interfaces identified in the file named EXCEPTION + * found in the source code files (the "Approved Interfaces"). The files of + * Non-GPL Code may instantiate templates or use macros or inline functions from + * the Approved Interfaces without causing the resulting work to be covered by + * the GNU General Public License. Only Red Hat, Inc. may make changes or + * additions to the list of Approved Interfaces. You must obey the GNU General + * Public License in all respects for all of the Program code and other code used + * in conjunction with the Program except the Non-GPL Code covered by this + * exception. If you modify this file, you may extend this exception to your + * version of the file, but you are not obligated to do so. If you do not wish to + * provide this exception without modification, you must delete this exception + * statement from your version and license this file solely under the GPL without + * exception. + * + * + * Author: Pete Rowley + * + * Copyright (C) 2007 Red Hat, Inc. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#ifdef HAVE_CONFIG_H +# include +#endif + + +/** + * Distributed Numeric Assignment plug-in + */ +#include "slapi-plugin.h" +#include +#include +#include +/*#include "portable.h"*/ +#include "nspr.h" +/*#include "slapi-private.h"*/ +/*#include "dirlite_strings.h"*/ +/*#include "dirver.h"*/ + +#include "prclist.h" +#include "ldif.h" + +/* get file mode flags for unix */ +#ifndef _WIN32 +#include +#endif + +#define DNA_PLUGIN_SUBSYSTEM "dna-plugin" +#define DNA_PLUGIN_VERSION 0x00010000 + +#define DNA_DN "cn=ipa-dna,cn=plugins,cn=config" /* temporary */ + +#define DNA_SUCCESS 0 +#define DNA_FAILURE -1 + +/** + * DNA config types + */ +#define DNA_TYPE "dnaType" +#define DNA_PREFIX "dnaPrefix" +#define DNA_NEXTVAL "dnaNextValue" +#define DNA_INTERVAL "dnaInterval" +#define DNA_GENERATE "dnaMagicRegen" +#define DNA_FILTER "dnaFilter" +#define DNA_SCOPE "dnaScope" + +#define FEATURE_DESC "Distributed Numeric Assignment" +#define PLUGIN_DESC "Distributed Numeric Assignment plugin" + +static Slapi_PluginDesc pdesc = { FEATURE_DESC, + "FreeIPA project", "FreeIPA/1.0", + PLUGIN_DESC }; + + +/** + * linked list of config entries + */ + +struct _defs { + PRCList list; + char *dn; + char *type; + char *prefix; + unsigned long nextval; + unsigned long interval; + struct slapi_filter *filter; + char *generate; + char *scope; +} dna_anchor; +typedef struct _defs configEntry; +static PRCList *config; +static PRRWLock *g_dna_cache_lock; + +static void *_PluginID = NULL; +static char *_PluginDN = NULL; + + +/* + * new value lock + */ +static Slapi_Mutex *g_new_value_lock; + +/** + * + * DNA plug-in management functions + * + */ +int dna_init(Slapi_PBlock *pb); +static int dna_start(Slapi_PBlock *pb); +static int dna_close(Slapi_PBlock *pb); +static int dna_postop_init(Slapi_PBlock *pb); + +/** + * + * Local operation functions + * + */ +static int loadPluginConfig(); +static int parseConfigEntry(Slapi_Entry *e); +static void deleteConfig(); +static void freeConfigEntry(configEntry **entry); + +/** + * + * helpers + * + */ +static char *dna_get_dn(Slapi_PBlock *pb); +static int dna_dn_is_config(char *dn); +static int dna_get_next_value(configEntry *config_entry, char **next_value_ret); + +/** + * + * the ops (where the real work is done) + * + */ +static int dna_config_check_post_op(Slapi_PBlock *pb); +static int dna_pre_op( Slapi_PBlock *pb, int modtype ); +static int dna_mod_pre_op( Slapi_PBlock *pb ); +static int dna_add_pre_op( Slapi_PBlock *pb ); + +/** + * debug functions - global, for the debugger + */ +void dnaDumpConfig(); +void dnaDumpConfigEntry(configEntry *); + +/** + * set the debug level + */ +#ifdef _WIN32 +int *module_ldap_debug = 0; + +void plugin_init_debug_level(int *level_ptr) +{ + module_ldap_debug = level_ptr; +} +#endif + +/** + * + * Deal with cache locking + * + */ +void dna_read_lock() +{ + PR_RWLock_Rlock(g_dna_cache_lock); +} + +void dna_write_lock() +{ + PR_RWLock_Wlock(g_dna_cache_lock); +} + +void dna_unlock() +{ + PR_RWLock_Unlock(g_dna_cache_lock); +} + +/** + * + * Get the dna plug-in version + * + */ +int dna_version() +{ + return DNA_PLUGIN_VERSION; +} + +/** + * Plugin identity mgmt + */ +void setPluginID(void * pluginID) +{ + _PluginID=pluginID; +} + +void * getPluginID() +{ + return _PluginID; +} + +void setPluginDN(char *pluginDN) +{ + _PluginDN = pluginDN; +} + +char * getPluginDN() +{ + return _PluginDN; +} + +/* + dna_init + ------------- + adds our callbacks to the list +*/ +int dna_init( Slapi_PBlock *pb ) +{ + int status = DNA_SUCCESS; + char * plugin_identity=NULL; + + slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "--> dna_init\n"); + + /** + * Store the plugin identity for later use. + * Used for internal operations + */ + + slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &plugin_identity); + PR_ASSERT (plugin_identity); + setPluginID(plugin_identity); + + if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, + SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN, + (void *) dna_start ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN, + (void *) dna_close ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, + (void *)&pdesc ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_MODIFY_FN, + (void *) dna_mod_pre_op ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_ADD_FN, + (void *) dna_add_pre_op ) != 0 || + /* the config change checking post op */ + slapi_register_plugin( + "postoperation", /* op type */ + 1, /* Enabled */ + "dna_init", /* this function desc */ + dna_postop_init, /* init func for post op */ + PLUGIN_DESC, /* plugin desc */ + NULL, /* ? */ + plugin_identity /* access control */ + ) + ) + { + slapi_log_error( SLAPI_LOG_FATAL, DNA_PLUGIN_SUBSYSTEM, + "dna_init: failed to register plugin\n" ); + status = DNA_FAILURE; + } + + slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "<-- dna_init\n"); + return status; +} + + +static int dna_postop_init(Slapi_PBlock *pb) +{ + int status = DNA_SUCCESS; + + if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, + SLAPI_PLUGIN_VERSION_01 ) != 0 || + slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, + (void *)&pdesc ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_POST_ADD_FN, + (void *) dna_config_check_post_op ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODRDN_FN, + (void *) dna_config_check_post_op ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_POST_DELETE_FN, + (void *) dna_config_check_post_op ) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODIFY_FN, + (void *) dna_config_check_post_op ) != 0 + ) + { + slapi_log_error( SLAPI_LOG_FATAL, DNA_PLUGIN_SUBSYSTEM, + "dna_postop_init: failed to register plugin\n" ); + status = DNA_FAILURE; + } + + return status; +} + +/* + dna_start + -------------- + Kicks off the config cache. + It is called after dna_init. +*/ +static int dna_start( Slapi_PBlock *pb ) +{ + char * plugindn = NULL; + + slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "--> dna_start\n"); + + config = &dna_anchor.list; + g_dna_cache_lock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "dna"); + g_new_value_lock = slapi_new_mutex(); + + if(!g_dna_cache_lock || !g_new_value_lock) + { + slapi_log_error( SLAPI_LOG_FATAL, DNA_PLUGIN_SUBSYSTEM, + "dna_start: lock creation failed\n" ); + + return DNA_FAILURE; + } + + /** + * Get the plug-in target dn from the system + * and store it for future use. This should avoid + * hardcoding of DN's in the code. + */ + slapi_pblock_get(pb, SLAPI_TARGET_DN, &plugindn); + if (plugindn == NULL || strlen(plugindn) == 0) + { + slapi_log_error( SLAPI_LOG_PLUGIN, DNA_PLUGIN_SUBSYSTEM , + "dna_start: had to use hard coded config dn\n"); + plugindn = DNA_DN; + } + else + { + slapi_log_error( SLAPI_LOG_PLUGIN, DNA_PLUGIN_SUBSYSTEM , + "dna_start: config at %s\n", plugindn); + + } + + setPluginDN(plugindn); + + /** + * Load the config for our plug-in + */ + PR_INIT_CLIST(config); + if (loadPluginConfig() != DNA_SUCCESS) + { + slapi_log_error( SLAPI_LOG_FATAL, DNA_PLUGIN_SUBSYSTEM, + "dna_start: unable to load plug-in configuration\n" ); + return DNA_FAILURE; + } + + slapi_log_error( SLAPI_LOG_PLUGIN, DNA_PLUGIN_SUBSYSTEM , "dna: ready for service\n"); + slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "<-- dna_start\n"); + + return DNA_SUCCESS; +} + +/* + dna_close + -------------- + closes down the cache +*/ +static int dna_close( Slapi_PBlock *pb ) +{ + slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "--> dna_close\n"); + + deleteConfig(); + + slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "<-- dna_close\n"); + + return DNA_SUCCESS; +} + +/* + * config looks like this + * - cn=myplugin + * --- ou=posix + * ------ cn=accounts + * ------ cn=groups + * --- cn=samba + * --- cn=etc + * ------ cn=etc etc + */ +static int loadPluginConfig() +{ + int status = DNA_SUCCESS; + int result; + int i; + Slapi_PBlock *search_pb; + Slapi_Entry **entries = NULL; + + slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "--> loadPluginConfig\n"); + + dna_write_lock(); + deleteConfig(); + + search_pb = slapi_pblock_new(); + + slapi_search_internal_set_pb(search_pb, DNA_DN, LDAP_SCOPE_SUBTREE, + "objectclass=*", NULL, 0, NULL, NULL, getPluginID(), 0); + slapi_search_internal_pb(search_pb); + slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_RESULT, &result); + + if (status != DNA_SUCCESS) + { + status = DNA_SUCCESS; + goto cleanup; + } + + slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + if (NULL == entries || entries[0] == NULL) + { + status = DNA_SUCCESS; + goto cleanup; + } + + for (i = 0; (entries[i] != NULL); i++) + { + status = parseConfigEntry(entries[i]); + } + +cleanup: + slapi_free_search_results_internal(search_pb); + slapi_pblock_destroy(search_pb); + dna_unlock(); + slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "<-- loadPluginConfig\n"); + + return status; +} + +static int parseConfigEntry(Slapi_Entry *e) +{ + char *value = NULL; + configEntry *entry = NULL; + configEntry *config_entry = NULL; + PRCList *list = NULL; + int entry_added = 0; + + slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "--> parseConfigEntry\n"); + + entry = (configEntry*) slapi_ch_calloc(1, sizeof(configEntry)); + if(0 == entry) + goto bail; + + value = slapi_entry_get_ndn(e); + if(value) { + entry->dn = strdup(value); + } + + slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM , "----------> dn [%s] \n",entry->dn,0,0); + + value = slapi_entry_attr_get_charptr(e, DNA_TYPE); + if(value) { + entry->type = value; + } + else + goto bail; + + slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM , "----------> dnaType [%s] \n",entry->type,0,0); + + value = slapi_entry_attr_get_charptr(e, DNA_NEXTVAL); + if (value) { + entry->nextval = strtoul(value,0,0); + slapi_ch_free_string(&value); + value = 0; + } + else + goto bail; + + slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM , "----------> dnaNextValue [%d] \n",entry->nextval,0,0); + + value = slapi_entry_attr_get_charptr(e, DNA_PREFIX); + if (value) { + entry->prefix = value; + } + + slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM , "----------> dnaPrefix [%s] \n",entry->prefix,0,0); + + value = slapi_entry_attr_get_charptr(e, DNA_INTERVAL); + if (value) { + entry->interval = strtoul(value,0,0); + slapi_ch_free_string(&value); + value = 0; + } + else + goto bail; + + slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM , "----------> dnaInterval [%s] \n",value,0,0); + + value = slapi_entry_attr_get_charptr(e, DNA_GENERATE); + if (value) { + entry->generate = value; + } + + slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM , "----------> dnaMagicRegen [%s] \n",entry->generate,0,0); + + value = slapi_entry_attr_get_charptr(e, DNA_FILTER); + if (value) { + entry->filter = slapi_str2filter(value); + } + else + goto bail; + + slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM , "----------> dnaFilter [%s] \n",value,0,0); + + slapi_ch_free_string(&value); + value = 0; + + value = slapi_entry_attr_get_charptr(e, DNA_SCOPE); + if (value) { + char *canonical_dn = slapi_dn_normalize(value); + entry->scope = canonical_dn; + } + + slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM , "----------> dnaScope [%s] \n",entry->scope,0,0); + + + /** + * Finally add the entry to the list + * we group by type then by filter + * and finally sort by dn length with longer dn's + * first - this allows the scope checking + * code to be simple and quick and + * cunningly linear + */ + if(!PR_CLIST_IS_EMPTY(config)) + { + list = PR_LIST_HEAD(config); + while(list != config) + { + config_entry = (configEntry*)list; + + if(slapi_attr_type_cmp(config_entry->type, entry->type,1)) + goto next; + + if(slapi_filter_compare(config_entry->filter, entry->filter)) + goto next; + + if(slapi_dn_issuffix(entry->scope,config_entry->scope)) + { + PR_INSERT_BEFORE(&(entry->list), list); + slapi_log_error( SLAPI_LOG_CONFIG, + DNA_PLUGIN_SUBSYSTEM , + "store [%s] before [%s] \n",entry->scope,config_entry->scope,0); + entry_added = 1; + break; + } + +next: + list = PR_NEXT_LINK (list); + + if(config == list) + { + /* add to tail */ + PR_INSERT_BEFORE(&(entry->list), list); + slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM , "store [%s] at tail\n",entry->scope,0,0); + entry_added = 1; + break; + } + } + } + else + { + /* first entry */ + PR_INSERT_LINK(&(entry->list), config); + slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM , "store [%s] at head \n",entry->scope,0,0); + entry_added = 1; + } + +bail: + if(0 == entry_added) + { + slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM , + "config entry [%s] skipped\n",entry->dn,0,0); + freeConfigEntry(&entry); + } + + slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "<-- parseConfigEntry\n"); + + return DNA_SUCCESS; +} + +static void freeConfigEntry(configEntry **entry) +{ + configEntry *e = *entry; + + if(e->dn) + { + slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM , + "freeing config entry [%s]\n",e->dn,0,0); + slapi_ch_free_string(&e->dn); + } + + if(e->type) + slapi_ch_free_string(&e->type); + + if(e->prefix) + slapi_ch_free_string(&e->prefix); + + if(e->filter) + slapi_filter_free(e->filter,1); + + if(e->generate) + slapi_ch_free_string(&e->generate); + + if(e->scope) + slapi_ch_free_string(&e->scope); + + slapi_ch_free((void**)entry); +} + +static void deleteConfigEntry(PRCList *entry) +{ + PR_REMOVE_LINK(entry); + freeConfigEntry((configEntry**)&entry); +} + +static void deleteConfig() +{ + PRCList *list; + + while(!PR_CLIST_IS_EMPTY(config)) + { + list = PR_LIST_HEAD(config); + deleteConfigEntry(list); + } + + return; +} + + +/**************************************************** + Helpers +****************************************************/ + +static char *dna_get_dn(Slapi_PBlock *pb) +{ + char *dn = 0; + slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "--> dna_get_dn\n"); + + if(slapi_pblock_get( pb, SLAPI_TARGET_DN, &dn )) + { + slapi_log_error( SLAPI_LOG_FATAL, DNA_PLUGIN_SUBSYSTEM, "dna_get_dn: failed to get dn of changed entry"); + goto bail; + } + +/* slapi_dn_normalize( dn ); +*/ +bail: + slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "<-- dna_get_dn\n"); + + return dn; +} + +/* config check + matching config dn or a descendent reloads config +*/ +static int dna_dn_is_config(char *dn) +{ + int ret = 0; + + slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "--> dna_is_config\n"); + + if(slapi_dn_issuffix(dn, getPluginDN())) + { + ret=1; + } + + slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "<-- dna_is_config\n"); + + return ret; +} + + +/**************************************************** + Functions that actually do things other + than config and startup +****************************************************/ + + +/* + * Perform ldap operationally atomic increment + * Return the next value to be assigned + * Method: + * 1. retrieve entry + * 2. remove current value, add new value in one operation + * 3. if failed, and less than 3 times, goto 1 + */ +static int dna_get_next_value(configEntry *config_entry, char **next_value_ret) +{ + int ret = LDAP_SUCCESS; + Slapi_DN *dn = 0; + char *attrlist[3]; + Slapi_Entry *e = 0; + int attempts = 0; + + slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "--> dna_get_next_value\n"); + + /* get pre-requisites to search */ + dn = slapi_sdn_new_dn_byref(config_entry->dn); + attrlist[0] = DNA_NEXTVAL; + attrlist[1] = DNA_INTERVAL; + attrlist[2] = 0; + + + /* the operation is constructed such that race conditions + * to increment the value are detected and avoided - one wins, + * one loses - however, there is no need for the server to compete + * with itself so we lock here + */ + + slapi_lock_mutex(g_new_value_lock); + + while(attempts < 3 && LDAP_SUCCESS == ret) + { + attempts++; + + /* do update */ + if(e) + { + slapi_entry_free(e); + e = 0; + } + + ret = slapi_search_internal_get_entry( dn, attrlist, &e,getPluginID()); + if(LDAP_SUCCESS == ret) + { + char *old_value; + + old_value = slapi_entry_attr_get_charptr(e, DNA_NEXTVAL); + if(old_value) + { + LDAPMod mod_add; + LDAPMod mod_delete; + LDAPMod *mods[3]; + Slapi_PBlock *pb = slapi_pblock_new(); + char *delete_val[2]; + char *add_val[2]; + char new_value[16]; + char *interval = 0; + + mods[0] = &mod_delete; + mods[1] = &mod_add; + mods[2] = 0; + + if(0 == pb) + goto bail; + + interval = slapi_entry_attr_get_charptr(e, DNA_INTERVAL); + if(0 == interval) + { + slapi_pblock_destroy(pb); + slapi_ch_free_string(&old_value); + goto bail; + } + + /* perform increment */ + + sprintf(new_value, "%lu", + strtoul(interval,0,0) + + strtoul(old_value,0,0)); + + delete_val[0] = old_value; + delete_val[1] = 0; + + mod_delete.mod_op = LDAP_MOD_DELETE; + mod_delete.mod_type = DNA_NEXTVAL; + mod_delete.mod_values = delete_val; + + add_val[0] = new_value; + add_val[1] = 0; + + mod_add.mod_op = LDAP_MOD_ADD; + mod_add.mod_type = DNA_NEXTVAL; + mod_add.mod_values = add_val; + + + mods[0] = &mod_delete; + mods[1] = &mod_add; + mods[2] = 0; + + slapi_modify_internal_set_pb( + pb, config_entry->dn, + mods, 0, 0, + getPluginID(), 0); + + slapi_modify_internal_pb(pb); + + slapi_pblock_get(pb, + SLAPI_PLUGIN_INTOP_RESULT, + &ret); + + slapi_pblock_destroy(pb); + slapi_ch_free_string(&interval); + + if(LDAP_SUCCESS == ret) + { + *next_value_ret = old_value; + break; + } + else + { + slapi_ch_free_string(&old_value); + if(LDAP_NO_SUCH_ATTRIBUTE != ret) + { + /* not the result of a race + to change the value + */ + break; + } + else + /* we lost the race to mod + try again + */ + ret = LDAP_SUCCESS; + } + } + else + break; + } + else + break; + } + +bail: + + slapi_unlock_mutex(g_new_value_lock); + + if(dn) + slapi_sdn_free(&dn); + + if(e) + slapi_entry_free(e); + + slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "<-- dna_get_next_value\n"); + + return ret; +} + +/* for mods and adds: + where dn's are supplied, the closest in scope + is used as long as the type and filter + are identical - otherwise all matches count +*/ + +static int dna_pre_op(Slapi_PBlock *pb, int modtype) +{ + char *dn = 0; + PRCList *list = 0; + configEntry *config_entry = 0; + struct slapi_entry *e = 0; + char *last_type = 0; + char *value = 0; + int generate = 0; + Slapi_Mods *smods = 0; + Slapi_Mod *smod = 0; + LDAPMod **mods; + int free_entry = 0; + int ret = 0; + + slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "--> dna_pre_op\n"); + + if(0 == (dn = dna_get_dn(pb))) + goto bail; + + if(dna_dn_is_config(dn)) + goto bail; + + if(LDAP_CHANGETYPE_ADD == modtype) + { + slapi_pblock_get( pb, SLAPI_ADD_ENTRY, &e); + } + else + { + /* xxxPAR: Ideally SLAPI_MODIFY_EXISTING_ENTRY should be + * available but it turns out that is only true if you are + * a dbm backend pre-op plugin - lucky dbm backend pre-op + * plugins. + * I think that is wrong since the entry is useful for filter + * tests and schema checks and this plugin shouldn't be limited + * to a single backend type, but I don't want that fight right + * now so we go get the entry here + * + slapi_pblock_get( pb, SLAPI_MODIFY_EXISTING_ENTRY, &e); + */ + Slapi_DN *tmp_dn = slapi_sdn_new_dn_byref(dn); + if(tmp_dn) + { + slapi_search_internal_get_entry( + tmp_dn, 0, &e,getPluginID()); + slapi_sdn_free(&tmp_dn); + free_entry = 1; + } + + /* grab the mods - we'll put them back later with + * our modifications appended + */ + slapi_pblock_get( pb, SLAPI_MODIFY_MODS, &mods); + smods = slapi_mods_new(); + slapi_mods_init_passin(smods, mods); + } + + if(0 == e) + goto bailmod; + + dna_read_lock(); + + if(!PR_CLIST_IS_EMPTY(config)) + { + list = PR_LIST_HEAD(config); + + while(list != config && LDAP_SUCCESS == ret) + { + config_entry = (configEntry*)list; + + /* did we already service this type? */ + if(last_type) + { + if(!slapi_attr_type_cmp(config_entry->type, last_type,1)) + goto next; + } + + /* is the entry in scope? */ + if(config_entry->scope) + { + if(!slapi_dn_issuffix(dn, config_entry->scope)) + goto next; + } + + /* does the entry match the filter? */ + if(config_entry->filter) + { + if(LDAP_SUCCESS != slapi_vattr_filter_test(pb, + e, + config_entry->filter,0)) + goto next; + } + + + if(LDAP_CHANGETYPE_ADD == modtype) + { + /* does attribute contain the magic value + or is the type not there? + */ + value = slapi_entry_attr_get_charptr( + e, config_entry->type); + if((value && + !slapi_UTF8CASECMP( + config_entry->generate, + value)) || + 0 == value) + { + generate = 1; + } + } + else + { + /* check mods for magic value */ + Slapi_Mod *next_mod = slapi_mod_new(); + smod = slapi_mods_get_first_smod( + smods, + next_mod); + while(smod) + { + char *type = (char *) + slapi_mod_get_type(smod); + + if(slapi_attr_types_equivalent( + type, + config_entry->type)) + { + struct berval *bv = + slapi_mod_get_first_value( + smod); + int len = strlen( + config_entry-> + generate); + + + if(len == bv->bv_len) + { + if(!slapi_UTF8NCASECMP( + bv->bv_val, + config_entry-> + generate, + len)) + + generate = 1; + break; + } + } + + slapi_mod_done(next_mod); + smod = slapi_mods_get_next_smod( + smods, + next_mod); + } + + slapi_mod_free(&next_mod); + } + + if(generate) + { + char *new_value; + int len; + + /* create the value to add */ + if((ret = dna_get_next_value(config_entry,&value))) + break; + + len = strlen(value) + 1; + if(config_entry->prefix) + { + len += strlen(config_entry->prefix); + } + + new_value = slapi_ch_malloc(len); + + if(config_entry->prefix) + { + strcpy(new_value, + config_entry->prefix); + strcat(new_value, value); + } + else + strcpy(new_value, value); + + /* do the mod */ + if(LDAP_CHANGETYPE_ADD == modtype) + { + /* add - add to entry */ + slapi_entry_attr_set_charptr( + e, + config_entry->type, + new_value); + } + else + { + /* mod - add to mods */ + slapi_mods_add_string( + smods, + LDAP_MOD_REPLACE, + config_entry->type, + new_value); + } + + /* free up */ + slapi_ch_free_string(&value); + slapi_ch_free_string(&new_value); + + /* make sure we don't generate for this + * type again + */ + if(LDAP_SUCCESS == ret) + { + last_type = config_entry->type; + } + + generate = 0; + } +next: + list = PR_NEXT_LINK (list); + } + } + + dna_unlock(); + +bailmod: + if(LDAP_CHANGETYPE_MODIFY == modtype) + { + /* these are the mods you made, really, + * I didn't change them, honest, just had a quick look + */ + mods = slapi_mods_get_ldapmods_passout(smods); + slapi_pblock_set( pb, SLAPI_MODIFY_MODS, mods); + slapi_mods_free(&smods); + } + +bail: + + if(free_entry && e) + slapi_entry_free(e); + + if(ret) + slapi_log_error( SLAPI_LOG_PLUGIN, DNA_PLUGIN_SUBSYSTEM , "dna_pre_op: operation failure [%d]\n", ret); + + slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "<-- dna_pre_op\n"); + + return ret; +} + + +static int dna_add_pre_op( Slapi_PBlock *pb ) +{ + return dna_pre_op(pb, LDAP_CHANGETYPE_ADD); +} + +static int dna_mod_pre_op( Slapi_PBlock *pb ) +{ + return dna_pre_op(pb, LDAP_CHANGETYPE_MODIFY); +} + +static int dna_config_check_post_op(Slapi_PBlock *pb) +{ + char *dn; + + slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "--> dna_config_check_post_op\n"); + + if((dn = dna_get_dn(pb))) + { + if(dna_dn_is_config(dn)) + loadPluginConfig(); + } + + slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "<-- dna_config_check_post_op\n"); + + return 0; +} + +/**************************************************** + End of + Functions that actually do things other + than config and startup +****************************************************/ + +/** + * debug functions to print config + */ +void dnaDumpConfig() +{ + PRCList *list; + + dna_read_lock(); + + if(!PR_CLIST_IS_EMPTY(config)) + { + list = PR_LIST_HEAD(config); + while(list != config) + { + dnaDumpConfigEntry((configEntry*)list); + list = PR_NEXT_LINK (list); + } + } + + dna_unlock(); +} + + +void dnaDumpConfigEntry(configEntry *entry) +{ + printf("<- type --------------> %s\n", entry->type); + printf("<---- prefix ---------> %s\n", entry->prefix); + printf("<---- next value -----> %lu\n", entry->nextval); + printf("<---- interval -------> %lu\n", entry->interval); + printf("<---- generate flag --> %s\n", entry->generate); +} + + -- cgit From 9b30f4674465b8e5f9bfcb359a9a9336dec0d120 Mon Sep 17 00:00:00 2001 From: "rcritten@redhat.com" Date: Wed, 5 Sep 2007 13:14:23 -0400 Subject: Enable LDAP SASL authentication using a forwarded kerberos ticket Handle both SASL auth and proxied authentication Refactor LDAP connection code to be simpler Other small bug fixes --- ipa-admintools/ipa-finduser | 4 +- ipa-admintools/ipa-usermod | 6 + ipa-server/ipaserver/ipaldap.py | 55 +++++--- ipa-server/xmlrpc-server/funcs.py | 237 +++++++++++++++++----------------- ipa-server/xmlrpc-server/ipaxmlrpc.py | 12 +- 5 files changed, 176 insertions(+), 138 deletions(-) diff --git a/ipa-admintools/ipa-finduser b/ipa-admintools/ipa-finduser index 167ac23d7..409d2e3de 100644 --- a/ipa-admintools/ipa-finduser +++ b/ipa-admintools/ipa-finduser @@ -50,7 +50,9 @@ def main(): client = ipaclient.IPAClient() users = client.find_users(args[1], sattrs=['dn','uid','cn','homeDirectory']) - if len(users) == 0: + counter = users[0] + users = users[1:] + if counter == 0: print "No entries found for", args[1] return 0 diff --git a/ipa-admintools/ipa-usermod b/ipa-admintools/ipa-usermod index 0c61f4097..317289a60 100644 --- a/ipa-admintools/ipa-usermod +++ b/ipa-admintools/ipa-usermod @@ -59,6 +59,9 @@ def main(): except ipa.ipaerror.IPAError, e: print "%s" % e.message return 1 + except kerberos.GSSError, e: + print "Could not initialize GSSAPI: %s/%s" % (e[0][0][0], e[0][1][0]) + return 1 if options.gecos: user.setValue('gecos', options.gecos) @@ -79,6 +82,9 @@ def main(): except xmlrpclib.ProtocolError, e: print "Unable to connect to IPA server: %s" % (e.errmsg) return 1 + except ipa.ipaerror.IPAError, e: + print "%s" % (e.message) + return 1 return 0 diff --git a/ipa-server/ipaserver/ipaldap.py b/ipa-server/ipaserver/ipaldap.py index 164509263..c0452b05a 100644 --- a/ipa-server/ipaserver/ipaldap.py +++ b/ipa-server/ipaserver/ipaldap.py @@ -35,13 +35,14 @@ import cStringIO import time import operator import struct +import ldap.sasl from ldap.controls import LDAPControl,DecodeControlTuples,EncodeControlTuples -from ldap.modlist import modifyModlist - from ldap.ldapobject import SimpleLDAPObject - from ipa import ipaerror, ipautil +# Global variable to define SASL auth +sasl_auth = ldap.sasl.sasl({},'GSSAPI') + class Entry: """This class represents an LDAP Entry object. An LDAP entry consists of a DN and a list of attributes. Each attribute consists of a name and a list of @@ -196,22 +197,34 @@ class IPAdmin(SimpleLDAPObject): raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e) def __localinit__(self): - SimpleLDAPObject.__init__(self,'ldaps://%s:%d' % (self.host,self.port)) + """If a CA certificate is provided then it is assumed that we are + doing SSL client authentication with proxy auth. + + If a CA certificate is not present then it is assumed that we are + using a forwarded kerberos ticket for SASL auth. SASL provides + its own encryption. + """ + if self.cacert is not None: + SimpleLDAPObject.__init__(self,'ldaps://%s:%d' % (self.host,self.port)) + else: + SimpleLDAPObject.__init__(self,'ldap://%s:%d' % (self.host,self.port)) def __init__(self,host,port,cacert,bindcert,bindkey,proxydn=None): - """We just set our instance variables and wrap the methods - the real work is - done in __localinit__ and __initPart2 - these are separated out this way so - that we can call them from places other than instance creation e.g. when - using the start command, we just need to reconnect, not create a new instance""" + """We just set our instance variables and wrap the methods - the real + work is done in __localinit__ and __initPart2 - these are separated + out this way so that we can call them from places other than + instance creation e.g. when we just need to reconnect, not create a + new instance""" # ldap.set_option(ldap.OPT_DEBUG_LEVEL,255) - ldap.set_option(ldap.OPT_X_TLS_CACERTFILE,cacert) - ldap.set_option(ldap.OPT_X_TLS_CERTFILE,bindcert) - ldap.set_option(ldap.OPT_X_TLS_KEYFILE,bindkey) + if cacert is not None: + ldap.set_option(ldap.OPT_X_TLS_CACERTFILE,cacert) + ldap.set_option(ldap.OPT_X_TLS_CERTFILE,bindcert) + ldap.set_option(ldap.OPT_X_TLS_KEYFILE,bindkey) self.__wrapmethods() self.port = port or 389 - self.sslport = 0 self.host = host + self.cacert = cacert self.bindcert = bindcert self.bindkey = bindkey self.proxydn = proxydn @@ -251,6 +264,12 @@ class IPAdmin(SimpleLDAPObject): def set_proxydn(self, proxydn): self.proxydn = proxydn + def set_keytab(self, keytab): + if keytab is not None: + os.environ["KRB5CCNAME"] = keytab + self.sasl_interactive_bind_s("", sasl_auth) + self.proxydn = None + def getEntry(self,*args): """This wraps the search function. It is common to just get one entry""" @@ -346,7 +365,8 @@ class IPAdmin(SimpleLDAPObject): sctrl = self.__get_server_controls__() try: - self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) + if sctrl is not None: + self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) self.add_s(*args) except ldap.ALREADY_EXISTS: raise ipaerror.gen_exception(ipaerror.LDAP_DUPLICATE) @@ -366,7 +386,8 @@ class IPAdmin(SimpleLDAPObject): raise ipaerror.gen_exception(ipaerror.LDAP_EMPTY_MODLIST) try: - self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) + if sctrl is not None: + self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) self.modify_s(dn, modlist) # this is raised when a 'delete' attribute isn't found. # it indicates the previous attribute was removed by another @@ -428,7 +449,8 @@ class IPAdmin(SimpleLDAPObject): modlist.append((operation, "nsAccountlock", "true")) try: - self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) + if sctrl is not None: + self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) self.modify_s(dn, modlist) except ldap.LDAPError, e: raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e) @@ -440,7 +462,8 @@ class IPAdmin(SimpleLDAPObject): sctrl = self.__get_server_controls__() try: - self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) + if sctrl is not None: + self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) self.delete_s(*args) except ldap.LDAPError, e: raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e) diff --git a/ipa-server/xmlrpc-server/funcs.py b/ipa-server/xmlrpc-server/funcs.py index f4e01b342..294b5b5f7 100644 --- a/ipa-server/xmlrpc-server/funcs.py +++ b/ipa-server/xmlrpc-server/funcs.py @@ -47,20 +47,31 @@ DefaultGroupContainer = "cn=groups,cn=accounts" # this is not anticipated. class IPAConnPool: def __init__(self): - self.numentries = 0 self.freelist = [] - def getConn(self, host, port, bindca, bindcert, bindkey, proxydn=None): - self.numentries = self.numentries + 1 + def getConn(self, host, port, bindca, bindcert, bindkey, proxydn=None, keytab=None): + conn = None if len(self.freelist) > 0: - conn = self.freelist.pop() - else: + for i in range(len(self.freelist)): + c = self.freelist[i] + if ((c.host == host) and (c.port == port)): + conn = self.freelist.pop(i) + break + if conn is None: conn = ipaserver.ipaldap.IPAdmin(host,port,bindca,bindcert,bindkey) - conn.set_proxydn(proxydn) + if proxydn is not None: + conn.set_proxydn(proxydn) + else: + conn.set_keytab(keytab) return conn def releaseConn(self, conn): - self.freelist.append(conn) + # We can't re-use SASL connections. If proxydn is None it means + # we have a keytab set. See ipaldap.set_keytab + if conn.proxydn is None: + conn.unbind_s() + else: + self.freelist.append(conn) class IPAServer: @@ -68,7 +79,8 @@ class IPAServer: global _LDAPPool # FIXME, this needs to be auto-discovered self.host = 'localhost' - self.port = 636 + self.port = 389 + self.sslport = 636 self.bindcert = "/usr/share/ipa/cert.pem" self.bindkey = "/usr/share/ipa/key.pem" self.bindca = "/usr/share/ipa/cacert.asc" @@ -79,24 +91,84 @@ class IPAServer: self.basedn = ipa.ipautil.realm_to_suffix(ipa.config.config.get_realm()) self.scope = ldap.SCOPE_SUBTREE self.princ = None + self.keytab = None def set_principal(self, princ): self.princ = princ + + def set_keytab(self, keytab): + self.keytab = keytab def get_dn_from_principal(self, princ): - """Given a kerberls principal get the LDAP uid""" + """Given a kerberos principal get the LDAP uid""" global _LDAPPool filter = "(krbPrincipalName=" + princ + ")" # The only anonymous search we should have - m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,None) + conn = _LDAPPool.getConn(self.host,self.sslport,self.bindca,self.bindcert,self.bindkey,None,None) try: - ent = m1.getEntry(self.basedn, self.scope, filter, ['dn']) + ent = conn.getEntry(self.basedn, self.scope, filter, ['dn']) finally: - _LDAPPool.releaseConn(m1) + _LDAPPool.releaseConn(conn) return "dn:" + ent.dn + def __setup_connection(self, opts): + """Set up common things done in the connection. + If there is a keytab then return None as the proxy dn and the keytab + otherwise return the proxy dn and None as the keytab. + + We only want one or the other used at one time and we prefer + the keytab. So if there is a keytab, return that and None for + proxy dn to make calling getConn() easier. + """ + + if opts: + if opts.get('keytab'): + self.set_keytab(opts['keytab']) + self.set_principal(None) + else: + self.set_keytab(None) + self.set_principal(opts['remoteuser']) + else: + self.set_keytab(None) + # The caller should have already set the principal + + if self.princ is not None: + return self.get_dn_from_principal(self.princ), None + else: + return None, self.keytab + + def getConnection(self, opts): + """Wrapper around IPAConnPool.getConn() so we don't have to pass + around self.* every time a connection is needed. + + For SASL connections (where we have a keytab) we can't set + the SSL variables for certificates. It confuses the ldap + module. + """ + global _LDAPPool + + (proxy_dn, keytab) = self.__setup_connection(opts) + + if keytab is not None: + bindca = None + bindcert = None + bindkey = None + port = self.port + else: + bindca = self.bindca + bindcert = self.bindcert + bindkey = self.bindkey + port = self.sslport + + return _LDAPPool.getConn(self.host,port,bindca,bindcert,bindkey,proxy_dn,keytab) + + def releaseConnection(self, conn): + global _LDAPPool + + _LDAPPool.releaseConn(conn) + def convert_entry(self, ent): entry = dict(ent.data) entry['dn'] = ent.dn @@ -110,24 +182,17 @@ class IPAServer: entry[key] = value[0] return entry - def __get_entry (self, base, filter, sattrs=None, opts=None): """Get a specific entry. Return as a dict of values. Multi-valued fields are represented as lists. """ - global _LDAPPool ent="" - if opts: - self.set_principal(opts['remoteuser']) - - dn = self.get_dn_from_principal(self.princ) - - m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn) + conn = self.getConnection(opts) try: - ent = m1.getEntry(base, self.scope, filter, sattrs) + ent = conn.getEntry(base, self.scope, filter, sattrs) finally: - _LDAPPool.releaseConn(m1) + self.releaseConnection(conn) return self.convert_entry(ent) @@ -137,8 +202,6 @@ class IPAServer: oldentry is a dict newentry is a dict """ - global _LDAPPool - oldentry = self.convert_scalar_values(oldentry) newentry = self.convert_scalar_values(newentry) @@ -150,16 +213,11 @@ class IPAServer: except KeyError, e: raise ipaerror.gen_exception(ipaerror.LDAP_MISSING_DN) - if opts: - self.set_principal(opts['remoteuser']) - - proxydn = self.get_dn_from_principal(self.princ) - - m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,proxydn) + conn = self.getConnection(opts) try: - res = m1.updateEntry(moddn, oldentry, newentry) + res = conn.updateEntry(moddn, oldentry, newentry) finally: - _LDAPPool.releaseConn(m1) + self.releaseConnection(conn) return res def __safe_filter(self, criteria): @@ -234,8 +292,6 @@ class IPAServer: attribute name and the value is either a string or in the case of a multi-valued field a list of values. user_container sets where in the tree the user is placed.""" - global _LDAPPool - if user_container is None: user_container = DefaultUserContainer @@ -288,16 +344,11 @@ class IPAServer: for u in user: entry.setValues(u, user[u]) - if opts: - self.set_principal(opts['remoteuser']) - - dn = self.get_dn_from_principal(self.princ) - - m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn) + conn = self.getConnection(opts) try: - res = m1.addEntry(entry) + res = conn.addEntry(entry) finally: - _LDAPPool.releaseConn(m1) + self.releaseConnection(conn) return res def get_add_schema (self): @@ -348,20 +399,13 @@ class IPAServer: """Return a list containing a User object for each existing user. """ - global _LDAPPool - - if opts: - self.set_principal(opts['remoteuser']) - - dn = self.get_dn_from_principal(self.princ) - filter = "(objectclass=posixAccount)" - m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn) + conn = self.getConnection(opts) try: - all_users = m1.getList(self.basedn, self.scope, filter, None) + all_users = conn.getList(self.basedn, self.scope, filter, None) finally: - _LDAPPool.releaseConn(m1) + self.releaseConnection(conn) users = [] for u in all_users: @@ -372,13 +416,6 @@ class IPAServer: def find_users (self, criteria, sattrs=None, opts=None): """Returns a list: counter followed by the results. If the results are truncated, counter will be set to -1.""" - global _LDAPPool - - if opts: - self.set_principal(opts['remoteuser']) - - dn = self.get_dn_from_principal(self.princ) - # Assume the list of fields to search will come from a central # configuration repository. A good format for that would be # a comma-separated list of fields @@ -394,21 +431,21 @@ class IPAServer: (exact_match_filter, partial_match_filter) = self.__generate_match_filters( search_fields, criteria_words) - m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn) + conn = self.getConnection(opts) try: try: - exact_results = m1.getListAsync(self.basedn, self.scope, + exact_results = conn.getListAsync(self.basedn, self.scope, exact_match_filter, sattrs) except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): exact_results = [0] try: - partial_results = m1.getListAsync(self.basedn, self.scope, + partial_results = conn.getListAsync(self.basedn, self.scope, partial_match_filter, sattrs) except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): partial_results = [0] finally: - _LDAPPool.releaseConn(m1) + self.releaseConnection(conn) exact_counter = exact_results[0] partial_counter = partial_results[0] @@ -450,13 +487,6 @@ class IPAServer: def mark_user_deleted (self, uid, opts=None): """Mark a user as inactive in LDAP. We aren't actually deleting users here, just making it so they can't log in, etc.""" - global _LDAPPool - - if opts: - self.set_principal(opts['remoteuser']) - - proxydn = self.get_dn_from_principal(self.princ) - user = self.get_user_by_uid(uid, ['dn', 'uid', 'nsAccountlock'], opts) # Are we doing an add or replace operation? @@ -467,11 +497,11 @@ class IPAServer: else: has_key = False - m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,proxydn) + conn = self.getConnection(opts) try: - res = m1.inactivateEntry(user['dn'], has_key) + res = conn.inactivateEntry(user['dn'], has_key) finally: - _LDAPPool.releaseConn(m1) + self.releaseConnection(conn) return res def delete_user (self, uid, opts=None): @@ -483,18 +513,15 @@ class IPAServer: The memberOf plugin handles removing the user from any other groups. """ - if opts: - self.set_principal(opts['remoteuser']) - - dn = self.get_dn_from_principal(self.princ) - user_dn = self.get_user_by_uid(uid, ['dn', 'uid', 'objectclass'], opts) if user_dn is None: raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND) - m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn) - res = m1.deleteEntry(user_dn['dn']) - _LDAPPool.releaseConn(m1) + conn = self.getConnection(opts) + try: + res = conn.deleteEntry(user_dn['dn']) + finally: + self.releaseConnection(conn) return res # Group support @@ -532,8 +559,6 @@ class IPAServer: attribute name and the value is either a string or in the case of a multi-valued field a list of values. group_container sets where in the tree the group is placed.""" - global _LDAPPool - if group_container is None: group_container = DefaultGroupContainer @@ -554,38 +579,26 @@ class IPAServer: for g in group: entry.setValues(g, group[g]) - if opts: - self.set_principal(opts['remoteuser']) - - dn = self.get_dn_from_principal(self.princ) - - m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn) + conn = self.getConnection(opts) try: - res = m1.addEntry(entry) + res = conn.addEntry(entry) finally: - _LDAPPool.releaseConn(m1) + self.releaseConnection(conn) def find_groups (self, criteria, sattrs=None, opts=None): """Return a list containing a User object for each existing group that matches the criteria. """ - global _LDAPPool - - if opts: - self.set_principal(opts['remoteuser']) - - dn = self.get_dn_from_principal(self.princ) - criteria = self.__safe_filter(criteria) filter = "(&(cn=%s)(objectClass=posixGroup))" % criteria - m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn) + conn = self.getConnection(opts) try: - results = m1.getList(self.basedn, self.scope, filter, sattrs) + results = conn.getList(self.basedn, self.scope, filter, sattrs) except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): results = [] finally: - _LDAPPool.releaseConn(m1) + self.releaseConnection(conn) groups = [] for u in results: @@ -599,9 +612,6 @@ class IPAServer: group is the cn of the group to be added to """ - if opts: - self.set_principal(opts['remoteuser']) - old_group = self.get_group_by_cn(group, None, opts) if old_group is None: raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND) @@ -652,9 +662,6 @@ class IPAServer: group is the cn of the group to be removed from """ - if opts: - self.set_principal(opts['remoteuser']) - old_group = self.get_group_by_cn(group, None, opts) if old_group is None: raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND) @@ -718,19 +725,16 @@ class IPAServer: The memberOf plugin handles removing the group from any other groups. """ - if opts: - self.set_principal(opts['remoteuser']) - - dn = self.get_dn_from_principal(self.princ) - group = self.get_group_by_cn(group_cn, ['dn', 'cn'], opts) if len(group) != 1: raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND) - m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn) - res = m1.deleteEntry(group[0]['dn']) - _LDAPPool.releaseConn(m1) + conn = self.getConnection(opts) + try: + res = conn.deleteEntry(group[0]['dn']) + finally: + self.releaseConnection(conn) return res def add_group_to_group(self, group, tgroup, opts=None): @@ -739,9 +743,6 @@ class IPAServer: tgroup is the cn of the group to be added to """ - if opts: - self.set_principal(opts['remoteuser']) - old_group = self.get_group_by_cn(tgroup, None, opts) if old_group is None: raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND) diff --git a/ipa-server/xmlrpc-server/ipaxmlrpc.py b/ipa-server/xmlrpc-server/ipaxmlrpc.py index 5dc60b51b..f2ddd35e8 100644 --- a/ipa-server/xmlrpc-server/ipaxmlrpc.py +++ b/ipa-server/xmlrpc-server/ipaxmlrpc.py @@ -126,13 +126,19 @@ class ModXMLRPCRequestHandler(object): def register_instance(self,instance): self.register_module(instance) - def _marshaled_dispatch(self, data, remoteuser): + def _marshaled_dispatch(self, data, req): """Dispatches an XML-RPC method from marshalled (XML) data.""" params, method = loads(data) + # Populate the Apache environment variables + req.add_common_vars() + opts={} - opts['remoteuser'] = remoteuser + opts['remoteuser'] = req.user + + if req.subprocess_env.get("KRB5CCNAME") is not None: + opts['keytab'] = req.subprocess_env.get("KRB5CCNAME") # Tack onto the end of the passed-in arguments any options we also # need @@ -263,7 +269,7 @@ class ModXMLRPCRequestHandler(object): req.allow_methods(['POST'],1) raise apache.SERVER_RETURN, apache.HTTP_METHOD_NOT_ALLOWED - response = self._marshaled_dispatch(req.read(), req.user) + response = self._marshaled_dispatch(req.read(), req) req.content_type = "text/xml" req.set_content_length(len(response)) -- cgit From 945713ca30475bc1f87029cf98adb50b24812c8e Mon Sep 17 00:00:00 2001 From: Kevin McCarthy Date: Thu, 6 Sep 2007 16:21:07 -0700 Subject: Fix dsinstance.py and krbinstance.py imports --- ipa-server/ipaserver/dsinstance.py | 2 +- ipa-server/ipaserver/krbinstance.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ipa-server/ipaserver/dsinstance.py b/ipa-server/ipaserver/dsinstance.py index 841bc31f2..1f0704486 100644 --- a/ipa-server/ipaserver/dsinstance.py +++ b/ipa-server/ipaserver/dsinstance.py @@ -24,7 +24,7 @@ import tempfile import shutil import logging import pwd -from util import * +from ipa.ipautil import * SHARE_DIR = "/usr/share/ipa/" diff --git a/ipa-server/ipaserver/krbinstance.py b/ipa-server/ipaserver/krbinstance.py index e17ab525b..d39a44e13 100644 --- a/ipa-server/ipaserver/krbinstance.py +++ b/ipa-server/ipaserver/krbinstance.py @@ -29,7 +29,7 @@ import os import pwd import socket import time -from util import * +from ipa.ipautil import * def host_to_domain(fqdn): s = fqdn.split(".") -- cgit From d036eb0ac95b541c0e4ab65a03acf72bcc8e27a8 Mon Sep 17 00:00:00 2001 From: Kevin McCarthy Date: Wed, 5 Sep 2007 15:54:04 -0700 Subject: Add password expiration messages to user show/edit pages. Add userhelper module to provide ui logic. Fix uid/email suggest to lowercase first. --- ipa-server/ipa-gui/ipagui/controllers.py | 7 +++++++ ipa-server/ipa-gui/ipagui/forms/user.py | 2 ++ ipa-server/ipa-gui/ipagui/helpers/__init__.py | 1 + ipa-server/ipa-gui/ipagui/helpers/userhelper.py | 23 +++++++++++++++++++++++ ipa-server/ipa-gui/ipagui/static/css/style.css | 6 ++++++ ipa-server/ipa-gui/ipagui/templates/useredit.kid | 18 ++++++++++++++++++ ipa-server/ipa-gui/ipagui/templates/usershow.kid | 18 ++++++++++++++++++ 7 files changed, 75 insertions(+) create mode 100644 ipa-server/ipa-gui/ipagui/helpers/__init__.py create mode 100644 ipa-server/ipa-gui/ipagui/helpers/userhelper.py diff --git a/ipa-server/ipa-gui/ipagui/controllers.py b/ipa-server/ipa-gui/ipagui/controllers.py index 5fb4be06b..047b51876 100644 --- a/ipa-server/ipa-gui/ipagui/controllers.py +++ b/ipa-server/ipa-gui/ipagui/controllers.py @@ -17,6 +17,7 @@ import ipa.ipaclient import ipa.user import xmlrpclib import forms.user +from helpers import userhelper from ipa import ipaerror ipa.config.init_config() @@ -203,6 +204,9 @@ class Root(controllers.RootController): if (len(givenname) == 0) or (len(sn) == 0): return "" + givenname = givenname.lower() + sn = sn.lower() + uid = givenname[0] + sn[:7] try: client.get_user_by_uid(uid) @@ -244,6 +248,9 @@ class Root(controllers.RootController): if (len(givenname) == 0) or (len(sn) == 0): return "" + givenname = givenname.lower() + sn = sn.lower() + # TODO - get from config domain = "freeipa.org" diff --git a/ipa-server/ipa-gui/ipagui/forms/user.py b/ipa-server/ipa-gui/ipagui/forms/user.py index b9b6f33d4..db4bd7d5b 100644 --- a/ipa-server/ipa-gui/ipagui/forms/user.py +++ b/ipa-server/ipa-gui/ipagui/forms/user.py @@ -23,6 +23,7 @@ class UserFields(): uid_hidden = widgets.HiddenField(name="uid") uidnumber_hidden = widgets.HiddenField(name="uidnumber") gidnumber_hidden = widgets.HiddenField(name="gidnumber") + krbPasswordExpiration_hidden = widgets.HiddenField(name="krbPasswordExpiration") user_orig = widgets.HiddenField(name="user_orig") @@ -53,6 +54,7 @@ class UserEditForm(widgets.Form): fields = [UserFields.givenname, UserFields.sn, UserFields.mail, UserFields.uid_hidden, UserFields.user_orig, UserFields.uidnumber_hidden, UserFields.gidnumber_hidden, + UserFields.krbPasswordExpiration_hidden, ] def __init__(self, *args, **kw): diff --git a/ipa-server/ipa-gui/ipagui/helpers/__init__.py b/ipa-server/ipa-gui/ipagui/helpers/__init__.py new file mode 100644 index 000000000..143f486c0 --- /dev/null +++ b/ipa-server/ipa-gui/ipagui/helpers/__init__.py @@ -0,0 +1 @@ +# __init__.py diff --git a/ipa-server/ipa-gui/ipagui/helpers/userhelper.py b/ipa-server/ipa-gui/ipagui/helpers/userhelper.py new file mode 100644 index 000000000..2a2571dbe --- /dev/null +++ b/ipa-server/ipa-gui/ipagui/helpers/userhelper.py @@ -0,0 +1,23 @@ +import sys +import datetime + +from ipa import ipautil + +def password_expires_in(datestr): + """Returns the number of days that password expires in. Returns a negative number + if the password is already expired.""" + if (datestr == None) or (datestr == ""): + return sys.maxint + + expdate = ipautil.parse_generalized_time(datestr) + if not expdate: + return sys.maxint + + delta = expdate - datetime.datetime.now() + return delta.days + +def password_is_expired(days): + return days < 0 + +def password_expires_soon(days): + return (not password_is_expired(days)) and (days < 7) diff --git a/ipa-server/ipa-gui/ipagui/static/css/style.css b/ipa-server/ipa-gui/ipagui/static/css/style.css index 9ea86ae01..cefb63169 100644 --- a/ipa-server/ipa-gui/ipagui/static/css/style.css +++ b/ipa-server/ipa-gui/ipagui/static/css/style.css @@ -140,6 +140,12 @@ body { font-weight: bolder; } +.warning_message { + font-size: 120%; + color: #ee0000; + font-weight: bolder; +} + .fielderror { color: red; font-weight: bold; diff --git a/ipa-server/ipa-gui/ipagui/templates/useredit.kid b/ipa-server/ipa-gui/ipagui/templates/useredit.kid index db47ab298..1f31139d1 100644 --- a/ipa-server/ipa-gui/ipagui/templates/useredit.kid +++ b/ipa-server/ipa-gui/ipagui/templates/useredit.kid @@ -8,6 +8,24 @@

Edit Person

+ + +
+ Password will expire in ${pw_expires_days} day${days_suffix} +
+
+ Password has expired +
+ ${form.display(action="userupdate", value=user)} diff --git a/ipa-server/ipa-gui/ipagui/templates/usershow.kid b/ipa-server/ipa-gui/ipagui/templates/usershow.kid index 4e73eba35..b4ec46634 100644 --- a/ipa-server/ipa-gui/ipagui/templates/usershow.kid +++ b/ipa-server/ipa-gui/ipagui/templates/usershow.kid @@ -8,6 +8,24 @@

View Person

+ + +
+ Password will expire in ${pw_expires_days} day${days_suffix} +
+
+ Password has expired +
+
Identity Details
-- cgit From 4e242b5dc1d84881be542ef890773e771619e429 Mon Sep 17 00:00:00 2001 From: Kevin McCarthy Date: Wed, 5 Sep 2007 16:59:55 -0700 Subject: Small UI tweeks - Rename buttons - Add fake "logged in as" text - Increase font size and spacing for sidebar - Fix search messages for no results - Open ipa footer link in new window --- ipa-server/ipa-gui/ipagui/controllers.py | 2 +- ipa-server/ipa-gui/ipagui/static/css/style.css | 14 ++++++++++++- ipa-server/ipa-gui/ipagui/templates/master.kid | 23 +++++++++++++++------- .../ipa-gui/ipagui/templates/usereditform.kid | 6 ++++-- ipa-server/ipa-gui/ipagui/templates/userlist.kid | 14 ++++++------- .../ipa-gui/ipagui/templates/usernewform.kid | 8 ++------ 6 files changed, 42 insertions(+), 25 deletions(-) diff --git a/ipa-server/ipa-gui/ipagui/controllers.py b/ipa-server/ipa-gui/ipagui/controllers.py index 047b51876..73945fe4c 100644 --- a/ipa-server/ipa-gui/ipagui/controllers.py +++ b/ipa-server/ipa-gui/ipagui/controllers.py @@ -108,7 +108,7 @@ class Root(controllers.RootController): def userupdate(self, **kw): """Updates an existing user""" restrict_post() - if kw.get('submit') == 'Cancel': + if kw.get('submit') == 'Cancel Edit': turbogears.flash("Edit user cancelled") raise turbogears.redirect('/usershow', uid=kw.get('uid')) diff --git a/ipa-server/ipa-gui/ipagui/static/css/style.css b/ipa-server/ipa-gui/ipagui/static/css/style.css index cefb63169..d64f6d559 100644 --- a/ipa-server/ipa-gui/ipagui/static/css/style.css +++ b/ipa-server/ipa-gui/ipagui/static/css/style.css @@ -29,12 +29,20 @@ body { margin:0; } +#header #logo { +} + +#header #login { + float:right; +} + #nav { background:#cc0000; color:#fff; min-height:3px; max-height:3px; + clear:both; } #nav ul { @@ -75,7 +83,11 @@ body { float:left; width:10%; padding: 5px; - font-size: small; + font-size: medium; +} + +#sidebar p { + line-height: 150%; } #sidebar h2 { diff --git a/ipa-server/ipa-gui/ipagui/templates/master.kid b/ipa-server/ipa-gui/ipagui/templates/master.kid index 2f39afc41..71412eaa4 100644 --- a/ipa-server/ipa-gui/ipagui/templates/master.kid +++ b/ipa-server/ipa-gui/ipagui/templates/master.kid @@ -26,10 +26,15 @@
diff --git a/ipa-server/ipa-gui/ipagui/templates/usernewform.kid b/ipa-server/ipa-gui/ipagui/templates/usernewform.kid index 5f21bde41..884483447 100644 --- a/ipa-server/ipa-gui/ipagui/templates/usernewform.kid +++ b/ipa-server/ipa-gui/ipagui/templates/usernewform.kid @@ -27,6 +27,9 @@ + --> + +
+ + + + diff --git a/ipa-server/ipa-gui/ipagui/templates/usernewform.kid b/ipa-server/ipa-gui/ipagui/templates/usernewform.kid index 884483447..daf131432 100644 --- a/ipa-server/ipa-gui/ipagui/templates/usernewform.kid +++ b/ipa-server/ipa-gui/ipagui/templates/usernewform.kid @@ -92,6 +92,7 @@ + + + + + + + -- cgit From 78bcc22c406ce8c7a06c1b8c5b6270f745118abc Mon Sep 17 00:00:00 2001 From: Kevin McCarthy Date: Fri, 7 Sep 2007 11:07:59 -0700 Subject: small release fixes: - Make password not required for add person - Fix for searching on '*' or '' --- ipa-server/ipa-gui/ipagui/forms/user.py | 4 ++-- ipa-server/xmlrpc-server/funcs.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ipa-server/ipa-gui/ipagui/forms/user.py b/ipa-server/ipa-gui/ipagui/forms/user.py index 334e61379..078e06ddd 100644 --- a/ipa-server/ipa-gui/ipagui/forms/user.py +++ b/ipa-server/ipa-gui/ipagui/forms/user.py @@ -26,8 +26,8 @@ class UserFields(): class UserNewValidator(validators.Schema): uid = validators.PlainText(not_empty=True) - userpassword = validators.String(not_empty=True) - userpassword_confirm = validators.String(not_empty=True) + userpassword = validators.String(not_empty=False) + userpassword_confirm = validators.String(not_empty=False) givenname = validators.String(not_empty=True) sn = validators.String(not_empty=True) mail = validators.Email(not_empty=True) diff --git a/ipa-server/xmlrpc-server/funcs.py b/ipa-server/xmlrpc-server/funcs.py index 294b5b5f7..66fabf4be 100644 --- a/ipa-server/xmlrpc-server/funcs.py +++ b/ipa-server/xmlrpc-server/funcs.py @@ -426,7 +426,7 @@ class IPAServer: criteria_words = re.split(r'\s+', criteria) criteria_words = filter(lambda value:value!="", criteria_words) if len(criteria_words) == 0: - return [] + return [0] (exact_match_filter, partial_match_filter) = self.__generate_match_filters( search_fields, criteria_words) -- cgit From 2377e8bcb05a11488a7e2cda05b574b64de9de9e Mon Sep 17 00:00:00 2001 From: "rcritten@redhat.com" Date: Fri, 7 Sep 2007 17:49:44 -0400 Subject: Add group command-line tools to the Makefile Updated installation instructions --- ipa-admintools/Makefile | 4 ++++ ipa-server/ipa-install/README | 45 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/ipa-admintools/Makefile b/ipa-admintools/Makefile index 4bed3b9a6..47822fc91 100644 --- a/ipa-admintools/Makefile +++ b/ipa-admintools/Makefile @@ -7,6 +7,10 @@ install: install -m 755 ipa-finduser $(SBINDIR) install -m 755 ipa-usermod $(SBINDIR) install -m 755 ipa-deluser $(SBINDIR) + install -m 755 ipa-addgroup $(SBINDIR) + install -m 755 ipa-delgroup $(SBINDIR) + install -m 755 ipa-findgroup $(SBINDIR) + install -m 755 ipa-groupmod $(SBINDIR) clean: rm -f *~ *.pyc diff --git a/ipa-server/ipa-install/README b/ipa-server/ipa-install/README index fd6b74736..16fc4a799 100644 --- a/ipa-server/ipa-install/README +++ b/ipa-server/ipa-install/README @@ -2,7 +2,8 @@ Required packages: krb5-server -fedora-ds-base / fedora-ds-base-devel +fedora-ds-base +fedora-ds-base-devel openldap-clients krb5-server-ldap cyrus-sasl-gssapi @@ -13,12 +14,40 @@ openssl-devel Installation example: -TEMPORARY: (until fedora ds scripts are fixed) -please use the fedora-ds.init.patch under share/ to patch your init scripts before -running ipa-server-install +TEMPORARY: until bug https://bugzilla.redhat.com/show_bug.cgi?id=248169 is + fixed. -cd ipa-install -make install -cd .. -/usr/sbin/ipa-server-install -u fds -r FREEIPA.ORG -p freeipa -m ipafree +Please apply the fedora-ds.init.patch in freeipa/ipa-server/ipa-install/share/ +to patch your init scripts before running ipa-server-install. This tells +FDS where to find its kerberos keytab. +Things done as root are denoted by #. Things done as a unix user are denoted +by %. + +# cd freeipa +# patch -p0 < ipa-server/ipa-install/share/fedora-ds.init.patch + +Now to do the installation. + +# cd freeipa +# make install +# /usr/sbin/ipa-server-install -u fds -r FREEIPA.ORG -p freeipa -P ipafree + +For more verbose output add the -d flag + +You have a basic working system with one super administrator (named admin). + +To create another administrative user: + +% kinit admin@FREEIPA.ORG +% /usr/sbin/ipa-adduser -f Test -l User test +% ldappasswd -Y GSSAPI -h localhost -s password uid=test,cn=users,cn=accounts,dc=freeipa,dc=org +% /usr/sbin/ipa-groupmod -a test admins + +An admin user is just a regular user in the group admin. + +Now you can destroy the old ticket and log in as test: + +% kdestroy +% kinit test@FREEIPA.ORG +% /usr/sbin/ipa-finduser test -- cgit
+ + +
+ + +