From kmacmill at redhat.com Wed Aug 1 15:12:53 2007 From: kmacmill at redhat.com (Karl MacMillan) Date: Wed, 01 Aug 2007 11:12:53 -0400 Subject: [Freeipa-devel] [PATCH] fix small bugs and add ds_newinst.pl fallback Message-ID: <1185981173.21720.4.camel@localhost.localdomain> Just committed a patch (below) for a few small bugs that evaded my testing yesterday (thanks Rob) and made dsinstance.py fallback to ds_newinst.pl if the newer setup-ds.pl is not present. And for anyone that missed it yesterday, there was what I hope is the last of the tree reorg. Karl [?1034hdiff -r 5f83fa3c16c2 ipa-server/ipa-install/ipa-server-install --- a/ipa-server/ipa-install/ipa-server-install Tue Jul 31 17:12:26 2007 -0400 +++ b/ipa-server/ipa-install/ipa-server-install Wed Aug 01 11:06:05 2007 -0400 @@ -34,7 +34,7 @@ from optparse import OptionParser from optparse import OptionParser import ipaserver.dsinstance import ipaserver.krbinstance -import ipaserver.util.run as run +from ipaserver.util import run def parse_options(): parser = OptionParser(version=VERSION) @@ -117,7 +117,7 @@ def main(): ds.restart() # Restart apache - run(["/sbin/server", "httpd", "restart"]) + run(["/sbin/service", "httpd", "restart"]) return 0 diff -r 5f83fa3c16c2 ipa-server/ipaserver/dsinstance.py --- a/ipa-server/ipaserver/dsinstance.py Tue Jul 31 17:12:26 2007 -0400 +++ b/ipa-server/ipaserver/dsinstance.py Wed Aug 01 10:46:45 2007 -0400 @@ -24,8 +24,6 @@ import shutil import shutil import logging import pwd -import os -import stat from util import * @@ -52,13 +50,10 @@ def realm_to_suffix(realm_name): return ",".join(terms) def find_server_root(): - try: - mode = os.stat(SERVER_ROOT_64)[ST_MODE] - if stat.IS_DIR(mode): - return SERVER_ROOT_64 - except: + if dir_exists(SERVER_ROOT_64): + return SERVER_ROOT_64 + else: return SERVER_ROOT_32 - INF_TEMPLATE = """ [General] @@ -137,8 +132,12 @@ class DsInstance: logging.debug(inf_txt) inf_fd = write_tmp_file(inf_txt) logging.debug("writing inf template") - args = ["/usr/sbin/setup-ds.pl", "--silent", "--logfile", "-", "-f", inf_fd.name, ] - logging.debug("calling ds_newinst.pl") + if file_exists("/usr/sbin/setup-ds.pl"): + args = ["/usr/sbin/setup-ds.pl", "--silent", "--logfile", "-", "-f", inf_fd.name] + logging.debug("calling setup-ds.pl") + else: + args = ["/usr/sbin/ds_newinst.pl", inf_fd.name] + logging.debug("calling ds_newinst.pl") run(args) logging.debug("completed creating ds instance") logging.debug("restarting ds instance") diff -r 5f83fa3c16c2 ipa-server/ipaserver/util.py --- a/ipa-server/ipaserver/util.py Tue Jul 31 17:12:26 2007 -0400 +++ b/ipa-server/ipaserver/util.py Wed Aug 01 10:59:50 2007 -0400 @@ -24,6 +24,8 @@ import tempfile import tempfile import logging import subprocess +import os +import stat def realm_to_suffix(realm_name): s = realm_name.split(".") @@ -56,3 +58,23 @@ def run(args, stdin=None): if p.returncode != 0: raise subprocess.CalledProcessError(p.returncode, args[0]) + +def file_exists(filename): + try: + mode = os.stat(filename)[stat.ST_MODE] + if stat.S_ISREG(mode): + return True + else: + return False + except: + return False + +def dir_exists(filename): + try: + mode = os.stat(filename)[stat.ST_MODE] + if stat.S_ISDIR(mode): + return True + else: + return False + except: + return False From kmacmill at redhat.com Wed Aug 1 15:50:04 2007 From: kmacmill at redhat.com (Karl MacMillan) Date: Wed, 01 Aug 2007 11:50:04 -0400 Subject: [Freeipa-devel] [PATCH] local dist Message-ID: <1185983404.21720.8.camel@localhost.localdomain> Committed the patch below to add a new local-dist target to the makefile. Normally distribution tarballs and rpms are made by using hg archive (so they are the latest _committed_ versions). This is good to catch missed adds and so that releases are made from exactly what is under version control. However, it sucks for testing because testing the rpms requires a commit of all changes. The local-dist target takes your current local version including changes that haven't been committed and makes tarballs and rpms. Karl [?1034hdiff -r d3e1dc5f6584 Makefile --- a/Makefile Wed Aug 01 11:09:12 2007 -0400 +++ b/Makefile Wed Aug 01 11:42:42 2007 -0400 @@ -53,10 +53,20 @@ version-update: > ipa-python/freeipa-python.spec -tarballs: +archive: -mkdir -p dist hg archive -t files dist/freeipa +local-archive: + -mkdir -p dist/freeipa + @for subdir in $(SUBDIRS); do \ + cp -pr $$subdir dist/freeipa/.; \ + done + +archive-cleanup: + rm -fr dist/freeipa + +tarballs: # ipa-server mv dist/freeipa/ipa-server dist/$(SERV_TARBALL_PREFIX) rm -f dist/$(SERV_TARBALL) @@ -74,9 +84,6 @@ tarballs: rm -f dist/$(PYTHON_TARBALL) cd dist; tar cfz $(PYTHON_TARBALL) $(PYTHON_TARBALL_PREFIX) rm -fr dist/$(PYTHON_TARBALL_PREFIX) - - # cleanup - rm -fr dist/freeipa rpm-ipa-server: cp dist/$(SERV_TARBALL) ~/rpmbuild/SOURCES/. @@ -98,7 +105,9 @@ rpm-ipa-python: rpms: rpm-ipa-server rpm-ipa-admin rpm-ipa-python -dist: version-update tarballs rpms +dist: version-update archive tarballs archive-cleanup rpms + +local-dist: clean version-update local-archive tarballs archive-cleanup rpms dist-clean: clean rm -fr dist From rcritten at redhat.com Wed Aug 1 18:54:31 2007 From: rcritten at redhat.com (Rob Crittenden) Date: Wed, 01 Aug 2007 14:54:31 -0400 Subject: [Freeipa-devel] xml-rpc functions Message-ID: <46B0D6E7.1060700@redhat.com> I think it would be helpful to identify all the major functions that the xml-rpc protocol will support. Off the top of my head I have: - add user - retrieve user by uid - modify user - search for a user - retrieve all users (could be a "*" of search for a user) - remove user (deactivate) - get list of groups - get group by id - modify group - add user(s) to group - remove user(s) from group - remove group - change password When doing a modify I wonder what we'll be sending. All attributes, just those modified, something else? rob -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3245 bytes Desc: S/MIME Cryptographic Signature URL: From kmccarth at redhat.com Wed Aug 1 19:19:35 2007 From: kmccarth at redhat.com (Kevin McCarthy) Date: Wed, 1 Aug 2007 12:19:35 -0700 Subject: [Freeipa-devel] xml-rpc functions In-Reply-To: <46B0D6E7.1060700@redhat.com> References: <46B0D6E7.1060700@redhat.com> Message-ID: <20070801191935.GA20393@moon.usersys.redhat.com> Rob Crittenden wrote: > When doing a modify I wonder what we'll be sending. All attributes, > just those modified, something else? It's easier (of course) to just send all the values from the web page onward. But then something along the line is going to have to convert it to a correct ldap modification command. I suppose we could embed original values in the page and do a comparison before sending off through xmlrpc. That probably isn't that hard to do. We probably want to filter what's sent over in any case, so a malicious user doesn't embed a 'memberOf' field in their post and we end up passing it through... -Kevin -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 2228 bytes Desc: not available URL: From prowley at redhat.com Wed Aug 1 19:24:20 2007 From: prowley at redhat.com (Pete Rowley) Date: Wed, 01 Aug 2007 12:24:20 -0700 Subject: [Freeipa-devel] xml-rpc functions In-Reply-To: <20070801191935.GA20393@moon.usersys.redhat.com> References: <46B0D6E7.1060700@redhat.com> <20070801191935.GA20393@moon.usersys.redhat.com> Message-ID: <46B0DDE4.60000@redhat.com> Kevin McCarthy wrote: > We probably want to filter what's sent over in any case, so a malicious > user doesn't embed a 'memberOf' field in their post and we end up > passing it through... > DS access control should be the thing guarding against that. -- Pete -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3241 bytes Desc: S/MIME Cryptographic Signature URL: From prowley at redhat.com Wed Aug 1 20:22:45 2007 From: prowley at redhat.com (Pete Rowley) Date: Wed, 01 Aug 2007 13:22:45 -0700 Subject: [Freeipa-devel] xml-rpc functions In-Reply-To: <46B0D6E7.1060700@redhat.com> References: <46B0D6E7.1060700@redhat.com> Message-ID: <46B0EB95.6020205@redhat.com> Rob Crittenden wrote: > I think it would be helpful to identify all the major functions that > the xml-rpc protocol will support. > > Off the top of my head I have: > > - add user > - retrieve user by uid > - modify user > - search for a user > - retrieve all users (could be a "*" of search for a user) The search/retrieve are likely to be simple calls into a general search and that's where the work is. That search of course deals with multiple users. > - remove user (deactivate) There is both delete and deactivate (disallow bind) also enumerate group membership. > > - get list of groups > - get group by id > - modify group > - add user(s) to group > - remove user(s) from group > - remove group > > - change password > Need some calls around storing UI configuration. That's certainly a good basic list that we can add to if we feel the need. > When doing a modify I wonder what we'll be sending. All attributes, > just those modified, something else? > The modify operations, these should be passed as a set I think. -- Pete -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3241 bytes Desc: S/MIME Cryptographic Signature URL: From kmacmill at redhat.com Wed Aug 1 21:01:04 2007 From: kmacmill at redhat.com (Karl MacMillan) Date: Wed, 01 Aug 2007 17:01:04 -0400 Subject: [Freeipa-devel] Milestone 1 released Message-ID: <1186002064.13495.4.camel@localhost.localdomain> Milestone 1 has been released. This Milestone includes installation scripts, an xml-rpc server for administration, a python library, and example administration tools for adding and querying users. All of these components are incomplete and likely full of bugs. This release is intended for developers only - it is not useful for anything other than as a developer platform and is known to be insecure. Do not download this unless you want to do development and are subscribed to freeipa-devel. See http://freeipa.com/page/Downloads to download the release. The next release is scheduled (somewhat optimistically) for next Fri (Aug. 10) to include features that just missed this release. Karl From ssorce at redhat.com Wed Aug 1 21:28:03 2007 From: ssorce at redhat.com (Simo Sorce) Date: Wed, 01 Aug 2007 17:28:03 -0400 Subject: [Freeipa-devel] xml-rpc functions In-Reply-To: <46B0D6E7.1060700@redhat.com> References: <46B0D6E7.1060700@redhat.com> Message-ID: <1186003683.3336.14.camel@localhost.localdomain> On Wed, 2007-08-01 at 14:54 -0400, Rob Crittenden wrote: > I think it would be helpful to identify all the major functions that the > xml-rpc protocol will support. > > Off the top of my head I have: > > - add user > - retrieve user by uid > - modify user > - search for a user > - retrieve all users (could be a "*" of search for a user) > - remove user (deactivate) > > - get list of groups can we remove this and instead implement group search, which can also do a search for *, but I want to discourage the practice of just listing all users/groups at all costs, it usually ends up being used in interfaces and break horribly as soon as you pass the few thousands objects mark (not to talk about trusts and trying to lists users/groups from other realms in future) > - get group by id > - modify group > - add user(s) to group > - remove user(s) from group > - remove group > > - change password > > When doing a modify I wonder what we'll be sending. All attributes, just > those modified, something else? I'd send just those modified, sending back and forth a 4MB image embedded in the jpegPhoto attribute for nothing seem a bit overkill ... Simo. From prowley at redhat.com Wed Aug 1 22:41:26 2007 From: prowley at redhat.com (Pete Rowley) Date: Wed, 01 Aug 2007 15:41:26 -0700 Subject: [Freeipa-devel] xml-rpc functions In-Reply-To: <1186003683.3336.14.camel@localhost.localdomain> References: <46B0D6E7.1060700@redhat.com> <1186003683.3336.14.camel@localhost.localdomain> Message-ID: <46B10C16.2090000@redhat.com> Simo Sorce wrote: > On Wed, 2007-08-01 at 14:54 -0400, Rob Crittenden wrote: > >> I think it would be helpful to identify all the major functions that the >> xml-rpc protocol will support. >> >> Off the top of my head I have: >> >> - add user >> - retrieve user by uid >> - modify user >> - search for a user >> - retrieve all users (could be a "*" of search for a user) >> - remove user (deactivate) >> >> - get list of groups >> > > can we remove this and instead implement group search, which can also do > a search for * I'm working on a memberof plugin, so I suggest we work with groups like this: enumerate users in a group: search (memberof=group dn) enumerate user group membership: retrieve entry memberof attribute test group membership: ldap compare group dn on memberof attribute add user to group: retrieve group, make sure it is a groupofuniquenames, add entry dn to uniquemeber attribute delete user from group: like above Group based access control: aci on cn=people, aci: (targetattr="whatever")(targetfilter="(memberOf=cn=group Z,cn=groups,dc=example,dc=com )")(version 3.0; acl "Example group X can do Y to the members of group Z"; allow (permissions) groupdn="ldap:///cn=group X,cn=groups,dc=example,dc=com";) we /could/ instead do this with the bind rule: aci: (targetattr="whatever")(targetfilter="(memberOf=cn=group Z,cn=groups,dc=example,dc=com )")(version 3.0; acl "Example group X can do Y to the members of group Z"; allow (permissions) userdn="ldap:///dc=realm,dc=com??sub?(memberOf=cn=group X,cn=groups,dc=example,dc=com )";) This allows us the flexibility to support whatever memberof says is a group in access control e.g. roles, dynamic groups, what have you. Doing group operations using memberof means we never have to retrieve those monster membership lists so things should scale a little better in the UI. -- Pete -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3241 bytes Desc: S/MIME Cryptographic Signature URL: From prowley at redhat.com Wed Aug 1 23:59:02 2007 From: prowley at redhat.com (Pete Rowley) Date: Wed, 01 Aug 2007 16:59:02 -0700 Subject: [Freeipa-devel] xml-rpc functions In-Reply-To: <46B0D6E7.1060700@redhat.com> References: <46B0D6E7.1060700@redhat.com> Message-ID: <46B11E46.1030308@redhat.com> Rob Crittenden wrote: > - get list of groups > - get group by id > - modify group > - add user(s) to group > - remove user(s) from group > - remove group Add/del group to/from group too. -- Pete -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3241 bytes Desc: S/MIME Cryptographic Signature URL: From kmccarth at redhat.com Thu Aug 2 18:24:50 2007 From: kmccarth at redhat.com (Kevin McCarthy) Date: Thu, 2 Aug 2007 11:24:50 -0700 Subject: [Freeipa-devel] Invalid method: __nonzero__ from xmlrpc server Message-ID: <20070802182450.GB16474@moon.usersys.redhat.com> I've finally gotten TurboGears templates and such working more to my liking, so I've been playing with the get_user() RPC calls. After calling get_user once or twice, the xmlrpc server starts returning 'Invalid method: __nonzero__' responses. Is this a known issue, or perhaps get_user is only half-baked at this point? Thanks, -Kevin -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 2228 bytes Desc: not available URL: From rcritten at redhat.com Thu Aug 2 18:36:19 2007 From: rcritten at redhat.com (Rob Crittenden) Date: Thu, 02 Aug 2007 14:36:19 -0400 Subject: [Freeipa-devel] kerberos auth issue Message-ID: <46B22423.4010808@redhat.com> I ran into a problem with my kerberos authentication in the gui and just as I was preparing the patch. The current code calls for the XML-RPC server to be protected by kerberos. If authenticated, the server takes REMOTE_USER and uses that as the uid when doing proxying (we could also do a search using it as krbPrincipalName) so the request comes in via something like ipa-finduser which makes the actual HTTP request using the XML-RPC client (rpcclient.py) It is in there, during the XML-RPC request, that the GSSAPI magic happens. Now this same code in rpcclient.py was orignally going to be used by the GUI as well (write once, use for both) but the GUI is making the request through turbogears/Apache so we won't have the kerberos ticket because forwarding doesn't seem to work. One could argue that we'd do the kerberos auth in the web server that the GUI attaches to, but then how do we pass in the principal name to the XML-RPC server? An unprotected URI? Seems risky and we'd still need to get Apache to set REMOTE_USER. So I'm not sure what to do. I've attached a picture that shows the current architecture. The RPC client is a library that we'll ship and will be used on any client machine. It cannot be trusted. This would be trivial if forwarded tickets actually worked. rob -------------- next part -------------- A non-text attachment was scrubbed... Name: ipa.odg Type: application/vnd.oasis.opendocument.graphics Size: 9421 bytes Desc: not available URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3245 bytes Desc: S/MIME Cryptographic Signature URL: From rcritten at redhat.com Thu Aug 2 18:48:50 2007 From: rcritten at redhat.com (Rob Crittenden) Date: Thu, 02 Aug 2007 14:48:50 -0400 Subject: [Freeipa-devel] Invalid method: __nonzero__ from xmlrpc server In-Reply-To: <20070802182450.GB16474@moon.usersys.redhat.com> References: <20070802182450.GB16474@moon.usersys.redhat.com> Message-ID: <46B22712.2090204@redhat.com> Kevin McCarthy wrote: > I've finally gotten TurboGears templates and such working more to my > liking, so I've been playing with the get_user() RPC calls. After > calling get_user once or twice, the xmlrpc server starts returning > 'Invalid method: __nonzero__' responses. > > Is this a known issue, or perhaps get_user is only half-baked at this > point? > > Thanks, While get_user() isn't fully baked that is a very strange response from the server. Can you snoop on the connection to see what data is being passed back and forth? Is Apache logging anything? I think I'd do the following: 1. Set LogLevel debug in /etc/httpd/conf/httpd.conf 2. Stick ssltap between the XML-RPC client and server. To do this update the URL in rpcclient.py to point to your ssltap tool. Then hit it until you get the error. rob -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3245 bytes Desc: S/MIME Cryptographic Signature URL: From kmacmill at redhat.com Thu Aug 2 19:30:31 2007 From: kmacmill at redhat.com (Karl MacMillan) Date: Thu, 02 Aug 2007 15:30:31 -0400 Subject: [Freeipa-devel] kerberos auth issue In-Reply-To: <46B22423.4010808@redhat.com> References: <46B22423.4010808@redhat.com> Message-ID: <1186083031.28938.11.camel@localhost.localdomain> On Thu, 2007-08-02 at 14:36 -0400, Rob Crittenden wrote: > I ran into a problem with my kerberos authentication in the gui > and just as I was preparing the patch. > > The current code calls for the XML-RPC server to be protected by > kerberos. If authenticated, the server takes REMOTE_USER and uses that > as the uid when doing proxying (we could also do a search using it as > krbPrincipalName) so the request comes in via something like > ipa-finduser which makes the actual HTTP request using the XML-RPC > client (rpcclient.py) > > It is in there, during the XML-RPC request, that the GSSAPI magic happens. > > Now this same code in rpcclient.py was orignally going to be used by the > GUI as well (write once, use for both) but the GUI is making the request > through turbogears/Apache so we won't have the kerberos ticket because > forwarding doesn't seem to work. One could argue that we'd do the > kerberos auth in the web server that the GUI attaches to, but then how > do we pass in the principal name to the XML-RPC server? An unprotected > URI? Seems risky and we'd still need to get Apache to set REMOTE_USER. > I thought that the backend of the xml-rpc library was going to be a python library that the web gui would use directly. The architecture would be: xmlrpc-client -----> xmlrpc-server -------> DS krb cert browser -----------> web server ----------> DS That eliminates all of the problems, right? Karl From ssorce at redhat.com Thu Aug 2 19:35:22 2007 From: ssorce at redhat.com (Simo Sorce) Date: Thu, 02 Aug 2007 15:35:22 -0400 Subject: [Freeipa-devel] kerberos auth issue In-Reply-To: <1186083031.28938.11.camel@localhost.localdomain> References: <46B22423.4010808@redhat.com> <1186083031.28938.11.camel@localhost.localdomain> Message-ID: <1186083322.3336.18.camel@localhost.localdomain> On Thu, 2007-08-02 at 15:30 -0400, Karl MacMillan wrote: > On Thu, 2007-08-02 at 14:36 -0400, Rob Crittenden wrote: > > I ran into a problem with my kerberos authentication in the gui > > and just as I was preparing the patch. > > > > The current code calls for the XML-RPC server to be protected by > > kerberos. If authenticated, the server takes REMOTE_USER and uses that > > as the uid when doing proxying (we could also do a search using it as > > krbPrincipalName) so the request comes in via something like > > ipa-finduser which makes the actual HTTP request using the XML-RPC > > client (rpcclient.py) > > > > It is in there, during the XML-RPC request, that the GSSAPI magic happens. > > > > Now this same code in rpcclient.py was orignally going to be used by the > > GUI as well (write once, use for both) but the GUI is making the request > > through turbogears/Apache so we won't have the kerberos ticket because > > forwarding doesn't seem to work. One could argue that we'd do the > > kerberos auth in the web server that the GUI attaches to, but then how > > do we pass in the principal name to the XML-RPC server? An unprotected > > URI? Seems risky and we'd still need to get Apache to set REMOTE_USER. > > > > I thought that the backend of the xml-rpc library was going to be a > python library that the web gui would use directly. The architecture > would be: > > xmlrpc-client -----> xmlrpc-server -------> DS > krb cert > browser -----------> web server ----------> DS > > That eliminates all of the problems, right? Even better if we can replace cert with krb on the right side as well, even without forwarding, just reauth as a service with LDAP/SASL/GSSAPI Simo. From rcritten at redhat.com Thu Aug 2 20:02:13 2007 From: rcritten at redhat.com (Rob Crittenden) Date: Thu, 02 Aug 2007 16:02:13 -0400 Subject: [Freeipa-devel] kerberos auth issue In-Reply-To: <1186083031.28938.11.camel@localhost.localdomain> References: <46B22423.4010808@redhat.com> <1186083031.28938.11.camel@localhost.localdomain> Message-ID: <46B23845.40407@redhat.com> Karl MacMillan wrote: > On Thu, 2007-08-02 at 14:36 -0400, Rob Crittenden wrote: >> I ran into a problem with my kerberos authentication in the gui >> and just as I was preparing the patch. >> >> The current code calls for the XML-RPC server to be protected by >> kerberos. If authenticated, the server takes REMOTE_USER and uses that >> as the uid when doing proxying (we could also do a search using it as >> krbPrincipalName) so the request comes in via something like >> ipa-finduser which makes the actual HTTP request using the XML-RPC >> client (rpcclient.py) >> >> It is in there, during the XML-RPC request, that the GSSAPI magic happens. >> >> Now this same code in rpcclient.py was orignally going to be used by the >> GUI as well (write once, use for both) but the GUI is making the request >> through turbogears/Apache so we won't have the kerberos ticket because >> forwarding doesn't seem to work. One could argue that we'd do the >> kerberos auth in the web server that the GUI attaches to, but then how >> do we pass in the principal name to the XML-RPC server? An unprotected >> URI? Seems risky and we'd still need to get Apache to set REMOTE_USER. >> > > I thought that the backend of the xml-rpc library was going to be a > python library that the web gui would use directly. The architecture > would be: > > xmlrpc-client -----> xmlrpc-server -------> DS > krb cert > browser -----------> web server ----------> DS > > That eliminates all of the problems, right? It does but it also means the two clients aren't playing on the same field. I don't think there is another easy way around it without introducing some ugly mechanism (uglier than a web server talking to a web server). I'll have to consider the impact on the client libraries. rob -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3245 bytes Desc: S/MIME Cryptographic Signature URL: From kmacmill at redhat.com Thu Aug 2 20:05:28 2007 From: kmacmill at redhat.com (Karl MacMillan) Date: Thu, 02 Aug 2007 16:05:28 -0400 Subject: [Freeipa-devel] kerberos auth issue In-Reply-To: <46B23845.40407@redhat.com> References: <46B22423.4010808@redhat.com> <1186083031.28938.11.camel@localhost.localdomain> <46B23845.40407@redhat.com> Message-ID: <1186085128.28938.15.camel@localhost.localdomain> On Thu, 2007-08-02 at 16:02 -0400, Rob Crittenden wrote: > Karl MacMillan wrote: > > On Thu, 2007-08-02 at 14:36 -0400, Rob Crittenden wrote: > >> I ran into a problem with my kerberos authentication in the gui > >> and just as I was preparing the patch. > >> > >> The current code calls for the XML-RPC server to be protected by > >> kerberos. If authenticated, the server takes REMOTE_USER and uses that > >> as the uid when doing proxying (we could also do a search using it as > >> krbPrincipalName) so the request comes in via something like > >> ipa-finduser which makes the actual HTTP request using the XML-RPC > >> client (rpcclient.py) > >> > >> It is in there, during the XML-RPC request, that the GSSAPI magic happens. > >> > >> Now this same code in rpcclient.py was orignally going to be used by the > >> GUI as well (write once, use for both) but the GUI is making the request > >> through turbogears/Apache so we won't have the kerberos ticket because > >> forwarding doesn't seem to work. One could argue that we'd do the > >> kerberos auth in the web server that the GUI attaches to, but then how > >> do we pass in the principal name to the XML-RPC server? An unprotected > >> URI? Seems risky and we'd still need to get Apache to set REMOTE_USER. > >> > > > > I thought that the backend of the xml-rpc library was going to be a > > python library that the web gui would use directly. The architecture > > would be: > > > > xmlrpc-client -----> xmlrpc-server -------> DS > > krb cert > > browser -----------> web server ----------> DS > > > > That eliminates all of the problems, right? > > It does but it also means the two clients aren't playing on the same > field. Sure - but that is a good thing. The interactivity of the web browser and the likelihood of viewing much more data mean that removing the xmlrpc layer could improve performance substantially. I'm not that worried about performance with the commandline tools because of how they are likely to be used. > I don't think there is another easy way around it without > introducing some ugly mechanism (uglier than a web server talking to a > web server). > Not certain what you mean. > I'll have to consider the impact on the client libraries. > Sure - it will require coding things somewhat differently. Karl From kmacmill at redhat.com Thu Aug 2 20:06:31 2007 From: kmacmill at redhat.com (Karl MacMillan) Date: Thu, 02 Aug 2007 16:06:31 -0400 Subject: [Freeipa-devel] kerberos auth issue In-Reply-To: <1186083322.3336.18.camel@localhost.localdomain> References: <46B22423.4010808@redhat.com> <1186083031.28938.11.camel@localhost.localdomain> <1186083322.3336.18.camel@localhost.localdomain> Message-ID: <1186085191.28938.17.camel@localhost.localdomain> On Thu, 2007-08-02 at 15:35 -0400, Simo Sorce wrote: > On Thu, 2007-08-02 at 15:30 -0400, Karl MacMillan wrote: > > On Thu, 2007-08-02 at 14:36 -0400, Rob Crittenden wrote: > > > I ran into a problem with my kerberos authentication in the gui > > > and just as I was preparing the patch. > > > > > > The current code calls for the XML-RPC server to be protected by > > > kerberos. If authenticated, the server takes REMOTE_USER and uses that > > > as the uid when doing proxying (we could also do a search using it as > > > krbPrincipalName) so the request comes in via something like > > > ipa-finduser which makes the actual HTTP request using the XML-RPC > > > client (rpcclient.py) > > > > > > It is in there, during the XML-RPC request, that the GSSAPI magic happens. > > > > > > Now this same code in rpcclient.py was orignally going to be used by the > > > GUI as well (write once, use for both) but the GUI is making the request > > > through turbogears/Apache so we won't have the kerberos ticket because > > > forwarding doesn't seem to work. One could argue that we'd do the > > > kerberos auth in the web server that the GUI attaches to, but then how > > > do we pass in the principal name to the XML-RPC server? An unprotected > > > URI? Seems risky and we'd still need to get Apache to set REMOTE_USER. > > > > > > > I thought that the backend of the xml-rpc library was going to be a > > python library that the web gui would use directly. The architecture > > would be: > > > > xmlrpc-client -----> xmlrpc-server -------> DS > > krb cert > > browser -----------> web server ----------> DS > > > > That eliminates all of the problems, right? > > Even better if we can replace cert with krb on the right side as well, > even without forwarding, just reauth as a service with LDAP/SASL/GSSAPI > Agreed, but the choice is being driven by ease of development right now and it seems the python kerb libraries aren't the best. Karl From ssorce at redhat.com Thu Aug 2 20:25:59 2007 From: ssorce at redhat.com (Simo Sorce) Date: Thu, 02 Aug 2007 16:25:59 -0400 Subject: [Freeipa-devel] kerberos auth issue In-Reply-To: <1186085191.28938.17.camel@localhost.localdomain> References: <46B22423.4010808@redhat.com> <1186083031.28938.11.camel@localhost.localdomain> <1186083322.3336.18.camel@localhost.localdomain> <1186085191.28938.17.camel@localhost.localdomain> Message-ID: <1186086359.3336.21.camel@localhost.localdomain> On Thu, 2007-08-02 at 16:06 -0400, Karl MacMillan wrote: > On Thu, 2007-08-02 at 15:35 -0400, Simo Sorce wrote: > > On Thu, 2007-08-02 at 15:30 -0400, Karl MacMillan wrote: > > > On Thu, 2007-08-02 at 14:36 -0400, Rob Crittenden wrote: > > > > I ran into a problem with my kerberos authentication in the gui > > > > and just as I was preparing the patch. > > > > > > > > The current code calls for the XML-RPC server to be protected by > > > > kerberos. If authenticated, the server takes REMOTE_USER and uses that > > > > as the uid when doing proxying (we could also do a search using it as > > > > krbPrincipalName) so the request comes in via something like > > > > ipa-finduser which makes the actual HTTP request using the XML-RPC > > > > client (rpcclient.py) > > > > > > > > It is in there, during the XML-RPC request, that the GSSAPI magic happens. > > > > > > > > Now this same code in rpcclient.py was orignally going to be used by the > > > > GUI as well (write once, use for both) but the GUI is making the request > > > > through turbogears/Apache so we won't have the kerberos ticket because > > > > forwarding doesn't seem to work. One could argue that we'd do the > > > > kerberos auth in the web server that the GUI attaches to, but then how > > > > do we pass in the principal name to the XML-RPC server? An unprotected > > > > URI? Seems risky and we'd still need to get Apache to set REMOTE_USER. > > > > > > > > > > I thought that the backend of the xml-rpc library was going to be a > > > python library that the web gui would use directly. The architecture > > > would be: > > > > > > xmlrpc-client -----> xmlrpc-server -------> DS > > > krb cert > > > browser -----------> web server ----------> DS > > > > > > That eliminates all of the problems, right? > > > > Even better if we can replace cert with krb on the right side as well, > > even without forwarding, just reauth as a service with LDAP/SASL/GSSAPI > > > > Agreed, but the choice is being driven by ease of development right now > and it seems the python kerb libraries aren't the best. it should be almost all handled by the sasl libraries linked to the ldap libraries. all we need to do is to do the equivalent of a kinit before we initiate the ldap bind. if the python bindings can't even do this then they are completely broken and worthless (and yes I am volunteering to do the job later on, when I finish to fight with the kpasswd stuff :-). Simo. From rcritten at redhat.com Thu Aug 2 20:33:05 2007 From: rcritten at redhat.com (Rob Crittenden) Date: Thu, 02 Aug 2007 16:33:05 -0400 Subject: [Freeipa-devel] kerberos auth issue In-Reply-To: <1186085128.28938.15.camel@localhost.localdomain> References: <46B22423.4010808@redhat.com> <1186083031.28938.11.camel@localhost.localdomain> <46B23845.40407@redhat.com> <1186085128.28938.15.camel@localhost.localdomain> Message-ID: <46B23F81.4080607@redhat.com> Karl MacMillan wrote: > On Thu, 2007-08-02 at 16:02 -0400, Rob Crittenden wrote: >> Karl MacMillan wrote: >>> On Thu, 2007-08-02 at 14:36 -0400, Rob Crittenden wrote: >>>> I ran into a problem with my kerberos authentication in the gui >>>> and just as I was preparing the patch. >>>> >>>> The current code calls for the XML-RPC server to be protected by >>>> kerberos. If authenticated, the server takes REMOTE_USER and uses that >>>> as the uid when doing proxying (we could also do a search using it as >>>> krbPrincipalName) so the request comes in via something like >>>> ipa-finduser which makes the actual HTTP request using the XML-RPC >>>> client (rpcclient.py) >>>> >>>> It is in there, during the XML-RPC request, that the GSSAPI magic happens. >>>> >>>> Now this same code in rpcclient.py was orignally going to be used by the >>>> GUI as well (write once, use for both) but the GUI is making the request >>>> through turbogears/Apache so we won't have the kerberos ticket because >>>> forwarding doesn't seem to work. One could argue that we'd do the >>>> kerberos auth in the web server that the GUI attaches to, but then how >>>> do we pass in the principal name to the XML-RPC server? An unprotected >>>> URI? Seems risky and we'd still need to get Apache to set REMOTE_USER. >>>> >>> I thought that the backend of the xml-rpc library was going to be a >>> python library that the web gui would use directly. The architecture >>> would be: >>> >>> xmlrpc-client -----> xmlrpc-server -------> DS >>> krb cert >>> browser -----------> web server ----------> DS >>> >>> That eliminates all of the problems, right? >> It does but it also means the two clients aren't playing on the same >> field. > > Sure - but that is a good thing. The interactivity of the web browser > and the likelihood of viewing much more data mean that removing the > xmlrpc layer could improve performance substantially. I'm not that > worried about performance with the commandline tools because of how they > are likely to be used. That's fine, it's just a change from what was originally discussed. >> I don't think there is another easy way around it without >> introducing some ugly mechanism (uglier than a web server talking to a >> web server). >> > > Not certain what you mean. I mean doing something funky like another SSL client auth and embed the kerberos ticket name in the xml-rpc request. >> I'll have to consider the impact on the client libraries. >> > > Sure - it will require coding things somewhat differently. The biggest change is that XML-RPC transforms the data. So while the calls between the CLI and GUI will probably have the same names the data format will be completely different. I'm not necessarily opposed to this I just want to be sure it really is our last option. rob -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3245 bytes Desc: S/MIME Cryptographic Signature URL: From kmccarth at redhat.com Thu Aug 2 20:47:46 2007 From: kmccarth at redhat.com (Kevin McCarthy) Date: Thu, 2 Aug 2007 13:47:46 -0700 Subject: [Freeipa-devel] Suggest install not overwrite /etc/ipa/ipa.conf Message-ID: <20070802204745.GD16474@moon.usersys.redhat.com> I just spent a little time trying to figure out why my xmlrpc calls broken suddenly. It turns out I forgot that redoing a 'make install' will overwrite the ipa.conf file. We may want to consider whether it's a good idea to do this. It certainly can be inconvient to remember to re-edit the conf file each time. -Kevin -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 2228 bytes Desc: not available URL: From kmccarth at redhat.com Thu Aug 2 20:56:28 2007 From: kmccarth at redhat.com (Kevin McCarthy) Date: Thu, 2 Aug 2007 13:56:28 -0700 Subject: [Freeipa-devel] Invalid method: __nonzero__ from xmlrpc server In-Reply-To: <20070802182450.GB16474@moon.usersys.redhat.com> References: <20070802182450.GB16474@moon.usersys.redhat.com> Message-ID: <20070802205628.GE16474@moon.usersys.redhat.com> Kevin McCarthy wrote: > I've finally gotten TurboGears templates and such working more to my > liking, so I've been playing with the get_user() RPC calls. After > calling get_user once or twice, the xmlrpc server starts returning > 'Invalid method: __nonzero__' responses. After following Rob's advice, I've found the problem is with the client side. If you send two get_user() requests, the second one sends a garbage request to the server: __nonzero__ It looks like this is because we're reusing the same xmlrpclib.ServerProxy over and over. I'm guessing you need to use a new one for each connection. When I change the code to: def setup_server(): # global server # if not server: # server = xmlrpclib.ServerProxy(server_url()) return xmlrpclib.ServerProxy(server_url()) def get_user(username): """Get a specific user""" server = setup_server() ... it works just fine. Although the point is moot (since we're changing the web gui) we probably didn't want to use a class variable anyway (I don't know if TurboGears is multi-threaded). -Kevin -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 2228 bytes Desc: not available URL: From kmccarth at redhat.com Thu Aug 2 21:17:50 2007 From: kmccarth at redhat.com (Kevin McCarthy) Date: Thu, 2 Aug 2007 14:17:50 -0700 Subject: [Freeipa-devel] [PATCH] Invalid method: __nonzero__ from xmlrpc server In-Reply-To: <20070802205628.GE16474@moon.usersys.redhat.com> References: <20070802182450.GB16474@moon.usersys.redhat.com> <20070802205628.GE16474@moon.usersys.redhat.com> Message-ID: <20070802211749.GF16474@moon.usersys.redhat.com> Kevin McCarthy wrote: > After following Rob's advice, I've found the problem is with the client > side. If you send two get_user() requests, the second one sends a > garbage request to the server: > it works just fine. Although the point is moot (since we're changing > the web gui) we probably didn't want to use a class variable anyway (I > don't know if TurboGears is multi-threaded). # HG changeset patch # User kmccarth at redhat.com # Date 1186089340 25200 # Node ID 976cae2e4f76528be7f73a05383c519b374bb4a1 # Parent 6e4eb9605ab04e11e41ff763107969e53fc8a388 Change server to not be global nor reused. diff -r 6e4eb9605ab0 -r 976cae2e4f76 ipa-python/rpcclient.py --- a/ipa-python/rpcclient.py Wed Aug 01 16:26:04 2007 -0400 +++ b/ipa-python/rpcclient.py Thu Aug 02 14:15:40 2007 -0700 @@ -34,17 +34,12 @@ def server_url(): def server_url(): return "http://" + config.config.get_server() + "/ipa" -# FIXME: do we want this set somewhere else? -server = None - def setup_server(): - global server - if not server: - server = xmlrpclib.ServerProxy(server_url()) + return xmlrpclib.ServerProxy(server_url()) def get_user(username): """Get a specific user""" - setup_server() + server = setup_server() try: result = server.get_user(username) myuser = result @@ -59,7 +54,7 @@ def get_user(username): def add_user(user): """Add a new user""" - setup_server() + server = setup_server() # FIXME: Get the realm from somewhere realm = config.config.get_realm() @@ -95,7 +90,7 @@ def get_add_schema(): """Get the list of attributes we need to ask when adding a new user. """ - setup_server() + server = setup_server() # FIXME: Hardcoded and designed for the TurboGears GUI. Do we want # this for the CLI as well? -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 2228 bytes Desc: not available URL: From kmccarth at redhat.com Thu Aug 2 22:03:35 2007 From: kmccarth at redhat.com (Kevin McCarthy) Date: Thu, 2 Aug 2007 15:03:35 -0700 Subject: [Freeipa-devel] [PATCH] use givenName instead of gn Message-ID: <20070802220335.GG16474@moon.usersys.redhat.com> # HG changeset patch # User kmccarth at redhat.com # Date 1186092119 25200 # Node ID 84332d3186f28b8b4a0a67b2567a2bbbe3981e60 # Parent 976cae2e4f76528be7f73a05383c519b374bb4a1 Rename 'gn' to 'givenName' diff -r 976cae2e4f76 -r 84332d3186f2 ipa-admintools/ipa-adduser --- a/ipa-admintools/ipa-adduser Thu Aug 02 14:15:40 2007 -0700 +++ b/ipa-admintools/ipa-adduser Thu Aug 02 15:01:59 2007 -0700 @@ -60,7 +60,7 @@ def main(): if len(args) != 2: usage() - user['gn'] = options.gn + user['givenName'] = options.gn user['sn'] = options.sn user['uid'] = args[1] if options.gecos: diff -r 976cae2e4f76 -r 84332d3186f2 ipa-python/rpcclient.py --- a/ipa-python/rpcclient.py Thu Aug 02 14:15:40 2007 -0700 +++ b/ipa-python/rpcclient.py Thu Aug 02 15:01:59 2007 -0700 @@ -72,9 +72,7 @@ def add_user(user): # FIXME: What is the default group for users? user['gidNumber'] ='501' user['krbPrincipalName'] = "%s@%s" % (user['uid'], realm) - user['cn'] = "%s %s" % (user['gn'], user['sn']) - if user.get('gn'): - del user['gn'] + user['cn'] = "%s %s" % (user['givenName'], user['sn']) try: result = server.add_user(user) diff -r 976cae2e4f76 -r 84332d3186f2 ipa-server/xmlrpc-server/funcs.py --- a/ipa-server/xmlrpc-server/funcs.py Thu Aug 02 14:15:40 2007 -0700 +++ b/ipa-server/xmlrpc-server/funcs.py Thu Aug 02 15:01:59 2007 -0700 @@ -146,7 +146,7 @@ def get_add_schema (): fields.append(field1) field1 = { - "name": "gn" , + "name": "givenName" , "label": "First name:", "type": "text", "validator": "string", -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 2228 bytes Desc: not available URL: From kmccarth at redhat.com Thu Aug 2 22:30:13 2007 From: kmccarth at redhat.com (Kevin McCarthy) Date: Thu, 2 Aug 2007 15:30:13 -0700 Subject: [Freeipa-devel] [PATCH] Invalid method: __nonzero__ from xmlrpc server In-Reply-To: <20070802211749.GF16474@moon.usersys.redhat.com> References: <20070802182450.GB16474@moon.usersys.redhat.com> <20070802205628.GE16474@moon.usersys.redhat.com> <20070802211749.GF16474@moon.usersys.redhat.com> Message-ID: <20070802223012.GH16474@moon.usersys.redhat.com> Kevin McCarthy wrote: > Kevin McCarthy wrote: > > After following Rob's advice, I've found the problem is with the client > > side. If you send two get_user() requests, the second one sends a > > garbage request to the server: > > > it works just fine. Although the point is moot (since we're changing > > the web gui) we probably didn't want to use a class variable anyway (I > > don't know if TurboGears is multi-threaded). The funny thing is, repeated calls to add_user() don't seem to generate this error, only get_user(). Perhaps someone else can see why that's the case... the only difference I see is the add_user() is passing a Hash versus get_user() only passing a string parameter. -Kevin -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 2228 bytes Desc: not available URL: From kmacmill at redhat.com Fri Aug 3 14:03:05 2007 From: kmacmill at redhat.com (Karl MacMillan) Date: Fri, 03 Aug 2007 10:03:05 -0400 Subject: [Freeipa-devel] [PATCH] use givenName instead of gn In-Reply-To: <20070802220335.GG16474@moon.usersys.redhat.com> References: <20070802220335.GG16474@moon.usersys.redhat.com> Message-ID: <1186149785.32430.3.camel@localhost.localdomain> On Thu, 2007-08-02 at 15:03 -0700, Kevin McCarthy wrote: > # HG changeset patch > # User kmccarth at redhat.com > # Date 1186092119 25200 > # Node ID 84332d3186f28b8b4a0a67b2567a2bbbe3981e60 > # Parent 976cae2e4f76528be7f73a05383c519b374bb4a1 > Rename 'gn' to 'givenName' > Committed - btw, sending patches in this form makes my job _very_ easy. I can directly import them into my tree and push and all of the changeset comments are preserved. Karl From kmacmill at redhat.com Fri Aug 3 14:04:33 2007 From: kmacmill at redhat.com (Karl MacMillan) Date: Fri, 03 Aug 2007 10:04:33 -0400 Subject: [Freeipa-devel] [PATCH] Invalid method: __nonzero__ from xmlrpc server In-Reply-To: <20070802211749.GF16474@moon.usersys.redhat.com> References: <20070802182450.GB16474@moon.usersys.redhat.com> <20070802205628.GE16474@moon.usersys.redhat.com> <20070802211749.GF16474@moon.usersys.redhat.com> Message-ID: <1186149873.32430.5.camel@localhost.localdomain> On Thu, 2007-08-02 at 14:17 -0700, Kevin McCarthy wrote: > Kevin McCarthy wrote: > > After following Rob's advice, I've found the problem is with the client > > side. If you send two get_user() requests, the second one sends a > > garbage request to the server: > > > it works just fine. Although the point is moot (since we're changing > > the web gui) we probably didn't want to use a class variable anyway (I > > don't know if TurboGears is multi-threaded). > > # HG changeset patch > # User kmccarth at redhat.com > # Date 1186089340 25200 > # Node ID 976cae2e4f76528be7f73a05383c519b374bb4a1 > # Parent 6e4eb9605ab04e11e41ff763107969e53fc8a388 > Change server to not be global nor reused. > I went ahead and committed this even though it may turn out to be the wrong fix. Karl From ssorce at redhat.com Fri Aug 3 14:15:39 2007 From: ssorce at redhat.com (Simo Sorce) Date: Fri, 03 Aug 2007 10:15:39 -0400 Subject: [Freeipa-devel] [PATCH] use givenName instead of gn In-Reply-To: <20070802220335.GG16474@moon.usersys.redhat.com> References: <20070802220335.GG16474@moon.usersys.redhat.com> Message-ID: <1186150539.23292.0.camel@localhost.localdomain> On Thu, 2007-08-02 at 15:03 -0700, Kevin McCarthy wrote: > # HG changeset patch > # User kmccarth at redhat.com > # Date 1186092119 25200 > # Node ID 84332d3186f28b8b4a0a67b2567a2bbbe3981e60 > # Parent 976cae2e4f76528be7f73a05383c519b374bb4a1 > Rename 'gn' to 'givenName' What do we use givenName for? Simo. From rcritten at redhat.com Fri Aug 3 14:24:04 2007 From: rcritten at redhat.com (Rob Crittenden) Date: Fri, 03 Aug 2007 10:24:04 -0400 Subject: [Freeipa-devel] [PATCH] use givenName instead of gn In-Reply-To: <1186150539.23292.0.camel@localhost.localdomain> References: <20070802220335.GG16474@moon.usersys.redhat.com> <1186150539.23292.0.camel@localhost.localdomain> Message-ID: <46B33A84.4010308@redhat.com> Simo Sorce wrote: > On Thu, 2007-08-02 at 15:03 -0700, Kevin McCarthy wrote: >> # HG changeset patch >> # User kmccarth at redhat.com >> # Date 1186092119 25200 >> # Node ID 84332d3186f28b8b4a0a67b2567a2bbbe3981e60 >> # Parent 976cae2e4f76528be7f73a05383c519b374bb4a1 >> Rename 'gn' to 'givenName' > > What do we use givenName for? > This is part of a proof-of-concept I did for the GUI. We want parts of the GUI to be generated dynamically so I experimented with providing the list of attributes to display via XML-RPC. I had separate first and last name entry fields, hence givenName. It is likely not something that we want to do in the long-run. rob -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3245 bytes Desc: S/MIME Cryptographic Signature URL: From kmacmill at redhat.com Fri Aug 3 14:30:52 2007 From: kmacmill at redhat.com (Karl MacMillan) Date: Fri, 03 Aug 2007 10:30:52 -0400 Subject: [PATCH] Re: [Freeipa-devel] Suggest install not overwrite /etc/ipa/ipa.conf In-Reply-To: <20070802204745.GD16474@moon.usersys.redhat.com> References: <20070802204745.GD16474@moon.usersys.redhat.com> Message-ID: <1186151452.32430.8.camel@localhost.localdomain> On Thu, 2007-08-02 at 13:47 -0700, Kevin McCarthy wrote: > I just spent a little time trying to figure out why my xmlrpc calls > broken suddenly. It turns out I forgot that redoing a 'make install' > will overwrite the ipa.conf file. We may want to consider whether it's > a good idea to do this. It certainly can be inconvient to remember to > re-edit the conf file each time. > Just committed the patch below to implement this suggestion. [?1034h# HG changeset patch # User "Karl MacMillan " # Date 1186151279 14400 # Node ID ae9112b5c733c14a04077d02bc6a50544e4a0901 # Parent b1d8edff7ea26807d8d61e442019a642e0498819 Only install the ipa.conf file if it does not exist. diff -r b1d8edff7ea2 -r ae9112b5c733 ipa-python/Makefile --- a/ipa-python/Makefile Thu Aug 02 14:15:40 2007 -0700 +++ b/ipa-python/Makefile Fri Aug 03 10:27:59 2007 -0400 @@ -8,7 +8,10 @@ install: -mkdir -p $(PACKAGEDIR) install -m 644 *.py $(PACKAGEDIR) -mkdir -p $(CONFIGDIR) - install -m 644 ipa.conf $(CONFIGDIR) + if ! [ -e $(CONFIGDIR)/ipa.conf ]; then \ + install -m 644 ipa.conf $(CONFIGDIR); \ + fi clean: - rm -f *~ *.pyc \ No newline at end of file + rm -f *~ *.pyc + From rcritten at redhat.com Fri Aug 3 15:42:54 2007 From: rcritten at redhat.com (Rob Crittenden) Date: Fri, 03 Aug 2007 11:42:54 -0400 Subject: [Freeipa-devel] kerberos auth issue In-Reply-To: <1186083031.28938.11.camel@localhost.localdomain> References: <46B22423.4010808@redhat.com> <1186083031.28938.11.camel@localhost.localdomain> Message-ID: <46B34CFE.1030906@redhat.com> Karl MacMillan wrote: > I thought that the backend of the xml-rpc library was going to be a > python library that the web gui would use directly. The architecture > would be: > > xmlrpc-client -----> xmlrpc-server -------> DS > krb cert > browser -----------> web server ----------> DS > > That eliminates all of the problems, right? > > Karl > Long, sorry in advance. Part of the problem I'm having is that rpcclient.py does a bit of data massaging. We can move some of this to the server side, it probably belonds there anyway (such as including extra attributes when adding a user). This move will be done later. But with other stuff some data conversion happens and that has to be in the client. As a test I copied rpcclient.py to webclient.py and updated my basic TG gui to use webclient.py instead. All I did was replace the server. calls with funcs. calls (effectively skipping the XML-RPC call). The one call I tested worked with no changes to the GUI code except changing the API names. So. I think that if we expand on this idea we can still have a more-or-less unified client API, but one goes through XML-RPC and one doesn't. The trick will be telling which is which. This is where I'm getting hung up. So simply copying rpcclient.py to webclient.py is obviously wrong, code duplication up the wazoo. Updating rpcclient.py to know the difference between a local and an XML-RPC call just feels wrong since rpcclient.py will be visible on untrusted client machines. I'm not sure I want to advertise to every IPA client that there is a local mechanism (b/c it won't be available). The function call names in the RPC client and server are the same (all 3 of them currently) and I wonder if we can use that to our advantage. A typical rpcclient.py call looks like: server = xmlrpclib.ServerProxy() result = server.somefunction(args) A typical webclient.py looks like: opts={} opts['remoteuser'] = req.user result = funcs.somefunction(args, opts) Otherwise everything else will be the same. I'm having a hard time abstracting this. I suppose we could add yet another layer on this and have a client.py that does all the actual work. The API would be: function(args, opts=None) If opts is None then we're doing RPC, otherwise local. But this will be a very, very long stack. req.user will hold the principal we're going to proxy as. Not sure how we'll do this in turbogears but we can probably fake it for a while (until we get it running in mod_python). So, any ideas? I have the feeling there is some clever python trick we can use. thanks rob -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3245 bytes Desc: S/MIME Cryptographic Signature URL: From kmccarth at redhat.com Fri Aug 3 16:04:50 2007 From: kmccarth at redhat.com (Kevin McCarthy) Date: Fri, 3 Aug 2007 09:04:50 -0700 Subject: [Freeipa-devel] kerberos auth issue In-Reply-To: <46B34CFE.1030906@redhat.com> References: <46B22423.4010808@redhat.com> <1186083031.28938.11.camel@localhost.localdomain> <46B34CFE.1030906@redhat.com> Message-ID: <20070803160450.GC12644@moon.usersys.redhat.com> Rob Crittenden wrote: > Part of the problem I'm having is that rpcclient.py does a bit of data > massaging. We can move some of this to the server side, it probably belonds > there anyway (such as including extra attributes when adding a user). This > move will be done later. > > But with other stuff some data conversion happens and that has to be in the > client. > As a test I copied rpcclient.py to webclient.py and updated my basic TG gui > to use webclient.py instead. All I did was replace the server. calls with > funcs. calls (effectively skipping the XML-RPC call). The one call I tested > worked with no changes to the GUI code except changing the API names. > > So. I think that if we expand on this idea we can still have a more-or-less > unified client API, but one goes through XML-RPC and one doesn't. The trick > will be telling which is which. This is where I'm getting hung up. > > So simply copying rpcclient.py to webclient.py is obviously wrong, code > duplication up the wazoo. It sounds like you want two layers on the client Client -> ipaclient.get_user() -> funcs.get_user() [does data massage] - or - xmlrpc.get_user() but you don't want to advertise that there's a local transport. Since we're open source, I don't think there will be too much confusion if we comment properly. Ideally I'd like an optional argument to the ipaclient constructor that enables the local client (it defaults to xmlrpc) class IPAClient: def __init__(local = false): if local: self.transport = funcs() else: self.transport = xmlrpc() def get_user(uid): result = self.transport.get_user(uid) [ data massage] return def add_user(user): [ data massage] result = self.transport.add_user(user) Maybe this was exactly what you were saying in your email though. :-) -Kevin -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 2228 bytes Desc: not available URL: From kmacmill at redhat.com Fri Aug 3 16:09:09 2007 From: kmacmill at redhat.com (Karl MacMillan) Date: Fri, 03 Aug 2007 12:09:09 -0400 Subject: [Freeipa-devel] kerberos auth issue In-Reply-To: <46B34CFE.1030906@redhat.com> References: <46B22423.4010808@redhat.com> <1186083031.28938.11.camel@localhost.localdomain> <46B34CFE.1030906@redhat.com> Message-ID: <1186157349.32430.44.camel@localhost.localdomain> On Fri, 2007-08-03 at 11:42 -0400, Rob Crittenden wrote: > Karl MacMillan wrote: > > I thought that the backend of the xml-rpc library was going to be a > > python library that the web gui would use directly. The architecture > > would be: > > > > xmlrpc-client -----> xmlrpc-server -------> DS > > krb cert > > browser -----------> web server ----------> DS > > > > That eliminates all of the problems, right? > > > > Karl > > > > Long, sorry in advance. > > Part of the problem I'm having is that rpcclient.py does a bit of data > massaging. We can move some of this to the server side, it probably > belonds there anyway (such as including extra attributes when adding a > user). This move will be done later. > Agreed - if our local client library does too much then the usability of the xmlrpc api will go down dramatically. > But with other stuff some data conversion happens and that has to be in > the client. > > As a test I copied rpcclient.py to webclient.py and updated my basic TG > gui to use webclient.py instead. All I did was replace the server. calls > with funcs. calls (effectively skipping the XML-RPC call). The one call > I tested worked with no changes to the GUI code except changing the API > names. > > So. I think that if we expand on this idea we can still have a > more-or-less unified client API, but one goes through XML-RPC and one > doesn't. The trick will be telling which is which. This is where I'm > getting hung up. > > So simply copying rpcclient.py to webclient.py is obviously wrong, code > duplication up the wazoo. > Now - I may just have this all wrong because I'm not familiar enough with the details, but what I had imagined was a bit different. I assumed that we would have a common layer underneath the xmlrpc server and web server: ---------- -------------- | xmlrpc | | web server | ---------- -------------- ------------------------------ | python serverlib.py | ------------------------------ || || \/ ------------------------------ | DS | ------------------------------ The role of serverlib.py is to abstract away the ldap detail so that we get calls like: adduser(realm, userObject) uerObject = finduser(realm, userName) It would handle authenticating to the DS locally, ldap calls, and provide a unified data model. That means userObject would be a python object that has all of the 'exported' attributes of a user (including custom attributes set by a local admin). Both the xmlrpc and web gui would be written to this api and would really only be concerned with the details unique to each. For the xmlrpc server that means, really, just marshalling / unmarshalling data and making calls to serverlib.py. The web server would just be the web presentation layer. Now, what seems to be concerning you is that the data model in serverlib.py will be very similar to the data model in rpcclient.py (and there will be some overlap in xmlrpc), correct? That seems easy to handle if you make the data objects (like userObject above) simple collections of data and factor out marshalling / unmarshalling to xmlrpc and ldap. So, you get something like user.py which is shared by serverlib.py and rpcclient.py (as in shipped in both) and used by the xmlrpc server. You would also have userldap.py (unique to serverlib.py) and userxmlprc.py. Those would contain functions for doing data conversion, etc., etc. Does that make any sense or help in any way? Karl From kmacmill at redhat.com Fri Aug 3 16:15:21 2007 From: kmacmill at redhat.com (Karl MacMillan) Date: Fri, 03 Aug 2007 12:15:21 -0400 Subject: [Freeipa-devel] kerberos auth issue In-Reply-To: <20070803160450.GC12644@moon.usersys.redhat.com> References: <46B22423.4010808@redhat.com> <1186083031.28938.11.camel@localhost.localdomain> <46B34CFE.1030906@redhat.com> <20070803160450.GC12644@moon.usersys.redhat.com> Message-ID: <1186157721.32430.50.camel@localhost.localdomain> On Fri, 2007-08-03 at 09:04 -0700, Kevin McCarthy wrote: > Rob Crittenden wrote: > > Part of the problem I'm having is that rpcclient.py does a bit of data > > massaging. We can move some of this to the server side, it probably belonds > > there anyway (such as including extra attributes when adding a user). This > > move will be done later. > > > > But with other stuff some data conversion happens and that has to be in the > > client. > > > > As a test I copied rpcclient.py to webclient.py and updated my basic TG gui > > to use webclient.py instead. All I did was replace the server. calls with > > funcs. calls (effectively skipping the XML-RPC call). The one call I tested > > worked with no changes to the GUI code except changing the API names. > > > > So. I think that if we expand on this idea we can still have a more-or-less > > unified client API, but one goes through XML-RPC and one doesn't. The trick > > will be telling which is which. This is where I'm getting hung up. > > > > So simply copying rpcclient.py to webclient.py is obviously wrong, code > > duplication up the wazoo. > > It sounds like you want two layers on the client > I don't think so (but may be wrong) - I think it is better to have separate objects for each 'transport' in separate libraries with a shared data model. That way rpcclient.py doesn't have all sorts of cruft that should never be seen on a client (like all of the local authentication). It also makes it easier for us to make changes to the server without having to update clients. Karl From kmccarth at redhat.com Fri Aug 3 16:51:36 2007 From: kmccarth at redhat.com (Kevin McCarthy) Date: Fri, 3 Aug 2007 09:51:36 -0700 Subject: [Freeipa-devel] Turbogears cleanup Message-ID: <20070803165135.GE12644@moon.usersys.redhat.com> I just wanted to send my current revision of TurboGears app to this list. I don't have any UI changes to show just yet, but I've restructured it a bit and thought it would be good to get some feedback. The biggest change is the factoring out of the user "Form" widget and template to form/user.py and templates/userform.kid. I've also changed the methods to use a more rest-ful naming convention: new->create, edit->update, show, list. I know it doesn't seem like much, but it took a fair amount of effort to figure out how to do that refactoring. I hope it will make it easier to support form reuse and external templates while using TurboGears validate and widget models. -Kevin -------------- next part -------------- A non-text attachment was scrubbed... Name: ipa-gui_2007_08_02_16_34.tar.bz2 Type: application/octet-stream Size: 93368 bytes Desc: not available URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 2228 bytes Desc: not available URL: From ssorce at redhat.com Fri Aug 3 16:57:10 2007 From: ssorce at redhat.com (Simo Sorce) Date: Fri, 03 Aug 2007 12:57:10 -0400 Subject: [Freeipa-devel] kerberos auth issue In-Reply-To: <1186157349.32430.44.camel@localhost.localdomain> References: <46B22423.4010808@redhat.com> <1186083031.28938.11.camel@localhost.localdomain> <46B34CFE.1030906@redhat.com> <1186157349.32430.44.camel@localhost.localdomain> Message-ID: <1186160230.23292.6.camel@localhost.localdomain> On Fri, 2007-08-03 at 12:09 -0400, Karl MacMillan wrote: > On Fri, 2007-08-03 at 11:42 -0400, Rob Crittenden wrote: > > Karl MacMillan wrote: > > > I thought that the backend of the xml-rpc library was going to be a > > > python library that the web gui would use directly. The architecture > > > would be: > > > > > > xmlrpc-client -----> xmlrpc-server -------> DS > > > krb cert > > > browser -----------> web server ----------> DS > > > > > > That eliminates all of the problems, right? > > > > > > Karl > > > > > > > Long, sorry in advance. > > > > Part of the problem I'm having is that rpcclient.py does a bit of data > > massaging. We can move some of this to the server side, it probably > > belonds there anyway (such as including extra attributes when adding a > > user). This move will be done later. > > > > Agreed - if our local client library does too much then the usability of > the xmlrpc api will go down dramatically. > > > But with other stuff some data conversion happens and that has to be in > > the client. > > > > As a test I copied rpcclient.py to webclient.py and updated my basic TG > > gui to use webclient.py instead. All I did was replace the server. calls > > with funcs. calls (effectively skipping the XML-RPC call). The one call > > I tested worked with no changes to the GUI code except changing the API > > names. > > > > So. I think that if we expand on this idea we can still have a > > more-or-less unified client API, but one goes through XML-RPC and one > > doesn't. The trick will be telling which is which. This is where I'm > > getting hung up. > > > > So simply copying rpcclient.py to webclient.py is obviously wrong, code > > duplication up the wazoo. > > > > Now - I may just have this all wrong because I'm not familiar enough > with the details, but what I had imagined was a bit different. I assumed > that we would have a common layer underneath the xmlrpc server and web > server: > > ---------- -------------- > | xmlrpc | | web server | > ---------- -------------- > ------------------------------ > | python serverlib.py | > ------------------------------ > || > || > \/ > ------------------------------ > | DS | > ------------------------------ > > The role of serverlib.py is to abstract away the ldap detail so that we > get calls like: > > adduser(realm, userObject) > uerObject = finduser(realm, userName) > > It would handle authenticating to the DS locally, ldap calls, and > provide a unified data model. That means userObject would be a python > object that has all of the 'exported' attributes of a user (including > custom attributes set by a local admin). > > Both the xmlrpc and web gui would be written to this api and would > really only be concerned with the details unique to each. For the xmlrpc > server that means, really, just marshalling / unmarshalling data and > making calls to serverlib.py. The web server would just be the web > presentation layer. > > Now, what seems to be concerning you is that the data model in > serverlib.py will be very similar to the data model in rpcclient.py (and > there will be some overlap in xmlrpc), correct? That seems easy to > handle if you make the data objects (like userObject above) simple > collections of data and factor out marshalling / unmarshalling to xmlrpc > and ldap. So, you get something like user.py which is shared by > serverlib.py and rpcclient.py (as in shipped in both) and used by the > xmlrpc server. > > You would also have userldap.py (unique to serverlib.py) and > userxmlprc.py. Those would contain functions for doing data conversion, > etc., etc. > > Does that make any sense or help in any way? This is how I would do it as well. The xml-rpc stuff should just be a transport layer to reach the server api. All functions to access data should be implemented there, UI specific handling will be instead in the CLI or the GUI as needed. Simo. From rcritten at redhat.com Fri Aug 3 18:13:19 2007 From: rcritten at redhat.com (Rob Crittenden) Date: Fri, 03 Aug 2007 14:13:19 -0400 Subject: [Freeipa-devel] kerberos auth issue In-Reply-To: <1186157349.32430.44.camel@localhost.localdomain> References: <46B22423.4010808@redhat.com> <1186083031.28938.11.camel@localhost.localdomain> <46B34CFE.1030906@redhat.com> <1186157349.32430.44.camel@localhost.localdomain> Message-ID: <46B3703F.3040909@redhat.com> Karl MacMillan wrote: > On Fri, 2007-08-03 at 11:42 -0400, Rob Crittenden wrote: >> Karl MacMillan wrote: >>> I thought that the backend of the xml-rpc library was going to be a >>> python library that the web gui would use directly. The architecture >>> would be: >>> >>> xmlrpc-client -----> xmlrpc-server -------> DS >>> krb cert >>> browser -----------> web server ----------> DS >>> >>> That eliminates all of the problems, right? >>> >>> Karl >>> >> Long, sorry in advance. >> >> Part of the problem I'm having is that rpcclient.py does a bit of data >> massaging. We can move some of this to the server side, it probably >> belonds there anyway (such as including extra attributes when adding a >> user). This move will be done later. >> > > Agreed - if our local client library does too much then the usability of > the xmlrpc api will go down dramatically. > >> But with other stuff some data conversion happens and that has to be in >> the client. >> >> As a test I copied rpcclient.py to webclient.py and updated my basic TG >> gui to use webclient.py instead. All I did was replace the server. calls >> with funcs. calls (effectively skipping the XML-RPC call). The one call >> I tested worked with no changes to the GUI code except changing the API >> names. >> >> So. I think that if we expand on this idea we can still have a >> more-or-less unified client API, but one goes through XML-RPC and one >> doesn't. The trick will be telling which is which. This is where I'm >> getting hung up. >> >> So simply copying rpcclient.py to webclient.py is obviously wrong, code >> duplication up the wazoo. >> > > Now - I may just have this all wrong because I'm not familiar enough > with the details, but what I had imagined was a bit different. I assumed > that we would have a common layer underneath the xmlrpc server and web > server: > > ---------- -------------- > | xmlrpc | | web server | > ---------- -------------- > ------------------------------ > | python serverlib.py | > ------------------------------ > || > || > \/ > ------------------------------ > | DS | > ------------------------------ That is the way it is architected now. The xmlrpc part does some translation of the unmarshalled data into a more application-specific form. That is where I'm having difficulties. > The role of serverlib.py is to abstract away the ldap detail so that we > get calls like: > > adduser(realm, userObject) > uerObject = finduser(realm, userName) > > It would handle authenticating to the DS locally, ldap calls, and > provide a unified data model. That means userObject would be a python > object that has all of the 'exported' attributes of a user (including > custom attributes set by a local admin). Yup, that is the goal. > Both the xmlrpc and web gui would be written to this api and would > really only be concerned with the details unique to each. For the xmlrpc > server that means, really, just marshalling / unmarshalling data and > making calls to serverlib.py. The web server would just be the web > presentation layer. That's the thing. Currently the XML-RPC client does more than unmarshall the data. It converts it into another python class. Remember that XML-RPC only defines like 6 data types. If we have each client convert that raw data into an object we'll have lots of code duplication. I'd like to do that work for the client. > Now, what seems to be concerning you is that the data model in > serverlib.py will be very similar to the data model in rpcclient.py (and > there will be some overlap in xmlrpc), correct? That seems easy to > handle if you make the data objects (like userObject above) simple > collections of data and factor out marshalling / unmarshalling to xmlrpc > and ldap. So, you get something like user.py which is shared by > serverlib.py and rpcclient.py (as in shipped in both) and used by the > xmlrpc server. Actually, the data model on the server side is an LDAP entry (the Entry class in ipaldap.py). The data model on the client side is the User object (not yet shipped). > You would also have userldap.py (unique to serverlib.py) and > userxmlprc.py. Those would contain functions for doing data conversion, > etc., etc. > > Does that make any sense or help in any way? It confirms that we're on the same page which is a good thing (TM). The main problem I'm having is not disclosing to the world that there is a special type of client that can connect directly. What I want to avoid is the situation where someone with no kerberos ticket is able to call the local functions and access the DS directly. From TurboGears to serverlib.py we have no TGT so we have to trust that TurboGears is setting the kerberos principal name correctly. If someone is able to change that they can proxy as anyone they want. I think Kevin has a decent model if that I can get that implemented. rob -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3245 bytes Desc: S/MIME Cryptographic Signature URL: From rcritten at redhat.com Mon Aug 6 15:54:42 2007 From: rcritten at redhat.com (Rob Crittenden) Date: Mon, 06 Aug 2007 11:54:42 -0400 Subject: [Freeipa-devel] [PATCH] Jumbo patch to add kerberos auth, do other stuff Message-ID: <46B74442.1030005@redhat.com> So this is a pretty huge patch and a smaller one. Going to need a few eyeballs on these. The smaller patch (which requires the jumbo one) does realm substitution for /etc/httpd/conf.d/ipa.conf. I think now that most of the hardcoding is now gone. The only things left I believe are ports and perhaps a hostname or two (set to localhost). The jumbo patch does the following: - Abstracted client class to work directly or over RPC - Update tools to use kerberos - Add User class - 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 So in other words, it touches just about everything. For this to work you'll need the PyKerberos RPM I sent to the list earlier. Now once all this stuff is built and installed, it won't quite work. Here are the steps I take to get a working system: 1. Import a user to test with: ldapmodify -x -D "cn=Directory Manager" -w PASSWORD < /usr/share/ipa/test-users.ldif 2. edit /etc/krb5.conf and replace the value of ldap_kadmind_dn with cn=Directory Manager. NOTE: if you decide to restart the krb5 service you'll have to switch this back otherwise you'll get a completely cryptic error back 3. set the new password for cn=Directory Manager in /var/kerberos/krb5kdc/ldappwd. I move the old file out of the way and create a new one. If you use freeipa as the DS password then the value is: cn=Directory Manager#{HEX}66726565697061 4. Set a password for test at REALM: kadmin.local -> cpw test at REALM 5. chmod 644 /usr/share/ipa/*.pem /usr/share/ipa/cacert.asc 6. (I'd do this as a user, not root): kinit test at REALM 7. /usr/sbin/ipa-finduser test This will exercise just about everything. Once step 7 returns a user you can try adding one with ipa-adduser. I haven't tested Kevin's revamped GUI yet but my old clunker works with this. Noted deficiencies of current code: 1. A bind is done with every operation. No attempt is made at caching LDAP connections 2. A new session is created even using the local way (for the GUI). There is surely a way to create one client object and re-use it 3. Fields for add user in the GUI are still hardcoded 4. ipa-adduser still has some odd options Kevin, some things of note for you: 1. I removed the userPassword question. Would have required another ACI to allow it and I just didn't feel like messing with it. 2. You want to import ipaclient now and invoke things like this: client = ipaclient.IPAClient(True) client.set_principal("test at REALM") users = client.add_user (kw) rob -------------- next part -------------- A non-text attachment was scrubbed... Name: jumbo.patch Type: text/x-patch Size: 61465 bytes Desc: not available URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: ipa.patch Type: text/x-patch Size: 3803 bytes Desc: not available URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3245 bytes Desc: S/MIME Cryptographic Signature URL: From kmccarth at redhat.com Tue Aug 7 19:16:13 2007 From: kmccarth at redhat.com (Kevin McCarthy) Date: Tue, 7 Aug 2007 12:16:13 -0700 Subject: [Freeipa-devel] [PATCH] Jumbo patch to add kerberos auth, do other stuff In-Reply-To: <46B74442.1030005@redhat.com> References: <46B74442.1030005@redhat.com> Message-ID: <20070807191613.GA22023@moon.usersys.redhat.com> Rob Crittenden wrote: > Kevin, some things of note for you: > > 1. I removed the userPassword question. Would have required another ACI to > allow it and I just didn't feel like messing with it. > 2. You want to import ipaclient now and invoke things like this: > > client = ipaclient.IPAClient(True) > client.set_principal("test at REALM") > users = client.add_user (kw) I can report success with the patch. I had to filter out the 'submit' button from the kw list, but after that was able to successfully add a user. I think we should change client.add_user() should take a User object, but otherwise it looks great. -Kevin -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 2228 bytes Desc: not available URL: From rcritten at redhat.com Wed Aug 8 13:22:57 2007 From: rcritten at redhat.com (Rob Crittenden) Date: Wed, 08 Aug 2007 09:22:57 -0400 Subject: [Freeipa-devel] [PATCH] Jumbo patch to add kerberos auth, do other stuff In-Reply-To: <20070807191613.GA22023@moon.usersys.redhat.com> References: <46B74442.1030005@redhat.com> <20070807191613.GA22023@moon.usersys.redhat.com> Message-ID: <46B9C3B1.20200@redhat.com> Kevin McCarthy wrote: > Rob Crittenden wrote: >> Kevin, some things of note for you: >> >> 1. I removed the userPassword question. Would have required another ACI to >> allow it and I just didn't feel like messing with it. >> 2. You want to import ipaclient now and invoke things like this: >> >> client = ipaclient.IPAClient(True) >> client.set_principal("test at REALM") >> users = client.add_user (kw) > > I can report success with the patch. I had to filter out the 'submit' > button from the kw list, but after that was able to successfully add a > user. > > I think we should change client.add_user() should take a User object, > but otherwise it looks great. > > -Kevin Yes, that would be more intuitive. It might be tricky doing updates but I think that is going to be an issue no matter how we do things. The question being whether the client sends all attributes or just the ones that changed. rob -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3245 bytes Desc: S/MIME Cryptographic Signature URL: From prowley at redhat.com Wed Aug 8 13:26:18 2007 From: prowley at redhat.com (Pete Rowley) Date: Wed, 08 Aug 2007 06:26:18 -0700 Subject: [Freeipa-devel] [PATCH] Jumbo patch to add kerberos auth, do other stuff In-Reply-To: <46B9C3B1.20200@redhat.com> References: <46B74442.1030005@redhat.com> <20070807191613.GA22023@moon.usersys.redhat.com> <46B9C3B1.20200@redhat.com> Message-ID: <46B9C47A.2050208@redhat.com> Rob Crittenden wrote: > Kevin McCarthy wrote: >> Rob Crittenden wrote: >>> Kevin, some things of note for you: >>> >>> 1. I removed the userPassword question. Would have required another >>> ACI to allow it and I just didn't feel like messing with it. >>> 2. You want to import ipaclient now and invoke things like this: >>> >>> client = ipaclient.IPAClient(True) >>> client.set_principal("test at REALM") >>> users = client.add_user (kw) >> >> I can report success with the patch. I had to filter out the 'submit' >> button from the kw list, but after that was able to successfully add a >> user. >> >> I think we should change client.add_user() should take a User object, >> but otherwise it looks great. >> >> -Kevin > > Yes, that would be more intuitive. > > It might be tricky doing updates but I think that is going to be an > issue no matter how we do things. The question being whether the > client sends all attributes or just the ones that changed. > For add user that isn't a problem. We will have to be careful for modifies though. -- Pete -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3241 bytes Desc: S/MIME Cryptographic Signature URL: From kmacmill at redhat.com Wed Aug 8 18:39:23 2007 From: kmacmill at redhat.com (Karl MacMillan) Date: Wed, 08 Aug 2007 14:39:23 -0400 Subject: [Freeipa-devel] [PATCH] Jumbo patch to add kerberos auth, do other stuff In-Reply-To: <46B9C47A.2050208@redhat.com> References: <46B74442.1030005@redhat.com> <20070807191613.GA22023@moon.usersys.redhat.com> <46B9C3B1.20200@redhat.com> <46B9C47A.2050208@redhat.com> Message-ID: <1186598363.9560.0.camel@localhost.localdomain> This doesn't apply to the current upstream - can you send me an updated patch? Karl On Wed, 2007-08-08 at 06:26 -0700, Pete Rowley wrote: > Rob Crittenden wrote: > > Kevin McCarthy wrote: > >> Rob Crittenden wrote: > >>> Kevin, some things of note for you: > >>> > >>> 1. I removed the userPassword question. Would have required another > >>> ACI to allow it and I just didn't feel like messing with it. > >>> 2. You want to import ipaclient now and invoke things like this: > >>> > >>> client = ipaclient.IPAClient(True) > >>> client.set_principal("test at REALM") > >>> users = client.add_user (kw) > >> > >> I can report success with the patch. I had to filter out the 'submit' > >> button from the kw list, but after that was able to successfully add a > >> user. > >> > >> I think we should change client.add_user() should take a User object, > >> but otherwise it looks great. > >> > >> -Kevin > > > > Yes, that would be more intuitive. > > > > It might be tricky doing updates but I think that is going to be an > > issue no matter how we do things. The question being whether the > > client sends all attributes or just the ones that changed. > > > For add user that isn't a problem. We will have to be careful for > modifies though. > > _______________________________________________ > Freeipa-devel mailing list > Freeipa-devel at redhat.com > https://www.redhat.com/mailman/listinfo/freeipa-devel From rcritten at redhat.com Wed Aug 8 19:19:28 2007 From: rcritten at redhat.com (Rob Crittenden) Date: Wed, 08 Aug 2007 15:19:28 -0400 Subject: [Freeipa-devel] [PATCH] Jumbo patch to add kerberos auth, do other stuff In-Reply-To: <1186598363.9560.0.camel@localhost.localdomain> References: <46B74442.1030005@redhat.com> <20070807191613.GA22023@moon.usersys.redhat.com> <46B9C3B1.20200@redhat.com> <46B9C47A.2050208@redhat.com> <1186598363.9560.0.camel@localhost.localdomain> Message-ID: <46BA1740.4080803@redhat.com> Karl MacMillan wrote: > This doesn't apply to the current upstream - can you send me an updated > patch? > Works for me: $ hg clone http://hg.et.redhat.com/freeipa/freeipa destination directory: freeipa requesting all changes adding changesets adding manifests adding file changes added 39 changesets with 191 changes to 115 files 48 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd freeipa/ $ patch -p1 < /tmp/jumbo.patch patching file ipa-admintools/freeipa-admintools.spec patching file ipa-admintools/freeipa-admintools.spec.in patching file ipa-admintools/ipa-adduser patching file ipa-admintools/ipa-finduser patching file ipa-python/freeipa-python.spec patching file ipa-python/freeipa-python.spec.in patching file ipa-python/ipaclient.py patching file ipa-python/krbtransport.py patching file ipa-python/rpcclient.py patching file ipa-python/user.py patching file ipa-server/freeipa-server.spec patching file ipa-server/freeipa-server.spec.in patching file ipa-server/ipa-install/Makefile patching file ipa-server/ipa-install/ipa-server-install patching file ipa-server/ipa-install/ipa-server-setupssl patching file ipa-server/ipa-install/share/bootstrap-template.ldif patching file ipa-server/ipa-install/share/certmap.conf.template patching file ipa-server/ipa-install/share/default-aci.ldif patching file ipa-server/ipa-install/test/Makefile patching file ipa-server/ipa-install/test/test-users-template.ldif patching file ipa-server/ipa-install/test/test-users.ldif patching file ipa-server/ipaserver/dsinstance.py patching file ipa-server/ipaserver/ipaldap.py patching file ipa-server/ipaserver/krbinstance.py patching file ipa-server/xmlrpc-server/funcs.py patching file ipa-server/xmlrpc-server/ipa.conf patching file ipa-server/xmlrpc-server/ipaxmlrpc.py $ patch -p1 < /tmp/ipa.patch patching file ipa-server/freeipa-server.spec patching file ipa-server/freeipa-server.spec.in patching file ipa-server/ipaserver/krbinstance.py patching file ipa-server/xmlrpc-server/Makefile patching file ipa-server/xmlrpc-server/ipa.conf -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3245 bytes Desc: S/MIME Cryptographic Signature URL: From kmacmill at redhat.com Wed Aug 8 19:28:56 2007 From: kmacmill at redhat.com (Karl MacMillan) Date: Wed, 08 Aug 2007 15:28:56 -0400 Subject: [Freeipa-devel] [PATCH] Jumbo patch to add kerberos auth, do other stuff In-Reply-To: <46BA1740.4080803@redhat.com> References: <46B74442.1030005@redhat.com> <20070807191613.GA22023@moon.usersys.redhat.com> <46B9C3B1.20200@redhat.com> <46B9C47A.2050208@redhat.com> <1186598363.9560.0.camel@localhost.localdomain> <46BA1740.4080803@redhat.com> Message-ID: <1186601336.9560.9.camel@localhost.localdomain> On Wed, 2007-08-08 at 15:19 -0400, Rob Crittenden wrote: > Karl MacMillan wrote: > > This doesn't apply to the current upstream - can you send me an updated > > patch? > > > > Works for me: > Sorry - I had a minor change to the spec file that was clashing. I've now merged this. I know it may need more work, but I'd rather it be available. Karl From rcritten at redhat.com Thu Aug 9 17:44:52 2007 From: rcritten at redhat.com (Rob Crittenden) Date: Thu, 09 Aug 2007 13:44:52 -0400 Subject: [Freeipa-devel] [PATCH] start services on bootup Message-ID: <46BB5294.8040802@redhat.com> I had already set Apache to start on boot. We need fedora-ds and the KDC to start automatically too. rob -------------- next part -------------- A non-text attachment was scrubbed... Name: start.patch Type: text/x-patch Size: 908 bytes Desc: not available URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3245 bytes Desc: S/MIME Cryptographic Signature URL: From ssorce at redhat.com Thu Aug 9 19:27:26 2007 From: ssorce at redhat.com (Simo Sorce) Date: Thu, 09 Aug 2007 15:27:26 -0400 Subject: [Freeipa-devel] kpasswd and minor fixes Message-ID: <1186687646.22941.10.camel@localhost.localdomain> Attached my latest work in creating a kpasswd daemon that proxies password changes to ldap. This make it possible to completely handle password changes with the pwd-extop plugin and always use the same codepath. As I have been traveling the local commit queue grow up and part of this stuff happened before the directory reorg ... Patches depend one on top of each other from lower number to higher, I omitted any changeset that has already been committed. Simo. -------------- next part -------------- A non-text attachment was scrubbed... Name: freeipa35.patch Type: text/x-patch Size: 6527 bytes Desc: not available URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: freeipa36.patch Type: text/x-patch Size: 33913 bytes Desc: not available URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: freeipa37.patch Type: text/x-patch Size: 16219 bytes Desc: not available URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: freeipa38.patch Type: text/x-patch Size: 942 bytes Desc: not available URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: freeipa39.patch Type: text/x-patch Size: 1038 bytes Desc: not available URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: freeipa40.patch Type: text/x-patch Size: 1129 bytes Desc: not available URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: freeipa54.patch Type: text/x-patch Size: 860 bytes Desc: not available URL: From ssorce at redhat.com Thu Aug 9 19:55:58 2007 From: ssorce at redhat.com (Simo Sorce) Date: Thu, 09 Aug 2007 15:55:58 -0400 Subject: [Freeipa-devel] kpasswd and minor fixes In-Reply-To: <1186687646.22941.10.camel@localhost.localdomain> References: <1186687646.22941.10.camel@localhost.localdomain> Message-ID: <1186689358.22941.14.camel@localhost.localdomain> Ehmm 33 and 35 should probbaly not be in this batch, just forget about them, I should have not sent them in. Simo. On Thu, 2007-08-09 at 15:27 -0400, Simo Sorce wrote: > Attached my latest work in creating a kpasswd daemon that proxies > password changes to ldap. > This make it possible to completely handle password changes with the > pwd-extop plugin and always use the same codepath. > > As I have been traveling the local commit queue grow up and part of this > stuff happened before the directory reorg ... > > Patches depend one on top of each other from lower number to higher, I > omitted any changeset that has already been committed. > > Simo. > > > _______________________________________________ > Freeipa-devel mailing list > Freeipa-devel at redhat.com > https://www.redhat.com/mailman/listinfo/freeipa-devel From rcritten at redhat.com Thu Aug 9 20:10:42 2007 From: rcritten at redhat.com (Rob Crittenden) Date: Thu, 09 Aug 2007 16:10:42 -0400 Subject: [Freeipa-devel] kpasswd and minor fixes In-Reply-To: <1186687646.22941.10.camel@localhost.localdomain> References: <1186687646.22941.10.camel@localhost.localdomain> Message-ID: <46BB74C2.501@redhat.com> Simo Sorce wrote: > Attached my latest work in creating a kpasswd daemon that proxies > password changes to ldap. > This make it possible to completely handle password changes with the > pwd-extop plugin and always use the same codepath. > > As I have been traveling the local commit queue grow up and part of this > stuff happened before the directory reorg ... > > Patches depend one on top of each other from lower number to higher, I > omitted any changeset that has already been committed. > > Simo. > Ignoring freeipa33 and 35... The freeipa36 patch is a little odd. It removes a bunch of code the re-adds it? In any case, as a general note I think we need autoconf-enable all of IPA but it currently defaults to installing in /usr as the prefix. This patch puts things into /usr/local. So I guess it should go into /usr as well for the time being. We'll need to update the RPM spec file to had a BuildRequires on kerberos and openldap (unless we want to link with mozldap). Should the IPA installer generate the keytab in FILE:/var/kerberos/krb5kdc/kpasswd.keytab? The realm name is hardcoded into the source. Can this be a cmd-line or config file option? Ideally it would be read out of /etc/ipa/ipa.conf. Is kpasswd a daemon? Should it use syslog for logging? How many concurrent connections at a time do we expect for this service? Should we use poll() instead of select()? The return value of ldap_pwd_change() is unused. How do we know the change was successful? There are places where result_err is set but this will never get into kpreply: to actually use the result and return something, I presume to the kerberos client. Instead it goes to done: and frees the connection. There are cases where the daemon will exit with an error. Are these really unrecoverable? I don't know kerberos internals so can't really comment on much of the code. rob -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3245 bytes Desc: S/MIME Cryptographic Signature URL: From rmeggins at redhat.com Thu Aug 9 20:11:34 2007 From: rmeggins at redhat.com (Richard Megginson) Date: Thu, 09 Aug 2007 14:11:34 -0600 Subject: [Freeipa-devel] kpasswd and minor fixes In-Reply-To: <46BB74C2.501@redhat.com> References: <1186687646.22941.10.camel@localhost.localdomain> <46BB74C2.501@redhat.com> Message-ID: <46BB74F6.1010807@redhat.com> Rob Crittenden wrote: > Simo Sorce wrote: >> Attached my latest work in creating a kpasswd daemon that proxies >> password changes to ldap. >> This make it possible to completely handle password changes with the >> pwd-extop plugin and always use the same codepath. >> >> As I have been traveling the local commit queue grow up and part of this >> stuff happened before the directory reorg ... >> >> Patches depend one on top of each other from lower number to higher, I >> omitted any changeset that has already been committed. >> >> Simo. >> > > Ignoring freeipa33 and 35... > > The freeipa36 patch is a little odd. It removes a bunch of code the > re-adds it? > > In any case, as a general note I think we need autoconf-enable all of > IPA but it currently defaults to installing in /usr as the prefix. > This patch puts things into /usr/local. So I guess it should go into > /usr as well for the time being. > > We'll need to update the RPM spec file to had a BuildRequires on > kerberos and openldap (unless we want to link with mozldap). I would suggest openldap, and use ossl2nss if possible if NSS is required. > > Should the IPA installer generate the keytab in > FILE:/var/kerberos/krb5kdc/kpasswd.keytab? > > The realm name is hardcoded into the source. Can this be a cmd-line or > config file option? Ideally it would be read out of /etc/ipa/ipa.conf. > > Is kpasswd a daemon? Should it use syslog for logging? > > How many concurrent connections at a time do we expect for this > service? Should we use poll() instead of select()? > > The return value of ldap_pwd_change() is unused. How do we know the > change was successful? > > There are places where result_err is set but this will never get into > kpreply: to actually use the result and return something, I presume to > the kerberos client. Instead it goes to done: and frees the connection. > > There are cases where the daemon will exit with an error. Are these > really unrecoverable? > > I don't know kerberos internals so can't really comment on much of the > code. > > rob > ------------------------------------------------------------------------ > > _______________________________________________ > Freeipa-devel mailing list > Freeipa-devel at redhat.com > https://www.redhat.com/mailman/listinfo/freeipa-devel -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3245 bytes Desc: S/MIME Cryptographic Signature URL: From ssorce at redhat.com Thu Aug 9 20:39:52 2007 From: ssorce at redhat.com (Simo Sorce) Date: Thu, 09 Aug 2007 16:39:52 -0400 Subject: [Freeipa-devel] kpasswd and minor fixes In-Reply-To: <46BB74C2.501@redhat.com> References: <1186687646.22941.10.camel@localhost.localdomain> <46BB74C2.501@redhat.com> Message-ID: <1186691992.22941.23.camel@localhost.localdomain> On Thu, 2007-08-09 at 16:10 -0400, Rob Crittenden wrote: > Simo Sorce wrote: > > Attached my latest work in creating a kpasswd daemon that proxies > > password changes to ldap. > > This make it possible to completely handle password changes with the > > pwd-extop plugin and always use the same codepath. > > > > As I have been traveling the local commit queue grow up and part of this > > stuff happened before the directory reorg ... > > > > Patches depend one on top of each other from lower number to higher, I > > omitted any changeset that has already been committed. > > > > Simo. > > > > Ignoring freeipa33 and 35... > > The freeipa36 patch is a little odd. It removes a bunch of code the > re-adds it? > > In any case, as a general note I think we need autoconf-enable all of > IPA but it currently defaults to installing in /usr as the prefix. This > patch puts things into /usr/local. So I guess it should go into /usr as > well for the time being. Uhmm I don't think I have touched anything about locations /me scrathes head > We'll need to update the RPM spec file to had a BuildRequires on > kerberos and openldap (unless we want to link with mozldap). openldap > Should the IPA installer generate the keytab in > FILE:/var/kerberos/krb5kdc/kpasswd.keytab? It should have been there, blame my newbiety with mercurial. /me will never get it right how the merge process really works :/ > The realm name is hardcoded into the source. Can this be a cmd-line or > config file option? Ideally it would be read out of /etc/ipa/ipa.conf. freeipa37.patch fixes this > Is kpasswd a daemon? Should it use syslog for logging? yeah I should changed all fprintf(stderr,.. to something that can choose between syslog and stderr for debugging, it's on my TODO list > How many concurrent connections at a time do we expect for this service? > Should we use poll() instead of select()? it is just for people that change password via the kpasswd protocol. It should be a very low traffic daemon. > The return value of ldap_pwd_change() is unused. How do we know the > change was successful? this is addressed in freeipa37.patch as well > There are places where result_err is set but this will never get into > kpreply: to actually use the result and return something, I presume to > the kerberos client. Instead it goes to done: and frees the connection. I think I got all of them in freeipa37.patch, but I will recheck > There are cases where the daemon will exit with an error. Are these > really unrecoverable? Some times they are. > I don't know kerberos internals so can't really comment on much of the code. Np, the code works, so I think I got them right ;-) If you think it is good enough I will push the patch. Simo. Simo. From rcritten at redhat.com Thu Aug 9 20:44:21 2007 From: rcritten at redhat.com (Rob Crittenden) Date: Thu, 09 Aug 2007 16:44:21 -0400 Subject: [Freeipa-devel] kpasswd and minor fixes In-Reply-To: <1186691992.22941.23.camel@localhost.localdomain> References: <1186687646.22941.10.camel@localhost.localdomain> <46BB74C2.501@redhat.com> <1186691992.22941.23.camel@localhost.localdomain> Message-ID: <46BB7CA5.20304@redhat.com> Simo Sorce wrote: > On Thu, 2007-08-09 at 16:10 -0400, Rob Crittenden wrote: >> Simo Sorce wrote: >>> Attached my latest work in creating a kpasswd daemon that proxies >>> password changes to ldap. >>> This make it possible to completely handle password changes with the >>> pwd-extop plugin and always use the same codepath. >>> >>> As I have been traveling the local commit queue grow up and part of this >>> stuff happened before the directory reorg ... >>> >>> Patches depend one on top of each other from lower number to higher, I >>> omitted any changeset that has already been committed. >>> >>> Simo. >>> >> Ignoring freeipa33 and 35... >> >> The freeipa36 patch is a little odd. It removes a bunch of code the >> re-adds it? >> >> In any case, as a general note I think we need autoconf-enable all of >> IPA but it currently defaults to installing in /usr as the prefix. This >> patch puts things into /usr/local. So I guess it should go into /usr as >> well for the time being. > > Uhmm I don't think I have touched anything about locations > /me scrathes head In this patch it installs the daemon into /usr/local/sbin rather than /usr/sbin. We should be doing our directly hardcoding at least consistently IMHO >> We'll need to update the RPM spec file to had a BuildRequires on >> kerberos and openldap (unless we want to link with mozldap). > > openldap > >> Should the IPA installer generate the keytab in >> FILE:/var/kerberos/krb5kdc/kpasswd.keytab? > > It should have been there, blame my newbiety with mercurial. > /me will never get it right how the merge process really works :/ > >> The realm name is hardcoded into the source. Can this be a cmd-line or >> config file option? Ideally it would be read out of /etc/ipa/ipa.conf. > > freeipa37.patch fixes this > >> Is kpasswd a daemon? Should it use syslog for logging? > > yeah I should changed all fprintf(stderr,.. to something that can choose > between syslog and stderr for debugging, it's on my TODO list > >> How many concurrent connections at a time do we expect for this service? >> Should we use poll() instead of select()? > > it is just for people that change password via the kpasswd protocol. It > should be a very low traffic daemon. > >> The return value of ldap_pwd_change() is unused. How do we know the >> change was successful? > > this is addressed in freeipa37.patch as well > >> There are places where result_err is set but this will never get into >> kpreply: to actually use the result and return something, I presume to >> the kerberos client. Instead it goes to done: and frees the connection. > > I think I got all of them in freeipa37.patch, but I will recheck > >> There are cases where the daemon will exit with an error. Are these >> really unrecoverable? > > Some times they are. > >> I don't know kerberos internals so can't really comment on much of the code. > > Np, the code works, so I think I got them right ;-) > > If you think it is good enough I will push the patch. > > Simo. > > Simo. > Well, maybe we should look at freeipa37.patch :-) rob -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3245 bytes Desc: S/MIME Cryptographic Signature URL: From rcritten at redhat.com Thu Aug 9 20:48:22 2007 From: rcritten at redhat.com (Rob Crittenden) Date: Thu, 09 Aug 2007 16:48:22 -0400 Subject: [Freeipa-devel] [PATCH] Jumbo patch to add kerberos auth, do other stuff In-Reply-To: <20070807191613.GA22023@moon.usersys.redhat.com> References: <46B74442.1030005@redhat.com> <20070807191613.GA22023@moon.usersys.redhat.com> Message-ID: <46BB7D96.7040309@redhat.com> Kevin McCarthy wrote: > Rob Crittenden wrote: >> Kevin, some things of note for you: >> >> 1. I removed the userPassword question. Would have required another ACI to >> allow it and I just didn't feel like messing with it. >> 2. You want to import ipaclient now and invoke things like this: >> >> client = ipaclient.IPAClient(True) >> client.set_principal("test at REALM") >> users = client.add_user (kw) > > I can report success with the patch. I had to filter out the 'submit' > button from the kw list, but after that was able to successfully add a > user. > > I think we should change client.add_user() should take a User object, > but otherwise it looks great. > I looked at this a bit today. The problem is I need to provide a single API. Data passing in through the XML-RPC interface will end up as a dict (the way it is now). If we start using objects in the GUI we end up with two APIs. I'm ok with that but the goal was to provide a single, consistent API. But I'm not going to make the change until we all agree on it. rob -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3245 bytes Desc: S/MIME Cryptographic Signature URL: From rcritten at redhat.com Thu Aug 9 20:56:23 2007 From: rcritten at redhat.com (Rob Crittenden) Date: Thu, 09 Aug 2007 16:56:23 -0400 Subject: [Freeipa-devel] kpasswd and minor fixes In-Reply-To: <46BB7CA5.20304@redhat.com> References: <1186687646.22941.10.camel@localhost.localdomain> <46BB74C2.501@redhat.com> <1186691992.22941.23.camel@localhost.localdomain> <46BB7CA5.20304@redhat.com> Message-ID: <46BB7F77.6080508@redhat.com> Rob Crittenden wrote: > Simo Sorce wrote: >> On Thu, 2007-08-09 at 16:10 -0400, Rob Crittenden wrote: >>> Simo Sorce wrote: >>>> Attached my latest work in creating a kpasswd daemon that proxies >>>> password changes to ldap. >>>> This make it possible to completely handle password changes with the >>>> pwd-extop plugin and always use the same codepath. >>>> >>>> As I have been traveling the local commit queue grow up and part of >>>> this >>>> stuff happened before the directory reorg ... >>>> >>>> Patches depend one on top of each other from lower number to higher, I >>>> omitted any changeset that has already been committed. >>>> >>>> Simo. >>>> >>> Ignoring freeipa33 and 35... >>> >>> The freeipa36 patch is a little odd. It removes a bunch of code the >>> re-adds it? >>> >>> In any case, as a general note I think we need autoconf-enable all of >>> IPA but it currently defaults to installing in /usr as the prefix. >>> This patch puts things into /usr/local. So I guess it should go into >>> /usr as well for the time being. >> >> Uhmm I don't think I have touched anything about locations >> /me scrathes head > > In this patch it installs the daemon into /usr/local/sbin rather than > /usr/sbin. We should be doing our directly hardcoding at least > consistently IMHO > >>> We'll need to update the RPM spec file to had a BuildRequires on >>> kerberos and openldap (unless we want to link with mozldap). >> >> openldap >> >>> Should the IPA installer generate the keytab in >>> FILE:/var/kerberos/krb5kdc/kpasswd.keytab? >> >> It should have been there, blame my newbiety with mercurial. >> /me will never get it right how the merge process really works :/ >> >>> The realm name is hardcoded into the source. Can this be a cmd-line >>> or config file option? Ideally it would be read out of >>> /etc/ipa/ipa.conf. >> >> freeipa37.patch fixes this >> >>> Is kpasswd a daemon? Should it use syslog for logging? >> >> yeah I should changed all fprintf(stderr,.. to something that can choose >> between syslog and stderr for debugging, it's on my TODO list >> >>> How many concurrent connections at a time do we expect for this >>> service? Should we use poll() instead of select()? >> >> it is just for people that change password via the kpasswd protocol. It >> should be a very low traffic daemon. >> >>> The return value of ldap_pwd_change() is unused. How do we know the >>> change was successful? >> >> this is addressed in freeipa37.patch as well >> >>> There are places where result_err is set but this will never get into >>> kpreply: to actually use the result and return something, I presume >>> to the kerberos client. Instead it goes to done: and frees the >>> connection. >> >> I think I got all of them in freeipa37.patch, but I will recheck >> >>> There are cases where the daemon will exit with an error. Are these >>> really unrecoverable? >> >> Some times they are. >> >>> I don't know kerberos internals so can't really comment on much of >>> the code. >> >> Np, the code works, so I think I got them right ;-) >> >> If you think it is good enough I will push the patch. >> >> Simo. >> >> Simo. >> > > Well, maybe we should look at freeipa37.patch :-) > > rob Oh, wait, I didn't notice the other patches. I guess I'll try applying these all to a tree to see what the end result looks like. rob -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3245 bytes Desc: S/MIME Cryptographic Signature URL: From ssorce at redhat.com Thu Aug 9 20:57:53 2007 From: ssorce at redhat.com (Simo Sorce) Date: Thu, 09 Aug 2007 16:57:53 -0400 Subject: [Freeipa-devel] kpasswd and minor fixes In-Reply-To: <1186687646.22941.10.camel@localhost.localdomain> References: <1186687646.22941.10.camel@localhost.localdomain> Message-ID: <1186693073.22941.25.camel@localhost.localdomain> And here there is another patch to address the kpasswd.keytab installation problem. Simo. -------------- next part -------------- A non-text attachment was scrubbed... Name: freeipa55.patch Type: text/x-patch Size: 1242 bytes Desc: not available URL: From rcritten at redhat.com Thu Aug 9 21:09:51 2007 From: rcritten at redhat.com (Rob Crittenden) Date: Thu, 09 Aug 2007 17:09:51 -0400 Subject: [Freeipa-devel] kpasswd and minor fixes In-Reply-To: <46BB7CA5.20304@redhat.com> References: <1186687646.22941.10.camel@localhost.localdomain> <46BB74C2.501@redhat.com> <1186691992.22941.23.camel@localhost.localdomain> <46BB7CA5.20304@redhat.com> Message-ID: <46BB829F.2080509@redhat.com> Rob Crittenden wrote: > Simo Sorce wrote: >> On Thu, 2007-08-09 at 16:10 -0400, Rob Crittenden wrote: >>> Simo Sorce wrote: >>>> Attached my latest work in creating a kpasswd daemon that proxies >>>> password changes to ldap. >>>> This make it possible to completely handle password changes with the >>>> pwd-extop plugin and always use the same codepath. >>>> >>>> As I have been traveling the local commit queue grow up and part of >>>> this >>>> stuff happened before the directory reorg ... >>>> >>>> Patches depend one on top of each other from lower number to higher, I >>>> omitted any changeset that has already been committed. >>>> >>>> Simo. >>>> >>> Ignoring freeipa33 and 35... >>> >>> The freeipa36 patch is a little odd. It removes a bunch of code the >>> re-adds it? >>> >>> In any case, as a general note I think we need autoconf-enable all of >>> IPA but it currently defaults to installing in /usr as the prefix. >>> This patch puts things into /usr/local. So I guess it should go into >>> /usr as well for the time being. >> >> Uhmm I don't think I have touched anything about locations >> /me scrathes head > > In this patch it installs the daemon into /usr/local/sbin rather than > /usr/sbin. We should be doing our directly hardcoding at least > consistently IMHO > >>> We'll need to update the RPM spec file to had a BuildRequires on >>> kerberos and openldap (unless we want to link with mozldap). >> >> openldap >> >>> Should the IPA installer generate the keytab in >>> FILE:/var/kerberos/krb5kdc/kpasswd.keytab? >> >> It should have been there, blame my newbiety with mercurial. >> /me will never get it right how the merge process really works :/ >> >>> The realm name is hardcoded into the source. Can this be a cmd-line >>> or config file option? Ideally it would be read out of >>> /etc/ipa/ipa.conf. >> >> freeipa37.patch fixes this >> >>> Is kpasswd a daemon? Should it use syslog for logging? >> >> yeah I should changed all fprintf(stderr,.. to something that can choose >> between syslog and stderr for debugging, it's on my TODO list >> >>> How many concurrent connections at a time do we expect for this >>> service? Should we use poll() instead of select()? >> >> it is just for people that change password via the kpasswd protocol. It >> should be a very low traffic daemon. >> >>> The return value of ldap_pwd_change() is unused. How do we know the >>> change was successful? >> >> this is addressed in freeipa37.patch as well >> >>> There are places where result_err is set but this will never get into >>> kpreply: to actually use the result and return something, I presume >>> to the kerberos client. Instead it goes to done: and frees the >>> connection. >> >> I think I got all of them in freeipa37.patch, but I will recheck >> >>> There are cases where the daemon will exit with an error. Are these >>> really unrecoverable? >> >> Some times they are. >> >>> I don't know kerberos internals so can't really comment on much of >>> the code. >> >> Np, the code works, so I think I got them right ;-) >> >> If you think it is good enough I will push the patch. >> >> Simo. >> >> Simo. >> > > Well, maybe we should look at freeipa37.patch :-) > > rob Ok, I got all the patches applied, here is a better review. ntpd is added to README as a requirement but it isn't added to the spec file nor do we configure it yet (not required for this patch but a necessary TODO) The previously mentioned /usr/local/sbin vs /usr/sbin for the ipa_kpasswd daemon install. What will the blacklist do to NAT'd addresses? What happens to a kpasswd request when someone from the same IP is also doing a request? What does the user see, in other words. There are still cases in handle_krb_packets() where errors are passed to done instead of kpreply, such as KRB5_KPASSWD_AUTHERROR. Should the port be hardcoded in ldap_pwd_change()? I have enough hardcoding in the code I've done so this isn't a hard stop :-) rob -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3245 bytes Desc: S/MIME Cryptographic Signature URL: From ssorce at redhat.com Thu Aug 9 21:23:10 2007 From: ssorce at redhat.com (Simo Sorce) Date: Thu, 09 Aug 2007 17:23:10 -0400 Subject: [Freeipa-devel] kpasswd and minor fixes In-Reply-To: <46BB829F.2080509@redhat.com> References: <1186687646.22941.10.camel@localhost.localdomain> <46BB74C2.501@redhat.com> <1186691992.22941.23.camel@localhost.localdomain> <46BB7CA5.20304@redhat.com> <46BB829F.2080509@redhat.com> Message-ID: <1186694590.22941.31.camel@localhost.localdomain> OK I have pushed the patches, the code need some working, but I think it is good enough for a first commit. comments follows. On Thu, 2007-08-09 at 17:09 -0400, Rob Crittenden wrote: > Ok, I got all the patches applied, here is a better review. > > ntpd is added to README as a requirement but it isn't added to the > spec > file nor do we configure it yet (not required for this patch but a > necessary TODO) yeah I placed it in the README exactly to avoid forgetting about it > The previously mentioned /usr/local/sbin vs /usr/sbin for the > ipa_kpasswd daemon install. fixed and pushed with the rest of the code > What will the blacklist do to NAT'd addresses? What happens to a > kpasswd > request when someone from the same IP is also doing a request? What > does > the user see, in other words. They will just see a password change failing. not sure I have a better alternative, I could probably go down the path of discovering what krbPrincipal is trying to do a password change and blacklist only on the IP+krbPrincipal combinartion. But in any case this is just a stopgap measure to fix a bug in the clients. I am trying to make the client use TCP instead, not sure this is possible without a couple of patches to client tools. > There are still cases in handle_krb_packets() where errors are passed > to > done instead of kpreply, such as KRB5_KPASSWD_AUTHERROR. Yeah they are catastrofic failures, IE no way to build a reply. > Should the port be hardcoded in ldap_pwd_change()? I have enough > hardcoding in the code I've done so this isn't a hard stop :-) No, we should get it from a DNS query, good catch. Simo. From kmccarth at redhat.com Thu Aug 9 21:38:08 2007 From: kmccarth at redhat.com (Kevin McCarthy) Date: Thu, 9 Aug 2007 14:38:08 -0700 Subject: [Freeipa-devel] [PATCH] Jumbo patch to add kerberos auth, do other stuff In-Reply-To: <46BB7D96.7040309@redhat.com> References: <46B74442.1030005@redhat.com> <20070807191613.GA22023@moon.usersys.redhat.com> <46BB7D96.7040309@redhat.com> Message-ID: <20070809213808.GC402@moon.usersys.redhat.com> Rob Crittenden wrote: >> I think we should change client.add_user() should take a User object, >> but otherwise it looks great. > > I looked at this a bit today. The problem is I need to provide a single > API. Data passing in through the XML-RPC interface will end up as a dict > (the way it is now). If we start using objects in the GUI we end up with > two APIs. > > I'm ok with that but the goal was to provide a single, consistent API. But > I'm not going to make the change until we all agree on it. Truthfully, TurboGears is happier with hashes and lists - I'm currently translating back and forth before passing to the UI. -Kevin -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 2228 bytes Desc: not available URL: From ssorce at redhat.com Thu Aug 9 21:52:36 2007 From: ssorce at redhat.com (Simo Sorce) Date: Thu, 09 Aug 2007 17:52:36 -0400 Subject: [Freeipa-devel] [PATCH] Jumbo patch to add kerberos auth, do other stuff In-Reply-To: <46BB7D96.7040309@redhat.com> References: <46B74442.1030005@redhat.com> <20070807191613.GA22023@moon.usersys.redhat.com> <46BB7D96.7040309@redhat.com> Message-ID: <1186696356.22941.33.camel@localhost.localdomain> On Thu, 2007-08-09 at 16:48 -0400, Rob Crittenden wrote: > Kevin McCarthy wrote: > > Rob Crittenden wrote: > >> Kevin, some things of note for you: > >> > >> 1. I removed the userPassword question. Would have required another ACI to > >> allow it and I just didn't feel like messing with it. > >> 2. You want to import ipaclient now and invoke things like this: > >> > >> client = ipaclient.IPAClient(True) > >> client.set_principal("test at REALM") > >> users = client.add_user (kw) > > > > I can report success with the patch. I had to filter out the 'submit' > > button from the kw list, but after that was able to successfully add a > > user. > > > > I think we should change client.add_user() should take a User object, > > but otherwise it looks great. > > > > I looked at this a bit today. The problem is I need to provide a single > API. Data passing in through the XML-RPC interface will end up as a dict > (the way it is now). If we start using objects in the GUI we end up with > two APIs. Isn't dict case sensitive? How do you handle the fact the attribute names are not ? Simo. From rcritten at redhat.com Thu Aug 9 21:59:14 2007 From: rcritten at redhat.com (Rob Crittenden) Date: Thu, 09 Aug 2007 17:59:14 -0400 Subject: [Freeipa-devel] [PATCH] Jumbo patch to add kerberos auth, do other stuff In-Reply-To: <1186696356.22941.33.camel@localhost.localdomain> References: <46B74442.1030005@redhat.com> <20070807191613.GA22023@moon.usersys.redhat.com> <46BB7D96.7040309@redhat.com> <1186696356.22941.33.camel@localhost.localdomain> Message-ID: <46BB8E32.7020106@redhat.com> Simo Sorce wrote: > On Thu, 2007-08-09 at 16:48 -0400, Rob Crittenden wrote: >> Kevin McCarthy wrote: >>> Rob Crittenden wrote: >>>> Kevin, some things of note for you: >>>> >>>> 1. I removed the userPassword question. Would have required another ACI to >>>> allow it and I just didn't feel like messing with it. >>>> 2. You want to import ipaclient now and invoke things like this: >>>> >>>> client = ipaclient.IPAClient(True) >>>> client.set_principal("test at REALM") >>>> users = client.add_user (kw) >>> I can report success with the patch. I had to filter out the 'submit' >>> button from the kw list, but after that was able to successfully add a >>> user. >>> >>> I think we should change client.add_user() should take a User object, >>> but otherwise it looks great. >>> >> I looked at this a bit today. The problem is I need to provide a single >> API. Data passing in through the XML-RPC interface will end up as a dict >> (the way it is now). If we start using objects in the GUI we end up with >> two APIs. > > Isn't dict case sensitive? How do you handle the fact the attribute > names are not ? The ipaldap.py code handles that on the server (it uses ldap.cidict which is not case-sensitive for attributes). It is up the the client to do it for their own purposes. rob -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3245 bytes Desc: S/MIME Cryptographic Signature URL: From rcritten at redhat.com Fri Aug 10 14:15:22 2007 From: rcritten at redhat.com (Rob Crittenden) Date: Fri, 10 Aug 2007 10:15:22 -0400 Subject: [Freeipa-devel] [PATCH] small patch for user location Message-ID: <46BC72FA.40504@redhat.com> The location in the tree where new users were added was hardcoded to ou=users,ou=default,$basedn This patch includes an optional argument so the caller can specify this if they want. rob -------------- next part -------------- A non-text attachment was scrubbed... Name: userloc.patch Type: text/x-patch Size: 1044 bytes Desc: not available URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3245 bytes Desc: S/MIME Cryptographic Signature URL: From ssorce at redhat.com Fri Aug 10 14:26:42 2007 From: ssorce at redhat.com (Simo Sorce) Date: Fri, 10 Aug 2007 10:26:42 -0400 Subject: [Freeipa-devel] [PATCH] small patch for user location In-Reply-To: <46BC72FA.40504@redhat.com> References: <46BC72FA.40504@redhat.com> Message-ID: <1186756002.22941.43.camel@localhost.localdomain> On Fri, 2007-08-10 at 10:15 -0400, Rob Crittenden wrote: > The location in the tree where new users were added was hardcoded to > ou=users,ou=default,$basedn > > This patch includes an optional argument so the caller can specify this > if they want. Looks ok, I'll push this. Simo. From kmccarth at redhat.com Fri Aug 10 15:55:21 2007 From: kmccarth at redhat.com (Kevin McCarthy) Date: Fri, 10 Aug 2007 08:55:21 -0700 Subject: [Freeipa-devel] [PATCH] initial IPA turbogears gui checkin Message-ID: <20070810155521.GC28334@moon.usersys.redhat.com> Attached is an initial check-in for the turbogears front-end. It has a very simple add/show/list user functionality and a lot of stubbing everywhere else. The patch also contains some of the turbogears template files generated by tg-admin when generating the framework. I don't know enough about these files to know what's necessary and what's not just yet so thought it would be better to check them all in. I've put the files under ipa-server/ipa-gui, but can move an resubmit if you prefer another place. -Kevin -------------- next part -------------- # HG changeset patch # User kmccarth at redhat.com # Date 1186919598 25200 # Node ID 59663a581e56401f8f3f7e802f064058d9052324 # Parent c7a2e210de44785bded93953d69c46e7b80ab047 Addiing initial turbogears web gui. Contains simple user add, list, and view pages. diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/README.txt Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,4 @@ +ipa-gui + +This is a TurboGears (http://www.turbogears.org) project. It can be +started by running the start-ipagui.py script. \ No newline at end of file diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/dev.cfg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/dev.cfg Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,66 @@ +[global] +# This is where all of your settings go for your development environment +# Settings that are the same for both development and production +# (such as template engine, encodings, etc.) all go in +# ipagui/config/app.cfg + +# DATABASE + +# pick the form for your database +# sqlobject.dburi="postgres://username at hostname/databasename" +# sqlobject.dburi="mysql://username:password at hostname:port/databasename" +# sqlobject.dburi="sqlite:///file_name_and_path" + +# If you have sqlite, here's a simple default to get you started +# in development +sqlobject.dburi="sqlite://%(current_dir_uri)s/devdata.sqlite" + + +# if you are using a database or table type without transactions +# (MySQL default, for example), you should turn off transactions +# by prepending notrans_ on the uri +# sqlobject.dburi="notrans_mysql://username:password at hostname:port/databasename" + +# for Windows users, sqlite URIs look like: +# sqlobject.dburi="sqlite:///drive_letter:/path/to/file" + +# SERVER + +# Some server parameters that you may want to tweak +# server.socket_port=8080 + +# Enable the debug output at the end on pages. +# log_debug_info_filter.on = False + +server.environment="development" +autoreload.package="ipagui" + +# Auto-Reload after code modification +# autoreload.on = True + +# Set to True if you'd like to abort execution if a controller gets an +# unexpected parameter. False by default +tg.strict_parameters = True + +# LOGGING +# Logging configuration generally follows the style of the standard +# Python logging module configuration. Note that when specifying +# log format messages, you need to use *() for formatting variables. +# Deployment independent log configuration is in ipagui/config/log.cfg +[logging] + +[[loggers]] +[[[ipagui]]] +level='DEBUG' +qualname='ipagui' +handlers=['debug_out'] + +[[[allinfo]]] +level='INFO' +handlers=['debug_out'] + +[[[access]]] +level='INFO' +qualname='turbogears.access' +handlers=['access_out'] +propagate=0 diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipa_gui.egg-info/PKG-INFO --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipa_gui.egg-info/PKG-INFO Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,15 @@ +Metadata-Version: 1.0 +Name: ipa-gui +Version: 1.0 +Summary: UNKNOWN +Home-page: UNKNOWN +Author: UNKNOWN +Author-email: UNKNOWN +License: UNKNOWN +Description: UNKNOWN +Platform: UNKNOWN +Classifier: Development Status :: 3 - Alpha +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Framework :: TurboGears diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipa_gui.egg-info/SOURCES.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipa_gui.egg-info/SOURCES.txt Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,21 @@ +README.txt +setup.py +start-ipagui.py +ipa_gui.egg-info/PKG-INFO +ipa_gui.egg-info/SOURCES.txt +ipa_gui.egg-info/dependency_links.txt +ipa_gui.egg-info/not-zip-safe +ipa_gui.egg-info/paster_plugins.txt +ipa_gui.egg-info/requires.txt +ipa_gui.egg-info/sqlobject.txt +ipa_gui.egg-info/top_level.txt +ipagui/__init__.py +ipagui/controllers.py +ipagui/json.py +ipagui/model.py +ipagui/release.py +ipagui/config/__init__.py +ipagui/templates/__init__.py +ipagui/tests/__init__.py +ipagui/tests/test_controllers.py +ipagui/tests/test_model.py diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipa_gui.egg-info/dependency_links.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipa_gui.egg-info/dependency_links.txt Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,1 @@ + diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipa_gui.egg-info/not-zip-safe --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipa_gui.egg-info/not-zip-safe Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,1 @@ + diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipa_gui.egg-info/paster_plugins.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipa_gui.egg-info/paster_plugins.txt Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,2 @@ +TurboGears +PasteScript diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipa_gui.egg-info/requires.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipa_gui.egg-info/requires.txt Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,1 @@ +TurboGears >= 1.0.2.2 \ No newline at end of file diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipa_gui.egg-info/sqlobject.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipa_gui.egg-info/sqlobject.txt Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,2 @@ +db_module=ipagui.model +history_dir=$base/ipagui/sqlobject-history diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipa_gui.egg-info/top_level.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipa_gui.egg-info/top_level.txt Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,1 @@ +ipagui diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipagui/config/app.cfg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipagui/config/app.cfg Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,51 @@ +[global] +# The settings in this file should not vary depending on the deployment +# environment. dev.cfg and prod.cfg are the locations for +# the different deployment settings. Settings in this file will +# be overridden by settings in those other files. + +# The commented out values below are the defaults + +# VIEW + +# which view (template engine) to use if one is not specified in the +# template name +# tg.defaultview = "kid" + +# The following kid settings determine the settings used by the kid serializer. + +# One of (html|html-strict|xhtml|xhtml-strict|xml|json) +# kid.outputformat="html" + +# kid.encoding="utf-8" + +# The sitetemplate is used for overall styling of a site that +# includes multiple TurboGears applications +# tg.sitetemplate="" + +# Allow every exposed function to be called as json, +# tg.allow_json = False + +# List of Widgets to include on every page. +# for exemple ['turbogears.mochikit'] +# tg.include_widgets = [] + +# Set to True if the scheduler should be started +# tg.scheduler = False + +# Set session or cookie +# session_filter.on = True + + +# compress the data sends to the web browser +# [/] +# gzip_filter.on = True +# gzip_filter.mime_types = ["application/x-javascript", "text/javascript", "text/html", "text/css", "text/plain"] + +[/static] +static_filter.on = True +static_filter.dir = "%(top_level_dir)s/static" + +[/favicon.ico] +static_filter.on = True +static_filter.file = "%(top_level_dir)s/static/images/favicon.ico" diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipagui/config/log.cfg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipagui/config/log.cfg Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,29 @@ +# LOGGING +# Logging is often deployment specific, but some handlers and +# formatters can be defined here. + +[logging] +[[formatters]] +[[[message_only]]] +format='*(message)s' + +[[[full_content]]] +format='*(asctime)s *(name)s *(levelname)s *(message)s' + +[[handlers]] +[[[debug_out]]] +class='StreamHandler' +level='DEBUG' +args='(sys.stdout,)' +formatter='full_content' + +[[[access_out]]] +class='StreamHandler' +level='INFO' +args='(sys.stdout,)' +formatter='message_only' + +[[[error_out]]] +class='StreamHandler' +level='ERROR' +args='(sys.stdout,)' diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipagui/controllers.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipagui/controllers.py Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,168 @@ +import cherrypy +import turbogears +from turbogears import controllers, expose, flash +from turbogears import validators, validate +from turbogears import widgets, paginate +from turbogears import error_handler +# from model import * +# import logging +# log = logging.getLogger("ipagui.controllers") +# import ipa.rpcclient +import ipa.config +import ipa.ipaclient +import ipa.user +import xmlrpclib +import forms.user + +ipa.config.init_config() +user_form = forms.user.UserFormWidget() + +client = ipa.ipaclient.IPAClient(True) +client.set_principal("test at FREEIPA.ORG") + +def restrict_post(): + if cherrypy.request.method != "POST": + turbogears.flash("This method only accepts posts") + raise turbogears.redirect("/") + +def user_to_hash(user): + return { + 'uid' : user.getValue('uid'), + 'givenName' : user.getValue('givenName'), + 'sn' : user.getValue('sn'), + 'mail' : user.getValue('mail'), + 'telephoneNumber': user.getValue('telephoneNumber'), + 'uidNumber': user.getValue('uidNumber'), + 'gidNumber': user.getValue('gidNumber'), + } + +class Root(controllers.RootController): + + @expose(template="ipagui.templates.welcome") + def index(self): + return dict() + + + ######## + # User # + ######## + + @expose("ipagui.templates.usernew") + def usernew(self, tg_errors=None): + """Displays the new user form""" + if tg_errors: + turbogears.flash("There was a problem with the form!") + + return dict(form=user_form) + + @expose() + def usercreate(self, **kw): + """Creates a new user""" + restrict_post() + if kw.get('submit') == 'Cancel': + turbogears.flash("Add user cancelled") + raise turbogears.redirect('/userlist') + + tg_errors, kw = self.uservalidate(**kw) + if tg_errors: + return dict(form=user_form, tg_template='ipagui.templates.usernew') + + try: + # rv = ipa.rpcclient.add_user(kw) + newuser = ipa.user.User(None) + newuser.setValue('uid', kw['uid']) + newuser.setValue('givenName', kw['givenName']) + newuser.setValue('sn', kw['sn']) + newuser.setValue('mail', kw['mail']) + newuser.setValue('telephoneNumber', kw['telephoneNumber']) + newuser2 = { + 'uid' : kw['uid'], + 'givenName' : kw['givenName'], + 'sn' : kw['sn'], + 'mail' : kw['mail'], + 'telephoneNumber': kw['telephoneNumber'] + } + rv = client.add_user(newuser2) + turbogears.flash("%s added!" % kw['uid']) + raise turbogears.redirect('/usershow', uid=kw['uid']) + except xmlrpclib.Fault, f: + turbogears.flash("User add failed: " + str(f.faultString)) + return dict(form=user_form, tg_template='ipagui.templates.usernew') + + + @expose("ipagui.templates.useredit") + def useredit(self, uid, tg_errors=None): + """Displays the edit user form""" + if tg_errors: + turbogears.flash("There was a problem with the form!") + + # user = ipa.rpcclient.get_user(uid) + user = client.get_user(uid) + return dict(form=user_form, user=user_to_hash(user)) + + @expose() + def userupdate(self, **kw): + """Updates an existing user""" + restrict_post() + if kw.get('submit') == 'Cancel': + turbogears.flash("Edit user cancelled") + raise turbogears.redirect('/usershow', uid=kw.get('uid')) + + tg_errors, kw = self.uservalidate(**kw) + if tg_errors: + return dict(form=user_form, user={}, tg_template='ipagui.templates.useredit') + + try: + # rv = ipa.rpcclient.add_user(kw) + turbogears.flash("%s updated!" % kw['uid']) + raise turbogears.redirect('/usershow', uid=kw['uid']) + except xmlrpclib.Fault, f: + turbogears.flash("User add failed: " + str(f.faultString)) + return dict(form=user_form, user={}, tg_template='ipagui.templates.useredit') + + + @expose("ipagui.templates.userlist") + @paginate('users', limit=3, allow_limit_override=True) + def userlist(self): + """Retrieve a list of all users and display them in one huge list""" + # users = ipa.rpcclient.get_all_users() + users = client.get_all_users() + return dict(users=users) + + + @expose("ipagui.templates.usershow") + def usershow(self, uid): + """Retrieve a single user for display""" + try: + # user = ipa.rpcclient.get_user(uid) + user = client.get_user(uid) + return dict(user=user_to_hash(user)) + except xmlrpclib.Fault, f: + turbogears.flash("User show failed: " + str(f.faultString)) + raise turbogears.redirect("/") + + @validate(form=user_form) + def uservalidate(self, tg_errors=None, **kw): + return tg_errors, kw + + @expose() + def userindex(self): + raise turbogears.redirect("/userlist") + + + ######### + # Group # + ######### + + @expose("ipagui.templates.groupindex") + def groupindex(self, tg_errors=None): + return dict() + + + ############ + # Resource # + ############ + + @expose("ipagui.templates.resindex") + def resindex(self, tg_errors=None): + return dict() diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipagui/forms/user.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipagui/forms/user.py Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,67 @@ +import turbogears +from turbogears import validators, widgets + +class UserFields(): + uid = widgets.TextField(name="uid", label="Login:") + userPassword = widgets.TextField(name="userPassword", label="Password:") + uidNumber = widgets.TextField(name="uidNumber", label="UID:") + gidNumber = widgets.TextField(name="gidNumber", label="GID:") + givenName = widgets.TextField(name="givenName", label="First name:") + sn = widgets.TextField(name="sn", label="Last name:") + mail = widgets.TextField(name="mail", label="E-mail address:") + telephoneNumber = widgets.TextField(name="telephoneNumber", label="Phone:") + + uid.validator = validators.PlainText(not_empty=True) + userPassword.validator = validators.String(not_empty=True) + givenName.validator = validators.String(not_empty=True) + sn.validator = validators.String(not_empty=True) + mail.validator = validators.Email(not_empty=True) + # validators.PhoneNumber may be a bit too picky, requiring an area code + telephoneNumber.validator = validators.PlainText(not_empty=True) + + +class UserFormWidget(widgets.Form): + params = ['user'] +# fields = [UserFields.uid, UserFields.userPassword, UserFields.givenName, +# UserFields.sn, UserFields.mail] + fields = [UserFields.uid, UserFields.givenName, + UserFields.uidNumber, UserFields.gidNumber, + UserFields.sn, UserFields.mail] + + def __init__(self, *args, **kw): + super(UserFormWidget,self).__init__(*args, **kw) + (self.template_c, self.template) = widgets.meta.load_kid_template("ipagui.templates.userform") + self.user = UserFields + + def update_params(self, params): + super(UserFormWidget,self).update_params(params) + params['has_foo'] = self.has_foo + + def has_foo(self): + return False + +# TODO - add dynamic field retrieval: +# myfields=[] +# schema = ipa.rpcclient.get_add_schema () +# +# # FIXME: What if schema is None or an error is thrown? +# +# for s in schema: +# required=False +# +# if (s['type'] == "text"): +# field = widgets.TextField(name=s['name'],label=s['label']) +# elif (s['type'] == "password"): +# field = widgets.PasswordField(name=s['name'],label=s['label']) +# +# if (s['required'] == "true"): +# required=True +# +# if (s['validator'] == "text"): +# field.validator=validators.PlainText(not_empty=required) +# elif (s['validator'] == "email"): +# field.validator=validators.Email(not_empty=required) +# elif (s['validator'] == "string"): +# field.validator=validators.String(not_empty=required) +# +# myfields.append(field) diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipagui/json.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipagui/json.py Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,10 @@ +# A JSON-based API(view) for your app. +# Most rules would look like: +# @jsonify.when("isinstance(obj, YourClass)") +# def jsonify_yourclass(obj): +# return [obj.val1, obj.val2] +# @jsonify can convert your objects to following types: +# lists, dicts, numbers and strings + +from turbojson.jsonify import jsonify + diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipagui/model.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipagui/model.py Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,9 @@ +from turbogears.database import PackageHub +from sqlobject import * + +hub = PackageHub('ipagui') +__connection__ = hub + +# class YourDataClass(SQLObject): +# pass + diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipagui/release.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipagui/release.py Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,14 @@ +# Release information about ipa-gui + +version = "1.0" + +# description = "Your plan to rule the world" +# long_description = "More description about your plan" +# author = "Your Name Here" +# email = "YourEmail at YourDomain" +# copyright = "Vintage 2006 - a good year indeed" + +# if it's open source, you might want to specify these +# url = "http://yourcool.site/" +# download_url = "http://yourcool.site/download" +# license = "MIT" diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipagui/static/css/style.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipagui/static/css/style.css Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,146 @@ +/* + * Quick mash-up of CSS for the TG quick start page. + */ + +html, body { + color: #000; + background:#fff; + margin: 0; + padding: 0; +} + +body { + min-width: 750px; +} + +#page { + background:#ccc; /* should be same as #sidebar */ + margin:0 auto; + width:100%; +} + + +#header { + background:#fff; +} + +#header h1 { + padding:5px; + margin:0; +} + + +#nav { + background:#cc0000; + color:#fff; + padding:5px; +} + +#nav ul { + margin:0; + padding:0; + list-style:none; +} + +#nav li { + display:inline; +} + +#nav a:visited { + color:#fff; +} +#nav a:link { + color:#fff; +} + + +#main_content { + background:#fff; + float:right; + width:85%; + border-left: 1px solid #000; + padding-left: 15px; + padding-bottom: 15px; +/* color: black; + font-size: 127%; + background-color: white; + margin: 0 auto 0 auto; + padding: 10px; + float: left; */ +} + + +#sidebar { + background:#ccc; /* should be same as #page */ + float:left; + width:10%; + /* border: 1px solid #aaa; + background-color: #eee; + margin: 0.5em; + padding: 1em; + float: left; + font-size: 88%; */ +} + +#sidebar h2 { + margin-top: 0; +} + +#sidebar ul { + margin-left: 1.5em; + padding-left: 0; +} + + +#footer { + background:#fff; + clear:both; + border-top: 1px solid #000; + /* color: #999; + background-color: white; + padding: 10px; + font-size: 80%; + text-align: center; + margin: 0 auto 1em auto; */ +} + + +.formsection { + color: #888888; + width: 90%; + font-weight: bold; + border-bottom: 1px solid; + margin: 20px 0px 20px 0px; +} + +.formtable { + width: 90%; +} + +.formtable th { + width: 15%; + text-align: right; +} + +#status_block { + margin: 0 auto 0.5em auto; + padding: 15px 10px 15px 55px; + background: #cec URL('../images/ok.png') left center no-repeat; + border: 1px solid #9c9; + width: 450px; + font-size: 120%; + font-weight: bolder; +} + +.notice { + margin: 0.5em auto 0.5em auto; + padding: 15px 10px 15px 55px; + width: 450px; + background: #eef URL('../images/info.png') left center no-repeat; + border: 1px solid #cce; +} + +.fielderror { + color: red; + font-weight: bold; +} diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipagui/static/images/favicon.ico Binary file ipa-server/ipa-gui/ipagui/static/images/favicon.ico has changed diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipagui/static/images/header_inner.png Binary file ipa-server/ipa-gui/ipagui/static/images/header_inner.png has changed diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipagui/static/images/info.png Binary file ipa-server/ipa-gui/ipagui/static/images/info.png has changed diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipagui/static/images/ok.png Binary file ipa-server/ipa-gui/ipagui/static/images/ok.png has changed diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipagui/static/images/tg_under_the_hood.png Binary file ipa-server/ipa-gui/ipagui/static/images/tg_under_the_hood.png has changed diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipagui/static/images/under_the_hood_blue.png Binary file ipa-server/ipa-gui/ipagui/static/images/under_the_hood_blue.png has changed diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipagui/templates/groupindex.kid --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipagui/templates/groupindex.kid Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,18 @@ + + + + +Group Listing + + + Groups go here. +
+
+
+
+
+
+
+ + diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipagui/templates/grouplayout.kid --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipagui/templates/grouplayout.kid Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,22 @@ + + + + + + +
+
+ +
+
+ + + + + diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipagui/templates/login.kid --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipagui/templates/login.kid Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,112 @@ + + + + + + Login + + + + +
+

Login

+

${message}

+
+ + + + + + + + + + + + +
+ + + +
+ + + +
+ +
+ + + + +
+
+ + diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipagui/templates/master.kid --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipagui/templates/master.kid Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,48 @@ + + + + + + + Your title goes here + + + + + +
+ + Login + + + Welcome ${tg.identity.user.display_name}. + Logout + +
+ +
+ + + + +
+ + + +
+ + + + diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipagui/templates/resindex.kid --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipagui/templates/resindex.kid Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,18 @@ + + + + +Resource Listing + + + Resources go here. +
+
+
+
+
+
+
+ + diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipagui/templates/reslayout.kid --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipagui/templates/reslayout.kid Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,22 @@ + + + + + + +
+
+ +
+
+ + + + + diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipagui/templates/useredit.kid --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipagui/templates/useredit.kid Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,13 @@ + + + + +Edit a Person + + +

Edit User

+ + ${form.display(action="userupdate", value=user)} + + diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipagui/templates/userform.kid --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipagui/templates/userform.kid Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,125 @@ +
+
+ +
Account Details
+ + + + + + + + + + + + + + + + + +
+ + + +
+ + + +
+ + + +
+ +
Identity Details
+ + + + + + + + + + +
+ + + + +
+ + + +
+ +
Contact Details
+ + + + + + + + + +
+ + + +
+ + + +
+ + + + + + + +
+
+ +
+
+ +
+ +
+
diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipagui/templates/userlayout.kid --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipagui/templates/userlayout.kid Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,22 @@ + + + + + + +
+
+ +
+
+ + + + + diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipagui/templates/userlist.kid --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipagui/templates/userlist.kid Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,26 @@ + + + + +User Listing + + +
+ People List +
+ Page: + + ${page} + ${page} + +

+ + ${user.cn} +
+
+

+
+ + diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipagui/templates/usernew.kid --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipagui/templates/usernew.kid Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,13 @@ + + + + + Add a Person + + +

Add New User

+ + ${form.display(action="usercreate")} + + diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipagui/templates/usershow.kid --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipagui/templates/usershow.kid Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,50 @@ + + + + + View a Person + + +

View User

+ +
Account Details
+ + + + + + + + + + + + + +
User ID:${user.get("uid")}
UID:${user.get("uidNumber")}
GID:${user.get("gidNumber")}
+ +
Identity Details
+ + + + + +
Full Name:${user.get("givenName")} ${user.get("sn")}
+ +
Contact Details
+ + + + + + + + + +
Email:${user.get("mail")}
Telephone:${user.get("telephoneNumber")}
+ + edit + + + diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipagui/templates/welcome.kid --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipagui/templates/welcome.kid Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,18 @@ + + + + +Welcome + + + +
+
+

Welcome to Free IPA

+
+ + + diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipagui/tests/test_controllers.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipagui/tests/test_controllers.py Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,32 @@ +import unittest +import turbogears +from turbogears import testutil +from ipagui.controllers import Root +import cherrypy + +cherrypy.root = Root() + +class TestPages(unittest.TestCase): + + def setUp(self): + turbogears.startup.startTurboGears() + + def tearDown(self): + """Tests for apps using identity need to stop CP/TG after each test to + stop the VisitManager thread. + See http://trac.turbogears.org/turbogears/ticket/1217 for details. + """ + turbogears.startup.stopTurboGears() + + def test_method(self): + "the index method should return a string called now" + import types + result = testutil.call(cherrypy.root.index) + assert type(result["now"]) == types.StringType + + def test_indextitle(self): + "The indexpage should have the right title" + testutil.createRequest("/") + response = cherrypy.response.body[0].lower() + assert "welcome to turbogears" in response + diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/ipagui/tests/test_model.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipagui/tests/test_model.py Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,22 @@ +# If your project uses a database, you can set up database tests +# similar to what you see below. Be sure to set the db_uri to +# an appropriate uri for your testing database. sqlite is a good +# choice for testing, because you can use an in-memory database +# which is very fast. + +from turbogears import testutil, database +# from ipagui.model import YourDataClass, User + +# database.set_db_uri("sqlite:///:memory:") + +# class TestUser(testutil.DBTest): +# def get_model(self): +# return User +# def test_creation(self): +# "Object creation should set the name" +# obj = User(user_name = "creosote", +# email_address = "spam at python.not", +# display_name = "Mr Creosote", +# password = "Wafer-thin Mint") +# assert obj.display_name == "Mr Creosote" + diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/sample-prod.cfg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/sample-prod.cfg Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,84 @@ +[global] +# This is where all of your settings go for your production environment. +# You'll copy this file over to your production server and provide it +# as a command-line option to your start script. +# Settings that are the same for both development and production +# (such as template engine, encodings, etc.) all go in +# ipagui/config/app.cfg + +# DATABASE + +# pick the form for your database +# sqlobject.dburi="postgres://username at hostname/databasename" +# sqlobject.dburi="mysql://username:password at hostname:port/databasename" +# sqlobject.dburi="sqlite:///file_name_and_path" + +# If you have sqlite, here's a simple default to get you started +# in development +sqlobject.dburi="sqlite://%(current_dir_uri)s/devdata.sqlite" + + +# if you are using a database or table type without transactions +# (MySQL default, for example), you should turn off transactions +# by prepending notrans_ on the uri +# sqlobject.dburi="notrans_mysql://username:password at hostname:port/databasename" + +# for Windows users, sqlite URIs look like: +# sqlobject.dburi="sqlite:///drive_letter:/path/to/file" + + +# SERVER + +server.environment="production" + +# Sets the number of threads the server uses +# server.thread_pool = 1 + +# if this is part of a larger site, you can set the path +# to the TurboGears instance here +# server.webpath="" + +# Set to True if you are deploying your App behind a proxy +# e.g. Apache using mod_proxy +# base_url_filter.on = False + +# Set to True if your proxy adds the x_forwarded_host header +# base_url_filter.use_x_forwarded_host = True + +# If your proxy does not add the x_forwarded_host header, set +# the following to the *public* host url. +# (Note: This will be overridden by the use_x_forwarded_host option +# if it is set to True and the proxy adds the header correctly. +# base_url_filter.base_url = "http://www.example.com" + +# Set to True if you'd like to abort execution if a controller gets an +# unexpected parameter. False by default +# tg.strict_parameters = False + +# LOGGING +# Logging configuration generally follows the style of the standard +# Python logging module configuration. Note that when specifying +# log format messages, you need to use *() for formatting variables. +# Deployment independent log configuration is in ipagui/config/log.cfg +[logging] + +[[handlers]] + +[[[access_out]]] +# set the filename as the first argument below +args="('server.log',)" +class='FileHandler' +level='INFO' +formatter='message_only' + +[[loggers]] +[[[ipagui]]] +level='ERROR' +qualname='ipagui' +handlers=['error_out'] + +[[[access]]] +level='INFO' +qualname='turbogears.access' +handlers=['access_out'] +propagate=0 diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/setup.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/setup.py Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,62 @@ +from setuptools import setup, find_packages +from turbogears.finddata import find_package_data + +import os +execfile(os.path.join("ipagui", "release.py")) + +setup( + name="ipa-gui", + version=version, + + # uncomment the following lines if you fill them out in release.py + #description=description, + #author=author, + #author_email=email, + #url=url, + #download_url=download_url, + #license=license, + + install_requires = [ + "TurboGears >= 1.0.2.2", + ], + scripts = ["start-ipagui.py"], + zip_safe=False, + packages=find_packages(), + package_data = find_package_data(where='ipagui', + package='ipagui'), + keywords = [ + # Use keywords if you'll be adding your package to the + # Python Cheeseshop + + # if this has widgets, uncomment the next line + # 'turbogears.widgets', + + # if this has a tg-admin command, uncomment the next line + # 'turbogears.command', + + # if this has identity providers, uncomment the next line + # 'turbogears.identity.provider', + + # If this is a template plugin, uncomment the next line + # 'python.templating.engines', + + # If this is a full application, uncomment the next line + # 'turbogears.app', + ], + classifiers = [ + 'Development Status :: 3 - Alpha', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Framework :: TurboGears', + # if this is an application that you'll distribute through + # the Cheeseshop, uncomment the next line + # 'Framework :: TurboGears :: Applications', + + # if this is a package that includes widgets that you'll distribute + # through the Cheeseshop, uncomment the next line + # 'Framework :: TurboGears :: Widgets', + ], + test_suite = 'nose.collector', + ) + diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/start-ipagui.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/start-ipagui.py Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,25 @@ +#!/usr/bin/python +import pkg_resources +pkg_resources.require("TurboGears") + +from turbogears import update_config, start_server +import cherrypy +cherrypy.lowercase_api = True +from os.path import * +import sys + +# first look on the command line for a desired config file, +# if it's not on the command line, then +# look for setup.py in this directory. If it's not there, this script is +# probably installed +if len(sys.argv) > 1: + update_config(configfile=sys.argv[1], + modulename="ipagui.config") +elif exists(join(dirname(__file__), "setup.py")): + update_config(configfile="dev.cfg",modulename="ipagui.config") +else: + update_config(configfile="prod.cfg",modulename="ipagui.config") + +from ipagui.controllers import Root + +start_server(Root()) diff -r c7a2e210de44 -r 59663a581e56 ipa-server/ipa-gui/test.cfg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/test.cfg Sun Aug 12 04:53:18 2007 -0700 @@ -0,0 +1,4 @@ +# You can place test-specific configuration options here (like test db uri, etc) + +sqlobject.dburi = "sqlite:///:memory:" + -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 2228 bytes Desc: not available URL: From kmccarth at redhat.com Fri Aug 10 16:26:29 2007 From: kmccarth at redhat.com (Kevin McCarthy) Date: Fri, 10 Aug 2007 09:26:29 -0700 Subject: [Freeipa-devel] [PATCH] initial IPA turbogears gui checkin In-Reply-To: <20070810155521.GC28334@moon.usersys.redhat.com> References: <20070810155521.GC28334@moon.usersys.redhat.com> Message-ID: <20070810162628.GD28334@moon.usersys.redhat.com> I notices afterwards that the patch doesn't include empty files and images. I (believe I) have prepared a bundle that contains these files. -Kevin -------------- next part -------------- A non-text attachment was scrubbed... Name: images.bundle Type: application/octet-stream Size: 89886 bytes Desc: not available URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 2228 bytes Desc: not available URL: From kmacmill at redhat.com Fri Aug 10 08:59:52 2007 From: kmacmill at redhat.com (Karl MacMillan) Date: Fri, 10 Aug 2007 04:59:52 -0400 Subject: [Freeipa-devel] [PATCH] initial IPA turbogears gui checkin In-Reply-To: <20070810162628.GD28334@moon.usersys.redhat.com> References: <20070810155521.GC28334@moon.usersys.redhat.com> <20070810162628.GD28334@moon.usersys.redhat.com> Message-ID: <1186736392.5625.0.camel@localhost.localdomain> On Fri, 2007-08-10 at 09:26 -0700, Kevin McCarthy wrote: > I notices afterwards that the patch doesn't include empty files and > images. I (believe I) have prepared a bundle that contains these files. > Also - any idea about what the requirements are for this (I'm working on the RPMS now). Karl From kmccarth at redhat.com Fri Aug 10 17:30:19 2007 From: kmccarth at redhat.com (Kevin McCarthy) Date: Fri, 10 Aug 2007 10:30:19 -0700 Subject: [Freeipa-devel] [PATCH] initial IPA turbogears gui checkin In-Reply-To: <1186736392.5625.0.camel@localhost.localdomain> References: <20070810155521.GC28334@moon.usersys.redhat.com> <20070810162628.GD28334@moon.usersys.redhat.com> <1186736392.5625.0.camel@localhost.localdomain> Message-ID: <20070810173019.GF28334@moon.usersys.redhat.com> Karl MacMillan wrote: > On Fri, 2007-08-10 at 09:26 -0700, Kevin McCarthy wrote: > > I notices afterwards that the patch doesn't include empty files and > > images. I (believe I) have prepared a bundle that contains these files. > > > > Also - any idea about what the requirements are for this (I'm working on > the RPMS now). The TurboGears rpm seemed to pull in all the required dependencies. -Kevin -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 2228 bytes Desc: not available URL: From kwirth at redhat.com Fri Aug 10 18:58:25 2007 From: kwirth at redhat.com (Karl Wirth) Date: Fri, 10 Aug 2007 14:58:25 -0400 Subject: [Freeipa-devel] freeIPAv2 requirements Message-ID: <46BCB551.6070908@redhat.com> Hello, I posted much more detailed requirements for freeIPAv2 You can see them at: http://www.freeipa.org/page/Roadmap#Release_2 Lets discuss! Do these look like the right requirements? What's missing? What shouldn't be there? Who wants to work on what parts? Best regards, Karl From kmacmill at redhat.com Fri Aug 10 11:08:53 2007 From: kmacmill at redhat.com (Karl MacMillan) Date: Fri, 10 Aug 2007 07:08:53 -0400 Subject: [Freeipa-devel] Updates to build system / spec files Message-ID: <1186744133.5625.26.camel@localhost.localdomain> I'm about to commit the following to make the build system build ipa_kpasswd and the slapi plugins. [?1034hdiff -r c7a2e210de44 Makefile --- a/Makefile Fri Aug 10 10:30:15 2007 -0400 +++ b/Makefile Fri Aug 10 06:48:27 2007 -0400 @@ -1,6 +1,9 @@ SUBDIRS=ipa-server ipa-admintools ipa-py SUBDIRS=ipa-server ipa-admintools ipa-python PRJ_PREFIX=freeipa + +# set to 1 to produce a debug build of all subprojects +#DEBUG=1 # Version numbers - this is for the entire server. After # updating this you should run the version-update @@ -26,12 +29,18 @@ PYTHON_TARBALL_PREFIX=$(PRJ_PREFIX)-pyth PYTHON_TARBALL_PREFIX=$(PRJ_PREFIX)-python-$(PYTHON_VERSION) PYTHON_TARBALL=$(PYTHON_TARBALL_PREFIX).tgz +ifeq ($(DEBUG),1) + export CFLAGS = -g -Wall -Wshadow + export LDFLAGS = -g +endif + + all: @for subdir in $(SUBDIRS); do \ (cd $$subdir && $(MAKE) $@) || exit 1; \ done -install: +install: all @for subdir in $(SUBDIRS); do \ (cd $$subdir && $(MAKE) $@) || exit 1; \ done diff -r c7a2e210de44 ipa-server/Makefile --- a/ipa-server/Makefile Fri Aug 10 10:30:15 2007 -0400 +++ b/ipa-server/Makefile Fri Aug 10 05:24:59 2007 -0400 @@ -1,4 +1,4 @@ SUBDIRS=ipa-install xmlrpc-server -SUBDIRS=ipa-install xmlrpc-server +SUBDIRS=ipa-install xmlrpc-server ipa-kpasswd ipa-slapi-plugins PYTHONDIR=$(DESTDIR)/usr/share/ipa/ipaserver all: diff -r c7a2e210de44 ipa-server/freeipa-server.spec --- a/ipa-server/freeipa-server.spec Fri Aug 10 10:30:15 2007 -0400 +++ b/ipa-server/freeipa-server.spec Fri Aug 10 07:00:20 2007 -0400 @@ -1,6 +1,6 @@ Name: freeipa-server Name: freeipa-server Version: 0.1.0 -Release: 3%{?dist} +Release: %{?dist} Summary: FreeIPA authentication server Group: System Environment/Base @@ -8,11 +8,14 @@ URL: http://www.freeipa.org URL: http://www.freeipa.org Source0: %{name}-%{version}.tgz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) -BuildArch: noarch +#BuildArch: -Requires: python fedora-ds-base krb5-server krb5-server-ldap nss-tools openldap-clients httpd mod_python mod_auth_kerb python-ldap freeipa-python ntpd cyrus-sasl-gssapi +BuildRequires: fedora-ds-base-devel openldap-devel krb5-devel nss-devel mozldap-devel openssl-devel mhash-devel + +Requires: python fedora-ds-base krb5-server krb5-server-ldap nss-tools openldap-clients httpd mod_python mod_auth_kerb python-ldap freeipa-python ntpd cyrus-sasl-gssapi nss %define httpd_conf /etc/httpd/conf.d +%define plugin_dir /usr/lib/fedora-ds/plugins %description FreeIPA is a server for identity, policy, and audit. @@ -20,9 +23,14 @@ FreeIPA is a server for identity, policy %prep %setup -q +%build + +make DESTDIR=%{buildroot} + %install rm -rf %{buildroot} mkdir -p %{buildroot}%{_sbindir} +mkdir -p %{buildroot}%{plugin_dir} make install DESTDIR=%{buildroot} @@ -35,12 +43,19 @@ rm -rf %{buildroot} %defattr(-,root,root,-) %{_sbindir}/ipa-server-install %{_sbindir}/ipa-server-setupssl +%{_sbindir}/ipa_kpasswd + %dir %{_usr}/share/ipa %{_usr}/share/ipa/* +%{plugin_dir}/libipa_pwd_extop.so + %changelog +* Fri Aug 10 2007 Karl MacMillan - 0.1.0-3 +- Added support for ipa_kpasswd and ipa_pwd_extop + * Mon Aug 5 2007 Rob Crittenden - 0.1.0-3 - Abstracted client class to work directly or over RPC diff -r c7a2e210de44 ipa-server/freeipa-server.spec.in --- a/ipa-server/freeipa-server.spec.in Fri Aug 10 10:30:15 2007 -0400 +++ b/ipa-server/freeipa-server.spec.in Fri Aug 10 07:00:18 2007 -0400 @@ -1,6 +1,6 @@ Name: freeipa-server Name: freeipa-server Version: VERSION -Release: 3%{?dist} +Release: %{?dist} Summary: FreeIPA authentication server Group: System Environment/Base @@ -8,11 +8,14 @@ URL: http://www.freeipa.org URL: http://www.freeipa.org Source0: %{name}-%{version}.tgz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) -BuildArch: noarch +#BuildArch: -Requires: python fedora-ds-base krb5-server krb5-server-ldap nss-tools openldap-clients httpd mod_python mod_auth_kerb python-ldap freeipa-python ntpd cyrus-sasl-gssapi +BuildRequires: fedora-ds-base-devel openldap-devel krb5-devel nss-devel mozldap-devel openssl-devel mhash-devel + +Requires: python fedora-ds-base krb5-server krb5-server-ldap nss-tools openldap-clients httpd mod_python mod_auth_kerb python-ldap freeipa-python ntpd cyrus-sasl-gssapi nss %define httpd_conf /etc/httpd/conf.d +%define plugin_dir /usr/lib/fedora-ds/plugins %description FreeIPA is a server for identity, policy, and audit. @@ -20,9 +23,14 @@ FreeIPA is a server for identity, policy %prep %setup -q +%build + +make DESTDIR=%{buildroot} + %install rm -rf %{buildroot} mkdir -p %{buildroot}%{_sbindir} +mkdir -p %{buildroot}%{plugin_dir} make install DESTDIR=%{buildroot} @@ -35,12 +43,19 @@ rm -rf %{buildroot} %defattr(-,root,root,-) %{_sbindir}/ipa-server-install %{_sbindir}/ipa-server-setupssl +%{_sbindir}/ipa_kpasswd + %dir %{_usr}/share/ipa %{_usr}/share/ipa/* +%{plugin_dir}/libipa_pwd_extop.so + %changelog +* Fri Aug 10 2007 Karl MacMillan - VERSION-3 +- Added support for ipa_kpasswd and ipa_pwd_extop + * Mon Aug 5 2007 Rob Crittenden - 0.1.0-3 - Abstracted client class to work directly or over RPC diff -r c7a2e210de44 ipa-server/ipa-install/Makefile --- a/ipa-server/ipa-install/Makefile Fri Aug 10 10:30:15 2007 -0400 +++ b/ipa-server/ipa-install/Makefile Fri Aug 10 06:42:06 2007 -0400 @@ -3,6 +3,7 @@ all: ; all: ; install: + -mkdir $(SBINDIR) install -m 755 ipa-server-install $(SBINDIR) install -m 755 ipa-server-setupssl $(SBINDIR) $(MAKE) -C share $@ diff -r c7a2e210de44 ipa-server/ipa-kpasswd/Makefile --- a/ipa-server/ipa-kpasswd/Makefile Fri Aug 10 10:30:15 2007 -0400 +++ b/ipa-server/ipa-kpasswd/Makefile Fri Aug 10 06:41:06 2007 -0400 @@ -1,5 +1,22 @@ all: -all: - gcc ipa_kpasswd.c -I/usr/include -lkrb5 -llber -lldap -g -o ipa_kpasswd +PREFIX ?= $(DESTDIR)/usr +SBIN = $(PREFIX)/sbin + +LDFLAGS +=-lkrb5 -llber -lldap +CFLAGS ?= -Wall -Wshadow -O2 + +OBJS = $(patsubst %.c,%.o,$(wildcard *.c)) + +all: $(OBJS) + $(CC) $(LDFLAGS) $(OBJS) -o ipa_kpasswd + +%.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< install: - cp -f ipa_kpasswd /usr/sbin/ + -mkdir -p $(SBIN) + install -m 755 ipa_kpasswd $(SBIN) + +clean: + rm -f *.o + rm -f ipa_kpasswd + rm -f *~ \ No newline at end of file diff -r c7a2e210de44 ipa-server/ipa-slapi-plugins/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-slapi-plugins/Makefile Fri Aug 10 04:40:02 2007 -0400 @@ -0,0 +1,17 @@ +SUBDIRS=ipa-pwd-extop + +all: + @for subdir in $(SUBDIRS); do \ + (cd $$subdir && $(MAKE) $@) || exit 1; \ + done + +install: + @for subdir in $(SUBDIRS); do \ + (cd $$subdir && $(MAKE) $@) || exit 1; \ + done + +clean: + @for subdir in $(SUBDIRS); do \ + (cd $$subdir && $(MAKE) $@) || exit 1; \ + done + rm -f *~ diff -r c7a2e210de44 ipa-server/ipa-slapi-plugins/ipa-pwd-extop/Makefile --- a/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/Makefile Fri Aug 10 10:30:15 2007 -0400 +++ b/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/Makefile Fri Aug 10 06:39:24 2007 -0400 @@ -1,5 +1,28 @@ all: -all: - gcc ipa_pwd_extop.c -I/usr/include -I/usr/include/nss3 -I/usr/include/mozldap -I/usr/include/nspr4 -I/usr/include/fedora-ds -lkrb5 -lmhash -llber -lssl -shared -g -fPIC -DPIC -Wl,-soname -Wl,libipa_pwd_extop.so -o libipa_pwd_extop.so +PREFIX ?= $(DESTDIR)/usr +LIBDIR = $(PREFIX)/lib/fedora-ds/plugins +SHAREDIR = $(DESTDIR)/usr/share/ipa + +SONAME = libipa_pwd_extop.so +LDFLAGS += -lkrb5 -llber -lldap -lmhash -llber -lssl +CFLAGS ?= -Wall -Wshadow -O2 +CFLAGS += -I/usr/include/fedora-ds -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: - cp -f libipa_pwd_extop.so /usr/lib/fedora-ds/plugins/ + -mkdir -p $(LIBDIR) + install -m 644 libipa_pwd_extop.so $(LIBDIR) + install -m 644 *.ldif $(SHAREDIR) + +clean: + rm -f *.o + rm -f $(SONAME) + rm -f *~ + + From ssorce at redhat.com Fri Aug 10 19:31:21 2007 From: ssorce at redhat.com (Simo Sorce) Date: Fri, 10 Aug 2007 15:31:21 -0400 Subject: [Freeipa-devel] Updates to build system / spec files In-Reply-To: <1186744133.5625.26.camel@localhost.localdomain> References: <1186744133.5625.26.camel@localhost.localdomain> Message-ID: <1186774281.22941.61.camel@localhost.localdomain> On Fri, 2007-08-10 at 07:08 -0400, Karl MacMillan wrote: > Version: 0.1.0 > -Release: 3%{?dist} > +Release: %{?dist} This one does not look right. Simo. From kmacmill at redhat.com Fri Aug 10 11:31:50 2007 From: kmacmill at redhat.com (Karl MacMillan) Date: Fri, 10 Aug 2007 07:31:50 -0400 Subject: [Freeipa-devel] [PATCH] initial IPA turbogears gui checkin In-Reply-To: <20070810173019.GF28334@moon.usersys.redhat.com> References: <20070810155521.GC28334@moon.usersys.redhat.com> <20070810162628.GD28334@moon.usersys.redhat.com> <1186736392.5625.0.camel@localhost.localdomain> <20070810173019.GF28334@moon.usersys.redhat.com> Message-ID: <1186745510.5625.28.camel@localhost.localdomain> On Fri, 2007-08-10 at 10:30 -0700, Kevin McCarthy wrote: > Karl MacMillan wrote: > > On Fri, 2007-08-10 at 09:26 -0700, Kevin McCarthy wrote: > > > I notices afterwards that the patch doesn't include empty files and > > > images. I (believe I) have prepared a bundle that contains these files. > > > > > > > Also - any idea about what the requirements are for this (I'm working on > > the RPMS now). > > The TurboGears rpm seemed to pull in all the required dependencies. > This has been committed. Karl From kmacmill at redhat.com Fri Aug 10 11:32:17 2007 From: kmacmill at redhat.com (Karl MacMillan) Date: Fri, 10 Aug 2007 07:32:17 -0400 Subject: [Freeipa-devel] Updates to build system / spec files In-Reply-To: <1186774281.22941.61.camel@localhost.localdomain> References: <1186744133.5625.26.camel@localhost.localdomain> <1186774281.22941.61.camel@localhost.localdomain> Message-ID: <1186745537.5625.30.camel@localhost.localdomain> On Fri, 2007-08-10 at 15:31 -0400, Simo Sorce wrote: > On Fri, 2007-08-10 at 07:08 -0400, Karl MacMillan wrote: > > Version: 0.1.0 > > -Release: 3%{?dist} > > +Release: %{?dist} > > This one does not look right. > Already reverted - thanks though. Karl From kmacmill at redhat.com Fri Aug 10 11:37:22 2007 From: kmacmill at redhat.com (Karl MacMillan) Date: Fri, 10 Aug 2007 07:37:22 -0400 Subject: [Freeipa-devel] [PATCH] start services on bootup In-Reply-To: <46BB5294.8040802@redhat.com> References: <46BB5294.8040802@redhat.com> Message-ID: <1186745842.5625.36.camel@localhost.localdomain> On Thu, 2007-08-09 at 13:44 -0400, Rob Crittenden wrote: > I had already set Apache to start on boot. We need fedora-ds and the KDC > to start automatically too. > This patch has been applied (Simo applied it but forgot to send an email). Karl From ssorce at redhat.com Fri Aug 10 20:18:36 2007 From: ssorce at redhat.com (Simo Sorce) Date: Fri, 10 Aug 2007 16:18:36 -0400 Subject: [Freeipa-devel] [PATCH] start services on bootup In-Reply-To: <1186745842.5625.36.camel@localhost.localdomain> References: <46BB5294.8040802@redhat.com> <1186745842.5625.36.camel@localhost.localdomain> Message-ID: <1186777116.22941.75.camel@localhost.localdomain> On Fri, 2007-08-10 at 07:37 -0400, Karl MacMillan wrote: > On Thu, 2007-08-09 at 13:44 -0400, Rob Crittenden wrote: > > I had already set Apache to start on boot. We need fedora-ds and the KDC > > to start automatically too. > > > > This patch has been applied (Simo applied it but forgot to send an > email). Ooops, any chance we can get commit hooks in the main repo? It would make things much easier. Simo. From kmacmill at redhat.com Fri Aug 10 12:19:40 2007 From: kmacmill at redhat.com (Karl MacMillan) Date: Fri, 10 Aug 2007 08:19:40 -0400 Subject: [Freeipa-devel] [PATCH] start services on bootup In-Reply-To: <1186777116.22941.75.camel@localhost.localdomain> References: <46BB5294.8040802@redhat.com> <1186745842.5625.36.camel@localhost.localdomain> <1186777116.22941.75.camel@localhost.localdomain> Message-ID: <1186748380.5625.70.camel@localhost.localdomain> On Fri, 2007-08-10 at 16:18 -0400, Simo Sorce wrote: > On Fri, 2007-08-10 at 07:37 -0400, Karl MacMillan wrote: > > On Thu, 2007-08-09 at 13:44 -0400, Rob Crittenden wrote: > > > I had already set Apache to start on boot. We need fedora-ds and the KDC > > > to start automatically too. > > > > > > > This patch has been applied (Simo applied it but forgot to send an > > email). > > Ooops, any chance we can get commit hooks in the main repo? It would > make things much easier. > Sure - I've never done that for mercurial, though. Feel free to take a stab at it :) Karl From ssorce at redhat.com Fri Aug 10 20:34:47 2007 From: ssorce at redhat.com (Simo Sorce) Date: Fri, 10 Aug 2007 16:34:47 -0400 Subject: [Freeipa-devel] [PATCH] start services on bootup In-Reply-To: <1186748380.5625.70.camel@localhost.localdomain> References: <46BB5294.8040802@redhat.com> <1186745842.5625.36.camel@localhost.localdomain> <1186777116.22941.75.camel@localhost.localdomain> <1186748380.5625.70.camel@localhost.localdomain> Message-ID: <1186778087.22941.77.camel@localhost.localdomain> On Fri, 2007-08-10 at 08:19 -0400, Karl MacMillan wrote: > On Fri, 2007-08-10 at 16:18 -0400, Simo Sorce wrote: > > On Fri, 2007-08-10 at 07:37 -0400, Karl MacMillan wrote: > > > On Thu, 2007-08-09 at 13:44 -0400, Rob Crittenden wrote: > > > > I had already set Apache to start on boot. We need fedora-ds and the KDC > > > > to start automatically too. > > > > > > > > > > This patch has been applied (Simo applied it but forgot to send an > > > email). > > > > Ooops, any chance we can get commit hooks in the main repo? It would > > make things much easier. > > > > Sure - I've never done that for mercurial, though. Feel free to take a > stab at it :) We should use this: http://www.selenic.com/mercurial/wiki/index.cgi/NotifyExtension But I guess you need access to the server to do it ? Simo. From kmccarth at redhat.com Fri Aug 10 21:04:15 2007 From: kmccarth at redhat.com (Kevin McCarthy) Date: Fri, 10 Aug 2007 14:04:15 -0700 Subject: [Freeipa-devel] hg ignore Message-ID: <20070810210415.GG28334@moon.usersys.redhat.com> I got tired of seeing .pyc files and such show up when I did an 'hg status', so poked in the man pages. If you put in your ~/.hgrc: [ui] username = My Name ignore = ~/.hgignore And then in ~/.hgignore: syntax: glob *.pyc *~ .*.swp It will ignore the files. Maybe everyone already figured this out, but just for other's benefit. -Kevin -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 2228 bytes Desc: not available URL: From kmccarth at redhat.com Fri Aug 10 21:11:35 2007 From: kmccarth at redhat.com (Kevin McCarthy) Date: Fri, 10 Aug 2007 14:11:35 -0700 Subject: [Freeipa-devel] [BUNDLE] set start-ipagui.py exec bit Message-ID: <20070810211135.GH28334@moon.usersys.redhat.com> Just noticed permission bits don't make it through for an 'export' either. Grr... is there a better way? Here's a bundle that may work. No changes. Just chmod +x -Kevin -------------- next part -------------- A non-text attachment was scrubbed... Name: execbit.bundle Type: application/octet-stream Size: 501 bytes Desc: not available URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 2228 bytes Desc: not available URL: From kmccarth at redhat.com Fri Aug 10 23:13:36 2007 From: kmccarth at redhat.com (Kevin McCarthy) Date: Fri, 10 Aug 2007 16:13:36 -0700 Subject: [Freeipa-devel] [PATCH] fix user.getValue() Message-ID: <20070810231335.GI28334@moon.usersys.redhat.com> I think we should be looking at the value instead of value[0] to determine what it is. I stumbled across this bug because I was able to submit telephoneNumber as an empty string. The code was blowing up trying to do a value[0] on "". This raises a point about whether we should allow empty strings into LDAP. Pete had some points about this, but I'll allow him to state them (since I'm not sure I understood them). -Kevin -------------- next part -------------- # HG changeset patch # User Kevin McCarthy # Date 1186790755 25200 # Node ID 50e8f67f0989c7f05d0d178dd85687ca4f2f76a7 # Parent dfe9b959b3608a49e387422196e7495a6bde000e Fix getValue. It should be looking at value, not value[0] diff -r dfe9b959b360 -r 50e8f67f0989 ipa-python/user.py --- a/ipa-python/user.py Fri Aug 10 14:37:09 2007 -0700 +++ b/ipa-python/user.py Fri Aug 10 17:05:55 2007 -0700 @@ -58,7 +58,7 @@ class User: 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): + if isinstance(value,list) or isinstance(value,tuple): return value[0] else: return value -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 2228 bytes Desc: not available URL: From prowley at redhat.com Fri Aug 10 23:32:56 2007 From: prowley at redhat.com (Pete Rowley) Date: Fri, 10 Aug 2007 16:32:56 -0700 Subject: [Freeipa-devel] [PATCH] initial Memberof checkin Message-ID: <46BCF5A8.9030104@redhat.com> Initial check in of memberof plugin. Populates the memberof attribute of those entries that allow it with the DNs of the groupofuniquenames groups that consider the entry a member. Nested groups are supported. The plugin requires to be the "owner" of the memberof attribute and so memberof must not be replicated (requires fractional replication) - it would be possible to replicate to a read only replica and not enable this plugin in that case. The plugin tries to do the minimum work for the particular operation, to be tolerant of asynchronous changes of state during an operation, and to be tolerant of inconsistent (e.g. mid-replicated) state in general. TODO: * move to a persistent queue model so that interrupted operations can be restarted * add a task to fix up memberof for entries so that an existing dit can be initialized and to recover from any other situation that might leave memberof inconsistent with the correct state * make sure we have equality indices set up for uniquemember and memberof types. * possibly extend the group types supported (future) -- Pete -------------- next part -------------- An embedded and charset-unspecified text was scrubbed... Name: patch.txt URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3241 bytes Desc: S/MIME Cryptographic Signature URL: From kmccarth at redhat.com Fri Aug 10 23:33:34 2007 From: kmccarth at redhat.com (Kevin McCarthy) Date: Fri, 10 Aug 2007 16:33:34 -0700 Subject: [Freeipa-devel] [PATCH] split user edit form out Message-ID: <20070810233334.GJ28334@moon.usersys.redhat.com> This patch splits the user edit and create forms. It adds css for required fields, and also a fix to make the edit form fields persist on validation error. -Kevin -------------- next part -------------- # HG changeset patch # User Kevin McCarthy # Date 1186788719 25200 # Node ID b530219c9afe7799d4ec49437f8c39622411793e # Parent 50e8f67f0989c7f05d0d178dd85687ca4f2f76a7 Split userform into edit and new forms. (They will likely diverge so no sense forcing them together). Add css for required fields. Add "_orig" hidden fields to the edit form in prep for sending only modified fields. diff -r 50e8f67f0989 -r b530219c9afe ipa-server/ipa-gui/ipagui/controllers.py --- a/ipa-server/ipa-gui/ipagui/controllers.py Fri Aug 10 17:05:55 2007 -0700 +++ b/ipa-server/ipa-gui/ipagui/controllers.py Fri Aug 10 16:31:59 2007 -0700 @@ -7,7 +7,6 @@ from turbogears import error_handler # from model import * # import logging # log = logging.getLogger("ipagui.controllers") -# import ipa.rpcclient import ipa.config import ipa.ipaclient import ipa.user @@ -15,7 +14,8 @@ import forms.user import forms.user ipa.config.init_config() -user_form = forms.user.UserFormWidget() +user_new_form = forms.user.UserNewForm() +user_edit_form = forms.user.UserEditForm() client = ipa.ipaclient.IPAClient(True) client.set_principal("test at FREEIPA.ORG") @@ -32,8 +32,12 @@ def user_to_hash(user): 'sn' : user.getValue('sn'), 'mail' : user.getValue('mail'), 'telephoneNumber': user.getValue('telephoneNumber'), - 'uidNumber': user.getValue('uidNumber'), - 'gidNumber': user.getValue('gidNumber'), + 'uidNumber' : user.getValue('uidNumber'), + 'gidNumber' : user.getValue('gidNumber'), + 'givenName_orig' : user.getValue('givenName'), + 'sn_orig' : user.getValue('sn'), + 'mail_orig' : user.getValue('mail'), + 'telephoneNumber_orig': user.getValue('telephoneNumber'), } class Root(controllers.RootController): @@ -53,7 +57,7 @@ class Root(controllers.RootController): if tg_errors: turbogears.flash("There was a problem with the form!") - return dict(form=user_form) + return dict(form=user_new_form) @expose() def usercreate(self, **kw): @@ -63,12 +67,11 @@ class Root(controllers.RootController): turbogears.flash("Add user cancelled") raise turbogears.redirect('/userlist') - tg_errors, kw = self.uservalidate(**kw) + tg_errors, kw = self.usercreatevalidate(**kw) if tg_errors: - return dict(form=user_form, tg_template='ipagui.templates.usernew') + return dict(form=user_new_form, tg_template='ipagui.templates.usernew') try: - # rv = ipa.rpcclient.add_user(kw) newuser = ipa.user.User(None) newuser.setValue('uid', kw['uid']) newuser.setValue('givenName', kw['givenName']) @@ -87,7 +90,7 @@ class Root(controllers.RootController): raise turbogears.redirect('/usershow', uid=kw['uid']) except xmlrpclib.Fault, f: turbogears.flash("User add failed: " + str(f.faultString)) - return dict(form=user_form, tg_template='ipagui.templates.usernew') + return dict(form=user_new_form, tg_template='ipagui.templates.usernew') @expose("ipagui.templates.useredit") @@ -96,9 +99,8 @@ class Root(controllers.RootController): if tg_errors: turbogears.flash("There was a problem with the form!") - # user = ipa.rpcclient.get_user(uid) user = client.get_user(uid) - return dict(form=user_form, user=user_to_hash(user)) + return dict(form=user_edit_form, user=user_to_hash(user)) @expose() def userupdate(self, **kw): @@ -108,24 +110,24 @@ class Root(controllers.RootController): turbogears.flash("Edit user cancelled") raise turbogears.redirect('/usershow', uid=kw.get('uid')) - tg_errors, kw = self.uservalidate(**kw) + tg_errors, kw = self.userupdatevalidate(**kw) if tg_errors: - return dict(form=user_form, user={}, tg_template='ipagui.templates.useredit') + return dict(form=user_edit_form, user=kw, + tg_template='ipagui.templates.useredit') try: - # rv = ipa.rpcclient.add_user(kw) turbogears.flash("%s updated!" % kw['uid']) raise turbogears.redirect('/usershow', uid=kw['uid']) except xmlrpclib.Fault, f: turbogears.flash("User add failed: " + str(f.faultString)) - return dict(form=user_form, user={}, tg_template='ipagui.templates.useredit') + return dict(form=user_edit_form, user=kw, + tg_template='ipagui.templates.useredit') @expose("ipagui.templates.userlist") @paginate('users', limit=3, allow_limit_override=True) def userlist(self): """Retrieve a list of all users and display them in one huge list""" - # users = ipa.rpcclient.get_all_users() users = client.get_all_users() return dict(users=users) @@ -134,15 +136,18 @@ class Root(controllers.RootController): def usershow(self, uid): """Retrieve a single user for display""" try: - # user = ipa.rpcclient.get_user(uid) user = client.get_user(uid) return dict(user=user_to_hash(user)) except xmlrpclib.Fault, f: turbogears.flash("User show failed: " + str(f.faultString)) raise turbogears.redirect("/") - @validate(form=user_form) - def uservalidate(self, tg_errors=None, **kw): + @validate(form=user_new_form) + def usercreatevalidate(self, tg_errors=None, **kw): + return tg_errors, kw + + @validate(form=user_edit_form) + def userupdatevalidate(self, tg_errors=None, **kw): return tg_errors, kw @expose() diff -r 50e8f67f0989 -r b530219c9afe ipa-server/ipa-gui/ipagui/forms/user.py --- a/ipa-server/ipa-gui/ipagui/forms/user.py Fri Aug 10 17:05:55 2007 -0700 +++ b/ipa-server/ipa-gui/ipagui/forms/user.py Fri Aug 10 16:31:59 2007 -0700 @@ -19,26 +19,48 @@ class UserFields(): # validators.PhoneNumber may be a bit too picky, requiring an area code telephoneNumber.validator = validators.PlainText(not_empty=True) + uid_hidden = widgets.HiddenField(name="uid") + uidNumber_hidden = widgets.HiddenField(name="uidNumber") + gidNumber_hidden = widgets.HiddenField(name="gidNumber") + givenName_orig = widgets.HiddenField(name="givenName_orig") + sn_orig = widgets.HiddenField(name="sn_orig") + mail_orig = widgets.HiddenField(name="mail_orig") + telephoneNumber_orig = widgets.HiddenField(name="telephoneNumber_orig") -class UserFormWidget(widgets.Form): + +class UserNewForm(widgets.Form): params = ['user'] -# fields = [UserFields.uid, UserFields.userPassword, UserFields.givenName, -# UserFields.sn, UserFields.mail] + fields = [UserFields.uid, UserFields.givenName, UserFields.uidNumber, UserFields.gidNumber, - UserFields.sn, UserFields.mail] + UserFields.sn, UserFields.mail] def __init__(self, *args, **kw): - super(UserFormWidget,self).__init__(*args, **kw) - (self.template_c, self.template) = widgets.meta.load_kid_template("ipagui.templates.userform") + super(UserNewForm,self).__init__(*args, **kw) + (self.template_c, self.template) = widgets.meta.load_kid_template("ipagui.templates.usernewform") self.user = UserFields def update_params(self, params): - super(UserFormWidget,self).update_params(params) + super(UserNewForm,self).update_params(params) params['has_foo'] = self.has_foo def has_foo(self): return False + + +class UserEditForm(widgets.Form): + params = ['user'] + + fields = [UserFields.givenName, UserFields.sn, UserFields.mail, + UserFields.givenName_orig, UserFields.sn_orig, UserFields.mail_orig, + UserFields.uid_hidden, + UserFields.uidNumber_hidden, UserFields.gidNumber_hidden] + + def __init__(self, *args, **kw): + super(UserEditForm,self).__init__(*args, **kw) + (self.template_c, self.template) = widgets.meta.load_kid_template("ipagui.templates.usereditform") + self.user = UserFields + # TODO - add dynamic field retrieval: # myfields=[] diff -r 50e8f67f0989 -r b530219c9afe ipa-server/ipa-gui/ipagui/static/css/style.css --- a/ipa-server/ipa-gui/ipagui/static/css/style.css Fri Aug 10 17:05:55 2007 -0700 +++ b/ipa-server/ipa-gui/ipagui/static/css/style.css Fri Aug 10 16:31:59 2007 -0700 @@ -144,3 +144,7 @@ body { color: red; font-weight: bold; } + +.requiredfield { + background: #eebbbb; +} diff -r 50e8f67f0989 -r b530219c9afe ipa-server/ipa-gui/ipagui/templates/usereditform.kid --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipagui/templates/usereditform.kid Fri Aug 10 16:31:59 2007 -0700 @@ -0,0 +1,124 @@ +
+
+ + +
+ +
Account Details
+ + + + + + + + + + + + + + + + + +
+ + ${value_for(user.uid)} +
+ + ${value_for(user.uidNumber)} +
+ + ${value_for(user.gidNumber)} +
+ +
Identity Details
+ + + + + + + + + + +
+ + + + +
+ + + +
+ +
Contact Details
+ + + + + + + + + +
+ + + +
+ + + +
+ + + + + + + +
+
+ +
+
+ +
+ + +
diff -r 50e8f67f0989 -r b530219c9afe ipa-server/ipa-gui/ipagui/templates/userform.kid --- a/ipa-server/ipa-gui/ipagui/templates/userform.kid Fri Aug 10 17:05:55 2007 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,125 +0,0 @@ -
-
- -
Account Details
- - - - - - - - - - - - - - - - - -
- - - -
- - - -
- - - -
- -
Identity Details
- - - - - - - - - - -
- - - - -
- - - -
- -
Contact Details
- - - - - - - - - -
- - - -
- - - -
- - - - - - - -
-
- -
-
- -
- -
-
diff -r 50e8f67f0989 -r b530219c9afe ipa-server/ipa-gui/ipagui/templates/usernewform.kid --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipagui/templates/usernewform.kid Fri Aug 10 16:31:59 2007 -0700 @@ -0,0 +1,125 @@ +
+
+ +
Account Details
+ + + + + + + + + + + + + + + + + +
+ + + +
+ + + +
+ + + +
+ +
Identity Details
+ + + + + + + + + + +
+ + + + +
+ + + +
+ +
Contact Details
+ + + + + + + + + +
+ + + +
+ + + +
+ + + + + + + +
+
+ +
+
+ +
+ +
+
-------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 2228 bytes Desc: not available URL: From prowley at redhat.com Fri Aug 10 23:47:49 2007 From: prowley at redhat.com (Pete Rowley) Date: Fri, 10 Aug 2007 16:47:49 -0700 Subject: [Freeipa-devel] [PATCH] fix user.getValue() In-Reply-To: <20070810231335.GI28334@moon.usersys.redhat.com> References: <20070810231335.GI28334@moon.usersys.redhat.com> Message-ID: <46BCF925.6020109@redhat.com> Kevin McCarthy wrote: > This raises a point about whether we should allow empty strings into > LDAP. Pete had some points about this, but I'll allow him to state them > (since I'm not sure I understood them). > RFC 4517 says this: "The Telephone Number syntax corresponds to the following ASN.1 type from [X.520]: PrintableString (SIZE(1..ub-telephone-number))" So there needs to be at least one character or the server should return an error. -- Pete -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3241 bytes Desc: S/MIME Cryptographic Signature URL: From kmccarth at redhat.com Fri Aug 10 23:58:04 2007 From: kmccarth at redhat.com (Kevin McCarthy) Date: Fri, 10 Aug 2007 16:58:04 -0700 Subject: [Freeipa-devel] [PATCH] fix user.getValue() In-Reply-To: <46BCF925.6020109@redhat.com> References: <20070810231335.GI28334@moon.usersys.redhat.com> <46BCF925.6020109@redhat.com> Message-ID: <20070810235804.GK28334@moon.usersys.redhat.com> Pete Rowley wrote: > Kevin McCarthy wrote: >> This raises a point about whether we should allow empty strings into >> LDAP. Pete had some points about this, but I'll allow him to state them >> (since I'm not sure I understood them). >> > RFC 4517 says this: > > "The Telephone Number syntax corresponds to the following ASN.1 type > from [X.520]: > > PrintableString (SIZE(1..ub-telephone-number))" > > > So there needs to be at least one character or the server should return an > error. So Rob, the next question is whether the web gui should strip zero-length fields, or the funcs.py server code should? In either case, I think the patch is still good. -Kevin -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 2228 bytes Desc: not available URL: From rcritten at redhat.com Mon Aug 13 13:31:43 2007 From: rcritten at redhat.com (Rob Crittenden) Date: Mon, 13 Aug 2007 09:31:43 -0400 Subject: [Freeipa-devel] [PATCH] fix user.getValue() In-Reply-To: <20070810231335.GI28334@moon.usersys.redhat.com> References: <20070810231335.GI28334@moon.usersys.redhat.com> Message-ID: <46C05D3F.60209@redhat.com> Kevin McCarthy wrote: > I think we should be looking at the value instead of value[0] to > determine what it is. I stumbled across this bug because I was able to > submit telephoneNumber as an empty string. The code was blowing up > trying to do a value[0] on "". > > This raises a point about whether we should allow empty strings into > LDAP. Pete had some points about this, but I'll allow him to state them > (since I'm not sure I understood them). > > -Kevin I suspect that the proper fix is to not allow empty attributes to be set in the first place. rob -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3245 bytes Desc: S/MIME Cryptographic Signature URL: From rcritten at redhat.com Mon Aug 13 20:38:57 2007 From: rcritten at redhat.com (Rob Crittenden) Date: Mon, 13 Aug 2007 16:38:57 -0400 Subject: [Freeipa-devel] [PATCH] basic connection pool and user search Message-ID: <46C0C161.70005@redhat.com> I added the shell of an LDAP connection pool. Since any one connection only uses one LDAP handle at a time and we run in Apache multi-process this basically creates one persistent connection per Apache process. This also adds a user search API: find_users(riteria, attrs) Right now it just searches on uid but I suspect we'll expand it to search on other stuff at some point. This was quite funky to do because I wanted to pass in the list of attrs to return. Apparently the XML-RPC call combines all arguments into a single tuple that gets passed. So this makes it different than when calling things directly (where each argument is separate). I had to do a bit of hoop-jumping to get it to work. I fully expect someone to say "Er, no, you want to do it this way..." And finally, now everything is proxied except for the initial user principle lookup. rob -------------- next part -------------- A non-text attachment was scrubbed... Name: search.patch Type: text/x-patch Size: 14117 bytes Desc: not available URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3245 bytes Desc: S/MIME Cryptographic Signature URL: From abartlet at samba.org Tue Aug 14 00:00:36 2007 From: abartlet at samba.org (Andrew Bartlett) Date: Tue, 14 Aug 2007 10:00:36 +1000 Subject: [Freeipa-devel] [PATCH] fix user.getValue() In-Reply-To: <46C05D3F.60209@redhat.com> References: <20070810231335.GI28334@moon.usersys.redhat.com> <46C05D3F.60209@redhat.com> Message-ID: <1187049636.3764.1.camel@localhost.localdomain> On Mon, 2007-08-13 at 09:31 -0400, Rob Crittenden wrote: > Kevin McCarthy wrote: > > I think we should be looking at the value instead of value[0] to > > determine what it is. I stumbled across this bug because I was able to > > submit telephoneNumber as an empty string. The code was blowing up > > trying to do a value[0] on "". > > > > This raises a point about whether we should allow empty strings into > > LDAP. Pete had some points about this, but I'll allow him to state them > > (since I'm not sure I understood them). > > > > -Kevin > > I suspect that the proper fix is to not allow empty attributes to be set > in the first place. Empty attributes sounds like a route to pain... Also perhaps ensure admins can't set an attribute to " ". Andrew Bartlett -- Andrew Bartlett http://samba.org/~abartlet/ Authentication Developer, Samba Team http://samba.org Samba Developer, Red Hat Inc. http://redhat.com -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 189 bytes Desc: This is a digitally signed message part URL: From kmccarth at redhat.com Tue Aug 14 00:13:28 2007 From: kmccarth at redhat.com (Kevin McCarthy) Date: Mon, 13 Aug 2007 17:13:28 -0700 Subject: [Freeipa-devel] [PATCH] fix user.getValue() In-Reply-To: <1187049636.3764.1.camel@localhost.localdomain> References: <20070810231335.GI28334@moon.usersys.redhat.com> <46C05D3F.60209@redhat.com> <1187049636.3764.1.camel@localhost.localdomain> Message-ID: <20070814001328.GA3238@moon.usersys.redhat.com> Andrew Bartlett wrote: > On Mon, 2007-08-13 at 09:31 -0400, Rob Crittenden wrote: > > Kevin McCarthy wrote: > > > I think we should be looking at the value instead of value[0] to > > > determine what it is. I stumbled across this bug because I was able to > > > submit telephoneNumber as an empty string. The code was blowing up > > > trying to do a value[0] on "". > > > > > > This raises a point about whether we should allow empty strings into > > > LDAP. Pete had some points about this, but I'll allow him to state them > > > (since I'm not sure I understood them). > > > > > > -Kevin > > > > I suspect that the proper fix is to not allow empty attributes to be set > > in the first place. > > Empty attributes sounds like a route to pain... Also perhaps ensure > admins can't set an attribute to " ". So should this be filtered out in funcs.py, or should the web (and commandline) gui filter out empty fields? Also, I didn't want it to get lost that the patch I sent should be applied regardless of how we filter empty attributes: value is either a string or a sequence. It was a bug/typo to check if value[0] is an instance of a list or tuple. It just became apparent when telephoneNumber was zero-length and we tried to grab value[0] of "". -Kevin -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 2228 bytes Desc: not available URL: From prowley at redhat.com Tue Aug 14 00:34:17 2007 From: prowley at redhat.com (Pete Rowley) Date: Mon, 13 Aug 2007 17:34:17 -0700 Subject: [Freeipa-devel] [PATCH] fix user.getValue() In-Reply-To: <20070814001328.GA3238@moon.usersys.redhat.com> References: <20070810231335.GI28334@moon.usersys.redhat.com> <46C05D3F.60209@redhat.com> <1187049636.3764.1.camel@localhost.localdomain> <20070814001328.GA3238@moon.usersys.redhat.com> Message-ID: <46C0F889.9050601@redhat.com> Kevin McCarthy wrote: > Andrew Bartlett wrote: > >> On Mon, 2007-08-13 at 09:31 -0400, Rob Crittenden wrote: >> >>> Kevin McCarthy wrote: >>> >>>> I think we should be looking at the value instead of value[0] to >>>> determine what it is. I stumbled across this bug because I was able to >>>> submit telephoneNumber as an empty string. The code was blowing up >>>> trying to do a value[0] on "". >>>> >>>> This raises a point about whether we should allow empty strings into >>>> LDAP. Pete had some points about this, but I'll allow him to state them >>>> (since I'm not sure I understood them). >>>> >>>> -Kevin >>>> >>> I suspect that the proper fix is to not allow empty attributes to be set >>> in the first place. >>> >> Empty attributes sounds like a route to pain... Also perhaps ensure >> admins can't set an attribute to " ". >> > > So should this be filtered out in funcs.py, or should the web (and > commandline) gui filter out empty fields? > > Also, I didn't want it to get lost that the patch I sent should be > applied regardless of how we filter empty attributes: value is > either a string or a sequence. It was a bug/typo to check if value[0] > is an instance of a list or tuple. It just became apparent when > telephoneNumber was zero-length and we tried to grab value[0] of "". > I think the server side rpc should return an error if asked to apply an empty value, that tends to get clients to do the right thing, and I don't think we ought to be in the business of silently cleaning up after rpc clients. I would _not_ filter out whitespace only values either since that takes effort on the part of the client, or in other words the client is probably specifically trying to do that for some reason - perhaps to work around us not allowing empty values :) -- Pete -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3241 bytes Desc: S/MIME Cryptographic Signature URL: From rcritten at redhat.com Tue Aug 14 02:33:18 2007 From: rcritten at redhat.com (Rob Crittenden) Date: Mon, 13 Aug 2007 22:33:18 -0400 Subject: [Freeipa-devel] [PATCH] fix user.getValue() In-Reply-To: <20070814001328.GA3238@moon.usersys.redhat.com> References: <20070810231335.GI28334@moon.usersys.redhat.com> <46C05D3F.60209@redhat.com> <1187049636.3764.1.camel@localhost.localdomain> <20070814001328.GA3238@moon.usersys.redhat.com> Message-ID: <46C1146E.5090701@redhat.com> Kevin McCarthy wrote: > Andrew Bartlett wrote: >> On Mon, 2007-08-13 at 09:31 -0400, Rob Crittenden wrote: >>> Kevin McCarthy wrote: >>>> I think we should be looking at the value instead of value[0] to >>>> determine what it is. I stumbled across this bug because I was able to >>>> submit telephoneNumber as an empty string. The code was blowing up >>>> trying to do a value[0] on "". >>>> >>>> This raises a point about whether we should allow empty strings into >>>> LDAP. Pete had some points about this, but I'll allow him to state them >>>> (since I'm not sure I understood them). >>>> >>>> -Kevin >>> I suspect that the proper fix is to not allow empty attributes to be set >>> in the first place. >> Empty attributes sounds like a route to pain... Also perhaps ensure >> admins can't set an attribute to " ". > > So should this be filtered out in funcs.py, or should the web (and > commandline) gui filter out empty fields? > > Also, I didn't want it to get lost that the patch I sent should be > applied regardless of how we filter empty attributes: value is > either a string or a sequence. It was a bug/typo to check if value[0] > is an instance of a list or tuple. It just became apparent when > telephoneNumber was zero-length and we tried to grab value[0] of "". > > -Kevin The marshalling/unmarshalling of data in the XML-RPC connection does odd things to the data types. I'll see tomorrow if that is the reason for value[0]. The difference may be that you aren't using the XML-RPC interface. rob -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3245 bytes Desc: S/MIME Cryptographic Signature URL: From rcritten at redhat.com Tue Aug 14 13:42:12 2007 From: rcritten at redhat.com (Rob Crittenden) Date: Tue, 14 Aug 2007 09:42:12 -0400 Subject: [Freeipa-devel] updates and deletes Message-ID: <46C1B134.5070102@redhat.com> We need to come to some decision on how we're going to handle updates and deletes (or really inactivations). The LDAP API for updates is a list of tuples in the form (mod_op,mod_type,mod_vals), where mod_op is the operation (one of MOD_ADD, MOD_DELETE, or MOD_REPLACE), mod_type is a string indicating the attribute type name, and mod_vals is either a string value or a list of string values to add, delete or replace respectively. With deletes id mod_vals is None then all attributes are deleted (probably something to test for and reject, I doubt we'd ever want this). We don't have to strictly follow this model, just laying out one possibility. We can also have 3 functions: add_attr, update_attr and del_attr. That might be simpler at the expense of verbosity (particularly over XML-RPC). For deletes/inactivations I need to know what attribute(s) to set/unset. rob -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3245 bytes Desc: S/MIME Cryptographic Signature URL: From rcritten at redhat.com Tue Aug 14 14:27:15 2007 From: rcritten at redhat.com (Rob Crittenden) Date: Tue, 14 Aug 2007 10:27:15 -0400 Subject: [Freeipa-devel] updates and deletes In-Reply-To: <46C1B134.5070102@redhat.com> References: <46C1B134.5070102@redhat.com> Message-ID: <46C1BBC3.7040502@redhat.com> Rob Crittenden wrote: > We need to come to some decision on how we're going to handle updates > and deletes (or really inactivations). > > The LDAP API for updates is a list of tuples in the form > (mod_op,mod_type,mod_vals), where mod_op is the operation (one of > MOD_ADD, MOD_DELETE, or MOD_REPLACE), mod_type is a string indicating > the attribute type name, and mod_vals is either a string value or a list > of string values to add, delete or replace respectively. With deletes id > mod_vals is None then all attributes are deleted (probably something to > test for and reject, I doubt we'd ever want this). You know, reading docs can be highly beneficial. The python-ldap package provides a function to generate the modlist given a new entry and the original entry. So we have 2 choices: 1. Have an API where the old and new records are both passed in (not very efficient over XML-RPC) 2. Have an API where the new record is passed in and I do a search to get the original record (not very efficient) I'm going to start poking at this, using model #1 for starters. rob -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3245 bytes Desc: S/MIME Cryptographic Signature URL: From kmccarth at redhat.com Tue Aug 14 16:51:32 2007 From: kmccarth at redhat.com (Kevin McCarthy) Date: Tue, 14 Aug 2007 09:51:32 -0700 Subject: [Freeipa-devel] [PATCH] split user edit form out Message-ID: <20070814165132.GA20835@moon.usersys.redhat.com> Resending. As near as I can tell this email somehow never made it to the list. Sorry if it's a dup. -Kevin ----- Forwarded message from Kevin McCarthy ----- Date: Fri, 10 Aug 2007 16:33:34 -0700 From: Kevin McCarthy To: freeipa-devel at redhat.com Subject: [PATCH] split user edit form out Message-ID: <20070810233334.GJ28334 at moon.usersys.redhat.com> User-Agent: Mutt/1.5.16 (2007-06-09) This patch splits the user edit and create forms. It adds css for required fields, and also a fix to make the edit form fields persist on validation error. -------------- next part -------------- # HG changeset patch # User Kevin McCarthy # Date 1186788719 25200 # Node ID b530219c9afe7799d4ec49437f8c39622411793e # Parent 50e8f67f0989c7f05d0d178dd85687ca4f2f76a7 Split userform into edit and new forms. (They will likely diverge so no sense forcing them together). Add css for required fields. Add "_orig" hidden fields to the edit form in prep for sending only modified fields. diff -r 50e8f67f0989 -r b530219c9afe ipa-server/ipa-gui/ipagui/controllers.py --- a/ipa-server/ipa-gui/ipagui/controllers.py Fri Aug 10 17:05:55 2007 -0700 +++ b/ipa-server/ipa-gui/ipagui/controllers.py Fri Aug 10 16:31:59 2007 -0700 @@ -7,7 +7,6 @@ from turbogears import error_handler # from model import * # import logging # log = logging.getLogger("ipagui.controllers") -# import ipa.rpcclient import ipa.config import ipa.ipaclient import ipa.user @@ -15,7 +14,8 @@ import forms.user import forms.user ipa.config.init_config() -user_form = forms.user.UserFormWidget() +user_new_form = forms.user.UserNewForm() +user_edit_form = forms.user.UserEditForm() client = ipa.ipaclient.IPAClient(True) client.set_principal("test at FREEIPA.ORG") @@ -32,8 +32,12 @@ def user_to_hash(user): 'sn' : user.getValue('sn'), 'mail' : user.getValue('mail'), 'telephoneNumber': user.getValue('telephoneNumber'), - 'uidNumber': user.getValue('uidNumber'), - 'gidNumber': user.getValue('gidNumber'), + 'uidNumber' : user.getValue('uidNumber'), + 'gidNumber' : user.getValue('gidNumber'), + 'givenName_orig' : user.getValue('givenName'), + 'sn_orig' : user.getValue('sn'), + 'mail_orig' : user.getValue('mail'), + 'telephoneNumber_orig': user.getValue('telephoneNumber'), } class Root(controllers.RootController): @@ -53,7 +57,7 @@ class Root(controllers.RootController): if tg_errors: turbogears.flash("There was a problem with the form!") - return dict(form=user_form) + return dict(form=user_new_form) @expose() def usercreate(self, **kw): @@ -63,12 +67,11 @@ class Root(controllers.RootController): turbogears.flash("Add user cancelled") raise turbogears.redirect('/userlist') - tg_errors, kw = self.uservalidate(**kw) + tg_errors, kw = self.usercreatevalidate(**kw) if tg_errors: - return dict(form=user_form, tg_template='ipagui.templates.usernew') + return dict(form=user_new_form, tg_template='ipagui.templates.usernew') try: - # rv = ipa.rpcclient.add_user(kw) newuser = ipa.user.User(None) newuser.setValue('uid', kw['uid']) newuser.setValue('givenName', kw['givenName']) @@ -87,7 +90,7 @@ class Root(controllers.RootController): raise turbogears.redirect('/usershow', uid=kw['uid']) except xmlrpclib.Fault, f: turbogears.flash("User add failed: " + str(f.faultString)) - return dict(form=user_form, tg_template='ipagui.templates.usernew') + return dict(form=user_new_form, tg_template='ipagui.templates.usernew') @expose("ipagui.templates.useredit") @@ -96,9 +99,8 @@ class Root(controllers.RootController): if tg_errors: turbogears.flash("There was a problem with the form!") - # user = ipa.rpcclient.get_user(uid) user = client.get_user(uid) - return dict(form=user_form, user=user_to_hash(user)) + return dict(form=user_edit_form, user=user_to_hash(user)) @expose() def userupdate(self, **kw): @@ -108,24 +110,24 @@ class Root(controllers.RootController): turbogears.flash("Edit user cancelled") raise turbogears.redirect('/usershow', uid=kw.get('uid')) - tg_errors, kw = self.uservalidate(**kw) + tg_errors, kw = self.userupdatevalidate(**kw) if tg_errors: - return dict(form=user_form, user={}, tg_template='ipagui.templates.useredit') + return dict(form=user_edit_form, user=kw, + tg_template='ipagui.templates.useredit') try: - # rv = ipa.rpcclient.add_user(kw) turbogears.flash("%s updated!" % kw['uid']) raise turbogears.redirect('/usershow', uid=kw['uid']) except xmlrpclib.Fault, f: turbogears.flash("User add failed: " + str(f.faultString)) - return dict(form=user_form, user={}, tg_template='ipagui.templates.useredit') + return dict(form=user_edit_form, user=kw, + tg_template='ipagui.templates.useredit') @expose("ipagui.templates.userlist") @paginate('users', limit=3, allow_limit_override=True) def userlist(self): """Retrieve a list of all users and display them in one huge list""" - # users = ipa.rpcclient.get_all_users() users = client.get_all_users() return dict(users=users) @@ -134,15 +136,18 @@ class Root(controllers.RootController): def usershow(self, uid): """Retrieve a single user for display""" try: - # user = ipa.rpcclient.get_user(uid) user = client.get_user(uid) return dict(user=user_to_hash(user)) except xmlrpclib.Fault, f: turbogears.flash("User show failed: " + str(f.faultString)) raise turbogears.redirect("/") - @validate(form=user_form) - def uservalidate(self, tg_errors=None, **kw): + @validate(form=user_new_form) + def usercreatevalidate(self, tg_errors=None, **kw): + return tg_errors, kw + + @validate(form=user_edit_form) + def userupdatevalidate(self, tg_errors=None, **kw): return tg_errors, kw @expose() diff -r 50e8f67f0989 -r b530219c9afe ipa-server/ipa-gui/ipagui/forms/user.py --- a/ipa-server/ipa-gui/ipagui/forms/user.py Fri Aug 10 17:05:55 2007 -0700 +++ b/ipa-server/ipa-gui/ipagui/forms/user.py Fri Aug 10 16:31:59 2007 -0700 @@ -19,26 +19,48 @@ class UserFields(): # validators.PhoneNumber may be a bit too picky, requiring an area code telephoneNumber.validator = validators.PlainText(not_empty=True) + uid_hidden = widgets.HiddenField(name="uid") + uidNumber_hidden = widgets.HiddenField(name="uidNumber") + gidNumber_hidden = widgets.HiddenField(name="gidNumber") + givenName_orig = widgets.HiddenField(name="givenName_orig") + sn_orig = widgets.HiddenField(name="sn_orig") + mail_orig = widgets.HiddenField(name="mail_orig") + telephoneNumber_orig = widgets.HiddenField(name="telephoneNumber_orig") -class UserFormWidget(widgets.Form): + +class UserNewForm(widgets.Form): params = ['user'] -# fields = [UserFields.uid, UserFields.userPassword, UserFields.givenName, -# UserFields.sn, UserFields.mail] + fields = [UserFields.uid, UserFields.givenName, UserFields.uidNumber, UserFields.gidNumber, - UserFields.sn, UserFields.mail] + UserFields.sn, UserFields.mail] def __init__(self, *args, **kw): - super(UserFormWidget,self).__init__(*args, **kw) - (self.template_c, self.template) = widgets.meta.load_kid_template("ipagui.templates.userform") + super(UserNewForm,self).__init__(*args, **kw) + (self.template_c, self.template) = widgets.meta.load_kid_template("ipagui.templates.usernewform") self.user = UserFields def update_params(self, params): - super(UserFormWidget,self).update_params(params) + super(UserNewForm,self).update_params(params) params['has_foo'] = self.has_foo def has_foo(self): return False + + +class UserEditForm(widgets.Form): + params = ['user'] + + fields = [UserFields.givenName, UserFields.sn, UserFields.mail, + UserFields.givenName_orig, UserFields.sn_orig, UserFields.mail_orig, + UserFields.uid_hidden, + UserFields.uidNumber_hidden, UserFields.gidNumber_hidden] + + def __init__(self, *args, **kw): + super(UserEditForm,self).__init__(*args, **kw) + (self.template_c, self.template) = widgets.meta.load_kid_template("ipagui.templates.usereditform") + self.user = UserFields + # TODO - add dynamic field retrieval: # myfields=[] diff -r 50e8f67f0989 -r b530219c9afe ipa-server/ipa-gui/ipagui/static/css/style.css --- a/ipa-server/ipa-gui/ipagui/static/css/style.css Fri Aug 10 17:05:55 2007 -0700 +++ b/ipa-server/ipa-gui/ipagui/static/css/style.css Fri Aug 10 16:31:59 2007 -0700 @@ -144,3 +144,7 @@ body { color: red; font-weight: bold; } + +.requiredfield { + background: #eebbbb; +} diff -r 50e8f67f0989 -r b530219c9afe ipa-server/ipa-gui/ipagui/templates/usereditform.kid --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipagui/templates/usereditform.kid Fri Aug 10 16:31:59 2007 -0700 @@ -0,0 +1,124 @@ +
+
+ + +
+ +
Account Details
+ + + + + + + + + + + + + + + + + +
+ + ${value_for(user.uid)} +
+ + ${value_for(user.uidNumber)} +
+ + ${value_for(user.gidNumber)} +
+ +
Identity Details
+ + + + + + + + + + +
+ + + + +
+ + + +
+ +
Contact Details
+ + + + + + + + + +
+ + + +
+ + + +
+ + + + + + + +
+
+ +
+
+ +
+ + +
diff -r 50e8f67f0989 -r b530219c9afe ipa-server/ipa-gui/ipagui/templates/userform.kid --- a/ipa-server/ipa-gui/ipagui/templates/userform.kid Fri Aug 10 17:05:55 2007 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,125 +0,0 @@ -
-
- -
Account Details
- - - - - - - - - - - - - - - - - -
- - - -
- - - -
- - - -
- -
Identity Details
- - - - - - - - - - -
- - - - -
- - - -
- -
Contact Details
- - - - - - - - - -
- - - -
- - - -
- - - - - - - -
-
- -
-
- -
- -
-
diff -r 50e8f67f0989 -r b530219c9afe ipa-server/ipa-gui/ipagui/templates/usernewform.kid --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipagui/templates/usernewform.kid Fri Aug 10 16:31:59 2007 -0700 @@ -0,0 +1,125 @@ +
+
+ +
Account Details
+ + + + + + + + + + + + + + + + + +
+ + + +
+ + + +
+ + + +
+ +
Identity Details
+ + + + + + + + + + +
+ + + + +
+ + + +
+ +
Contact Details
+ + + + + + + + + +
+ + + +
+ + + +
+ + + + + + + +
+
+ +
+
+ +
+ +
+
-------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 2228 bytes Desc: not available URL: From kmccarth at redhat.com Tue Aug 14 16:53:58 2007 From: kmccarth at redhat.com (Kevin McCarthy) Date: Tue, 14 Aug 2007 09:53:58 -0700 Subject: [Freeipa-devel] [PATCH] password generator Message-ID: <20070814165358.GB20835@moon.usersys.redhat.com> Attached is the password generator method and ajax code for the user edit/new forms. I'm also adding the prototype javascipt library, http://www.prototypejs.org/, distributed under an MIT license. Most of this patch is just the prototype.js file, but keep scrolling - my changes are at the bottom. Thanks, -Kevin -------------- next part -------------- # HG changeset patch # User Kevin McCarthy # Date 1187109613 25200 # Node ID f3afb0502eec30b6fcb25e1c4cbcebb20cd59ead # Parent b530219c9afe7799d4ec49437f8c39622411793e - Add password generator method to controller. This uses the random.SystemRandom() method to generate an 8-digit alphanumeric password. - Add ajax call to usernew and useredit forms to generate a new password - Add the prototype javascript library: http://www.prototypejs.org/ prototype is distributed with the MIT license. - Add a checkbox to toggle editing (and displaying) the password. - Change usershow template to use same field labels as the edit and new forms. diff -r b530219c9afe -r f3afb0502eec ipa-server/ipa-gui/ipagui/controllers.py --- a/ipa-server/ipa-gui/ipagui/controllers.py Fri Aug 10 16:31:59 2007 -0700 +++ b/ipa-server/ipa-gui/ipagui/controllers.py Tue Aug 14 09:40:13 2007 -0700 @@ -1,3 +1,4 @@ import cherrypy +import random import cherrypy import turbogears from turbogears import controllers, expose, flash @@ -16,6 +17,8 @@ ipa.config.init_config() ipa.config.init_config() user_new_form = forms.user.UserNewForm() user_edit_form = forms.user.UserEditForm() + +password_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" client = ipa.ipaclient.IPAClient(True) client.set_principal("test at FREEIPA.ORG") @@ -137,7 +140,7 @@ class Root(controllers.RootController): """Retrieve a single user for display""" try: user = client.get_user(uid) - return dict(user=user_to_hash(user)) + return dict(user=user_to_hash(user), fields=forms.user.UserFields()) except xmlrpclib.Fault, f: turbogears.flash("User show failed: " + str(f.faultString)) raise turbogears.redirect("/") @@ -154,6 +157,16 @@ class Root(controllers.RootController): def userindex(self): raise turbogears.redirect("/userlist") + @expose() + def generate_password(self): + password = "" + generator = random.SystemRandom() + for char in range(8): + index = generator.randint(0, len(password_chars) - 1) + password += password_chars[index] + + return password + ######### # Group # diff -r b530219c9afe -r f3afb0502eec ipa-server/ipa-gui/ipagui/static/css/style.css --- a/ipa-server/ipa-gui/ipagui/static/css/style.css Fri Aug 10 16:31:59 2007 -0700 +++ b/ipa-server/ipa-gui/ipagui/static/css/style.css Tue Aug 14 09:40:13 2007 -0700 @@ -120,6 +120,10 @@ body { .formtable th { width: 15%; text-align: right; +} + +.small { + font-size: x-small; } #status_block { diff -r b530219c9afe -r f3afb0502eec ipa-server/ipa-gui/ipagui/static/javascript/prototype.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ipa-server/ipa-gui/ipagui/static/javascript/prototype.js Tue Aug 14 09:40:13 2007 -0700 @@ -0,0 +1,3277 @@ +/* Prototype JavaScript framework, version 1.5.1.1 + * (c) 2005-2007 Sam Stephenson + * + * Prototype is freely distributable under the terms of an MIT-style license. + * For details, see the Prototype web site: http://www.prototypejs.org/ + * +/*--------------------------------------------------------------------------*/ + +var Prototype = { + Version: '1.5.1.1', + + Browser: { + IE: !!(window.attachEvent && !window.opera), + Opera: !!window.opera, + WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1, + Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1 + }, + + BrowserFeatures: { + XPath: !!document.evaluate, + ElementExtensions: !!window.HTMLElement, + SpecificElementExtensions: + (document.createElement('div').__proto__ !== + document.createElement('form').__proto__) + }, + + ScriptFragment: ']*>([\\S\\s]*?)<\/script>', + JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, + + emptyFunction: function() { }, + K: function(x) { return x } +} + +var Class = { + create: function() { + return function() { + this.initialize.apply(this, arguments); + } + } +} + +var Abstract = new Object(); + +Object.extend = function(destination, source) { + for (var property in source) { + destination[property] = source[property]; + } + return destination; +} + +Object.extend(Object, { + inspect: function(object) { + try { + if (object === undefined) return 'undefined'; + if (object === null) return 'null'; + return object.inspect ? object.inspect() : object.toString(); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } + }, + + toJSON: function(object) { + var type = typeof object; + switch(type) { + case 'undefined': + case 'function': + case 'unknown': return; + case 'boolean': return object.toString(); + } + if (object === null) return 'null'; + if (object.toJSON) return object.toJSON(); + if (object.ownerDocument === document) return; + var results = []; + for (var property in object) { + var value = Object.toJSON(object[property]); + if (value !== undefined) + results.push(property.toJSON() + ': ' + value); + } + return '{' + results.join(', ') + '}'; + }, + + keys: function(object) { + var keys = []; + for (var property in object) + keys.push(property); + return keys; + }, + + values: function(object) { + var values = []; + for (var property in object) + values.push(object[property]); + return values; + }, + + clone: function(object) { + return Object.extend({}, object); + } +}); + +Function.prototype.bind = function() { + var __method = this, args = $A(arguments), object = args.shift(); + return function() { + return __method.apply(object, args.concat($A(arguments))); + } +} + +Function.prototype.bindAsEventListener = function(object) { + var __method = this, args = $A(arguments), object = args.shift(); + return function(event) { + return __method.apply(object, [event || window.event].concat(args)); + } +} + +Object.extend(Number.prototype, { + toColorPart: function() { + return this.toPaddedString(2, 16); + }, + + succ: function() { + return this + 1; + }, + + times: function(iterator) { + $R(0, this, true).each(iterator); + return this; + }, + + toPaddedString: function(length, radix) { + var string = this.toString(radix || 10); + return '0'.times(length - string.length) + string; + }, + + toJSON: function() { + return isFinite(this) ? this.toString() : 'null'; + } +}); + +Date.prototype.toJSON = function() { + return '"' + this.getFullYear() + '-' + + (this.getMonth() + 1).toPaddedString(2) + '-' + + this.getDate().toPaddedString(2) + 'T' + + this.getHours().toPaddedString(2) + ':' + + this.getMinutes().toPaddedString(2) + ':' + + this.getSeconds().toPaddedString(2) + '"'; +}; + +var Try = { + these: function() { + var returnValue; + + for (var i = 0, length = arguments.length; i < length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) {} + } + + return returnValue; + } +} + +/*--------------------------------------------------------------------------*/ + +var PeriodicalExecuter = Class.create(); +PeriodicalExecuter.prototype = { + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + stop: function() { + if (!this.timer) return; + clearInterval(this.timer); + this.timer = null; + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.callback(this); + } finally { + this.currentlyExecuting = false; + } + } + } +} +Object.extend(String, { + interpret: function(value) { + return value == null ? '' : String(value); + }, + specialChar: { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '\\': '\\\\' + } +}); + +Object.extend(String.prototype, { + gsub: function(pattern, replacement) { + var result = '', source = this, match; + replacement = arguments.callee.prepareReplacement(replacement); + + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += String.interpret(replacement(match)); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + }, + + sub: function(pattern, replacement, count) { + replacement = this.gsub.prepareReplacement(replacement); + count = count === undefined ? 1 : count; + + return this.gsub(pattern, function(match) { + if (--count < 0) return match[0]; + return replacement(match); + }); + }, + + scan: function(pattern, iterator) { + this.gsub(pattern, iterator); + return this; + }, + + truncate: function(length, truncation) { + length = length || 30; + truncation = truncation === undefined ? '...' : truncation; + return this.length > length ? + this.slice(0, length - truncation.length) + truncation : this; + }, + + strip: function() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + }, + + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, + + stripScripts: function() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + }, + + extractScripts: function() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); + var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + }, + + evalScripts: function() { + return this.extractScripts().map(function(script) { return eval(script) }); + }, + + escapeHTML: function() { + var self = arguments.callee; + self.text.data = this; + return self.div.innerHTML; + }, + + unescapeHTML: function() { + var div = document.createElement('div'); + div.innerHTML = this.stripTags(); + return div.childNodes[0] ? (div.childNodes.length > 1 ? + $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) : + div.childNodes[0].nodeValue) : ''; + }, + + toQueryParams: function(separator) { + var match = this.strip().match(/([^?#]*)(#.*)?$/); + if (!match) return {}; + + return match[1].split(separator || '&').inject({}, function(hash, pair) { + if ((pair = pair.split('='))[0]) { + var key = decodeURIComponent(pair.shift()); + var value = pair.length > 1 ? pair.join('=') : pair[0]; + if (value != undefined) value = decodeURIComponent(value); + + if (key in hash) { + if (hash[key].constructor != Array) hash[key] = [hash[key]]; + hash[key].push(value); + } + else hash[key] = value; + } + return hash; + }); + }, + + toArray: function() { + return this.split(''); + }, + + succ: function() { + return this.slice(0, this.length - 1) + + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); + }, + + times: function(count) { + var result = ''; + for (var i = 0; i < count; i++) result += this; + return result; + }, + + camelize: function() { + var parts = this.split('-'), len = parts.length; + if (len == 1) return parts[0]; + + var camelized = this.charAt(0) == '-' + ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) + : parts[0]; + + for (var i = 1; i < len; i++) + camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); + + return camelized; + }, + + capitalize: function() { + return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); + }, + + underscore: function() { + return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase(); + }, + + dasherize: function() { + return this.gsub(/_/,'-'); + }, + + inspect: function(useDoubleQuotes) { + var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) { + var character = String.specialChar[match[0]]; + return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16); + }); + if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; + return "'" + escapedString.replace(/'/g, '\\\'') + "'"; + }, + + toJSON: function() { + return this.inspect(true); + }, + + unfilterJSON: function(filter) { + return this.sub(filter || Prototype.JSONFilter, '#{1}'); + }, + + isJSON: function() { + var str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); + return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); + }, + + evalJSON: function(sanitize) { + var json = this.unfilterJSON(); + try { + if (!sanitize || json.isJSON()) return eval('(' + json + ')'); + } catch (e) { } + throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); + }, + + include: function(pattern) { + return this.indexOf(pattern) > -1; + }, + + startsWith: function(pattern) { + return this.indexOf(pattern) === 0; + }, + + endsWith: function(pattern) { + var d = this.length - pattern.length; + return d >= 0 && this.lastIndexOf(pattern) === d; + }, + + empty: function() { + return this == ''; + }, + + blank: function() { + return /^\s*$/.test(this); + } +}); + +if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, { + escapeHTML: function() { + return this.replace(/&/g,'&').replace(//g,'>'); + }, + unescapeHTML: function() { + return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); + } +}); + +String.prototype.gsub.prepareReplacement = function(replacement) { + if (typeof replacement == 'function') return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; +} + +String.prototype.parseQuery = String.prototype.toQueryParams; + +Object.extend(String.prototype.escapeHTML, { + div: document.createElement('div'), + text: document.createTextNode('') +}); + +with (String.prototype.escapeHTML) div.appendChild(text); + +var Template = Class.create(); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; +Template.prototype = { + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + return this.template.gsub(this.pattern, function(match) { + var before = match[1]; + if (before == '\\') return match[2]; + return before + String.interpret(object[match[3]]); + }); + } +} + +var $break = {}, $continue = new Error('"throw $continue" is deprecated, use "return" instead'); + +var Enumerable = { + each: function(iterator) { + var index = 0; + try { + this._each(function(value) { + iterator(value, index++); + }); + } catch (e) { + if (e != $break) throw e; + } + return this; + }, + + eachSlice: function(number, iterator) { + var index = -number, slices = [], array = this.toArray(); + while ((index += number) < array.length) + slices.push(array.slice(index, index+number)); + return slices.map(iterator); + }, + + all: function(iterator) { + var result = true; + this.each(function(value, index) { + result = result && !!(iterator || Prototype.K)(value, index); + if (!result) throw $break; + }); + return result; + }, + + any: function(iterator) { + var result = false; + this.each(function(value, index) { + if (result = !!(iterator || Prototype.K)(value, index)) + throw $break; + }); + return result; + }, + + collect: function(iterator) { + var results = []; + this.each(function(value, index) { + results.push((iterator || Prototype.K)(value, index)); + }); + return results; + }, + + detect: function(iterator) { + var result; + this.each(function(value, index) { + if (iterator(value, index)) { + result = value; + throw $break; + } + }); + return result; + }, + + findAll: function(iterator) { + var results = []; + this.each(function(value, index) { + if (iterator(value, index)) + results.push(value); + }); + return results; + }, + + grep: function(pattern, iterator) { + var results = []; + this.each(function(value, index) { + var stringValue = value.toString(); + if (stringValue.match(pattern)) + results.push((iterator || Prototype.K)(value, index)); + }) + return results; + }, + + include: function(object) { + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + }, + + inGroupsOf: function(number, fillWith) { + fillWith = fillWith === undefined ? null : fillWith; + return this.eachSlice(number, function(slice) { + while(slice.length < number) slice.push(fillWith); + return slice; + }); + }, + + inject: function(memo, iterator) { + this.each(function(value, index) { + memo = iterator(memo, value, index); + }); + return memo; + }, + + invoke: function(method) { + var args = $A(arguments).slice(1); + return this.map(function(value) { + return value[method].apply(value, args); + }); + }, + + max: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (result == undefined || value >= result) + result = value; + }); + return result; + }, + + min: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (result == undefined || value < result) + result = value; + }); + return result; + }, + + partition: function(iterator) { + var trues = [], falses = []; + this.each(function(value, index) { + ((iterator || Prototype.K)(value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + }, + + pluck: function(property) { + var results = []; + this.each(function(value, index) { + results.push(value[property]); + }); + return results; + }, + + reject: function(iterator) { + var results = []; + this.each(function(value, index) { + if (!iterator(value, index)) + results.push(value); + }); + return results; + }, + + sortBy: function(iterator) { + return this.map(function(value, index) { + return {value: value, criteria: iterator(value, index)}; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + }, + + toArray: function() { + return this.map(); + }, + + zip: function() { + var iterator = Prototype.K, args = $A(arguments); + if (typeof args.last() == 'function') + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + return iterator(collections.pluck(index)); + }); + }, + + size: function() { + return this.toArray().length; + }, + + inspect: function() { + return '#'; + } +} + +Object.extend(Enumerable, { + map: Enumerable.collect, + find: Enumerable.detect, + select: Enumerable.findAll, + member: Enumerable.include, + entries: Enumerable.toArray +}); +var $A = Array.from = function(iterable) { + if (!iterable) return []; + if (iterable.toArray) { + return iterable.toArray(); + } else { + var results = []; + for (var i = 0, length = iterable.length; i < length; i++) + results.push(iterable[i]); + return results; + } +} + +if (Prototype.Browser.WebKit) { + $A = Array.from = function(iterable) { + if (!iterable) return []; + if (!(typeof iterable == 'function' && iterable == '[object NodeList]') && + iterable.toArray) { + return iterable.toArray(); + } else { + var results = []; + for (var i = 0, length = iterable.length; i < length; i++) + results.push(iterable[i]); + return results; + } + } +} + +Object.extend(Array.prototype, Enumerable); + +if (!Array.prototype._reverse) + Array.prototype._reverse = Array.prototype.reverse; + +Object.extend(Array.prototype, { + _each: function(iterator) { + for (var i = 0, length = this.length; i < length; i++) + iterator(this[i]); + }, + + clear: function() { + this.length = 0; + return this; + }, + + first: function() { + return this[0]; + }, + + last: function() { + return this[this.length - 1]; + }, + + compact: function() { + return this.select(function(value) { + return value != null; + }); + }, + + flatten: function() { + return this.inject([], function(array, value) { + return array.concat(value && value.constructor == Array ? + value.flatten() : [value]); + }); + }, + + without: function() { + var values = $A(arguments); + return this.select(function(value) { + return !values.include(value); + }); + }, + + indexOf: function(object) { + for (var i = 0, length = this.length; i < length; i++) + if (this[i] == object) return i; + return -1; + }, + + reverse: function(inline) { + return (inline !== false ? this : this.toArray())._reverse(); + }, + + reduce: function() { + return this.length > 1 ? this : this[0]; + }, + + uniq: function(sorted) { + return this.inject([], function(array, value, index) { + if (0 == index || (sorted ? array.last() != value : !array.include(value))) + array.push(value); + return array; + }); + }, + + clone: function() { + return [].concat(this); + }, + + size: function() { + return this.length; + }, + + inspect: function() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + }, + + toJSON: function() { + var results = []; + this.each(function(object) { + var value = Object.toJSON(object); + if (value !== undefined) results.push(value); + }); + return '[' + results.join(', ') + ']'; + } +}); + +Array.prototype.toArray = Array.prototype.clone; + +function $w(string) { + string = string.strip(); + return string ? string.split(/\s+/) : []; +} + +if (Prototype.Browser.Opera){ + Array.prototype.concat = function() { + var array = []; + for (var i = 0, length = this.length; i < length; i++) array.push(this[i]); + for (var i = 0, length = arguments.length; i < length; i++) { + if (arguments[i].constructor == Array) { + for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) + array.push(arguments[i][j]); + } else { + array.push(arguments[i]); + } + } + return array; + } +} +var Hash = function(object) { + if (object instanceof Hash) this.merge(object); + else Object.extend(this, object || {}); +}; + +Object.extend(Hash, { + toQueryString: function(obj) { + var parts = []; + parts.add = arguments.callee.addPair; + + this.prototype._each.call(obj, function(pair) { + if (!pair.key) return; + var value = pair.value; + + if (value && typeof value == 'object') { + if (value.constructor == Array) value.each(function(value) { + parts.add(pair.key, value); + }); + return; + } + parts.add(pair.key, value); + }); + + return parts.join('&'); + }, + + toJSON: function(object) { + var results = []; + this.prototype._each.call(object, function(pair) { + var value = Object.toJSON(pair.value); + if (value !== undefined) results.push(pair.key.toJSON() + ': ' + value); + }); + return '{' + results.join(', ') + '}'; + } +}); + +Hash.toQueryString.addPair = function(key, value, prefix) { + key = encodeURIComponent(key); + if (value === undefined) this.push(key); + else this.push(key + '=' + (value == null ? '' : encodeURIComponent(value))); +} + +Object.extend(Hash.prototype, Enumerable); +Object.extend(Hash.prototype, { + _each: function(iterator) { + for (var key in this) { + var value = this[key]; + if (value && value == Hash.prototype[key]) continue; + + var pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + }, + + keys: function() { + return this.pluck('key'); + }, + + values: function() { + return this.pluck('value'); + }, + + merge: function(hash) { + return $H(hash).inject(this, function(mergedHash, pair) { + mergedHash[pair.key] = pair.value; + return mergedHash; + }); + }, + + remove: function() { + var result; + for(var i = 0, length = arguments.length; i < length; i++) { + var value = this[arguments[i]]; + if (value !== undefined){ + if (result === undefined) result = value; + else { + if (result.constructor != Array) result = [result]; + result.push(value) + } + } + delete this[arguments[i]]; + } + return result; + }, + + toQueryString: function() { + return Hash.toQueryString(this); + }, + + inspect: function() { + return '#'; + }, + + toJSON: function() { + return Hash.toJSON(this); + } +}); + +function $H(object) { + if (object instanceof Hash) return object; + return new Hash(object); +}; + +// Safari iterates over shadowed properties +if (function() { + var i = 0, Test = function(value) { this.key = value }; + Test.prototype.key = 'foo'; + for (var property in new Test('bar')) i++; + return i > 1; +}()) Hash.prototype._each = function(iterator) { + var cache = []; + for (var key in this) { + var value = this[key]; + if ((value && value == Hash.prototype[key]) || cache.include(key)) continue; + cache.push(key); + var pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } +}; +ObjectRange = Class.create(); +Object.extend(ObjectRange.prototype, Enumerable); +Object.extend(ObjectRange.prototype, { + initialize: function(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + }, + + _each: function(iterator) { + var value = this.start; + while (this.include(value)) { + iterator(value); + value = value.succ(); + } + }, + + include: function(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } +}); + +var $R = function(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +} + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new XMLHttpRequest()}, + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')} + ) || false; + }, + + activeRequestCount: 0 +} + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responder) { + if (!this.include(responder)) + this.responders.push(responder); + }, + + unregister: function(responder) { + this.responders = this.responders.without(responder); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (typeof responder[callback] == 'function') { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) {} + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { + Ajax.activeRequestCount++; + }, + onComplete: function() { + Ajax.activeRequestCount--; + } +}); + +Ajax.Base = function() {}; +Ajax.Base.prototype = { + setOptions: function(options) { + this.options = { + method: 'post', + asynchronous: true, + contentType: 'application/x-www-form-urlencoded', + encoding: 'UTF-8', + parameters: '' + } + Object.extend(this.options, options || {}); + + this.options.method = this.options.method.toLowerCase(); + if (typeof this.options.parameters == 'string') + this.options.parameters = this.options.parameters.toQueryParams(); + } +} + +Ajax.Request = Class.create(); +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + +Ajax.Request.prototype = Object.extend(new Ajax.Base(), { + _complete: false, + + initialize: function(url, options) { + this.transport = Ajax.getTransport(); + this.setOptions(options); + this.request(url); + }, + + request: function(url) { + this.url = url; + this.method = this.options.method; + var params = Object.clone(this.options.parameters); + + if (!['get', 'post'].include(this.method)) { + // simulate other verbs over post + params['_method'] = this.method; + this.method = 'post'; + } + + this.parameters = params; + + if (params = Hash.toQueryString(params)) { + // when GET, append parameters to URL + if (this.method == 'get') + this.url += (this.url.include('?') ? '&' : '?') + params; + else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) + params += '&_='; + } + + try { + if (this.options.onCreate) this.options.onCreate(this.transport); + Ajax.Responders.dispatch('onCreate', this, this.transport); + + this.transport.open(this.method.toUpperCase(), this.url, + this.options.asynchronous); + + if (this.options.asynchronous) + setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10); + + this.transport.onreadystatechange = this.onStateChange.bind(this); + this.setRequestHeaders(); + + this.body = this.method == 'post' ? (this.options.postBody || params) : null; + this.transport.send(this.body); + + /* Force Firefox to handle ready state 4 for synchronous requests */ + if (!this.options.asynchronous && this.transport.overrideMimeType) + this.onStateChange(); + + } + catch (e) { + this.dispatchException(e); + } + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState > 1 && !((readyState == 4) && this._complete)) + this.respondToReadyState(this.transport.readyState); + }, + + setRequestHeaders: function() { + var headers = { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Prototype-Version': Prototype.Version, + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }; + + if (this.method == 'post') { + headers['Content-type'] = this.options.contentType + + (this.options.encoding ? '; charset=' + this.options.encoding : ''); + + /* Force "Connection: close" for older Mozilla browsers to work + * around a bug where XMLHttpRequest sends an incorrect + * Content-length header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType && + (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) + headers['Connection'] = 'close'; + } + + // user-defined headers + if (typeof this.options.requestHeaders == 'object') { + var extras = this.options.requestHeaders; + + if (typeof extras.push == 'function') + for (var i = 0, length = extras.length; i < length; i += 2) + headers[extras[i]] = extras[i+1]; + else + $H(extras).each(function(pair) { headers[pair.key] = pair.value }); + } + + for (var name in headers) + this.transport.setRequestHeader(name, headers[name]); + }, + + success: function() { + return !this.transport.status + || (this.transport.status >= 200 && this.transport.status < 300); + }, + + respondToReadyState: function(readyState) { + var state = Ajax.Request.Events[readyState]; + var transport = this.transport, json = this.evalJSON(); + + if (state == 'Complete') { + try { + this._complete = true; + (this.options['on' + this.transport.status] + || this.options['on' + (this.success() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(transport, json); + } catch (e) { + this.dispatchException(e); + } + + var contentType = this.getHeader('Content-type'); + if (contentType && contentType.strip(). + match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i)) + this.evalResponse(); + } + + try { + (this.options['on' + state] || Prototype.emptyFunction)(transport, json); + Ajax.Responders.dispatch('on' + state, this, transport, json); + } catch (e) { + this.dispatchException(e); + } + + if (state == 'Complete') { + // avoid memory leak in MSIE: clean up + this.transport.onreadystatechange = Prototype.emptyFunction; + } + }, + + getHeader: function(name) { + try { + return this.transport.getResponseHeader(name); + } catch (e) { return null } + }, + + evalJSON: function() { + try { + var json = this.getHeader('X-JSON'); + return json ? json.evalJSON() : null; + } catch (e) { return null } + }, + + evalResponse: function() { + try { + return eval((this.transport.responseText || '').unfilterJSON()); + } catch (e) { + this.dispatchException(e); + } + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Updater = Class.create(); + +Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { + initialize: function(container, url, options) { + this.container = { + success: (container.success || container), + failure: (container.failure || (container.success ? null : container)) + } + + this.transport = Ajax.getTransport(); + this.setOptions(options); + + var onComplete = this.options.onComplete || Prototype.emptyFunction; + this.options.onComplete = (function(transport, param) { + this.updateContent(); + onComplete(transport, param); + }).bind(this); + + this.request(url); + }, + + updateContent: function() { + var receiver = this.container[this.success() ? 'success' : 'failure']; + var response = this.transport.responseText; + + if (!this.options.evalScripts) response = response.stripScripts(); + + if (receiver = $(receiver)) { + if (this.options.insertion) + new this.options.insertion(receiver, response); + else + receiver.update(response); + } + + if (this.success()) { + if (this.onComplete) + setTimeout(this.onComplete.bind(this), 10); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(); +Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { + initialize: function(container, url, options) { + this.setOptions(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = {}; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.options.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(request) { + if (this.options.decay) { + this.decay = (request.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = request.responseText; + } + this.timer = setTimeout(this.onTimerEvent.bind(this), + this.decay * this.frequency * 1000); + }, + + onTimerEvent: function() { + this.updater = new Ajax.Updater(this.container, this.url, this.options); + } +}); +function $(element) { + if (arguments.length > 1) { + for (var i = 0, elements = [], length = arguments.length; i < length; i++) + elements.push($(arguments[i])); + return elements; + } + if (typeof element == 'string') + element = document.getElementById(element); + return Element.extend(element); +} + +if (Prototype.BrowserFeatures.XPath) { + document._getElementsByXPath = function(expression, parentElement) { + var results = []; + var query = document.evaluate(expression, $(parentElement) || document, + null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + for (var i = 0, length = query.snapshotLength; i < length; i++) + results.push(query.snapshotItem(i)); + return results; + }; + + document.getElementsByClassName = function(className, parentElement) { + var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]"; + return document._getElementsByXPath(q, parentElement); + } + +} else document.getElementsByClassName = function(className, parentElement) { + var children = ($(parentElement) || document.body).getElementsByTagName('*'); + var elements = [], child, pattern = new RegExp("(^|\\s)" + className + "(\\s|$)"); + for (var i = 0, length = children.length; i < length; i++) { + child = children[i]; + var elementClassName = child.className; + if (elementClassName.length == 0) continue; + if (elementClassName == className || elementClassName.match(pattern)) + elements.push(Element.extend(child)); + } + return elements; +}; + +/*--------------------------------------------------------------------------*/ + +if (!window.Element) var Element = {}; + +Element.extend = function(element) { + var F = Prototype.BrowserFeatures; + if (!element || !element.tagName || element.nodeType == 3 || + element._extended || F.SpecificElementExtensions || element == window) + return element; + + var methods = {}, tagName = element.tagName, cache = Element.extend.cache, + T = Element.Methods.ByTag; + + // extend methods for all tags (Safari doesn't need this) + if (!F.ElementExtensions) { + Object.extend(methods, Element.Methods), + Object.extend(methods, Element.Methods.Simulated); + } + + // extend methods for specific tags + if (T[tagName]) Object.extend(methods, T[tagName]); + + for (var property in methods) { + var value = methods[property]; + if (typeof value == 'function' && !(property in element)) + element[property] = cache.findOrStore(value); + } + + element._extended = Prototype.emptyFunction; + return element; +}; + +Element.extend.cache = { + findOrStore: function(value) { + return this[value] = this[value] || function() { + return value.apply(null, [this].concat($A(arguments))); + } + } +}; + +Element.Methods = { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function(element) { + element = $(element); + Element[Element.visible(element) ? 'hide' : 'show'](element); + return element; + }, + + hide: function(element) { + $(element).style.display = 'none'; + return element; + }, + + show: function(element) { + $(element).style.display = ''; + return element; + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + return element; + }, + + update: function(element, html) { + html = typeof html == 'undefined' ? '' : html.toString(); + $(element).innerHTML = html.stripScripts(); + setTimeout(function() {html.evalScripts()}, 10); + return element; + }, + + replace: function(element, html) { + element = $(element); + html = typeof html == 'undefined' ? '' : html.toString(); + if (element.outerHTML) { + element.outerHTML = html.stripScripts(); + } else { + var range = element.ownerDocument.createRange(); + range.selectNodeContents(element); + element.parentNode.replaceChild( + range.createContextualFragment(html.stripScripts()), element); + } + setTimeout(function() {html.evalScripts()}, 10); + return element; + }, + + inspect: function(element) { + element = $(element); + var result = '<' + element.tagName.toLowerCase(); + $H({'id': 'id', 'className': 'class'}).each(function(pair) { + var property = pair.first(), attribute = pair.last(); + var value = (element[property] || '').toString(); + if (value) result += ' ' + attribute + '=' + value.inspect(true); + }); + return result + '>'; + }, + + recursivelyCollect: function(element, property) { + element = $(element); + var elements = []; + while (element = element[property]) + if (element.nodeType == 1) + elements.push(Element.extend(element)); + return elements; + }, + + ancestors: function(element) { + return $(element).recursivelyCollect('parentNode'); + }, + + descendants: function(element) { + return $A($(element).getElementsByTagName('*')).each(Element.extend); + }, + + firstDescendant: function(element) { + element = $(element).firstChild; + while (element && element.nodeType != 1) element = element.nextSibling; + return $(element); + }, + + immediateDescendants: function(element) { + if (!(element = $(element).firstChild)) return []; + while (element && element.nodeType != 1) element = element.nextSibling; + if (element) return [element].concat($(element).nextSiblings()); + return []; + }, + + previousSiblings: function(element) { + return $(element).recursivelyCollect('previousSibling'); + }, + + nextSiblings: function(element) { + return $(element).recursivelyCollect('nextSibling'); + }, + + siblings: function(element) { + element = $(element); + return element.previousSiblings().reverse().concat(element.nextSiblings()); + }, + + match: function(element, selector) { + if (typeof selector == 'string') + selector = new Selector(selector); + return selector.match($(element)); + }, + + up: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(element.parentNode); + var ancestors = element.ancestors(); + return expression ? Selector.findElement(ancestors, expression, index) : + ancestors[index || 0]; + }, + + down: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return element.firstDescendant(); + var descendants = element.descendants(); + return expression ? Selector.findElement(descendants, expression, index) : + descendants[index || 0]; + }, + + previous: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); + var previousSiblings = element.previousSiblings(); + return expression ? Selector.findElement(previousSiblings, expression, index) : + previousSiblings[index || 0]; + }, + + next: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); + var nextSiblings = element.nextSiblings(); + return expression ? Selector.findElement(nextSiblings, expression, index) : + nextSiblings[index || 0]; + }, + + getElementsBySelector: function() { + var args = $A(arguments), element = $(args.shift()); + return Selector.findChildElements(element, args); + }, + + getElementsByClassName: function(element, className) { + return document.getElementsByClassName(className, element); + }, + + readAttribute: function(element, name) { + element = $(element); + if (Prototype.Browser.IE) { + if (!element.attributes) return null; + var t = Element._attributeTranslations; + if (t.values[name]) return t.values[name](element, name); + if (t.names[name]) name = t.names[name]; + var attribute = element.attributes[name]; + return attribute ? attribute.nodeValue : null; + } + return element.getAttribute(name); + }, + + getHeight: function(element) { + return $(element).getDimensions().height; + }, + + getWidth: function(element) { + return $(element).getDimensions().width; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + var elementClassName = element.className; + if (elementClassName.length == 0) return false; + if (elementClassName == className || + elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) + return true; + return false; + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element).add(className); + return element; + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element).remove(className); + return element; + }, + + toggleClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className); + return element; + }, + + observe: function() { + Event.observe.apply(Event, arguments); + return $A(arguments).first(); + }, + + stopObserving: function() { + Event.stopObserving.apply(Event, arguments); + return $A(arguments).first(); + }, + + // removes whitespace-only text node children + cleanWhitespace: function(element) { + element = $(element); + var node = element.firstChild; + while (node) { + var nextNode = node.nextSibling; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + element.removeChild(node); + node = nextNode; + } + return element; + }, + + empty: function(element) { + return $(element).innerHTML.blank(); + }, + + descendantOf: function(element, ancestor) { + element = $(element), ancestor = $(ancestor); + while (element = element.parentNode) + if (element == ancestor) return true; + return false; + }, + + scrollTo: function(element) { + element = $(element); + var pos = Position.cumulativeOffset(element); + window.scrollTo(pos[0], pos[1]); + return element; + }, + + getStyle: function(element, style) { + element = $(element); + style = style == 'float' ? 'cssFloat' : style.camelize(); + var value = element.style[style]; + if (!value) { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css[style] : null; + } + if (style == 'opacity') return value ? parseFloat(value) : 1.0; + return value == 'auto' ? null : value; + }, + + getOpacity: function(element) { + return $(element).getStyle('opacity'); + }, + + setStyle: function(element, styles, camelized) { + element = $(element); + var elementStyle = element.style; + + for (var property in styles) + if (property == 'opacity') element.setOpacity(styles[property]) + else + elementStyle[(property == 'float' || property == 'cssFloat') ? + (elementStyle.styleFloat === undefined ? 'cssFloat' : 'styleFloat') : + (camelized ? property : property.camelize())] = styles[property]; + + return element; + }, + + setOpacity: function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + return element; + }, + + getDimensions: function(element) { + element = $(element); + var display = $(element).getStyle('display'); + if (display != 'none' && display != null) // Safari bug + return {width: element.offsetWidth, height: element.offsetHeight}; + + // All *Width and *Height properties give 0 on elements with display none, + // so enable the element temporarily + var els = element.style; + var originalVisibility = els.visibility; + var originalPosition = els.position; + var originalDisplay = els.display; + els.visibility = 'hidden'; + els.position = 'absolute'; + els.display = 'block'; + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + els.display = originalDisplay; + els.position = originalPosition; + els.visibility = originalVisibility; + return {width: originalWidth, height: originalHeight}; + }, + + makePositioned: function(element) { + element = $(element); + var pos = Element.getStyle(element, 'position'); + if (pos == 'static' || !pos) { + element._madePositioned = true; + element.style.position = 'relative'; + // Opera returns the offset relative to the positioning context, when an + // element is position relative but top and left have not been defined + if (window.opera) { + element.style.top = 0; + element.style.left = 0; + } + } + return element; + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + return element; + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return element; + element._overflow = element.style.overflow || 'auto'; + if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') + element.style.overflow = 'hidden'; + return element; + }, + + undoClipping: function(element) { + element = $(element); + if (!element._overflow) return element; + element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; + element._overflow = null; + return element; + } +}; + +Object.extend(Element.Methods, { + childOf: Element.Methods.descendantOf, + childElements: Element.Methods.immediateDescendants +}); + +if (Prototype.Browser.Opera) { + Element.Methods._getStyle = Element.Methods.getStyle; + Element.Methods.getStyle = function(element, style) { + switch(style) { + case 'left': + case 'top': + case 'right': + case 'bottom': + if (Element._getStyle(element, 'position') == 'static') return null; + default: return Element._getStyle(element, style); + } + }; +} +else if (Prototype.Browser.IE) { + Element.Methods.getStyle = function(element, style) { + element = $(element); + style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); + var value = element.style[style]; + if (!value && element.currentStyle) value = element.currentStyle[style]; + + if (style == 'opacity') { + if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) + if (value[1]) return parseFloat(value[1]) / 100; + return 1.0; + } + + if (value == 'auto') { + if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) + return element['offset'+style.capitalize()] + 'px'; + return null; + } + return value; + }; + + Element.Methods.setOpacity = function(element, value) { + element = $(element); + var filter = element.getStyle('filter'), style = element.style; + if (value == 1 || value === '') { + style.filter = filter.replace(/alpha\([^\)]*\)/gi,''); + return element; + } else if (value < 0.00001) value = 0; + style.filter = filter.replace(/alpha\([^\)]*\)/gi, '') + + 'alpha(opacity=' + (value * 100) + ')'; + return element; + }; + + // IE is missing .innerHTML support for TABLE-related elements + Element.Methods.update = function(element, html) { + element = $(element); + html = typeof html == 'undefined' ? '' : html.toString(); + var tagName = element.tagName.toUpperCase(); + if (['THEAD','TBODY','TR','TD'].include(tagName)) { + var div = document.createElement('div'); + switch (tagName) { + case 'THEAD': + case 'TBODY': + div.innerHTML = '' + html.stripScripts() + '
'; + depth = 2; + break; + case 'TR': + div.innerHTML = '' + html.stripScripts() + '
'; + depth = 3; + break; + case 'TD': + div.innerHTML = '
' + html.stripScripts() + '
'; + depth = 4; + } + $A(element.childNodes).each(function(node) { element.removeChild(node) }); + depth.times(function() { div = div.firstChild }); + $A(div.childNodes).each(function(node) { element.appendChild(node) }); + } else { + element.innerHTML = html.stripScripts(); + } + setTimeout(function() { html.evalScripts() }, 10); + return element; + } +} +else if (Prototype.Browser.Gecko) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1) ? 0.999999 : + (value === '') ? '' : (value < 0.00001) ? 0 : value; + return element; + }; +} + +Element._attributeTranslations = { + names: { + colspan: "colSpan", + rowspan: "rowSpan", + valign: "vAlign", + datetime: "dateTime", + accesskey: "accessKey", + tabindex: "tabIndex", + enctype: "encType", + maxlength: "maxLength", + readonly: "readOnly", + longdesc: "longDesc" + }, + values: { + _getAttr: function(element, attribute) { + return element.getAttribute(attribute, 2); + }, + _flag: function(element, attribute) { + return $(element).hasAttribute(attribute) ? attribute : null; + }, + style: function(element) { + return element.style.cssText.toLowerCase(); + }, + title: function(element) { + var node = element.getAttributeNode('title'); + return node.specified ? node.nodeValue : null; + } + } +}; + +(function() { + Object.extend(this, { + href: this._getAttr, + src: this._getAttr, + type: this._getAttr, + disabled: this._flag, + checked: this._flag, + readonly: this._flag, + multiple: this._flag + }); +}).call(Element._attributeTranslations.values); + +Element.Methods.Simulated = { + hasAttribute: function(element, attribute) { + var t = Element._attributeTranslations, node; + attribute = t.names[attribute] || attribute; + node = $(element).getAttributeNode(attribute); + return node && node.specified; + } +}; + +Element.Methods.ByTag = {}; + +Object.extend(Element, Element.Methods); + +if (!Prototype.BrowserFeatures.ElementExtensions && + document.createElement('div').__proto__) { + window.HTMLElement = {}; + window.HTMLElement.prototype = document.createElement('div').__proto__; + Prototype.BrowserFeatures.ElementExtensions = true; +} + +Element.hasAttribute = function(element, attribute) { + if (element.hasAttribute) return element.hasAttribute(attribute); + return Element.Methods.Simulated.hasAttribute(element, attribute); +}; + +Element.addMethods = function(methods) { + var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; + + if (!methods) { + Object.extend(Form, Form.Methods); + Object.extend(Form.Element, Form.Element.Methods); + Object.extend(Element.Methods.ByTag, { + "FORM": Object.clone(Form.Methods), + "INPUT": Object.clone(Form.Element.Methods), + "SELECT": Object.clone(Form.Element.Methods), + "TEXTAREA": Object.clone(Form.Element.Methods) + }); + } + + if (arguments.length == 2) { + var tagName = methods; + methods = arguments[1]; + } + + if (!tagName) Object.extend(Element.Methods, methods || {}); + else { + if (tagName.constructor == Array) tagName.each(extend); + else extend(tagName); + } + + function extend(tagName) { + tagName = tagName.toUpperCase(); + if (!Element.Methods.ByTag[tagName]) + Element.Methods.ByTag[tagName] = {}; + Object.extend(Element.Methods.ByTag[tagName], methods); + } + + function copy(methods, destination, onlyIfAbsent) { + onlyIfAbsent = onlyIfAbsent || false; + var cache = Element.extend.cache; + for (var property in methods) { + var value = methods[property]; + if (!onlyIfAbsent || !(property in destination)) + destination[property] = cache.findOrStore(value); + } + } + + function findDOMClass(tagName) { + var klass; + var trans = { + "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", + "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", + "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", + "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", + "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": + "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": + "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": + "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": + "FrameSet", "IFRAME": "IFrame" + }; + if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName.capitalize() + 'Element'; + if (window[klass]) return window[klass]; + + window[klass] = {}; + window[klass].prototype = document.createElement(tagName).__proto__; + return window[klass]; + } + + if (F.ElementExtensions) { + copy(Element.Methods, HTMLElement.prototype); + copy(Element.Methods.Simulated, HTMLElement.prototype, true); + } + + if (F.SpecificElementExtensions) { + for (var tag in Element.Methods.ByTag) { + var klass = findDOMClass(tag); + if (typeof klass == "undefined") continue; + copy(T[tag], klass.prototype); + } + } + + Object.extend(Element, Element.Methods); + delete Element.ByTag; +}; + +var Toggle = { display: Element.toggle }; + +/*--------------------------------------------------------------------------*/ + +Abstract.Insertion = function(adjacency) { + this.adjacency = adjacency; +} + +Abstract.Insertion.prototype = { + initialize: function(element, content) { + this.element = $(element); + this.content = content.stripScripts(); + + if (this.adjacency && this.element.insertAdjacentHTML) { + try { + this.element.insertAdjacentHTML(this.adjacency, this.content); + } catch (e) { + var tagName = this.element.tagName.toUpperCase(); + if (['TBODY', 'TR'].include(tagName)) { + this.insertContent(this.contentFromAnonymousTable()); + } else { + throw e; + } + } + } else { + this.range = this.element.ownerDocument.createRange(); + if (this.initializeRange) this.initializeRange(); + this.insertContent([this.range.createContextualFragment(this.content)]); + } + + setTimeout(function() {content.evalScripts()}, 10); + }, + + contentFromAnonymousTable: function() { + var div = document.createElement('div'); + div.innerHTML = '' + this.content + '
'; + return $A(div.childNodes[0].childNodes[0].childNodes); + } +} + +var Insertion = new Object(); + +Insertion.Before = Class.create(); +Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { + initializeRange: function() { + this.range.setStartBefore(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, this.element); + }).bind(this)); + } +}); + +Insertion.Top = Class.create(); +Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(true); + }, + + insertContent: function(fragments) { + fragments.reverse(false).each((function(fragment) { + this.element.insertBefore(fragment, this.element.firstChild); + }).bind(this)); + } +}); + +Insertion.Bottom = Class.create(); +Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.appendChild(fragment); + }).bind(this)); + } +}); + +Insertion.After = Class.create(); +Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { + initializeRange: function() { + this.range.setStartAfter(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, + this.element.nextSibling); + }).bind(this)); + } +}); + +/*--------------------------------------------------------------------------*/ + +Element.ClassNames = Class.create(); +Element.ClassNames.prototype = { + initialize: function(element) { + this.element = $(element); + }, + + _each: function(iterator) { + this.element.className.split(/\s+/).select(function(name) { + return name.length > 0; + })._each(iterator); + }, + + set: function(className) { + this.element.className = className; + }, + + add: function(classNameToAdd) { + if (this.include(classNameToAdd)) return; + this.set($A(this).concat(classNameToAdd).join(' ')); + }, + + remove: function(classNameToRemove) { + if (!this.include(classNameToRemove)) return; + this.set($A(this).without(classNameToRemove).join(' ')); + }, + + toString: function() { + return $A(this).join(' '); + } +}; + +Object.extend(Element.ClassNames.prototype, Enumerable); +/* Portions of the Selector class are derived from Jack Slocum?s DomQuery, + * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style + * license. Please see http://www.yui-ext.com/ for more information. */ + +var Selector = Class.create(); + +Selector.prototype = { + initialize: function(expression) { + this.expression = expression.strip(); + this.compileMatcher(); + }, + + compileMatcher: function() { + // Selectors with namespaced attributes can't use the XPath version + if (Prototype.BrowserFeatures.XPath && !(/\[[\w-]*?:/).test(this.expression)) + return this.compileXPathMatcher(); + + var e = this.expression, ps = Selector.patterns, h = Selector.handlers, + c = Selector.criteria, le, p, m; + + if (Selector._cache[e]) { + this.matcher = Selector._cache[e]; return; + } + this.matcher = ["this.matcher = function(root) {", + "var r = root, h = Selector.handlers, c = false, n;"]; + + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i in ps) { + p = ps[i]; + if (m = e.match(p)) { + this.matcher.push(typeof c[i] == 'function' ? c[i](m) : + new Template(c[i]).evaluate(m)); + e = e.replace(m[0], ''); + break; + } + } + } + + this.matcher.push("return h.unique(n);\n}"); + eval(this.matcher.join('\n')); + Selector._cache[this.expression] = this.matcher; + }, + + compileXPathMatcher: function() { + var e = this.expression, ps = Selector.patterns, + x = Selector.xpath, le, m; + + if (Selector._cache[e]) { + this.xpath = Selector._cache[e]; return; + } + + this.matcher = ['.//*']; + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i in ps) { + if (m = e.match(ps[i])) { + this.matcher.push(typeof x[i] == 'function' ? x[i](m) : + new Template(x[i]).evaluate(m)); + e = e.replace(m[0], ''); + break; + } + } + } + + this.xpath = this.matcher.join(''); + Selector._cache[this.expression] = this.xpath; + }, + + findElements: function(root) { + root = root || document; + if (this.xpath) return document._getElementsByXPath(this.xpath, root); + return this.matcher(root); + }, + + match: function(element) { + return this.findElements(document).include(element); + }, + + toString: function() { + return this.expression; + }, + + inspect: function() { + return "#"; + } +}; + +Object.extend(Selector, { + _cache: {}, + + xpath: { + descendant: "//*", + child: "/*", + adjacent: "/following-sibling::*[1]", + laterSibling: '/following-sibling::*', + tagName: function(m) { + if (m[1] == '*') return ''; + return "[local-name()='" + m[1].toLowerCase() + + "' or local-name()='" + m[1].toUpperCase() + "']"; + }, + className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", + id: "[@id='#{1}']", + attrPresence: "[@#{1}]", + attr: function(m) { + m[3] = m[5] || m[6]; + return new Template(Selector.xpath.operators[m[2]]).evaluate(m); + }, + pseudo: function(m) { + var h = Selector.xpath.pseudos[m[1]]; + if (!h) return ''; + if (typeof h === 'function') return h(m); + return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); + }, + operators: { + '=': "[@#{1}='#{3}']", + '!=': "[@#{1}!='#{3}']", + '^=': "[starts-with(@#{1}, '#{3}')]", + '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", + '*=': "[contains(@#{1}, '#{3}')]", + '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", + '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" + }, + pseudos: { + 'first-child': '[not(preceding-sibling::*)]', + 'last-child': '[not(following-sibling::*)]', + 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', + 'empty': "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]", + 'checked': "[@checked]", + 'disabled': "[@disabled]", + 'enabled': "[not(@disabled)]", + 'not': function(m) { + var e = m[6], p = Selector.patterns, + x = Selector.xpath, le, m, v; + + var exclusion = []; + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i in p) { + if (m = e.match(p[i])) { + v = typeof x[i] == 'function' ? x[i](m) : new Template(x[i]).evaluate(m); + exclusion.push("(" + v.substring(1, v.length - 1) + ")"); + e = e.replace(m[0], ''); + break; + } + } + } + return "[not(" + exclusion.join(" and ") + ")]"; + }, + 'nth-child': function(m) { + return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m); + }, + 'nth-last-child': function(m) { + return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m); + }, + 'nth-of-type': function(m) { + return Selector.xpath.pseudos.nth("position() ", m); + }, + 'nth-last-of-type': function(m) { + return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m); + }, + 'first-of-type': function(m) { + m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m); + }, + 'last-of-type': function(m) { + m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m); + }, + 'only-of-type': function(m) { + var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m); + }, + nth: function(fragment, m) { + var mm, formula = m[6], predicate; + if (formula == 'even') formula = '2n+0'; + if (formula == 'odd') formula = '2n+1'; + if (mm = formula.match(/^(\d+)$/)) // digit only + return '[' + fragment + "= " + mm[1] + ']'; + if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b + if (mm[1] == "-") mm[1] = -1; + var a = mm[1] ? Number(mm[1]) : 1; + var b = mm[2] ? Number(mm[2]) : 0; + predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " + + "((#{fragment} - #{b}) div #{a} >= 0)]"; + return new Template(predicate).evaluate({ + fragment: fragment, a: a, b: b }); + } + } + } + }, + + criteria: { + tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', + className: 'n = h.className(n, r, "#{1}", c); c = false;', + id: 'n = h.id(n, r, "#{1}", c); c = false;', + attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;', + attr: function(m) { + m[3] = (m[5] || m[6]); + return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m); + }, + pseudo: function(m) { + if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); + return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); + }, + descendant: 'c = "descendant";', + child: 'c = "child";', + adjacent: 'c = "adjacent";', + laterSibling: 'c = "laterSibling";' + }, + + patterns: { + // combinators must be listed first + // (and descendant needs to be last combinator) + laterSibling: /^\s*~\s*/, + child: /^\s*>\s*/, + adjacent: /^\s*\+\s*/, + descendant: /^\s/, + + // selectors follow + tagName: /^\s*(\*|[\w\-]+)(\b|$)?/, + id: /^#([\w\-\*]+)(\b|$)/, + className: /^\.([\w\-\*]+)(\b|$)/, + pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|\s|(?=:))/, + attrPresence: /^\[([\w]+)\]/, + attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\]]*?)\4|([^'"][^\]]*?)))?\]/ + }, + + handlers: { + // UTILITY FUNCTIONS + // joins two collections + concat: function(a, b) { + for (var i = 0, node; node = b[i]; i++) + a.push(node); + return a; + }, + + // marks an array of nodes for counting + mark: function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node._counted = true; + return nodes; + }, + + unmark: function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node._counted = undefined; + return nodes; + }, + + // mark each child node with its position (for nth calls) + // "ofType" flag indicates whether we're indexing for nth-of-type + // rather than nth-child + index: function(parentNode, reverse, ofType) { + parentNode._counted = true; + if (reverse) { + for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { + node = nodes[i]; + if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++; + } + } else { + for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) + if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++; + } + }, + + // filters out duplicates and extends all nodes + unique: function(nodes) { + if (nodes.length == 0) return nodes; + var results = [], n; + for (var i = 0, l = nodes.length; i < l; i++) + if (!(n = nodes[i])._counted) { + n._counted = true; + results.push(Element.extend(n)); + } + return Selector.handlers.unmark(results); + }, + + // COMBINATOR FUNCTIONS + descendant: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + h.concat(results, node.getElementsByTagName('*')); + return results; + }, + + child: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) { + for (var j = 0, children = [], child; child = node.childNodes[j]; j++) + if (child.nodeType == 1 && child.tagName != '!') results.push(child); + } + return results; + }, + + adjacent: function(nodes) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + var next = this.nextElementSibling(node); + if (next) results.push(next); + } + return results; + }, + + laterSibling: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + h.concat(results, Element.nextSiblings(node)); + return results; + }, + + nextElementSibling: function(node) { + while (node = node.nextSibling) + if (node.nodeType == 1) return node; + return null; + }, + + previousElementSibling: function(node) { + while (node = node.previousSibling) + if (node.nodeType == 1) return node; + return null; + }, + + // TOKEN FUNCTIONS + tagName: function(nodes, root, tagName, combinator) { + tagName = tagName.toUpperCase(); + var results = [], h = Selector.handlers; + if (nodes) { + if (combinator) { + // fastlane for ordinary descendant combinators + if (combinator == "descendant") { + for (var i = 0, node; node = nodes[i]; i++) + h.concat(results, node.getElementsByTagName(tagName)); + return results; + } else nodes = this[combinator](nodes); + if (tagName == "*") return nodes; + } + for (var i = 0, node; node = nodes[i]; i++) + if (node.tagName.toUpperCase() == tagName) results.push(node); + return results; + } else return root.getElementsByTagName(tagName); + }, + + id: function(nodes, root, id, combinator) { + var targetNode = $(id), h = Selector.handlers; + if (!nodes && root == document) return targetNode ? [targetNode] : []; + if (nodes) { + if (combinator) { + if (combinator == 'child') { + for (var i = 0, node; node = nodes[i]; i++) + if (targetNode.parentNode == node) return [targetNode]; + } else if (combinator == 'descendant') { + for (var i = 0, node; node = nodes[i]; i++) + if (Element.descendantOf(targetNode, node)) return [targetNode]; + } else if (combinator == 'adjacent') { + for (var i = 0, node; node = nodes[i]; i++) + if (Selector.handlers.previousElementSibling(targetNode) == node) + return [targetNode]; + } else nodes = h[combinator](nodes); + } + for (var i = 0, node; node = nodes[i]; i++) + if (node == targetNode) return [targetNode]; + return []; + } + return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; + }, + + className: function(nodes, root, className, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + return Selector.handlers.byClassName(nodes, root, className); + }, + + byClassName: function(nodes, root, className) { + if (!nodes) nodes = Selector.handlers.descendant([root]); + var needle = ' ' + className + ' '; + for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { + nodeClassName = node.className; + if (nodeClassName.length == 0) continue; + if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) + results.push(node); + } + return results; + }, + + attrPresence: function(nodes, root, attr) { + var results = []; + for (var i = 0, node; node = nodes[i]; i++) + if (Element.hasAttribute(node, attr)) results.push(node); + return results; + }, + + attr: function(nodes, root, attr, value, operator) { + if (!nodes) nodes = root.getElementsByTagName("*"); + var handler = Selector.operators[operator], results = []; + for (var i = 0, node; node = nodes[i]; i++) { + var nodeValue = Element.readAttribute(node, attr); + if (nodeValue === null) continue; + if (handler(nodeValue, value)) results.push(node); + } + return results; + }, + + pseudo: function(nodes, name, value, root, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + if (!nodes) nodes = root.getElementsByTagName("*"); + return Selector.pseudos[name](nodes, value, root); + } + }, + + pseudos: { + 'first-child': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (Selector.handlers.previousElementSibling(node)) continue; + results.push(node); + } + return results; + }, + 'last-child': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (Selector.handlers.nextElementSibling(node)) continue; + results.push(node); + } + return results; + }, + 'only-child': function(nodes, value, root) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) + results.push(node); + return results; + }, + 'nth-child': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root); + }, + 'nth-last-child': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, true); + }, + 'nth-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, false, true); + }, + 'nth-last-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, true, true); + }, + 'first-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, "1", root, false, true); + }, + 'last-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, "1", root, true, true); + }, + 'only-of-type': function(nodes, formula, root) { + var p = Selector.pseudos; + return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); + }, + + // handles the an+b logic + getIndices: function(a, b, total) { + if (a == 0) return b > 0 ? [b] : []; + return $R(1, total).inject([], function(memo, i) { + if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i); + return memo; + }); + }, + + // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type + nth: function(nodes, formula, root, reverse, ofType) { + if (nodes.length == 0) return []; + if (formula == 'even') formula = '2n+0'; + if (formula == 'odd') formula = '2n+1'; + var h = Selector.handlers, results = [], indexed = [], m; + h.mark(nodes); + for (var i = 0, node; node = nodes[i]; i++) { + if (!node.parentNode._counted) { + h.index(node.parentNode, reverse, ofType); + indexed.push(node.parentNode); + } + } + if (formula.match(/^\d+$/)) { // just a number + formula = Number(formula); + for (var i = 0, node; node = nodes[i]; i++) + if (node.nodeIndex == formula) results.push(node); + } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b + if (m[1] == "-") m[1] = -1; + var a = m[1] ? Number(m[1]) : 1; + var b = m[2] ? Number(m[2]) : 0; + var indices = Selector.pseudos.getIndices(a, b, nodes.length); + for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { + for (var j = 0; j < l; j++) + if (node.nodeIndex == indices[j]) results.push(node); + } + } + h.unmark(nodes); + h.unmark(indexed); + return results; + }, + + 'empty': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + // IE treats comments as element nodes + if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue; + results.push(node); + } + return results; + }, + + 'not': function(nodes, selector, root) { + var h = Selector.handlers, selectorType, m; + var exclusions = new Selector(selector).findElements(root); + h.mark(exclusions); + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node._counted) results.push(node); + h.unmark(exclusions); + return results; + }, + + 'enabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node.disabled) results.push(node); + return results; + }, + + 'disabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (node.disabled) results.push(node); + return results; + }, + + 'checked': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (node.checked) results.push(node); + return results; + } + }, + + operators: { + '=': function(nv, v) { return nv == v; }, + '!=': function(nv, v) { return nv != v; }, + '^=': function(nv, v) { return nv.startsWith(v); }, + '$=': function(nv, v) { return nv.endsWith(v); }, + '*=': function(nv, v) { return nv.include(v); }, + '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, + '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); } + }, + + matchElements: function(elements, expression) { + var matches = new Selector(expression).findElements(), h = Selector.handlers; + h.mark(matches); + for (var i = 0, results = [], element; element = elements[i]; i++) + if (element._counted) results.push(element); + h.unmark(matches); + return results; + }, + + findElement: function(elements, expression, index) { + if (typeof expression == 'number') { + index = expression; expression = false; + } + return Selector.matchElements(elements, expression || '*')[index || 0]; + }, + + findChildElements: function(element, expressions) { + var exprs = expressions.join(','), expressions = []; + exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { + expressions.push(m[1].strip()); + }); + var results = [], h = Selector.handlers; + for (var i = 0, l = expressions.length, selector; i < l; i++) { + selector = new Selector(expressions[i].strip()); + h.concat(results, selector.findElements(element)); + } + return (l > 1) ? h.unique(results) : results; + } +}); + +function $$() { + return Selector.findChildElements(document, $A(arguments)); +} +var Form = { + reset: function(form) { + $(form).reset(); + return form; + }, + + serializeElements: function(elements, getHash) { + var data = elements.inject({}, function(result, element) { + if (!element.disabled && element.name) { + var key = element.name, value = $(element).getValue(); + if (value != null) { + if (key in result) { + if (result[key].constructor != Array) result[key] = [result[key]]; + result[key].push(value); + } + else result[key] = value; + } + } + return result; + }); + + return getHash ? data : Hash.toQueryString(data); + } +}; + +Form.Methods = { + serialize: function(form, getHash) { + return Form.serializeElements(Form.getElements(form), getHash); + }, + + getElements: function(form) { + return $A($(form).getElementsByTagName('*')).inject([], + function(elements, child) { + if (Form.Element.Serializers[child.tagName.toLowerCase()]) + elements.push(Element.extend(child)); + return elements; + } + ); + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) return $A(inputs).map(Element.extend); + + for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || (name && input.name != name)) + continue; + matchingInputs.push(Element.extend(input)); + } + + return matchingInputs; + }, + + disable: function(form) { + form = $(form); + Form.getElements(form).invoke('disable'); + return form; + }, + + enable: function(form) { + form = $(form); + Form.getElements(form).invoke('enable'); + return form; + }, + + findFirstElement: function(form) { + return $(form).getElements().find(function(element) { + return element.type != 'hidden' && !element.disabled && + ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + }); + }, + + focusFirstElement: function(form) { + form = $(form); + form.findFirstElement().activate(); + return form; + }, + + request: function(form, options) { + form = $(form), options = Object.clone(options || {}); + + var params = options.parameters; + options.parameters = form.serialize(true); + + if (params) { + if (typeof params == 'string') params = params.toQueryParams(); + Object.extend(options.parameters, params); + } + + if (form.hasAttribute('method') && !options.method) + options.method = form.method; + + return new Ajax.Request(form.readAttribute('action'), options); + } +} + +/*--------------------------------------------------------------------------*/ + +Form.Element = { + focus: function(element) { + $(element).focus(); + return element; + }, + + select: function(element) { + $(element).select(); + return element; + } +} + +Form.Element.Methods = { + serialize: function(element) { + element = $(element); + if (!element.disabled && element.name) { + var value = element.getValue(); + if (value != undefined) { + var pair = {}; + pair[element.name] = value; + return Hash.toQueryString(pair); + } + } + return ''; + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + return Form.Element.Serializers[method](element); + }, + + clear: function(element) { + $(element).value = ''; + return element; + }, + + present: function(element) { + return $(element).value != ''; + }, + + activate: function(element) { + element = $(element); + try { + element.focus(); + if (element.select && (element.tagName.toLowerCase() != 'input' || + !['button', 'reset', 'submit'].include(element.type))) + element.select(); + } catch (e) {} + return element; + }, + + disable: function(element) { + element = $(element); + element.blur(); + element.disabled = true; + return element; + }, + + enable: function(element) { + element = $(element); + element.disabled = false; + return element; + } +} + +/*--------------------------------------------------------------------------*/ + +var Field = Form.Element; +var $F = Form.Element.Methods.getValue; + +/*--------------------------------------------------------------------------*/ + +Form.Element.Serializers = { + input: function(element) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element); + default: + return Form.Element.Serializers.textarea(element); + } + }, + + inputSelector: function(element) { + return element.checked ? element.value : null; + }, + + textarea: function(element) { + return element.value; + }, + + select: function(element) { + return this[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + }, + + selectOne: function(element) { + var index = element.selectedIndex; + return index >= 0 ? this.optionValue(element.options[index]) : null; + }, + + selectMany: function(element) { + var values, length = element.length; + if (!length) return null; + + for (var i = 0, values = []; i < length; i++) { + var opt = element.options[i]; + if (opt.selected) values.push(this.optionValue(opt)); + } + return values; + }, + + optionValue: function(opt) { + // extend element because hasAttribute may not be native + return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; + } +} + +/*--------------------------------------------------------------------------*/ + +Abstract.TimedObserver = function() {} +Abstract.TimedObserver.prototype = { + initialize: function(element, frequency, callback) { + this.frequency = frequency; + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + this.registerCallback(); + }, + + registerCallback: function() { + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + var value = this.getValue(); + var changed = ('string' == typeof this.lastValue && 'string' == typeof value + ? this.lastValue != value : String(this.lastValue) != String(value)); + if (changed) { + this.callback(this.element, value); + this.lastValue = value; + } + } +} + +Form.Element.Observer = Class.create(); +Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(); +Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = function() {} +Abstract.EventObserver.prototype = { + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + Form.getElements(this.element).each(this.registerCallback.bind(this)); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + default: + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +} + +Form.Element.EventObserver = Class.create(); +Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(); +Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); +if (!window.Event) { + var Event = new Object(); +} + +Object.extend(Event, { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + KEY_HOME: 36, + KEY_END: 35, + KEY_PAGEUP: 33, + KEY_PAGEDOWN: 34, + + element: function(event) { + return $(event.target || event.srcElement); + }, + + isLeftClick: function(event) { + return (((event.which) && (event.which == 1)) || + ((event.button) && (event.button == 1))); + }, + + pointerX: function(event) { + return event.pageX || (event.clientX + + (document.documentElement.scrollLeft || document.body.scrollLeft)); + }, + + pointerY: function(event) { + return event.pageY || (event.clientY + + (document.documentElement.scrollTop || document.body.scrollTop)); + }, + + stop: function(event) { + if (event.preventDefault) { + event.preventDefault(); + event.stopPropagation(); + } else { + event.returnValue = false; + event.cancelBubble = true; + } + }, + + // find the first node with the given tagName, starting from the + // node the event was triggered on; traverses the DOM upwards + findElement: function(event, tagName) { + var element = Event.element(event); + while (element.parentNode && (!element.tagName || + (element.tagName.toUpperCase() != tagName.toUpperCase()))) + element = element.parentNode; + return element; + }, + + observers: false, + + _observeAndCache: function(element, name, observer, useCapture) { + if (!this.observers) this.observers = []; + if (element.addEventListener) { + this.observers.push([element, name, observer, useCapture]); + element.addEventListener(name, observer, useCapture); + } else if (element.attachEvent) { + this.observers.push([element, name, observer, useCapture]); + element.attachEvent('on' + name, observer); + } + }, + + unloadCache: function() { + if (!Event.observers) return; + for (var i = 0, length = Event.observers.length; i < length; i++) { + Event.stopObserving.apply(this, Event.observers[i]); + Event.observers[i][0] = null; + } + Event.observers = false; + }, + + observe: function(element, name, observer, useCapture) { + element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (Prototype.Browser.WebKit || element.attachEvent)) + name = 'keydown'; + + Event._observeAndCache(element, name, observer, useCapture); + }, + + stopObserving: function(element, name, observer, useCapture) { + element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (Prototype.Browser.WebKit || element.attachEvent)) + name = 'keydown'; + + if (element.removeEventListener) { + element.removeEventListener(name, observer, useCapture); + } else if (element.detachEvent) { + try { + element.detachEvent('on' + name, observer); + } catch (e) {} + } + } +}); + +/* prevent memory leaks in IE */ +if (Prototype.Browser.IE) + Event.observe(window, 'unload', Event.unloadCache, false); +var Position = { + // set to true if needed, warning: firefox performance problems + // NOT neeeded for page scrolling, only if draggable contained in + // scrollable elements + includeScrollOffsets: false, + + // must be called before calling withinIncludingScrolloffset, every time the + // page is scrolled + prepare: function() { + this.deltaX = window.pageXOffset + || document.documentElement.scrollLeft + || document.body.scrollLeft + || 0; + this.deltaY = window.pageYOffset + || document.documentElement.scrollTop + || document.body.scrollTop + || 0; + }, + + realOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return [valueL, valueT]; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return [valueL, valueT]; + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if(element.tagName=='BODY') break; + var p = Element.getStyle(element, 'position'); + if (p == 'relative' || p == 'absolute') break; + } + } while (element); + return [valueL, valueT]; + }, + + offsetParent: function(element) { + if (element.offsetParent) return element.offsetParent; + if (element == document.body) return element; + + while ((element = element.parentNode) && element != document.body) + if (Element.getStyle(element, 'position') != 'static') + return element; + + return document.body; + }, + + // caches x/y coordinate pair to use with overlap + within: function(element, x, y) { + if (this.includeScrollOffsets) + return this.withinIncludingScrolloffsets(element, x, y); + this.xcomp = x; + this.ycomp = y; + this.offset = this.cumulativeOffset(element); + + return (y >= this.offset[1] && + y < this.offset[1] + element.offsetHeight && + x >= this.offset[0] && + x < this.offset[0] + element.offsetWidth); + }, + + withinIncludingScrolloffsets: function(element, x, y) { + var offsetcache = this.realOffset(element); + + this.xcomp = x + offsetcache[0] - this.deltaX; + this.ycomp = y + offsetcache[1] - this.deltaY; + this.offset = this.cumulativeOffset(element); + + return (this.ycomp >= this.offset[1] && + this.ycomp < this.offset[1] + element.offsetHeight && + this.xcomp >= this.offset[0] && + this.xcomp < this.offset[0] + element.offsetWidth); + }, + + // within must be called directly before + overlap: function(mode, element) { + if (!mode) return 0; + if (mode == 'vertical') + return ((this.offset[1] + element.offsetHeight) - this.ycomp) / + element.offsetHeight; + if (mode == 'horizontal') + return ((this.offset[0] + element.offsetWidth) - this.xcomp) / + element.offsetWidth; + }, + + page: function(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + // Safari fix + if (element.offsetParent == document.body) + if (Element.getStyle(element,'position')=='absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + if (!window.opera || element.tagName=='BODY') { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + + return [valueL, valueT]; + }, + + clone: function(source, target) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || {}) + + // find page position of source + source = $(source); + var p = Position.page(source); + + // find coordinate system to use + target = $(target); + var delta = [0, 0]; + var parent = null; + // delta [0,0] will do fine with position: fixed elements, + // position:absolute needs offsetParent deltas + if (Element.getStyle(target,'position') == 'absolute') { + parent = Position.offsetParent(target); + delta = Position.page(parent); + } + + // correct by body offsets (fixes Safari) + if (parent == document.body) { + delta[0] -= document.body.offsetLeft; + delta[1] -= document.body.offsetTop; + } + + // set position + if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if(options.setWidth) target.style.width = source.offsetWidth + 'px'; + if(options.setHeight) target.style.height = source.offsetHeight + 'px'; + }, + + absolutize: function(element) { + element = $(element); + if (element.style.position == 'absolute') return; + Position.prepare(); + + var offsets = Position.positionedOffset(element); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.width = width + 'px'; + element.style.height = height + 'px'; + }, + + relativize: function(element) { + element = $(element); + if (element.style.position == 'relative') return; + Position.prepare(); + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + } +} + +// Safari returns margins on body which is incorrect if the child is absolutely +// positioned. For performance reasons, redefine Position.cumulativeOffset for +// KHTML/WebKit only. +if (Prototype.Browser.WebKit) { + Position.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return [valueL, valueT]; + } +} + +Element.addMethods(); \ No newline at end of file diff -r b530219c9afe -r f3afb0502eec ipa-server/ipa-gui/ipagui/templates/master.kid --- a/ipa-server/ipa-gui/ipagui/templates/master.kid Fri Aug 10 16:31:59 2007 -0700 +++ b/ipa-server/ipa-gui/ipagui/templates/master.kid Tue Aug 14 09:40:13 2007 -0700 @@ -9,6 +9,7 @@ + diff -r b530219c9afe -r f3afb0502eec ipa-server/ipa-gui/ipagui/templates/usereditform.kid --- a/ipa-server/ipa-gui/ipagui/templates/usereditform.kid Fri Aug 10 16:31:59 2007 -0700 +++ b/ipa-server/ipa-gui/ipagui/templates/usereditform.kid Tue Aug 14 09:40:13 2007 -0700 @@ -19,17 +19,50 @@ - + diff -r b530219c9afe -r f3afb0502eec ipa-server/ipa-gui/ipagui/templates/usernewform.kid --- a/ipa-server/ipa-gui/ipagui/templates/usernewform.kid Fri Aug 10 16:31:59 2007 -0700 +++ b/ipa-server/ipa-gui/ipagui/templates/usernewform.kid Tue Aug 14 09:40:13 2007 -0700 @@ -16,7 +16,7 @@ - + diff -r b530219c9afe -r f3afb0502eec ipa-server/ipa-gui/ipagui/templates/usershow.kid --- a/ipa-server/ipa-gui/ipagui/templates/usershow.kid Fri Aug 10 16:31:59 2007 -0700 +++ b/ipa-server/ipa-gui/ipagui/templates/usershow.kid Tue Aug 14 09:40:13 2007 -0700 @@ -11,15 +11,21 @@
Account Details
- + - + - +
User ID: + ${user.get("uid")}
UID: + ${user.get("uidNumber")}
GID: + ${user.get("gidNumber")}
@@ -27,19 +33,31 @@
Identity Details
- - + + + + + +
Full Name:${user.get("givenName")} ${user.get("sn")} + ${user.get("givenName")}
+ ${user.get("sn")}
Contact Details
- + - +
Email: + ${user.get("mail")}
Telephone: + ${user.get("telephoneNumber")}
-------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 2228 bytes Desc: not available URL: From kmccarth at redhat.com Tue Aug 14 19:01:03 2007 From: kmccarth at redhat.com (Kevin McCarthy) Date: Tue, 14 Aug 2007 12:01:03 -0700 Subject: [Freeipa-devel] [PATCH] fix user.getValue() In-Reply-To: <20070810231335.GI28334@moon.usersys.redhat.com> References: <20070810231335.GI28334@moon.usersys.redhat.com> Message-ID: <20070814190102.GC20835@moon.usersys.redhat.com> Kevin McCarthy wrote: > I think we should be looking at the value instead of value[0] to > determine what it is. I stumbled across this bug because I was able to > submit telephoneNumber as an empty string. The code was blowing up > trying to do a value[0] on "". Don't apply this patch. Rob figured out that the data is different when it comes from the xmlrpc layer versus the funcs.py layer. He's coding up a separate patch to address the issue. -Kevin -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 2228 bytes Desc: not available URL: From rcritten at redhat.com Tue Aug 14 21:12:46 2007 From: rcritten at redhat.com (Rob Crittenden) Date: Tue, 14 Aug 2007 17:12:46 -0400 Subject: [Freeipa-devel] [PATCH] update user Message-ID: <46C21ACE.4070509@redhat.com> Initial patch to update an existing user entry. One thing you cannot do is remove a field and expect to see it go away. I wasn't sure what we wanted to do so I erred on the side of caution (it is fixable by switching 1 to 0 in the mod_s() call in ipaldap.py. This also ensures that Apache is using the forked model so the LDAP connection pools don't blow up. And finally tweak user.py a little bit to try to ensure that blank values can't get into an entry and that blank values don't cause it to blow up. I also added a helper function to user.py to convert a user entry into a dict. I needed this for updates since python-ldap provides a function that compares 2 dicts and returns the list of updates. rob -------------- next part -------------- A non-text attachment was scrubbed... Name: update.patch Type: text/x-patch Size: 10442 bytes Desc: not available URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3245 bytes Desc: S/MIME Cryptographic Signature URL: From kmccarth at redhat.com Tue Aug 14 21:38:41 2007 From: kmccarth at redhat.com (Kevin McCarthy) Date: Tue, 14 Aug 2007 14:38:41 -0700 Subject: [Freeipa-devel] Additional instructions to install README Message-ID: <20070814213841.GD20835@moon.usersys.redhat.com> Could we add the following packagese to the ipa-install README file: fedora-ds-base-devel mhash-devel -Kevin -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 2228 bytes Desc: not available URL: From kmacmill at redhat.com Wed Aug 15 13:16:17 2007 From: kmacmill at redhat.com (Karl MacMillan) Date: Wed, 15 Aug 2007 09:16:17 -0400 Subject: [Freeipa-devel] updates and deletes In-Reply-To: <46C1BBC3.7040502@redhat.com> References: <46C1B134.5070102@redhat.com> <46C1BBC3.7040502@redhat.com> Message-ID: <1187183777.2674.14.camel@localhost.localdomain> On Tue, 2007-08-14 at 10:27 -0400, Rob Crittenden wrote: > Rob Crittenden wrote: > > We need to come to some decision on how we're going to handle updates > > and deletes (or really inactivations). > > > > The LDAP API for updates is a list of tuples in the form > > (mod_op,mod_type,mod_vals), where mod_op is the operation (one of > > MOD_ADD, MOD_DELETE, or MOD_REPLACE), mod_type is a string indicating > > the attribute type name, and mod_vals is either a string value or a list > > of string values to add, delete or replace respectively. With deletes id > > mod_vals is None then all attributes are deleted (probably something to > > test for and reject, I doubt we'd ever want this). > > You know, reading docs can be highly beneficial. The python-ldap package > provides a function to generate the modlist given a new entry and the > original entry. So we have 2 choices: > > 1. Have an API where the old and new records are both passed in (not > very efficient over XML-RPC) > 2. Have an API where the new record is passed in and I do a search to > get the original record (not very efficient) > > I'm going to start poking at this, using model #1 for starters. > If we don't expose the attributes directly but hide them behind methods we could also track changes to the python object, generate a modlist from that, and pass only mods to the XML-RPC layer. Karl From ssorce at redhat.com Wed Aug 15 14:30:25 2007 From: ssorce at redhat.com (Simo Sorce) Date: Wed, 15 Aug 2007 10:30:25 -0400 Subject: [Freeipa-devel] updates and deletes In-Reply-To: <1187183777.2674.14.camel@localhost.localdomain> References: <46C1B134.5070102@redhat.com> <46C1BBC3.7040502@redhat.com> <1187183777.2674.14.camel@localhost.localdomain> Message-ID: <1187188225.3446.58.camel@localhost.localdomain> On Wed, 2007-08-15 at 09:16 -0400, Karl MacMillan wrote: > If we don't expose the attributes directly but hide them behind methods > we could also track changes to the python object, generate a modlist > from that, and pass only mods to the XML-RPC layer. +1 From ssorce at redhat.com Wed Aug 15 15:39:42 2007 From: ssorce at redhat.com (Simo Sorce) Date: Wed, 15 Aug 2007 11:39:42 -0400 Subject: [Freeipa-devel] Pushed cleanups Message-ID: <1187192382.3446.61.camel@localhost.localdomain> I have pushed clean-ups to ipa-kpasswd.c and ipa_pwd_extop.c to address Wall warnings. I didn't send a patch to the list because they were obvious fixes. Simo. From ssorce at redhat.com Wed Aug 15 17:13:11 2007 From: ssorce at redhat.com (Simo Sorce) Date: Wed, 15 Aug 2007 13:13:11 -0400 Subject: [Freeipa-devel] Makefile patch Message-ID: <1187197991.3446.63.camel@localhost.localdomain> I don't like dev stuff to touch my home so here is a patch to keep the rpmbuild root inside the dev tree. Simo. -------------- next part -------------- A non-text attachment was scrubbed... Name: Makefile.patch Type: text/x-patch Size: 2797 bytes Desc: not available URL: From kmccarth at redhat.com Wed Aug 15 18:00:32 2007 From: kmccarth at redhat.com (Kevin McCarthy) Date: Wed, 15 Aug 2007 11:00:32 -0700 Subject: [Freeipa-devel] [PATCH] basic connection pool and user search In-Reply-To: <46C0C161.70005@redhat.com> References: <46C0C161.70005@redhat.com> Message-ID: <20070815180031.GA16630@moon.usersys.redhat.com> Rob Crittenden wrote: > diff -r 491d5b50aabb -r 90e45700faff ipa-server/xmlrpc-server/funcs.py > --- a/ipa-server/xmlrpc-server/funcs.py Fri Aug 10 08:15:23 2007 -0400 > +++ b/ipa-server/xmlrpc-server/funcs.py Mon Aug 13 16:41:38 2007 -0400 [snip] > @@ -210,12 +248,24 @@ class IPAServer: > """Return a list containing a User object for each > existing user. > """ > + global _LDAPPool > + > + if opts: ^^^ this seems to be using a variable opts that isn't defined in get_all_users(). did I miss a patch? > + self.set_principal(opts['remoteuser']) > + > + try: > + dn = self.get_dn_from_principal(self.princ) > + except ldap.LDAPError, e: > + raise xmlrpclib.Fault(1, e) > + except ipaserver.ipaldap.NoSuchEntryError: > + raise xmlrpclib.Fault(2, "No such user") > > # FIXME: Is this the filter we want or should it be more specific? > filter = "(objectclass=posixAccount)" > try: > - m1 = ipaserver.ipaldap.IPAdmin(self.host,self.port,self.bindca,self.bindcert,self.bindkey) > + m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn) > all_users = m1.getList(self.basedn, self.scope, filter, None) > + _LDAPPool.releaseConn(m1) > except ldap.LDAPError, e: > raise xmlrpclib.Fault(1, e) > except ipaserver.ipaldap.NoSuchEntryError: -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 2228 bytes Desc: not available URL: From kmacmill at redhat.com Wed Aug 15 19:58:07 2007 From: kmacmill at redhat.com (Karl MacMillan) Date: Wed, 15 Aug 2007 15:58:07 -0400 Subject: [Freeipa-devel] [PATCH] initial Memberof checkin In-Reply-To: <46BCF5A8.9030104@redhat.com> References: <46BCF5A8.9030104@redhat.com> Message-ID: <1187207887.2838.32.camel@localhost.localdomain> On Fri, 2007-08-10 at 16:32 -0700, Pete Rowley wrote: > Initial check in of memberof plugin. > > Populates the memberof attribute of those entries that allow it with the > DNs of the groupofuniquenames groups that consider the entry a member. > Nested groups are supported. > > The plugin requires to be the "owner" of the memberof attribute and so > memberof must not be replicated (requires fractional replication) - it > would be possible to replicate to a read only replica and not enable > this plugin in that case. The plugin tries to do the minimum work for > the particular operation, to be tolerant of asynchronous changes of > state during an operation, and to be tolerant of inconsistent (e.g. > mid-replicated) state in general. > This was merged with the following patch to enable building it by default. Karl [?1034hdiff -r b3eaa9b17b2f ipa-server/freeipa-server.spec --- a/ipa-server/freeipa-server.spec Fri Aug 10 16:06:23 2007 -0700 +++ b/ipa-server/freeipa-server.spec Wed Aug 15 15:55:19 2007 -0400 @@ -49,6 +49,7 @@ rm -rf %{buildroot} %{_usr}/share/ipa/* %{plugin_dir}/libipa_pwd_extop.so +%{plugin_dir}/libipa-memberof-plugin.so %changelog diff -r b3eaa9b17b2f ipa-server/freeipa-server.spec.in --- a/ipa-server/freeipa-server.spec.in Fri Aug 10 16:06:23 2007 -0700 +++ b/ipa-server/freeipa-server.spec.in Wed Aug 15 15:54:42 2007 -0400 @@ -49,6 +49,7 @@ rm -rf %{buildroot} %{_usr}/share/ipa/* %{plugin_dir}/libipa_pwd_extop.so +%{plugin_dir}/libipa-memberof-plugin.so %changelog diff -r b3eaa9b17b2f ipa-server/ipa-slapi-plugins/Makefile --- a/ipa-server/ipa-slapi-plugins/Makefile Fri Aug 10 16:06:23 2007 -0700 +++ b/ipa-server/ipa-slapi-plugins/Makefile Wed Aug 15 15:50:09 2007 -0400 @@ -1,4 +1,4 @@ SUBDIRS=ipa-pwd-extop -SUBDIRS=ipa-pwd-extop +SUBDIRS=ipa-pwd-extop ipa-memberof all: @for subdir in $(SUBDIRS); do \ diff -r b3eaa9b17b2f ipa-server/ipa-slapi-plugins/ipa-memberof/Makefile --- a/ipa-server/ipa-slapi-plugins/ipa-memberof/Makefile Fri Aug 10 16:06:23 2007 -0700 +++ b/ipa-server/ipa-slapi-plugins/ipa-memberof/Makefile Wed Aug 15 15:55:18 2007 -0400 @@ -1,5 +1,28 @@ all: -all: - gcc ipa-memberof.c -I/usr/include/nss3 -I/usr/include/mozldap -I/usr/include/nspr4 -I/usr/include/fedora-ds -I/usr/include -llber -shared -fPIC -DPIC -g -Wl,-soname -Wl,libipa-memberof-plugin.so -o libipa-memberof-plugin.so +PREFIX ?= $(DESTDIR)/usr +LIBDIR = $(PREFIX)/lib/fedora-ds/plugins +SHAREDIR = $(DESTDIR)/usr/share/ipa + +SONAME = libipa-memberof-plugin.so +LDFLAGS += -llber +CFLAGS ?= -Wall -Wshadow -O2 +CFLAGS += -I/usr/include/fedora-ds -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: - cp -f libipa-memberof-plugin.so /usr/lib/fedora-ds/plugins/ + -mkdir -p $(LIBDIR) + install -m 644 $(SONAME) $(LIBDIR) + install -m 644 *.ldif $(SHAREDIR) + +clean: + rm -f *.o + rm -f $(SONAME) + rm -f *~ + + From prowley at redhat.com Wed Aug 15 22:10:09 2007 From: prowley at redhat.com (Pete Rowley) Date: Wed, 15 Aug 2007 15:10:09 -0700 Subject: [Freeipa-devel] [PATCH] fix wall warnings Message-ID: <46C379C1.4030905@redhat.com> I thought I was compiling with -wall but it turns out I wasn't, just a few mods needed for a clean compile. -- Pete -------------- next part -------------- An embedded and charset-unspecified text was scrubbed... Name: diffs.txt URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3241 bytes Desc: S/MIME Cryptographic Signature URL: From prowley at redhat.com Thu Aug 16 00:20:39 2007 From: prowley at redhat.com (Pete Rowley) Date: Wed, 15 Aug 2007 17:20:39 -0700 Subject: [Freeipa-devel] updates and deletes In-Reply-To: <46C1B134.5070102@redhat.com> References: <46C1B134.5070102@redhat.com> Message-ID: <46C39857.30001@redhat.com> Rob Crittenden wrote: > We don't have to strictly follow this model, just laying out one > possibility. We can also have 3 functions: add_attr, update_attr and > del_attr. That might be simpler at the expense of verbosity > (particularly over XML-RPC). What is update_attr intended to do? Single value add/delete? > > For deletes/inactivations I need to know what attribute(s) to set/unset. The inactivation scheme used by the console and the DS scripts uses a role based attribute scheme. It would be good to use that for compatibility, but ultimately it sets the nsAccountlock attribute in the entry to "true." -- Pete -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3241 bytes Desc: S/MIME Cryptographic Signature URL: From ssorce at redhat.com Thu Aug 16 00:47:57 2007 From: ssorce at redhat.com (Simo Sorce) Date: Wed, 15 Aug 2007 20:47:57 -0400 Subject: [Freeipa-devel] updates and deletes In-Reply-To: <46C39857.30001@redhat.com> References: <46C1B134.5070102@redhat.com> <46C39857.30001@redhat.com> Message-ID: <1187225277.27768.0.camel@localhost.localdomain> On Wed, 2007-08-15 at 17:20 -0700, Pete Rowley wrote: > Rob Crittenden wrote: > > We don't have to strictly follow this model, just laying out one > > possibility. We can also have 3 functions: add_attr, update_attr and > > del_attr. That might be simpler at the expense of verbosity > > (particularly over XML-RPC). > What is update_attr intended to do? Single value add/delete? > > > > For deletes/inactivations I need to know what attribute(s) to set/unset. > The inactivation scheme used by the console and the DS scripts uses a > role based attribute scheme. It would be good to use that for > compatibility, but ultimately it sets the nsAccountlock attribute in the > entry to "true." We need to use something that our KDC will understand, need to dig into the kldap schema to tell what. Simo. From ssorce at redhat.com Thu Aug 16 02:19:54 2007 From: ssorce at redhat.com (Simo Sorce) Date: Wed, 15 Aug 2007 22:19:54 -0400 Subject: [Freeipa-devel] pre drop fixes Message-ID: <1187230794.27768.5.camel@localhost.localdomain> I've pushed a few fixes before we do the drop. - the latest fedora-ds-base packages changed all dir names from fedora-ds to dirsrv, all scripts, .c and .py files have been migrated - improved ipa-kpasswd so that we can start it by default, daemonize and use syslog - fixed an rpm dependency (ntpd -> ntp) that was preventing me from install the package - fix the pwd extop conf and install the plugin by default - other minor fixes Simo. From rcritten at redhat.com Thu Aug 16 02:52:28 2007 From: rcritten at redhat.com (Rob Crittenden) Date: Wed, 15 Aug 2007 22:52:28 -0400 Subject: [Freeipa-devel] pre drop fixes In-Reply-To: <1187230794.27768.5.camel@localhost.localdomain> References: <1187230794.27768.5.camel@localhost.localdomain> Message-ID: <46C3BBEC.9050405@redhat.com> Simo Sorce wrote: > I've pushed a few fixes before we do the drop. > > - the latest fedora-ds-base packages changed all > dir names from fedora-ds to dirsrv, all scripts, > .c and .py files have been migrated > - improved ipa-kpasswd so that we can start it by > default, daemonize and use syslog > - fixed an rpm dependency (ntpd -> ntp) that was > preventing me from install the package > - fix the pwd extop conf and install the plugin > by default > - other minor fixes > > IMHO other than one-liner fixes anything checked-in should be submitted as a patch for review. rob -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3245 bytes Desc: S/MIME Cryptographic Signature URL: From ssorce at redhat.com Thu Aug 16 13:29:41 2007 From: ssorce at redhat.com (Simo Sorce) Date: Thu, 16 Aug 2007 09:29:41 -0400 Subject: [Freeipa-devel] pre drop fixes In-Reply-To: <46C3BBEC.9050405@redhat.com> References: <1187230794.27768.5.camel@localhost.localdomain> <46C3BBEC.9050405@redhat.com> Message-ID: <1187270981.27768.23.camel@localhost.localdomain> On Wed, 2007-08-15 at 22:52 -0400, Rob Crittenden wrote: > Simo Sorce wrote: > > I've pushed a few fixes before we do the drop. > > > > - the latest fedora-ds-base packages changed all > > dir names from fedora-ds to dirsrv, all scripts, > > .c and .py files have been migrated > > - improved ipa-kpasswd so that we can start it by > > default, daemonize and use syslog > > - fixed an rpm dependency (ntpd -> ntp) that was > > preventing me from install the package > > - fix the pwd extop conf and install the plugin > > by default > > - other minor fixes > > > > > > IMHO other than one-liner fixes anything checked-in should be submitted > as a patch for review. Ok, to me they were so obvious fixes I just went on, but I will do, in any case these are the changes: http://hg.et.redhat.com/freeipa/freeipa?cs=978755491cee http://hg.et.redhat.com/freeipa/freeipa?cs=d9c36fdde2f6 http://hg.et.redhat.com/freeipa/freeipa?cs=c8df04e83786 http://hg.et.redhat.com/freeipa/freeipa?cs=9033be6c56e6 http://hg.et.redhat.com/freeipa/freeipa?cs=86700a911248 http://hg.et.redhat.com/freeipa/freeipa?cs=db7b591e5350 http://hg.et.redhat.com/freeipa/freeipa?cs=4fbf6063bd7f http://hg.et.redhat.com/freeipa/freeipa?cs=091a781b18fb Btw if you want to follow whats being pushed you can subscribe to this RSS: http://hg.et.redhat.com/freeipa/freeipa?style=rss I find it incredibly useful, and can speed up pushing changes as I am confident of what's going on (at least for me). I think we are being a bit conservative on the review thing, there are still GUI patches pending on the list and I am not sure who should commit them, Pete? Simo. Simo. From rcritten at redhat.com Thu Aug 16 13:46:18 2007 From: rcritten at redhat.com (Rob Crittenden) Date: Thu, 16 Aug 2007 09:46:18 -0400 Subject: [Freeipa-devel] [PATCH] basic connection pool and user search In-Reply-To: <20070815180031.GA16630@moon.usersys.redhat.com> References: <46C0C161.70005@redhat.com> <20070815180031.GA16630@moon.usersys.redhat.com> Message-ID: <46C4552A.7010705@redhat.com> Kevin McCarthy wrote: > Rob Crittenden wrote: >> diff -r 491d5b50aabb -r 90e45700faff ipa-server/xmlrpc-server/funcs.py >> --- a/ipa-server/xmlrpc-server/funcs.py Fri Aug 10 08:15:23 2007 -0400 >> +++ b/ipa-server/xmlrpc-server/funcs.py Mon Aug 13 16:41:38 2007 -0400 > > [snip] > >> @@ -210,12 +248,24 @@ class IPAServer: >> """Return a list containing a User object for each >> existing user. >> """ >> + global _LDAPPool >> + >> + if opts: > ^^^ > this seems to be using a variable opts that isn't defined in > get_all_users(). did I miss a patch? No, I missed one :-( diff -r d914656e4f87 -r fc81940f81a1 ipa-server/xmlrpc-server/funcs.py --- a/ipa-server/xmlrpc-server/funcs.py Tue Aug 14 17:22:05 2007 -0400 +++ b/ipa-server/xmlrpc-server/funcs.py Thu Aug 16 10:26:34 2007 -0400 @@ -244,7 +244,7 @@ class IPAServer: return fields - def get_all_users (self): + def get_all_users (self, args=None, opts=None): """Return a list containing a User object for each existing user. """ I tested it with both client types and it works for me. Basically this change allows for authenticated operations. We will only get opts from the XML-RPC side. rob -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3245 bytes Desc: S/MIME Cryptographic Signature URL: From rcritten at redhat.com Thu Aug 16 13:50:58 2007 From: rcritten at redhat.com (Rob Crittenden) Date: Thu, 16 Aug 2007 09:50:58 -0400 Subject: [Freeipa-devel] updates and deletes In-Reply-To: <1187183777.2674.14.camel@localhost.localdomain> References: <46C1B134.5070102@redhat.com> <46C1BBC3.7040502@redhat.com> <1187183777.2674.14.camel@localhost.localdomain> Message-ID: <46C45642.9000809@redhat.com> Karl MacMillan wrote: > On Tue, 2007-08-14 at 10:27 -0400, Rob Crittenden wrote: >> Rob Crittenden wrote: >>> We need to come to some decision on how we're going to handle updates >>> and deletes (or really inactivations). >>> >>> The LDAP API for updates is a list of tuples in the form >>> (mod_op,mod_type,mod_vals), where mod_op is the operation (one of >>> MOD_ADD, MOD_DELETE, or MOD_REPLACE), mod_type is a string indicating >>> the attribute type name, and mod_vals is either a string value or a list >>> of string values to add, delete or replace respectively. With deletes id >>> mod_vals is None then all attributes are deleted (probably something to >>> test for and reject, I doubt we'd ever want this). >> You know, reading docs can be highly beneficial. The python-ldap package >> provides a function to generate the modlist given a new entry and the >> original entry. So we have 2 choices: >> >> 1. Have an API where the old and new records are both passed in (not >> very efficient over XML-RPC) >> 2. Have an API where the new record is passed in and I do a search to >> get the original record (not very efficient) >> >> I'm going to start poking at this, using model #1 for starters. >> > > If we don't expose the attributes directly but hide them behind methods > we could also track changes to the python object, generate a modlist > from that, and pass only mods to the XML-RPC layer. I'm going to leave this to Kevin to answer. I'm not sure how it would work with the GUI. I think that everything goes through setValue() so we could theoretically track changes. The LDAP API provides a very nice function that generates the modlist for us. It would be really nice to be able to use that. rob -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3245 bytes Desc: S/MIME Cryptographic Signature URL: From rcritten at redhat.com Thu Aug 16 14:01:42 2007 From: rcritten at redhat.com (Rob Crittenden) Date: Thu, 16 Aug 2007 10:01:42 -0400 Subject: [Freeipa-devel] updates and deletes In-Reply-To: <46C39857.30001@redhat.com> References: <46C1B134.5070102@redhat.com> <46C39857.30001@redhat.com> Message-ID: <46C458C6.8090800@redhat.com> Pete Rowley wrote: > Rob Crittenden wrote: >> We don't have to strictly follow this model, just laying out one >> possibility. We can also have 3 functions: add_attr, update_attr and >> del_attr. That might be simpler at the expense of verbosity >> (particularly over XML-RPC). > What is update_attr intended to do? Single value add/delete? I dunno, just thinking out loud, before I found the provided modifyModList function that does all this for us. >> For deletes/inactivations I need to know what attribute(s) to set/unset. > The inactivation scheme used by the console and the DS scripts uses a > role based attribute scheme. It would be good to use that for > compatibility, but ultimately it sets the nsAccountlock attribute in the > entry to "true." Ok, it's a start anyway. I'll start with this and then add in anything additional that Simo finds for kerberos. rob -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3245 bytes Desc: S/MIME Cryptographic Signature URL: From kmccarth at redhat.com Thu Aug 16 16:33:57 2007 From: kmccarth at redhat.com (Kevin McCarthy) Date: Thu, 16 Aug 2007 09:33:57 -0700 Subject: [Freeipa-devel] updates and deletes In-Reply-To: <46C45642.9000809@redhat.com> References: <46C1B134.5070102@redhat.com> <46C1BBC3.7040502@redhat.com> <1187183777.2674.14.camel@localhost.localdomain> <46C45642.9000809@redhat.com> Message-ID: <20070816163357.GA13261@moon.usersys.redhat.com> Rob Crittenden wrote: > Karl MacMillan wrote: >> If we don't expose the attributes directly but hide them behind methods >> we could also track changes to the python object, generate a modlist >> from that, and pass only mods to the XML-RPC layer. > > I'm going to leave this to Kevin to answer. I'm not sure how it would work > with the GUI. I think that everything goes through setValue() so we could > theoretically track changes. > > The LDAP API provides a very nice function that generates the modlist for > us. It would be really nice to be able to use that. The gui needs to track changes across multiple pages. I was originally planning on using hidden fields for each mutable field (phone_orig, etc). Then manually comparing at the end before submitting. Rob's latest API patch has changed how I do things. Now I'm taking user = client.get_user(uid) user_data = b64encode(dumps(user.toDict())) and stuffing user_data into a hidden field. During the final update, I just orig_user = loads(b64decode(kw.get('user_orig'))) new_user = dict(orig_user) new_user['givenname'] = kw.get('givenname') new_user['sn'] = kw.get('sn') new_user['mail'] = kw.get('mail') new_user['telephoneNumber'] = kw.get('telephoneNumber') client.update_user(orig_user, new_user) Either way, if I have the original and new values, I can pass in the hashes or call setters on fields that have changed. Both work for me. -Kevin -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 2228 bytes Desc: not available URL: From ssorce at redhat.com Thu Aug 16 17:01:15 2007 From: ssorce at redhat.com (Simo Sorce) Date: Thu, 16 Aug 2007 13:01:15 -0400 Subject: [Freeipa-devel] updates and deletes In-Reply-To: <20070816163357.GA13261@moon.usersys.redhat.com> References: <46C1B134.5070102@redhat.com> <46C1BBC3.7040502@redhat.com> <1187183777.2674.14.camel@localhost.localdomain> <46C45642.9000809@redhat.com> <20070816163357.GA13261@moon.usersys.redhat.com> Message-ID: <1187283675.27768.58.camel@localhost.localdomain> On Thu, 2007-08-16 at 09:33 -0700, Kevin McCarthy wrote: > Rob Crittenden wrote: > > Karl MacMillan wrote: > >> If we don't expose the attributes directly but hide them behind methods > >> we could also track changes to the python object, generate a modlist > >> from that, and pass only mods to the XML-RPC layer. > > > > I'm going to leave this to Kevin to answer. I'm not sure how it would work > > with the GUI. I think that everything goes through setValue() so we could > > theoretically track changes. > > > > The LDAP API provides a very nice function that generates the modlist for > > us. It would be really nice to be able to use that. > > The gui needs to track changes across multiple pages. I was originally > planning on using hidden fields for each mutable field (phone_orig, etc). > Then manually comparing at the end before submitting. > > Rob's latest API patch has changed how I do things. Now I'm taking > user = client.get_user(uid) > user_data = b64encode(dumps(user.toDict())) > and stuffing user_data into a hidden field. > > During the final update, I just > orig_user = loads(b64decode(kw.get('user_orig'))) > new_user = dict(orig_user) > new_user['givenname'] = kw.get('givenname') > new_user['sn'] = kw.get('sn') > new_user['mail'] = kw.get('mail') > new_user['telephoneNumber'] = kw.get('telephoneNumber') > client.update_user(orig_user, new_user) > > Either way, if I have the original and new values, I can pass in the > hashes or call setters on fields that have changed. Both work for me. Is this stuff done in the client side (ie the browser) ? I have the gut feeling that we may want to handle things like this on the server side so that we can adjust the logic the way we like, but I have no clear mind on this. Simo. From kmccarth at redhat.com Thu Aug 16 18:46:30 2007 From: kmccarth at redhat.com (Kevin McCarthy) Date: Thu, 16 Aug 2007 11:46:30 -0700 Subject: [Freeipa-devel] updates and deletes In-Reply-To: <1187283675.27768.58.camel@localhost.localdomain> References: <46C1B134.5070102@redhat.com> <46C1BBC3.7040502@redhat.com> <1187183777.2674.14.camel@localhost.localdomain> <46C45642.9000809@redhat.com> <20070816163357.GA13261@moon.usersys.redhat.com> <1187283675.27768.58.camel@localhost.localdomain> Message-ID: <20070816184630.GD13261@moon.usersys.redhat.com> Simo Sorce wrote: > On Thu, 2007-08-16 at 09:33 -0700, Kevin McCarthy wrote: > > Either way, if I have the original and new values, I can pass in the > > hashes or call setters on fields that have changed. Both work for me. > > Is this stuff done in the client side (ie the browser) ? This is done on the server, inside TurboGears. -Kevin -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 2228 bytes Desc: not available URL: From duffy at redhat.com Thu Aug 16 20:39:23 2007 From: duffy at redhat.com (=?ISO-8859-1?Q?M=E1ir=EDn_Duffy?=) Date: Thu, 16 Aug 2007 16:39:23 -0400 Subject: [Freeipa-devel] Questions about LDAP Attribute Usage Message-ID: <46C4B5FB.3000506@redhat.com> Hey, I've skimmed through RFC2256, but I'm still having trouble understanding it how exactly some of the attributes in LDAP are actually used. For example: "5.52. houseIdentifier This attribute is used to identify a building within a location." That still doesn't illustrate its usage very well. If there was an example I think it would be more clear. For instance, if your street address is '123 Sesame Street' and the '123' was considered the houseIdentifier, that would make sense to me but I don't know if that is the intended usage of this attribute. Sometimes the attributes don't even have *that* much of an explanation: "5.17. postalAddress ( 2.5.4.16 NAME 'postalAddress' EQUALITY caseIgnoreListMatch SUBSTR caseIgnoreListSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 )" Is the postalAddress supposed to be just the '123 Sesame Street' part? Or is it the whole 'Joe Smith, 123 Sesame Street, New York, NY 10001' text? If so, how is this information stored, all in one blob in the attribute? Also, how would the postalAddress attribute interact with seemingly related attributes like 'postalCode' and 'postOfficeBox'? How does one deal with international postal addresses as well - are any special considerations needed here? I also have some more general questions (I apologize for the n00bness of all these!): 1. Can attributes contain each other? Could 'postalCode' be inserted into 'postalAddress' ? 2. Can one user have multiples of an attribute? For example, a postalAddress for home and a postalAddress for office and a postalAddress for deliveries? Or can they only have one of each attribute? 3. If users can have multiple of the same attribute, is there any way to guarantee ordering between them, so in the context of a company employee the office version of postalAddress is used first? 4. When mapping attributes to fields in the webui, is there any document more useful than RFC2256 for understanding better the common usages of many of these attributes? 5. With respect to FreeIPA v1, are any of these attributes about users absolutely *required* in all or most usages? I understand a user's particular policy may dictate different requirements, but for v1 are there going to be a default set of requirements that can be customizable in later versions? Or will the required fields always be customizable? Thanks! ~m From rmeggins at redhat.com Thu Aug 16 20:45:55 2007 From: rmeggins at redhat.com (Richard Megginson) Date: Thu, 16 Aug 2007 14:45:55 -0600 Subject: [Freeipa-devel] Questions about LDAP Attribute Usage In-Reply-To: <46C4B5FB.3000506@redhat.com> References: <46C4B5FB.3000506@redhat.com> Message-ID: <46C4B783.2050901@redhat.com> M?ir?n Duffy wrote: > Hey, > > I've skimmed through RFC2256, but I'm still having trouble > understanding it how exactly some of the attributes in LDAP are > actually used. For example: > > "5.52. houseIdentifier This attribute is used to identify a building > within a location." > > That still doesn't illustrate its usage very well. If there was an > example I think it would be more clear. For instance, if your street > address is '123 Sesame Street' and the '123' was considered the > houseIdentifier, that would make sense to me but I don't know if that > is the intended usage of this attribute. Sometimes the attributes > don't even have *that* much of an explanation: > > "5.17. postalAddress > ( 2.5.4.16 NAME 'postalAddress' EQUALITY caseIgnoreListMatch > SUBSTR caseIgnoreListSubstringsMatch > SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 )" > > Is the postalAddress supposed to be just the '123 Sesame Street' part? > Or is it the whole 'Joe Smith, 123 Sesame Street, New York, NY 10001' > text? If so, how is this information stored, all in one blob in the > attribute? > Also, how would the postalAddress attribute interact with seemingly > related attributes like 'postalCode' and 'postOfficeBox'? How does one > deal with international postal addresses as well - are any special > considerations needed here? > > I also have some more general questions (I apologize for the n00bness > of all these!): > > 1. Can attributes contain each other? Could 'postalCode' be inserted > into 'postalAddress' ? You mean, something like postalAddress: $houseIdentifier Sesame Street $ Anytown, IA $postalCode No. LDAP has no provision for macro or variable substitution. > > 2. Can one user have multiples of an attribute? For example, a > postalAddress for home and a postalAddress for office and a > postalAddress for deliveries? Or can they only have one of each > attribute? It depends on if the attribute is defined as SINGLE-VALUE or not. But in LDAP attribute values are not ordered, and there is no well defined or standard mechanism for distinguishing different purposes for each value in a multi-valued attribute. In this case, it is better to create new attributes e.g. workPostalAddress, deliveryPostalAddress, etc. > > 3. If users can have multiple of the same attribute, is there any way > to guarantee ordering between them, so in the context of a company > employee the office version of postalAddress is used first? No. Attribute values in LDAPv3 are SETs, which have no ordering guarantees. OpenLDAP supports a way to keep ordered lists, but Fedora/Red Hat DS does not support that. > > 4. When mapping attributes to fields in the webui, is there any > document more useful than RFC2256 for understanding better the common > usages of many of these attributes? I think http://www.ietf.org/rfc/rfc4517.txt can clarify most of these. As you have discovered, RFC2256 is woefully incomplete, which is why rfc4511-4517 (LDAPv3bis) were created to revise 2251-2256 (LDAPv3). > > 5. With respect to FreeIPA v1, are any of these attributes about users > absolutely *required* in all or most usages? I understand a user's > particular policy may dictate different requirements, but for v1 are > there going to be a default set of requirements that can be > customizable in later versions? Or will the required fields always be > customizable? > > Thanks! > ~m > > _______________________________________________ > Freeipa-devel mailing list > Freeipa-devel at redhat.com > https://www.redhat.com/mailman/listinfo/freeipa-devel -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 3245 bytes Desc: S/MIME Cryptographic Signature URL: From kmccarth at redhat.com Thu Aug 16 21:34:35 2007 From: kmccarth at redhat.com (Kevin McCarthy) Date: Thu, 16 Aug 2007 14:34:35 -0700 Subject: [Freeipa-devel] [PATCH] basic connection pool and user search In-Reply-To: <46C4552A.7010705@redhat.com> References: <46C0C161.70005@redhat.com> <20070815180031.GA16630@moon.usersys.redhat.com> <46C4552A.7010705@redhat.com> Message-ID: <20070816213431.GG13261@moon.usersys.redhat.com> Rob Crittenden wrote: > Kevin McCarthy wrote: >> Rob Crittenden wrote: >>> diff -r 491d5b50aabb -r 90e45700faff ipa-server/xmlrpc-server/funcs.py >>> --- a/ipa-server/xmlrpc-server/funcs.py Fri Aug 10 08:15:23 2007 -0400 >>> +++ b/ipa-server/xmlrpc-server/funcs.py Mon Aug 13 16:41:38 2007 -0400 >> [snip] >>> @@ -210,12 +248,24 @@ class IPAServer: >>> """Return a list containing a User object for each >>> existing user. >>> """ >>> + global _LDAPPool >>> + + if opts: >> ^^^ >> this seems to be using a variable opts that isn't defined in >> get_all_users(). did I miss a patch? > > No, I missed one :-( > > diff -r d914656e4f87 -r fc81940f81a1 ipa-server/xmlrpc-server/funcs.py > --- a/ipa-server/xmlrpc-server/funcs.py Tue Aug 14 17:22:05 2007 -0400 > +++ b/ipa-server/xmlrpc-server/funcs.py Thu Aug 16 10:26:34 2007 -0400 > @@ -244,7 +244,7 @@ class IPAServer: > > return fields > > - def get_all_users (self): > + def get_all_users (self, args=None, opts=None): > """Return a list containing a User object for each > existing user. > """ Looks good - note both patches need to be committed. -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 2228 bytes Desc: not available URL: From kmccarth at redhat.com Thu Aug 16 21:37:26 2007 From: kmccarth at redhat.com (Kevin McCarthy) Date: Thu, 16 Aug 2007 14:37:26 -0700 Subject: [Freeipa-devel] [PATCH] update user In-Reply-To: <46C21ACE.4070509@redhat.com> References: <46C21ACE.4070509@redhat.com> Message-ID: <20070816213726.GH13261@moon.usersys.redhat.com> Rob Crittenden wrote: > Initial patch to update an existing user entry. One thing you cannot do is > remove a field and expect to see it go away. I wasn't sure what we wanted > to do so I erred on the side of caution (it is fixable by switching 1 to 0 > in the mod_s() call in ipaldap.py. > > This also ensures that Apache is using the forked model so the LDAP > connection pools don't blow up. > > And finally tweak user.py a little bit to try to ensure that blank values > can't get into an entry and that blank values don't cause it to blow up. > > I also added a helper function to user.py to convert a user entry into a > dict. I needed this for updates since python-ldap provides a function that > compares 2 dicts and returns the list of updates. Looks good. Rob, now that I've tested the xmlrpc layer isn't changing the attributes, I think getValue() needs to be changed to: - if (len(value) < 1): - return value - if isinstance(value[0],list) or isinstance(value[0],tuple): + if isinstance(value,list) or isinstance(value,tuple): return value[0] else: return value -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/x-pkcs7-signature Size: 2228 bytes Desc: not available URL: From ssorce at redhat.com Thu Aug 16 22:04:55 2007 From: ssorce at redhat.com (Simo Sorce) Date: Thu, 16 Aug 2007 18:04:55 -0400 Subject: [Freeipa-devel] [PATCHES] minor rpm/plugin fixes +ipa-client-install Message-ID: <1187301895.27768.77.camel@localhost.localdomain> as per $SUBJECT note: dnsclient.py is a copy of what you find in the authconfig package, we will probably need to share this code later on but right now it is just easier to copy it off. Simo. -------------- next part -------------- A non-text attachment was scrubbed... Name: freeipa82.patch Type: text/x-patch Size: 1922 bytes Desc: not available URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: freeipa83.patch Type: text/x-patch Size: 1568 bytes Desc: not available URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: freeipa84.patch Type: text/x-patch Size: 30421 bytes Desc: not available URL: From kmccarth at redhat.com Thu Aug 16 22:13:45 2007 From: kmccarth at redhat.com (Kevin McCarthy) Date: Thu, 16 Aug 2007 15:13:45 -0700 Subject: [Freeipa-devel] [PATCH] update user webgui Message-ID: <20070816221345.GI13261@moon.usersys.redhat.com> This patch enables updating for the web gui. Turbogears seems to be sending unicode strings, which is choking the ldap code. For now, I've converted the to regular python strings, but this will affect i18n in the future. The server is returning lowercase ldap attribute names now, so this fixes the web gui as well as ipaclient.py to refer to lowercase attributes. Lastly, there's a fix for user.getValue(). After debugging we've determined that the xmlrpc layer isn't goofing up hash values (thank goodness), and that getValue() was working by coincidence. -Kevin -------------- next part -------------- # HG changeset patch # User Kevin McCarthy # Date 1187301936 25200 # Node ID 8d8234203e0e9cbfbc8d104e761d631da11da24a # Parent 45eb7e8045f8d79dde6136e1a973471e57c82f21 Add update user to gui. Fix fields to be lowercase in web gui (server now returns them lowercase). Fix ipaclient.py to refer to lowercase fields when adding a user. Fix user.getValue() to check isinstance(value,list) instead of value[0]. diff -r 45eb7e8045f8 -r 8d8234203e0e ipa-python/ipaclient.py --- a/ipa-python/ipaclient.py Wed Aug 15 10:48:34 2007 -0700 +++ b/ipa-python/ipaclient.py Thu Aug 16 15:05:36 2007 -0700 @@ -54,18 +54,18 @@ class IPAClient: # 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('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' + 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']) + 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'] diff -r 45eb7e8045f8 -r 8d8234203e0e ipa-python/user.py --- a/ipa-python/user.py Wed Aug 15 10:48:34 2007 -0700 +++ b/ipa-python/user.py Thu Aug 16 15:05:36 2007 -0700 @@ -58,9 +58,7 @@ class User: def getValue(self,name): """Get the first value for the attribute named name""" value = self.data.get(name,[None]) - if (len(value) < 1): - return value - if isinstance(value[0],list) or isinstance(value[0],tuple): + if isinstance(value,list) or isinstance(value,tuple): return value[0] else: return value diff -r 45eb7e8045f8 -r 8d8234203e0e ipa-server/ipa-gui/ipagui/controllers.py --- a/ipa-server/ipa-gui/ipagui/controllers.py Wed Aug 15 10:48:34 2007 -0700 +++ b/ipa-server/ipa-gui/ipagui/controllers.py Thu Aug 16 15:05:36 2007 -0700 @@ -1,4 +1,7 @@ import random import random +from pickle import dumps, loads +from base64 import b64encode, b64decode + import cherrypy import turbogears from turbogears import controllers, expose, flash @@ -8,6 +11,7 @@ from turbogears import error_handler # from model import * # import logging # log = logging.getLogger("ipagui.controllers") + import ipa.config import ipa.ipaclient import ipa.user @@ -28,20 +32,26 @@ def restrict_post(): turbogears.flash("This method only accepts posts") raise turbogears.redirect("/") -def user_to_hash(user): - return { - 'uid' : user.getValue('uid'), - 'givenName' : user.getValue('givenName'), - 'sn' : user.getValue('sn'), - 'mail' : user.getValue('mail'), - 'telephoneNumber': user.getValue('telephoneNumber'), - 'uidNumber' : user.getValue('uidNumber'), - 'gidNumber' : user.getValue('gidNumber'), - 'givenName_orig' : user.getValue('givenName'), - 'sn_orig' : user.getValue('sn'), - 'mail_orig' : user.getValue('mail'), - 'telephoneNumber_orig': user.getValue('telephoneNumber'), - } +def to_ldap_hash(orig): + """LDAP hashes expect all values to be a list. This method converts single + entries to a list.""" + new={} + for (k,v) in orig.iteritems(): + if v == None: + continue + if not isinstance(v, list) and k != 'dn': + v = [v] + new[k] = v + + return new + +def set_ldap_value(hash, key, value): + """Converts unicode strings to normal strings + (because LDAP is choking on unicode strings""" + if value != None: + value = str(value) + hash[key] = value + class Root(controllers.RootController): @@ -75,20 +85,14 @@ class Root(controllers.RootController): return dict(form=user_new_form, tg_template='ipagui.templates.usernew') try: - newuser = ipa.user.User(None) - newuser.setValue('uid', kw['uid']) - newuser.setValue('givenName', kw['givenName']) - newuser.setValue('sn', kw['sn']) - newuser.setValue('mail', kw['mail']) - newuser.setValue('telephoneNumber', kw['telephoneNumber']) - newuser2 = { - 'uid' : kw['uid'], - 'givenName' : kw['givenName'], - 'sn' : kw['sn'], - 'mail' : kw['mail'], - 'telephoneNumber': kw['telephoneNumber'] - } - rv = client.add_user(newuser2) + new_user = {} + set_ldap_value(new_user, 'uid', kw.get('uid')) + set_ldap_value(new_user, 'givenname', kw.get('givenname')) + set_ldap_value(new_user, 'sn', kw.get('sn')) + set_ldap_value(new_user, 'mail', kw.get('mail')) + set_ldap_value(new_user, 'telephonenumber', kw.get('telephonenumber')) + + rv = client.add_user(new_user) turbogears.flash("%s added!" % kw['uid']) raise turbogears.redirect('/usershow', uid=kw['uid']) except xmlrpclib.Fault, f: @@ -103,7 +107,11 @@ class Root(controllers.RootController): turbogears.flash("There was a problem with the form!") user = client.get_user(uid) - return dict(form=user_edit_form, user=user_to_hash(user)) + user_hash = user.toDict() + # store a copy of the original user for the update later + user_data = b64encode(dumps(user_hash)) + user_hash['user_orig'] = user_data + return dict(form=user_edit_form, user=user_hash) @expose() def userupdate(self, **kw): @@ -119,10 +127,22 @@ class Root(controllers.RootController): tg_template='ipagui.templates.useredit') try: + orig_user = loads(b64decode(kw.get('user_orig'))) + + new_user = dict(orig_user) + set_ldap_value(new_user, 'givenname', kw.get('givenname')) + set_ldap_value(new_user, 'sn', kw.get('sn')) + set_ldap_value(new_user, 'mail', kw.get('mail')) + set_ldap_value(new_user, 'telephonenumber', kw.get('telephonenumber')) + + orig_user = to_ldap_hash(orig_user) + new_user = to_ldap_hash(new_user) + + rv = client.update_user(orig_user, new_user) turbogears.flash("%s updated!" % kw['uid']) raise turbogears.redirect('/usershow', uid=kw['uid']) except xmlrpclib.Fault, f: - turbogears.flash("User add failed: " + str(f.faultString)) + turbogears.flash("User update failed: " + str(f.faultString)) return dict(form=user_edit_form, user=kw, tg_template='ipagui.templates.useredit') @@ -140,7 +160,7 @@ class Root(controllers.RootController): """Retrieve a single user for display""" try: user = client.get_user(uid) - return dict(user=user_to_hash(user), fields=forms.user.UserFields()) + return dict(user=user.toDict(), fields=forms.user.UserFields()) except xmlrpclib.Fault, f: turbogears.flash("User show failed: " + str(f.faultString)) raise turbogears.redirect("/") diff -r 45eb7e8045f8 -r 8d8234203e0e ipa-server/ipa-gui/ipagui/forms/user.py --- a/ipa-server/ipa-gui/ipagui/forms/user.py Wed Aug 15 10:48:34 2007 -0700 +++ b/ipa-server/ipa-gui/ipagui/forms/user.py Thu Aug 16 15:05:36 2007 -0700 @@ -3,36 +3,34 @@ from turbogears import validators, widge class UserFields(): uid = widgets.TextField(name="uid", label="Login:") - userPassword = widgets.TextField(name="userPassword", label="Password:") - uidNumber = widgets.TextField(name="uidNumber", label="UID:") - gidNumber = widgets.TextField(name="gidNumber", label="GID:") - givenName = widgets.TextField(name="givenName", label="First name:") + userpassword = widgets.TextField(name="userpassword", label="Password:") + uidnumber = widgets.TextField(name="uidnumber", label="UID:") + gidnumber = widgets.TextField(name="gidnumber", label="GID:") + givenname = widgets.TextField(name="givenname", label="First name:") sn = widgets.TextField(name="sn", label="Last name:") mail = widgets.TextField(name="mail", label="E-mail address:") - telephoneNumber = widgets.TextField(name="telephoneNumber", label="Phone:") + telephonenumber = widgets.TextField(name="telephonenumber", label="Phone:") uid.validator = validators.PlainText(not_empty=True) - userPassword.validator = validators.String(not_empty=True) - givenName.validator = validators.String(not_empty=True) + userpassword.validator = validators.String(not_empty=True) + givenname.validator = validators.String(not_empty=True) sn.validator = validators.String(not_empty=True) mail.validator = validators.Email(not_empty=True) # validators.PhoneNumber may be a bit too picky, requiring an area code - telephoneNumber.validator = validators.PlainText(not_empty=True) + telephonenumber.validator = validators.PlainText(not_empty=True) uid_hidden = widgets.HiddenField(name="uid") - uidNumber_hidden = widgets.HiddenField(name="uidNumber") - gidNumber_hidden = widgets.HiddenField(name="gidNumber") - givenName_orig = widgets.HiddenField(name="givenName_orig") - sn_orig = widgets.HiddenField(name="sn_orig") - mail_orig = widgets.HiddenField(name="mail_orig") - telephoneNumber_orig = widgets.HiddenField(name="telephoneNumber_orig") + uidnumber_hidden = widgets.HiddenField(name="uidnumber") + gidnumber_hidden = widgets.HiddenField(name="gidnumber") + + user_orig = widgets.HiddenField(name="user_orig") class UserNewForm(widgets.Form): params = ['user'] - fields = [UserFields.uid, UserFields.givenName, - UserFields.uidNumber, UserFields.gidNumber, + fields = [UserFields.uid, UserFields.givenname, + UserFields.uidnumber, UserFields.gidnumber, UserFields.sn, UserFields.mail] def __init__(self, *args, **kw): @@ -51,10 +49,10 @@ class UserEditForm(widgets.Form): class UserEditForm(widgets.Form): params = ['user'] - fields = [UserFields.givenName, UserFields.sn, UserFields.mail, - UserFields.givenName_orig, UserFields.sn_orig, UserFields.mail_orig, - UserFields.uid_hidden, - UserFields.uidNumber_hidden, UserFields.gidNumber_hidden] + fields = [UserFields.givenname, UserFields.sn, UserFields.mail, + UserFields.uid_hidden, UserFields.user_orig, + UserFields.uidnumber_hidden, UserFields.gidnumber_hidden, + ] def __init__(self, *args, **kw): super(UserEditForm,self).__init__(*args, **kw) diff -r 45eb7e8045f8 -r 8d8234203e0e ipa-server/ipa-gui/ipagui/templates/usereditform.kid --- a/ipa-server/ipa-gui/ipagui/templates/usereditform.kid Wed Aug 15 10:48:34 2007 -0700 +++ b/ipa-server/ipa-gui/ipagui/templates/usereditform.kid Thu Aug 16 15:05:36 2007 -0700 @@ -21,13 +21,13 @@ -

${len(users)} results returned:

- +