Add support for temp usernames and an interstitial to confirm username

When a user now logs in for the first time for any external auth (LDAP, JWT, Keystone, Github, Google, Dex), they will be presented with a confirmation screen that affords them the opportunity to change their Quay-assigned username.

Addresses most of the user issues around #74
This commit is contained in:
Joseph Schorr 2016-09-08 18:43:50 -04:00
parent 840ea4e768
commit 1e3b354201
18 changed files with 388 additions and 24 deletions

View file

@ -66,7 +66,7 @@ def handle_invite_code(invite_code, user):
return True
def user_view(user):
def user_view(user, previous_username=None):
def org_view(o, user_admin=True):
admin_org = AdministerOrganizationPermission(o.username)
org_response = {
@ -105,7 +105,7 @@ def user_view(user):
'avatar': avatar.get_data_for_user(user),
}
user_admin = UserAdminPermission(user.username)
user_admin = UserAdminPermission(previous_username if previous_username else user.username)
if user_admin.can():
user_response.update({
'can_create_repo': True,
@ -117,6 +117,7 @@ def user_view(user):
'invoice_email_address': user.invoice_email_address,
'preferred_namespace': not (user.stripe_id is None),
'tag_expiration': user.removed_tag_expiration_s,
'prompts': model.user.get_user_prompts(user),
})
analytics_metadata = user_analytics.get_user_analytics_metadata(user)
@ -217,7 +218,7 @@ class User(ApiResource):
'UserView': {
'type': 'object',
'description': 'Describes a user',
'required': ['verified', 'anonymous', 'avatar'],
'required': ['anonymous', 'avatar'],
'properties': {
'verified': {
'type': 'boolean',
@ -283,6 +284,7 @@ class User(ApiResource):
""" Update a users details such as password or email. """
user = get_authenticated_user()
user_data = request.get_json()
previous_username = None
try:
if 'password' in user_data:
@ -324,19 +326,29 @@ class User(ApiResource):
else:
model.user.update_email(user, new_email, auto_verify=not features.MAILING)
if ('username' in user_data and user_data['username'] != user.username and
features.USER_RENAME):
new_username = user_data['username']
if model.user.get_user_or_org(new_username) is not None:
# Username already used
raise request_error(message='Username is already in use')
# Check for username rename. A username can be renamed if the feature is enabled OR the user
# currently has a confirm_username prompt.
if 'username' in user_data:
confirm_username = model.user.has_user_prompt(user, 'confirm_username')
new_username = user_data.get('username')
previous_username = user.username
model.user.change_username(user.id, new_username)
rename_allowed = features.USER_RENAME or confirm_username
username_changing = new_username and new_username != previous_username
if rename_allowed and username_changing:
if model.user.get_user_or_org(new_username) is not None:
# Username already used.
raise request_error(message='Username is already in use')
user = model.user.change_username(user.id, new_username)
elif confirm_username:
model.user.remove_user_prompt(user, 'confirm_username')
except model.user.InvalidPasswordException, ex:
raise request_error(exception=ex)
return user_view(user)
return user_view(user, previous_username=previous_username)
@show_if(features.USER_CREATION)
@show_if(features.DIRECT_LOGIN)

View file

@ -37,11 +37,11 @@ def get_user(service, token):
'access_token': token,
'alt': 'json',
}
get_user = client.get(service.user_endpoint(), params=token_param)
if get_user.status_code != requests.codes.ok:
got_user = client.get(service.user_endpoint(), params=token_param)
if got_user.status_code != requests.codes.ok:
return {}
return get_user.json()
return got_user.json()
def conduct_oauth_login(service, user_id, username, email, metadata={}):
@ -65,7 +65,7 @@ def conduct_oauth_login(service, user_id, username, email, metadata={}):
to_login = model.user.create_federated_user(new_username, email, service_name.lower(),
user_id, set_password_notification=True,
metadata=metadata)
metadata=metadata, confirm_username=True)
# Success, tell analytics
analytics.track(to_login.username, 'register', {'service': service_name.lower()})
@ -75,7 +75,7 @@ def conduct_oauth_login(service, user_id, username, email, metadata={}):
logger.debug('Aliasing with state: %s', state)
analytics.alias(to_login.username, state)
except model.InvalidEmailAddressException as ieex:
except model.InvalidEmailAddressException:
message = "The e-mail address %s is already associated " % (email, )
message = message + "with an existing %s account." % (app.config['REGISTRY_TITLE_SHORT'], )
message = message + "\nPlease log in with your username and password and "
@ -87,7 +87,10 @@ def conduct_oauth_login(service, user_id, username, email, metadata={}):
return render_ologin_error(service_name, ex.message)
if common_login(to_login):
return redirect(url_for('web.index'))
if model.user.has_user_prompts(to_login):
return redirect(url_for('web.updateuser'))
else:
return redirect(url_for('web.index'))
return render_ologin_error(service_name)

View file

@ -170,6 +170,12 @@ def new():
return index('')
@web.route('/updateuser')
@no_cache
def updateuser():
return index('')
@web.route('/confirminvite')
@no_cache
def confirm_invite():