Enforcing - like gmail: first character of your username should be a letter (a-z) or number.

1,176 views
Skip to first unread message

Rob_McC

unread,
Aug 6, 2012, 9:22:07 AM8/6/12
to web...@googlegroups.com
I notice that several characters are not allowed in web2py user names, like ~ and !,
       but "." (period) and "-" are allowed.

Quick Question:
Is there an easy way to restrict usernames to only allow a-z as first letter?

Reason:
I will have other uses for the url with .   and - in them.

Reference:
gmail signup
The first character of your username should be a letter (a-z) or number.

Thanks, sure enjoying web2py and learning Python and this user forum.
-------
Notes:
I have this in 

/models/db.py


# require username, not email on signup

auth
.define_tables(username=True)


I did see this about adding a character..


Massimo Di Pierro

unread,
Aug 6, 2012, 9:42:42 AM8/6/12
to web...@googlegroups.com
auth.define_tables(username=True)
db.auth_user.username.requires.insert(0,IS_MATCH("[a-z].*"))

Rob_McC

unread,
Aug 10, 2012, 11:42:29 AM8/10/12
to web...@googlegroups.com

Thanks for getting me started with:

auth.define_tables(username=True)
db
.auth_user.username.requires.insert(0,IS_MATCH("[a-z].*"))

the .insert seemed to give me error. 

 I did get it working

auth.define_tables(username=True)
db
.auth_user.username.requires=IS_MATCH('[a-z].*', error_message='letters only')


Important Note:
 I had to add, IS_NOT_IN_DB, or new registrations would OVERWRITE existing ones,
  which surprised me a lot.

 To make is same as google user name policy:
They allow periods, but NOT as a first letter - I added to your RegEx
I'm almost got it, I want to use exactly what Google uses, (see image attached) - I bet this could be on RegEx, but won't have separate messages then.

auth.settings.table_user.username.requires = [IS_NOT_IN_DB(db, auth.settings.table_user.username),
    IS_LENGTH
(30,6,'must be between 6-30 letters'),
    IS_MATCH
('[.a-z].*', error_message='lower case only and periods only'),
    IS_EXPR
("value[:1]<>'.'", error_message='User name can\'t start with period'),
    IS_EXPR
("value.find(' ') < 0", error_message='User name can\'t contain spaces'),
    IS_EXPR
("value.find('-') < 0", error_message='User name can\'t contain dashes'),
    IS_EXPR
("value.find('_') < 0", error_message='User name can\'t contain underscores'),
   
]

I'll keep testing, thanks
Rob

 
Screen Shot 2012-08-10 at 10.52.28 AM.png

Jonathan Lundell

unread,
Aug 10, 2012, 11:52:12 AM8/10/12
to web...@googlegroups.com
On 10 Aug 2012, at 8:42 AM, Rob_McC <mrmcc...@gmail.com> wrote:
 To make is same as google user name policy:
They allow periods, but NOT as a first letter - I added to your RegEx
I'm almost got it, I want to use exactly what Google uses, (see image attached) - I bet this could be on RegEx, but won't have separate messages then.

auth.settings.table_user.username.requires = [IS_NOT_IN_DB(db, auth.settings.table_user.username),
    IS_LENGTH(30,6,'must be between 6-30 letters'),
    IS_MATCH('[.a-z].*', error_message='lower case only and periods only'),
    IS_EXPR("value[:1]<>'.'", error_message='User name can\'t start with period'),
    IS_EXPR("value.find(' ') < 0", error_message='User name can\'t contain spaces'),
    IS_EXPR("value.find('-') < 0", error_message='User name can\'t contain dashes'),
    IS_EXPR("value.find('_') < 0", error_message='User name can\'t contain underscores'),
    ]

I'll keep testing, thanks
Rob


'[.a-z].*' is probably too permissive, or the error message is wrong. Do you mean "User name must start with lower case or a period"?

(Does Google really forbid upper case?)

The message "lower case only and periods only" is confusing; it sounds like that's the rule for the entire name. Is that right? 

"User name can't contain hyphens": double quotes make the apostrophe 

Jonathan Lundell

unread,
Aug 10, 2012, 11:53:47 AM8/10/12
to web...@googlegroups.com
On 10 Aug 2012, at 8:42 AM, Rob_McC <mrmcc...@gmail.com> wrote:
I'm almost got it, I want to use exactly what Google uses, (see image attached) - I bet this could be on RegEx, but won't have separate messages then.


Do you really want separate messages? It's annoying to try different names and to run into a succession of different objections. Better, it seems to me, to show all the rules at once.

Alternatively, describe the rules elsewhere and then use your individual error messages.

Rob_McC

unread,
Aug 10, 2012, 12:18:47 PM8/10/12
to web...@googlegroups.com
Thanks Jonathan.

Q1:
>(Does Google really forbid upper case?)
. I wouldn't say forbid, but if your name google user name is jonlun, or jon.lun  you can sign in with JonLun, or JON.LUN  -
  But. I notice in web2py, I can have users like JSMITH, jSmith, smith - and I can't think of a reason I would want that.

Q2:
> Do you mean "User name must start with lower case or a period"?
. No, I want just like google, you can have periods, but NOT as first letter. (I have plans for first letter NOT being a period)
   When you say: '[.a-z].*' is probably too permissive,
   is that because someone could enter names like john..........smith  ?

. I will work on making the messages more clear.
  I will have a comment, on the form,
   users will see,so they don't have to wait for the error box to learn what is acceptable

Thanks for very fast response, I'll keep testing it out...

Rob

Anthony

unread,
Aug 10, 2012, 12:18:57 PM8/10/12
to web...@googlegroups.com
auth.define_tables(username=True)
db
.auth_user.username.requires.insert(0,IS_MATCH("[a-z].*"))
the .insert seemed to give me error. 

Hmm, it should work. By default, the username field has an IS_MATCH and and IS_NOT_IN_DB validator, so you should be able to insert into the beginning of the list. What error did you get? Is there some code you're not showing?
 
Important Note:
 I had to add, IS_NOT_IN_DB, or new registrations would OVERWRITE existing ones,
  which surprised me a lot.

Are you sure? That doesn't seem right. Anyway, they do need to be unique, so the IS_NOT_IN_DB is necessary, which is why the IS_MATCH was inserted above -- to preserve the existing IS_NOT_IN_DB validator.

Anthony

Rob_McC

unread,
Aug 10, 2012, 12:41:24 PM8/10/12
to web...@googlegroups.com
Anthony:

Didn't seem right to me either...

>By default, the username field has an IS_MATCH and and IS_NOT_IN_DB validator, 
. I did see the web2py  regex for that somewhere. and noticed the appropriate validators.

Here is what I tested: (using user names)

0- start a blank web2py app with only this:

auth.define_tables(username=True)
## create all tables needed by auth if not custom tables
auth
.define_tables()
auth
.settings.table_user.username.requires=IS_MATCH('[a-z].*', error_message='letters only')



1- sign up user: jsmith with email jsmith[at]gmail.com
     - examine user's profiile.. all is well.

2- Then, log out

3- Try to register him again, and it will not work because of the duplicate email address,
    but if I changed the email address to ,  jsmithxxx[at]gmail.com 
   and left user name jsmith -  AND enter a new password, not jsmith's  password, it lets me in
    to jsmith's original account.

I just tried it again, and I believe that without the 
IS_NOT_IN_DB it behaves this way.

Maybe I'm doing something wrong... when I add the IS_NOT_IN_DB validator, it all behaves the way I want.

Hope that helps explain what I observed.

Thanks
Rob


Anthony

unread,
Aug 10, 2012, 1:45:57 PM8/10/12
to web...@googlegroups.com
3- Try to register him again, and it will not work because of the duplicate email address,
    but if I changed the email address to ,  jsmithxxx[at]gmail.com 
   and left user name jsmith -  AND enter a new password, not jsmith's  password, it lets me in
    to jsmith's original account.

Yes, but I don't think the new account is overwriting the old account. Rather, upon successful registration, the user is automatically logged in (unless registration requires verification or approval). The login happens by querying for the username and taking the first matching record, which will be the original account. Note, this should only happen at registration. If you logout and then try to log back in, the login should fail because the password for the new account will be compared to that of the old account and won't match. Anyway, this is why usernames have to be unique and you shouldn't overwrite the default validators as you did.

Anthony

Rob_McC

unread,
Aug 10, 2012, 2:12:54 PM8/10/12
to web...@googlegroups.com
Anthony:

You're correct about log gin out and back in, but I did gain access to jsmith's account upon registration,
 and I could (and did)
     change his password in profile, and now I control his account - locking smith out.

I did assume that the "old validator" would still fire, and not be replaced
with just my validator.- but used WITH my validator.

Anyhow, should work just fine, but something to be aware of I think.

Maybe a note in the documentation would be in order - I stumbled upon this only by chance
trying dozens of combinations for not allowing capital letter etc.  and if I never changed the email address,
I would have thought everything was just fine.

Be happy to do any testing of this issue.

Thanks again, I'm learning a lot ..

Rob




Anthony

unread,
Aug 10, 2012, 3:09:00 PM8/10/12
to web...@googlegroups.com
On Friday, August 10, 2012 2:12:54 PM UTC-4, Rob_McC wrote:
Anthony:

You're correct about log gin out and back in, but I did gain access to jsmith's account upon registration,
 and I could (and did)
     change his password in profile, and now I control his account - locking smith out.

I did assume that the "old validator" would still fire, and not be replaced
with just my validator.- but used WITH my validator.

db.auth_user.username.requires = [list, of, validators]
db
.auth_user.username.requires = IS_MATCH(...)

The above replaces a list with a single validator. In Python, if you assign a new value to an object that was a list, it does not get appended to the list -- it replaces the list (as it would replace any other type of object). If you want to mutate an existing list, you have to use .insert(), .append(), .extend(), +, etc., which is what Massimo originally instructed. Also, the book section on customizing Auth says the following:

If you add a field called "username", it will be used in place of "email" for login. If you do, you will need to add a validator as well:

1.
auth_table.username.requires = IS_NOT_IN_DB(db, auth_table.username)

I suppose we could add a sterner warning, though. Perhaps we should force an IS_NOT_IN_DB validator on username/email when registration is processed in case there isn't one.

Anthony

Rob_McC

unread,
Aug 11, 2012, 11:51:22 PM8/11/12
to web...@googlegroups.com


I never got this to work, as I mentioned above
auth.define_tables(username=True)
db
.auth_user.username.requires.insert(0,IS_MATCH("[a-z].*"))

I get this error:

Ticket ID
127.0.0.1.2012-08-11.23-47-00.29b15810-8243-46b8-802f-153225e295fe
<type 'exceptions.AttributeError'> 'tuple' object has no attribute 'insert'

I now understand the .insert with lists etc, and why if I don't .insert, it will not fire the web2py validators.
 But I don't see what is wrong with the example


Thanks,
Rob
Rob

Jonathan Lundell

unread,
Aug 12, 2012, 12:05:59 AM8/12/12
to web...@googlegroups.com
On 11 Aug 2012, at 8:51 PM, Rob_McC <mrmcc...@gmail.com> wrote:
> I never got this to work, as I mentioned above
> auth.define_tables(username=True)
> db.auth_user.username.requires.insert(0,IS_MATCH("[a-z].*"))
>
> I get this error:
>
> Ticket ID
> 127.0.0.1.2012-08-11.23-47-00.29b15810-8243-46b8-802f-153225e295fe
> <type 'exceptions.AttributeError'> 'tuple' object has no attribute 'insert'
>
> I now understand the .insert with lists etc, and why if I don't .insert, it will not fire the web2py validators.
> But I don't see what is wrong with the example
>

Tuples, unlike lists, are immutable, so no insert. I suppose you could write

db.auth_user.username.requires = (IS_MATCH("[a-z].*"),) + db.auth_user.username.requires

Anthony

unread,
Aug 12, 2012, 12:14:39 AM8/12/12
to web...@googlegroups.com
I never got this to work, as I mentioned above
auth.define_tables(username=True)
db
.auth_user.username.requires.insert(0,IS_MATCH("[a-z].*"))

Is that the exact code, with nothing coming between the auth = Auth(...) line (not shown above) and the auth.define_tables() line, and nothing coming between the above two lines? I ask because when auth.define_tables() creates the auth_user table, the username field gets a list of validators, not a tuple. Somewhere in your code, it's getting a tuple of validators.

Anthony

Rob_McC

unread,
Aug 13, 2012, 11:00:21 AM8/13/12
to web...@googlegroups.com
Anthony:

Thanks for continued help.

I greated a new simple app, inserted the two line, where I hope they should go. (in bold, below is entire source)
This is the only thing chaned in the simple app.

I Get The Same error:

<type 'exceptions.AttributeError'> 'tuple' object has no attribute 'insert'


File: RAM_SIMPLE_INSERT/MODELS/DB.PY

# -*- coding: utf-8 -*-

#########################################################################
## This scaffolding model makes your app work on Google App Engine too
## File is released under public domain and you can use without limitations
#########################################################################

## if SSL/HTTPS is properly configured and you want all HTTP requests to
## be redirected to HTTPS, uncomment the line below:
# request.requires_https()

if not request.env.web2py_runtime_gae:
   
## if NOT running on Google App Engine use SQLite or other DB
    db
= DAL('sqlite://storage.sqlite')
else:
   
## connect to Google BigTable (optional 'google:datastore://namespace')
    db
= DAL('google:datastore')
   
## store sessions and tickets there
    session
.connect(request, response, db = db)
   
## or store session in Memcache, Redis, etc.
   
## from gluon.contrib.memdb import MEMDB
   
## from google.appengine.api.memcache import Client
   
## session.connect(request, response, db = MEMDB(Client()))

## by default give a view/generic.extension to all actions from localhost
## none otherwise. a pattern can be 'controller/function.extension'
response
.generic_patterns = ['*'] if request.is_local else []
## (optional) optimize handling of static files
# response.optimize_css = 'concat,minify,inline'
# response.optimize_js = 'concat,minify,inline'

#########################################################################
## Here is sample code if you need for
## - email capabilities
## - authentication (registration, login, logout, ... )
## - authorization (role based authorization)
## - services (xml, csv, json, xmlrpc, jsonrpc, amf, rss)
## - old style crud actions
## (more options discussed in gluon/tools.py)
#########################################################################

from gluon.tools import Auth, Crud, Service, PluginManager, prettydate
auth
= Auth(db, hmac_key=Auth.get_or_create_key())
crud
, service, plugins = Crud(db), Service(), PluginManager()


## create all tables needed by auth if not custom tables
#auth.define_tables()


auth.define_tables(username=True)
db
.auth_user.username.requires.insert(0,IS_MATCH("[a-z].*"))


## configure email
mail
=auth.settings.mailer
mail
.settings.server = 'logging' or 'smtp.gmail.com:587'
mail
.settings.sender = 'y...@gmail.com'
mail
.settings.login = 'username:password'

## configure auth policy
auth
.settings.registration_requires_verification = False
auth
.settings.registration_requires_approval = False
auth
.settings.reset_password_requires_verification = True

## if you need to use OpenID, Facebook, MySpace, Twitter, Linkedin, etc.
## register with janrain.com, write your domain:api_key in private/janrain.key
from gluon.contrib.login_methods.rpx_account import use_janrain
use_janrain
(auth,filename='private/janrain.key')

#########################################################################
## Define your tables below (or better in another model file) for example
##
## >>> db.define_table('mytable',Field('myfield','string'))
##
## Fields can be 'string','text','password','integer','double','boolean'
##       'date','time','datetime','blob','upload', 'reference TABLENAME'
## There is an implicit 'id integer autoincrement' field
## Consult manual for more options, validators, etc.
##
## More API examples for controllers:
##
## >>> db.mytable.insert(myfield='value')
## >>> rows=db(db.mytable.myfield=='value').select(db.mytable.ALL)
## >>> for row in rows: print row.id, row.myfield
#########################################################################



Anthony

unread,
Aug 13, 2012, 11:16:07 AM8/13/12
to web...@googlegroups.com
Hmm, can you try with trunk? I just tried the same thing with trunk and don't get any error.

Anthony

Anthony

unread,
Aug 13, 2012, 11:19:17 AM8/13/12
to web...@googlegroups.com
Just looked at the 1.99.7 code, and it is a tuple there -- but it has been changed to a list in trunk, so should work in the upcoming 2.0 release.

Anthony

Jonathan Lundell

unread,
Aug 13, 2012, 11:35:03 AM8/13/12
to web...@googlegroups.com
In any case, while tuples are immutable, they can be concatenated and replaced.  Just don't use insert or append. 
--
 
 
 

Rob_McC

unread,
Aug 13, 2012, 4:28:24 PM8/13/12
to web...@googlegroups.com
Just tried with web2py 2.0 release. It worked .


auth
.define_tables(username=True)
db
.auth_user.username.requires.insert(0,IS_MATCH("[a-z].*"))

So, thanks everyone, this certainly answered my first post about the error.

I'll posts my working username code, that behaves the way Google usernames do, once I'm done.

Much appreciated everyone,
Rob



Jonathan Lundell

unread,
Aug 13, 2012, 4:33:42 PM8/13/12
to web...@googlegroups.com
Good. BTW, are you deliberately forbidding upper-case letters?

Rob_McC

unread,
Aug 14, 2012, 11:11:42 AM8/14/12
to web...@googlegroups.com
Hey Jon:

Q:
  > BTW, are you deliberately forbidding upper-case letters?

A:
Yes, just like Google does, usernames are lowercase,
   although if your gmail username is
   johnsmith
   you can log in with
   JohnSmith, or
   JOHNSMITH, or
   Johnsmith
                etc.                     but there is ONLY ONE user.
 
  At least for my app, Just case lowercase names are best...
  user's have enough trouble remembering names without burden of case-sensitivity.
  
-------
So, with the of this forum, I think I have nearly completed
my Google-like Registration policy in web2py. I'll continue to test.

Here is code, entire file (based on simple app, is attached  db.py)

Concern:
I hope I'm restricting the length of password correctly?
 I just followed discussion on this forum.
 Note:
       Curiously, in version 1.99, the .insert works,
         where id didn't with username.
--

thanks all,
~ Rob

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

db.py....

from gluon.tools import Auth, Crud, Service, PluginManager, prettydate
auth
= Auth(db, hmac_key=Auth.get_or_create_key())
crud
, service, plugins = Crud(db), Service(), PluginManager()

## -  START CUSTOMIZATION  - - - - - - - - - - - - - - - - - - - - - - ##

# | Summary:
# |  Modify web2py to allow user registrations similar to
# |  Google registrations.
# |  i.e.
# |   - lower case only [a-z]
# |   - numbers [0-9] and period are OK
# |   - can't end in a period
# |   - can't start with a period
# |   - can't have consecutive periods
# |   - min 8 letter password
# |   - username can't be changed once registered
# |
# |  Note: Messages are nearly same as Google displays



## create all tables needed by auth if not custom tables
#  use usernames rather than email addresses to register
auth
.define_tables(username=True)

# allow username only on registration, but can only
# be viewed (readable) in Profile
# user can't change username once registered.

if auth.is_logged_in():
    db
.auth_user.username.writable = False
    db
.auth_user.username.readable = True


#custom message for password length - like Google
# ref:
"""
https://groups.google.com/forum/?fromgroups#!searchin/web2py/$20default$20length$20for$20password/web2py/k5os3bMz228/vG-UOLbhcBUJ[1-25]
"""

db
.auth_user.password.requires.insert(0,IS_LENGTH(minsize=8))
db
.auth_user.password.requires = CRYPT(key=auth.settings.hmac_key, min_length=8)

#add a comments to exlain policy
db
.auth_user.password.comment='minimum 8 letters'
db
.auth_user.username.comment='min. 6 letters (a-z), you may use numbers, and periods.'

# apply nearly identical username policy and message that Google Accounts use.
# this OVERWRITES web2py's default username validation
# reference and thanks to web2py community for help:
#   https://groups.google.com/forum/?fromgroups#!starred/web2py/HBODB00HMfU[1-25]

auth
.settings.table_user.username.requires = [IS_LENGTH(30,6,'Please use between 6 and 30 characters.'),
    IS_MATCH
('^[a-z0-9.]*$', error_message='Please use only letters (a-z) and numbers (0-9), and periods.'),
    IS_NOT_EMPTY
(error_message='You can\'t leave this empty. '),
    IS_EXPR
("value[0]<>'.'", error_message='The FIRST character of your username should be a letter (a-z) or number.'),
    IS_EXPR
("value[-1]<>'.'", error_message='The LAST character of your username should be a letter (a-z) or number.'),
    IS_EXPR
("str(value).find('..')==-1",error_message='A fan of punctuation! Alas, usernames can\'t have consecutive periods.'),
    IS_NOT_IN_DB
(db, auth.settings.table_user.username, 'Someone already has that username. ')
   
]
 
## -  END CUSTOMIZATION  - - - - - - - - - - - - - - - - - - - - - - ##


db.py

Rob_McC

unread,
Aug 14, 2012, 11:34:23 AM8/14/12
to web...@googlegroups.com
Just a couple screenshots, Register and Profile.

Screen Shot 2012-08-14 at 11.31.25 AM.png
Screen Shot 2012-08-14 at 11.32.01 AM.png

Anthony

unread,
Aug 14, 2012, 11:47:59 AM8/14/12
to web...@googlegroups.com
Note, you shouldn't need:

db.auth_user.password.requires.insert(0,IS_LENGTH(minsize=8))

given that you specify min_length in the CRYPT validator. You might consider the IS_STRONG validator as well.

Anthony

Jonathan Lundell

unread,
Aug 14, 2012, 11:54:10 AM8/14/12
to web...@googlegroups.com
On 14 Aug 2012, at 8:11 AM, Rob_McC <mrmcc...@gmail.com> wrote:
Hey Jon:

Q:
  > BTW, are you deliberately forbidding upper-case letters?

A:
 Yes, just like Google does, usernames are lowercase,
   although if your gmail username is
   johnsmith
   you can log in with
   JohnSmith, or
   JOHNSMITH, or
   Johnsmith
                etc.                     but there is ONLY ONE user.
  
  At least for my app, Just case lowercase names are best...
  user's have enough trouble remembering names without burden of case-sensitivity.

An alternative is to let the user enter any case, but append the IS_LOWER validator, which isn't a validator, but forces the field to lower case. 

Jonathan Lundell

unread,
Aug 14, 2012, 11:56:39 AM8/14/12
to web...@googlegroups.com
On 14 Aug 2012, at 8:47 AM, Anthony <abas...@gmail.com> wrote:
Note, you shouldn't need:

db.auth_user.password.requires.insert(0,IS_LENGTH(minsize=8))

given that you specify min_length in the CRYPT validator. You might consider the IS_STRONG validator as well.

It's maybe worth pointing out that these validators should be imposed only when registering or changing a password, not during login. The problem with having password validators on login is that they leak password constraints to an attacker. (Of course, the registration form can be used to extract this information as well, but still...)

Rob_McC

unread,
Aug 14, 2012, 12:19:49 PM8/14/12
to web...@googlegroups.com
Thanks.

-1-
>you might consider the IS_STRONG validator as well.
. What a quick  and easy way to increase security of passwords, thanks for tip.
  This is what I love about web2py.

 http://web2py.com/books/default/chapter/29/7
Example:
requires = IS_STRONG(min=10, special=2, upper=2)
where
    min is minimum length of the value
    special is the minimum number of required special characters special characters are any of the following !@#$%^&*(){}[]-+
    upper is the minimum number of upper case characters

-2-

> Note, you shouldn't need :

db.auth_user.password.requires.insert(0,IS_LENGTH(minsize=8))

- I removed it and tested, work well without it, the post wasn't clear to me if I need both,
  or just this one,

-3-

>validators on login is that they leak password constraints to an attacker.
  (Of course, the registration form can be used to extract this information as well, but still...)

- I think I understand, when you say "leak"--
     is it just a matter than anyone would see the message  on the screen, ie. min 8 letters?
       or is there more of a  technical security leak you are referring to.

Thanks once again...

Rob


Anthony

unread,
Aug 14, 2012, 12:33:29 PM8/14/12
to web...@googlegroups.com
It's maybe worth pointing out that these validators should be imposed only when registering or changing a password, not during login. The problem with having password validators on login is that they leak password constraints to an attacker. (Of course, the registration form can be used to extract this information as well, but still...)

Looks like the code does remove the min_length constraint of CRYPT for login: http://code.google.com/p/web2py/source/browse/gluon/tools.py#1829, but doesn't do anything about IS_STRONG. Do you think we should change that?

Anthony

Jonathan Lundell

unread,
Aug 14, 2012, 12:38:51 PM8/14/12
to web...@googlegroups.com
On 14 Aug 2012, at 9:19 AM, Rob_McC <mrmcc...@gmail.com> wrote:
>validators on login is that they leak password constraints to an attacker.
  (Of course, the registration form can be used to extract this information as well, but still...)

- I think I understand, when you say "leak"-- 
     is it just a matter than anyone would see the message  on the screen, ie. min 8 letters?
       or is there more of a  technical security leak you are referring to.

Just that they would see it, telling them that they needn't try guessing passwords less than 8 characters.

There's a cosmetic reason to suppress the validator as well, in that there's no particular point in telling the user anything more than that they got their login wrong. 

It's trivial to implement: just make adding the validator(s) conditional on the current request. Something like:

if request.args(0) != 'login':
add validators

will suffice.

Jonathan Lundell

unread,
Aug 14, 2012, 12:48:41 PM8/14/12
to web...@googlegroups.com
On 14 Aug 2012, at 9:33 AM, Anthony <abas...@gmail.com> wrote:
It's maybe worth pointing out that these validators should be imposed only when registering or changing a password, not during login. The problem with having password validators on login is that they leak password constraints to an attacker. (Of course, the registration form can be used to extract this information as well, but still...)

Looks like the code does remove the min_length constraint of CRYPT for login: http://code.google.com/p/web2py/source/browse/gluon/tools.py#1829, but doesn't do anything about IS_STRONG. Do you think we should change that?


I think so, if we can do it safely there.

Massimo Di Pierro

unread,
Aug 14, 2012, 8:45:08 PM8/14/12
to web...@googlegroups.com
If you have a proposal for a change now is the time. web2py 2.0 approaching fast....

villas

unread,
Aug 15, 2012, 10:56:40 AM8/15/12
to web...@googlegroups.com
+1 
This customisation seems like an excellent idea to include in Welcome not least because it really demonstrates how to customise auth.  For public websites it is important to have strong defaults built-in.  For intranets,  it would be trivial to weaken this (e.g. comment out IS_STRONG or reduce length etc).


Rob_McC

unread,
Aug 15, 2012, 11:17:14 AM8/15/12
to web...@googlegroups.com
villas:

>because it really demonstrates how to customise auth.
. thanks for comment...

. I wonder, would it be a good idea for me to upload an EXAMPLE
  .w2p that summarized this thread?
        which would just be the "Welcome" app, with the customized auth etc.?

. I also wonder, that the most users, would want to customize auth / usernames etc.
  in a similar manner.

As I learn web2py, I find myself asking a lot of questions on this forum,
   and taking bits and pieces of discussion threads to get things working.

I have had an excellent experience progressing with web2py, because of the
  help I've received on this forum.

~ Rob

Anthony

unread,
Aug 15, 2012, 12:58:26 PM8/15/12
to web...@googlegroups.com
I did assume that the "old validator" would still fire, and not be replaced
with just my validator.- but used WITH my validator.

db.auth_user.username.requires = [list, of, validators]
db
.auth_user.username.requires = IS_MATCH(...)

FYI, I just submitted a patch that will force an IS_NOT_IN_DB validator on the "username" field (or "email" field if used for login) during registration, in case you accidentally overwrite the default validator as above.

Anthony

Rob_McC

unread,
Aug 16, 2012, 8:56:22 PM8/16/12
to web...@googlegroups.com
Anthony:

. Thanks for submitting a patch.

Quick Question:

With all of the below validations....
1- Do they ALL execute
or
2- Execution stops at the first one that fails,

What I mean is say a user uses a username="bill"
the first validator will display the message, but does web2py continue to evaluate the others?


If so..
  Wouldn't it be wise to put the most likely validators at the beginning of the list?

Thanks, I'm almost finished my example for this, just working on the "no bad words" for username feature.

Rob


Rob

Anthony

unread,
Aug 16, 2012, 9:19:45 PM8/16/12
to web...@googlegroups.com
With all of the below validations....
1- Do they ALL execute
or
2- Execution stops at the first one that fails,

What I mean is say a user uses a username="bill"
the first validator will display the message, but does web2py continue to evaluate the others?


If so..
  Wouldn't it be wise to put the most likely validators at the beginning of the list?

Validation stops at the first validator that fails. I suppose it would be slightly more efficient to put the validators that are most likely to fail at the beginning of the list, though probably makes little difference (keeping validators that access the db, such as IS_NOT_IN_DB, at the end, though is probably a good idea -- no reason to hit the db unless all the other validators have passed).

Anthony

villas

unread,
Aug 17, 2012, 7:45:03 AM8/17/12
to web...@googlegroups.com
Hi Rob

I hope Massimo will consider to incorporate this into Welcome.  It is much to the credit of web2py that security is given top priority and it is apt that the default scheme should be a thoughtful implementation. 

A well thought out Auth configuration is useful to everyone and the fact that it mirrors Gmail makes it a notable feature.  For those that don't like it,  it is much easier to disable or water-down than it is to reinvent -- as this thread amply illustrates. 

Otherwise,  as this is more of a configuration rather than an app,  I would suggest uploading it as a recipe to web2pyslices.

Many thanks, 
David

Rob_McC

unread,
Aug 17, 2012, 9:16:14 AM8/17/12
to web...@googlegroups.com
Thanks David and Anthony:

1- I will reorder validtors for final example, most common ones first.

2- Good suggestion:
    I'll try to write  a web2pyslices, I like that site a lot as it can offer
    more complete solutions than these discussions often do.

2- A validator?

Question:
Would it be worth considering an actual validator for username and user-like fields.



IS_LIKE_GOOGLE
IS_LIKE_GOOGLE validator enforces a username policy similar to Google Accounts (as of 2012),
min 6 - 30 characters, only letters (a-z) and numbers (0-9), and periods, can't start or end with a period,
can't have consecutive periods, and of course can't exist in database.

Note: if an optional
IS_LIKE_GOOGLE(db, 'table.fieldname') - then any user created user type field would be validated,


Cheers,
Today,
I'm going to work on "bad words" in username feature, this should complete my example.

Rob

Rob_McC

unread,
Aug 17, 2012, 10:18:57 AM8/17/12
to web...@googlegroups.com
Still trying to make my username example, google-like.

Google prohibits a very small set of bad words.

I have a BADWORDS working outside of the validation, but when I insert in validation, I get error.

Any help would be appreciated, I bet it is one little thing :)

I get error

<type 'exceptions.NameError'> name 'BADWORDS' is not defined

- Thanks!
Rob


Summary:
 
badlist
= ['frig', 'asdf', 'poop']
BADWORDS
= re.compile(r'|'.join(badlist))


auth
.settings.table_user.username.requires = [IS_LENGTH(30,6,'Please use between 6 and 30 characters.'),
    IS_MATCH
('^[a-z0-9.]*$', error_message='Please use only letters (a-z) and numbers (0-9), and periods.'),
    IS_NOT_EMPTY
(error_message='You can\'t leave this empty. '),
    IS_EXPR
("value[0]<>'.'", error_message='The FIRST character of your username should be a letter (a-z) or number.'),
    IS_EXPR
("value[-1]<>'.'", error_message='The LAST character of your username should be a letter (a-z) or number.'),
    IS_EXPR
("str(value).find('..')==-1",error_message='A fan of punctuation! Alas, usernames can\'t have consecutive periods.'),

    IS_EXPR
("BADWORDS.search(value)", error_message='Bad word'),  
    IS_NOT_IN_DB
(db, auth.settings.table_user.username, 'Someone already has that username. ')
   
]




Complete code, just insert in  a new app. like Welcome..



## -  START CUSTOMIZATION  - - - - - - - - - - - - - - - - - - - - - - ##

# | Summary:
# |  Modify web2py to allow user registrations similar to
# |  Google registrations.
# |  i.e.
# |   - lower case only [a-z]
# |   - numbers [0-9] and period are OK
# |   - can't end in a period
# |   - can't start with a period
# |   - can't have consecutive periods
# |   - min 8 letter password
# |   - username can't be changed once registered
# |
# |  Note: Messages are nearly same as Google displays


## create all tables needed by auth if not custom tables
#  use usernames rather than email addresses to register
auth
.define_tables(username=True)

# allow username only on registration, but can only
# be viewed (readable) in Profile
# user can't change username once registered.

#if auth.is_logged_in():
#    db.auth_user.username.writable = False
#    db.auth_user.username.readable = True
#add a comments to exlain policy


db
.auth_user.username.comment='NO BAD WORDS..min. 6 letters (a-z), you may use numbers, and periods.'


# apply nearly identical username policy and message that Google Accounts use.
# this OVERWRITES web2py's default username validation
# reference and thanks to web2py community for help:
#   https://groups.google.com/forum/?fromgroups#!starred/web2py/HBODB00HMfU[1-25]


# this import is required in web2py
import base64, re

#let's assume:
#  username can't contain spaces, just a-z and periods

# 'frig' is a very bad word, and "poop" too :)
# 'sadf' is a racial slur

# so even if a person's name as frig, or asdf in it
# we will not let them use that.

# asdf    - is a bad username
# asdfyou - is a bad username
# youasdf - is a bad username
 
badlist
= ['frig', 'asdf', 'poop']
BADWORDS
= re.compile(r'|'.join(badlist))


auth
.settings.table_user.username.requires = [IS_LENGTH(30,6,'Please use between 6 and 30 characters.'),
    IS_MATCH
('^[a-z0-9.]*$', error_message='Please use only letters (a-z) and numbers (0-9), and periods.'),
    IS_NOT_EMPTY
(error_message='You can\'t leave this empty. '),
    IS_EXPR
("value[0]<>'.'", error_message='The FIRST character of your username should be a letter (a-z) or number.'),
    IS_EXPR
("value[-1]<>'.'", error_message='The LAST character of your username should be a letter (a-z) or number.'),
    IS_EXPR
("str(value).find('..')==-1",error_message='A fan of punctuation! Alas, usernames can\'t have consecutive periods.'),

    IS_EXPR
("BADWORDS.search(value)", error_message='Bad word'),  
    IS_NOT_IN_DB
(db, auth.settings.table_user.username, 'Someone already has that username. ')
   
]

Jonathan Lundell

unread,
Aug 17, 2012, 10:29:30 AM8/17/12
to web...@googlegroups.com
On 17 Aug 2012, at 7:18 AM, Rob_McC <mrmcc...@gmail.com> wrote:
Still trying to make my username example, google-like.

Google prohibits a very small set of bad words.

I have a BADWORDS working outside of the validation, but when I insert in validation, I get error.

Any help would be appreciated, I bet it is one little thing :)

IS_EXPR is an imported function, and runs in its own namespace. It doesn't see your globals.

Rather than use IS_EXPR, you'd be better off writing a custom validator. An added benefit of doing it that way is that you can include the overhead of BADWORDS in the validator's execution code (not its init), so it only gets executed when the validator is actually used.

Validators are easy to write. Choose a simple one from gluon/validators.py to use as a template. I suggest IS_EQUAL_TO.

--
 
 
 


Anthony

unread,
Aug 17, 2012, 1:19:22 PM8/17/12
to web...@googlegroups.com
IS_EXPR executes the expression in an environment that only contains the value being validated (but not any other globals defined in your model). For this to work, you would have to include all of the necessary code in the expression (i.e., the definition of badlist and BADWORDS). A better option is probably a custom validator.

Anthony

Anthony

unread,
Aug 17, 2012, 1:23:05 PM8/17/12
to web...@googlegroups.com
Question:
Would it be worth considering an actual validator for username and user-like fields.



IS_LIKE_GOOGLE
IS_LIKE_GOOGLE validator enforces a username policy similar to Google Accounts (as of 2012),
min 6 - 30 characters, only letters (a-z) and numbers (0-9), and periods, can't start or end with a period,
can't have consecutive periods, and of course can't exist in database.

Note: if an optional
IS_LIKE_GOOGLE(db, 'table.fieldname') - then any user created user type field would be validated,

Not a bad idea, but I would make it more generic and configurable, much like the IS_STRONG validator for passwords. Maybe something like IS_USERNAME, and have it take several arguments to configure the various validation options (including ability to provide a custom bad words list, and custom error messages for each potential problem).

Anthony

Anthony

unread,
Aug 19, 2012, 11:23:23 AM8/19/12
to web...@googlegroups.com
FYI, in trunk you can now do:

badlist = ['frig', 'asdf', 'poop']
BADWORDS
= re.compile(r'|'.join(badlist))
...
    IS_EXPR
("BADWORDS.search(value)", error_message='Bad word',
        environment
=dict(BADWORDS=BADWORDS))

You can pass an environment dictionary to IS_EXPR -- the expression will be executed in an environment that includes the objects in the dictionary, so you can pass any global objects needed by the expression in that dictionary.

Anthony
Reply all
Reply to author
Forward
0 new messages