Skip to content

Commit 5d816dd

Browse files
committed
- issue2550920 - Optionally detect duplicate username at registration.
Added config option to allow detection of duplicate username when the user tries to register. Previously user was rejected when dupliate name found at confirmation step. Optional as it can make username guessing easier. Testing is in place for this. Also attempted to make the unfriendly error message: 'node with key "username" exists' into a translatable friendly error: "Username 'username' already exists." This is missing any test. It is also fragile as I capture the ValueError exception and see that the exception matches: 'node with key "username" exists' If it does reassert the friendly message. Otherwise just re-raise existing exception. If the "node with key..." message is translated the friendly override will not trigger.
1 parent 361f1e2 commit 5d816dd

File tree

4 files changed

+72
-4
lines changed

4 files changed

+72
-4
lines changed

roundup/cgi/actions.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1094,6 +1094,18 @@ def handle(self):
10941094

10951095
# generate the one-time-key and store the props for later
10961096
user_props = props[('user', None)]
1097+
# check that admin has requested username available check
1098+
check_user = self.db.config['WEB_REGISTRATION_PREVALIDATE_USERNAME']
1099+
if check_user:
1100+
try:
1101+
user_found = self.db.user.lookup(user_props['username'])
1102+
# if user is found reject the request.
1103+
raise Reject(
1104+
_("Username '%s' is already used.")%user_props['username'])
1105+
except KeyError:
1106+
# user not found this is what we want.
1107+
pass
1108+
10971109
for propname, proptype in self.db.user.getprops().items():
10981110
value = user_props.get(propname, None)
10991111
if value is None:

roundup/configuration.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -803,6 +803,12 @@ def str2value(self, value):
803803
"registration form. This limits the rate at which bots\n"
804804
"can attempt to sign up. Limit can be disabled by setting\n"
805805
"the value to 0."),
806+
(BooleanOption, 'registration_prevalidate_username', "no",
807+
"When registering a user, check that the username\n"
808+
"is available before sending confirmation email.\n"
809+
"Usually a username conflict is detected when\n"
810+
"confirming the registration. Disabled by default as\n"
811+
"it can be used for guessing existing usernames.\n" ),
806812
(SameSiteSettingOption, 'samesite_cookie_setting', "Lax",
807813
"""Set the mode of the SameSite cookie option for
808814
the session cookie. Choices are 'Lax' or

roundup/roundupdb.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,24 @@ def confirm_registration(self, otk):
119119
cl = self.user
120120

121121
props['roles'] = self.config.NEW_WEB_USER_ROLES
122-
userid = cl.create(**props)
123-
# clear the props from the otk database
122+
try:
123+
# ASSUME:: ValueError raised during create due to key value
124+
# conflict. I an use message in exception to determine
125+
# when I should intercept the exception with a more
126+
# friendly error message. If i18n is used to translate
127+
# original exception message this will fail and translated
128+
# text (probably unfriendly) will be used.
129+
userid = cl.create(**props)
130+
except ValueError as e:
131+
username = props['username']
132+
# Try to make error message less cryptic to the user.
133+
if str(e) == 'node with key "%s" exists' % username:
134+
raise ValueError(
135+
_("Username '%s' already exists."%username))
136+
else:
137+
raise
138+
139+
# clear the props from the otk database
124140
self.getOTKManager().destroy(otk)
125141
# commit cl.create (and otk changes)
126142
self.commit()

test/test_cgi.py

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
from roundup.cgi import client, actions, exceptions
1717
from roundup.cgi.exceptions import FormError, NotFound, Redirect
18-
from roundup.exceptions import UsageError
18+
from roundup.exceptions import UsageError, Reject
1919
from roundup.cgi.templating import HTMLItem, HTMLRequest, NoTemplate
2020
from roundup.cgi.templating import HTMLProperty, _HTMLItem, anti_csrf_nonce
2121
from roundup.cgi.form_parser import FormParser
@@ -1542,7 +1542,7 @@ def testEditCSVRestore(self):
15421542
k = self.db.keyword.getnode('2')
15431543
self.assertEqual(k.name, 'newkey2')
15441544

1545-
def testRegisterAction(self):
1545+
def testRegisterActionDelay(self):
15461546
from roundup.cgi.timestamp import pack_timestamp
15471547

15481548
# need to set SENDMAILDEBUG to prevent
@@ -1608,6 +1608,40 @@ def testRegisterAction(self):
16081608
if os.path.exists(SENDMAILDEBUG):
16091609
os.remove(SENDMAILDEBUG)
16101610

1611+
def testRegisterActionUnusedUserCheck(self):
1612+
# need to set SENDMAILDEBUG to prevent
1613+
# downstream issue when email is sent on successful
1614+
# issue creation. Also delete the file afterwards
1615+
# just tomake sure that someother test looking for
1616+
# SENDMAILDEBUG won't trip over ours.
1617+
if 'SENDMAILDEBUG' not in os.environ:
1618+
os.environ['SENDMAILDEBUG'] = 'mail-test1.log'
1619+
SENDMAILDEBUG = os.environ['SENDMAILDEBUG']
1620+
1621+
nodeid = self.db.user.create(username='iexist',
1622+
password=password.Password('foo'))
1623+
1624+
# enable check and remove delay time
1625+
self.db.config.WEB_REGISTRATION_PREVALIDATE_USERNAME = 1
1626+
self.db.config.WEB_REGISTRATION_DELAY = 0
1627+
1628+
# Make a request with existing user. Use iexist.
1629+
# do not need opaqueregister as we have disabled the delay check
1630+
cl = self._make_client({'username':'iexist', 'password':'secret',
1631+
'@confirm@password':'secret', 'address':'[email protected]'},
1632+
nodeid=None, userid='2')
1633+
with self.assertRaises(Reject) as cm:
1634+
actions.RegisterAction(cl).handle()
1635+
self.assertEqual(cm.exception.args,
1636+
("Username 'iexist' is already used.",))
1637+
1638+
cl = self._make_client({'username':'[email protected]',
1639+
'password':'secret',
1640+
'@confirm@password':'secret', 'address':'[email protected]'},
1641+
nodeid=None, userid='2')
1642+
self.assertRaises(Redirect, actions.RegisterAction(cl).handle)
1643+
1644+
16111645
def testserve_static_files(self):
16121646
# make a client instance
16131647
cl = self._make_client({})

0 commit comments

Comments
 (0)