[Zope] How to use "lower" attribute (or function) to catch duplicate User IDs?

Dylan Reinhardt zope@dylanreinhardt.com
Wed, 26 Feb 2003 21:32:14 -0800


At 07:58 PM 2/26/2003, Bob King wrote:
>I am new to Zope (less than a week) and I'm in the process of writing a 
>small portal where users can sign themselves up.

Welcome.

>Right now it is working, but I have a few known problems - one of which is 
>that my routine that checks for duplicate User IDs is case-sensitive (i.e. 
>it would allow "bob","Bob" and "BOB" all to register).

That's the default behavior.  If you want to make sure that doesn't happen, 
you could do one of two things:
1. Force all user names to upper or lower case when you register them
2. Do a check for case-insensitive matches by single-casing your input and 
testing it against a single-case version of each existing user.

The first solution is easy:

<dtml-call "manage_addUser(username.lower(), password, roles)">

The second a bit more cumbersome:
---------------
<dtml-call "REQUEST.set('matches', [])">

<dtml-in objectIds prefix=object>
    <dtml-if "object_item.lower() == username.lower()">
       <dtml-call "matches.append(1)">
    </dtml-if>
</dtml-in>

<dtml-if matches>
   Sorry, can't do that... we've got a name like <dtml-var username> already.
<dtml-else>
    <dtml-call "manage_addUser(username, password, roles)">
</dtml-if>
---------------

>SECURITY QUESTION: - In order for this to work I had to set the security 
>for on acl_users (for this folder) to "Manage users" for "Anonymous".

On first glance, that strikes me as a bad idea.

>Does this open up any way for someone to gain access directly to the 
>acl_users folder?

Probably, though I don't have an exploit to offer you.  In general, it's a 
good idea to keep permissions as low as possible.

A better way to do this, generally, is to write a method that performs 
*just* this privileged operation and give that method a proxy role that is 
sufficient to perform the operation.  You can then call that method in the 
context of a regular user's privileges and they will still be able to do 
whatever it is that method does.  Proxy roles are well worth looking into 
carefully, as they are a potential source of insecurity if written 
uncautiously.

>1) How to track a "state" within a given DTML Document or Method?  The 
>Verify form is very cumbersome as I go through each "test" twice.

Within a method, the easiest way is to set a value in the REQUEST object, eg:

<dtml-call "REQUEST.set('some_var', some_python_expression)">

Between methods, there are ways of passing values:
<dtml-var "some_other_method(arg1=value1, arg2=value2)">

  and placing variables into the namespace:
<dtml-let spam="'pork shoulder and ham'">
    <dtml-var some_method>
</dtml-let>

Note the use of double and single quotes above... the outer double quotes 
denote this as a Python expression, the inner single quotes convey that the 
expression consists of a string value.

Tracking state between hits can be done in a number of ways, but I would 
recommend a bit more reading first.

>If any errors exist, then I retest each parameter prior to redisplaying 
>the form in order to flag the associated input box with an error 
>message.  I had to do that way because I couldn't figure out a way to 
>accomplish it like this in DTML:
>
>If email-good then
>     email-error=0
>else
>     email-error=1

In DTML, it's:

<dtml-if email_good>
     <dtml-call "REQUEST.set('email_error', 0)">
<dtml-else>
    <dtml-call "REQUEST.set('email_error', 1)">
</dtml-if>

I would strongly advise you to get in the habit of using underscores 
instead of dashes in your variable names (as shown above).  When the 
variable email-error is evaluated as part of a Python expression, it will 
translate as "email minus error".  There are ways around this, of course, 
but the easiest thing to do as a beginner is to cultivate good naming habits.

>Then check, if all error values=0, submit the form, otherwise display the 
>errors.

If you're performing a lot of these tests, I'd advise running them in 
sequence instead of nesting them.  It's heck of a lot easier to keep track 
of it all and you're going to want to tell the user *all* the ways they 
screwed up, not just the first one.  :-)

Here's a quick sketch with a few different types of tests:

----------------

<!-- use a list object to collect errors -->
<dtml-call "REQUEST.set('errors', [])">

<dtml-unless expected_field_1>
    <dtml-call "errors.append('You need to enter a value for Expected Field 
1')">
</dtml-if>

<dtml-if "_.len(expected_field_2) < 3">
   <dtml-call "errors.append('Expected Field 2 must contain at least three 
letters or digits')">
</dtml-if>

<dtml-if "expected_field_3 in ['yo mamma', 'up yours', 'lamer']">
   <dtml-call "errors.append('You need to provide a value for Expected 
Field 3 that is less insulting')">
</dtml-if>

<!-- more tests go here -->

<dtml-if errors>
    You had errors:<UL>
    <dtml-in errors prefix=error>
       <LI><dtml-var error_item></LI>
    </dtml-in>
    </UL>
<dtml-else>
    <!-- call your form processing routine:  -->
    <dtml-call "some_thing(REQUEST.form)">
</dtml-if>

----------------

 From here, I'd recommend some reading...

Dig in to The Zope Book and be aware that there are two versions available:
http://www.zope.org/Documentation/Books/ZopeBook/

I'd also highly recommend Dieter Maurer's book in progress:
http://www.dieter.handshake.de/pyprojects/zope/book/book.html

After that, feel free to let us know if you have more questions.

Dylan