Allocate guest user IDs numericcally
The current random IDs are ugly and confusing when presented in UIs. This makes them prettier and easier to read. Also, disable non-automated registration of numeric IDs so that we don't need to worry so much about people carving out our automated address space and us needing to keep retrying ID registration.pull/550/head
parent
489f92e0e5
commit
79a1c0574b
|
@ -21,7 +21,6 @@ from synapse.api.errors import (
|
||||||
AuthError, Codes, SynapseError, RegistrationError, InvalidCaptchaError
|
AuthError, Codes, SynapseError, RegistrationError, InvalidCaptchaError
|
||||||
)
|
)
|
||||||
from ._base import BaseHandler
|
from ._base import BaseHandler
|
||||||
import synapse.util.stringutils as stringutils
|
|
||||||
from synapse.util.async import run_on_reactor
|
from synapse.util.async import run_on_reactor
|
||||||
from synapse.http.client import CaptchaServerHttpClient
|
from synapse.http.client import CaptchaServerHttpClient
|
||||||
|
|
||||||
|
@ -45,6 +44,8 @@ class RegistrationHandler(BaseHandler):
|
||||||
self.distributor.declare("registered_user")
|
self.distributor.declare("registered_user")
|
||||||
self.captcha_client = CaptchaServerHttpClient(hs)
|
self.captcha_client = CaptchaServerHttpClient(hs)
|
||||||
|
|
||||||
|
self._next_generated_user_id = None
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def check_username(self, localpart, guest_access_token=None):
|
def check_username(self, localpart, guest_access_token=None):
|
||||||
yield run_on_reactor()
|
yield run_on_reactor()
|
||||||
|
@ -91,7 +92,7 @@ class RegistrationHandler(BaseHandler):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
localpart : The local part of the user ID to register. If None,
|
localpart : The local part of the user ID to register. If None,
|
||||||
one will be randomly generated.
|
one will be generated.
|
||||||
password (str) : The password to assign to this user so they can
|
password (str) : The password to assign to this user so they can
|
||||||
login again. This can be None which means they cannot login again
|
login again. This can be None which means they cannot login again
|
||||||
via a password (e.g. the user is an application service user).
|
via a password (e.g. the user is an application service user).
|
||||||
|
@ -108,6 +109,18 @@ class RegistrationHandler(BaseHandler):
|
||||||
if localpart:
|
if localpart:
|
||||||
yield self.check_username(localpart, guest_access_token=guest_access_token)
|
yield self.check_username(localpart, guest_access_token=guest_access_token)
|
||||||
|
|
||||||
|
was_guest = guest_access_token is not None
|
||||||
|
|
||||||
|
if not was_guest:
|
||||||
|
try:
|
||||||
|
int(localpart)
|
||||||
|
raise RegistrationError(
|
||||||
|
400,
|
||||||
|
"Numeric user IDs are reserved for guest users."
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
user = UserID(localpart, self.hs.hostname)
|
user = UserID(localpart, self.hs.hostname)
|
||||||
user_id = user.to_string()
|
user_id = user.to_string()
|
||||||
|
|
||||||
|
@ -118,40 +131,36 @@ class RegistrationHandler(BaseHandler):
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
token=token,
|
token=token,
|
||||||
password_hash=password_hash,
|
password_hash=password_hash,
|
||||||
was_guest=guest_access_token is not None,
|
was_guest=was_guest,
|
||||||
make_guest=make_guest,
|
make_guest=make_guest,
|
||||||
)
|
)
|
||||||
|
|
||||||
yield registered_user(self.distributor, user)
|
yield registered_user(self.distributor, user)
|
||||||
else:
|
else:
|
||||||
# autogen a random user ID
|
# autogen a sequential user ID
|
||||||
attempts = 0
|
attempts = 0
|
||||||
user_id = None
|
|
||||||
token = None
|
token = None
|
||||||
while not user_id:
|
user = None
|
||||||
try:
|
while not user:
|
||||||
localpart = self._generate_user_id()
|
localpart = yield self._generate_user_id(attempts > 0)
|
||||||
user = UserID(localpart, self.hs.hostname)
|
user = UserID(localpart, self.hs.hostname)
|
||||||
user_id = user.to_string()
|
user_id = user.to_string()
|
||||||
yield self.check_user_id_is_valid(user_id)
|
yield self.check_user_id_is_valid(user_id)
|
||||||
if generate_token:
|
if generate_token:
|
||||||
token = self.auth_handler().generate_access_token(user_id)
|
token = self.auth_handler().generate_access_token(user_id)
|
||||||
|
try:
|
||||||
yield self.store.register(
|
yield self.store.register(
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
token=token,
|
token=token,
|
||||||
password_hash=password_hash,
|
password_hash=password_hash,
|
||||||
make_guest=make_guest
|
make_guest=make_guest
|
||||||
)
|
)
|
||||||
|
|
||||||
yield registered_user(self.distributor, user)
|
|
||||||
except SynapseError:
|
except SynapseError:
|
||||||
# if user id is taken, just generate another
|
# if user id is taken, just generate another
|
||||||
user_id = None
|
user_id = None
|
||||||
token = None
|
token = None
|
||||||
attempts += 1
|
attempts += 1
|
||||||
if attempts > 5:
|
yield registered_user(self.distributor, user)
|
||||||
raise RegistrationError(
|
|
||||||
500, "Cannot generate user ID.")
|
|
||||||
|
|
||||||
# We used to generate default identicons here, but nowadays
|
# We used to generate default identicons here, but nowadays
|
||||||
# we want clients to generate their own as part of their branding
|
# we want clients to generate their own as part of their branding
|
||||||
|
@ -283,8 +292,16 @@ class RegistrationHandler(BaseHandler):
|
||||||
errcode=Codes.EXCLUSIVE
|
errcode=Codes.EXCLUSIVE
|
||||||
)
|
)
|
||||||
|
|
||||||
def _generate_user_id(self):
|
@defer.inlineCallbacks
|
||||||
return "-" + stringutils.random_string(18)
|
def _generate_user_id(self, reseed=False):
|
||||||
|
if reseed or self._next_generated_user_id is None:
|
||||||
|
self._next_generated_user_id = (
|
||||||
|
yield self.store.find_next_generated_user_id_localpart()
|
||||||
|
)
|
||||||
|
|
||||||
|
id = self._next_generated_user_id
|
||||||
|
self._next_generated_user_id += 1
|
||||||
|
defer.returnValue(str(id))
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _validate_captcha(self, ip_addr, private_key, challenge, response):
|
def _validate_captcha(self, ip_addr, private_key, challenge, response):
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
from synapse.api.errors import StoreError, Codes
|
from synapse.api.errors import StoreError, Codes
|
||||||
|
@ -351,3 +353,37 @@ class RegistrationStore(SQLBaseStore):
|
||||||
|
|
||||||
ret = yield self.runInteraction("count_users", _count_users)
|
ret = yield self.runInteraction("count_users", _count_users)
|
||||||
defer.returnValue(ret)
|
defer.returnValue(ret)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def find_next_generated_user_id_localpart(self):
|
||||||
|
"""
|
||||||
|
Gets the localpart of the next generated user ID.
|
||||||
|
|
||||||
|
Generated user IDs are integers, and we aim for them to be as small as
|
||||||
|
we can. Unfortunately, it's possible some of them are already taken by
|
||||||
|
existing users, and there may be gaps in the already taken range. This
|
||||||
|
function returns the start of the first allocatable gap. This is to
|
||||||
|
avoid the case of ID 10000000 being pre-allocated, so us wasting the
|
||||||
|
first (and shortest) many generated user IDs.
|
||||||
|
"""
|
||||||
|
def _find_next_generated_user_id(txn):
|
||||||
|
txn.execute("SELECT name FROM users")
|
||||||
|
rows = self.cursor_to_dict(txn)
|
||||||
|
|
||||||
|
regex = re.compile("^@(\d+):")
|
||||||
|
|
||||||
|
found = set()
|
||||||
|
|
||||||
|
for r in rows:
|
||||||
|
user_id = r["name"]
|
||||||
|
match = regex.search(user_id)
|
||||||
|
if match:
|
||||||
|
found.add(int(match.group(1)))
|
||||||
|
for i in xrange(len(found) + 1):
|
||||||
|
if i not in found:
|
||||||
|
return i
|
||||||
|
|
||||||
|
defer.returnValue((yield self.runInteraction(
|
||||||
|
"find_next_generated_user_id",
|
||||||
|
_find_next_generated_user_id
|
||||||
|
)))
|
||||||
|
|
Loading…
Reference in New Issue