diff --git a/config.py b/config.py index f810007e8..df5a90a7b 100644 --- a/config.py +++ b/config.py @@ -162,6 +162,9 @@ class DefaultConfig(object): # Feature Flag: Dockerfile build support. FEATURE_BUILD_SUPPORT = True + # Feature Flag: Whether emails are enabled. + FEATURE_MAILING = True + DISTRIBUTED_STORAGE_CONFIG = { 'local_eu': ['LocalStorage', {'storage_path': 'test/data/registry/eu'}], 'local_us': ['LocalStorage', {'storage_path': 'test/data/registry/us'}], diff --git a/data/model/legacy.py b/data/model/legacy.py index 8a5a3b9fa..a9301eaa4 100644 --- a/data/model/legacy.py +++ b/data/model/legacy.py @@ -103,7 +103,7 @@ def hash_password(password, salt=None): def is_create_user_allowed(): return True -def create_user(username, password, email): +def create_user(username, password, email, auto_verify=False): """ Creates a regular user, if allowed. """ if not validate_password(password): raise InvalidPasswordException(INVALID_PASSWORD_MESSAGE) @@ -112,11 +112,8 @@ def create_user(username, password, email): raise TooManyUsersException() created = _create_user(username, email) - - # Store the password hash - pw_hash = hash_password(password) - created.password_hash = pw_hash - + created.password_hash = hash_password(password) + created.verified = auto_verify created.save() return created @@ -353,12 +350,11 @@ def remove_team(org_name, team_name, removed_by_username): team.delete_instance(recursive=True, delete_nullable=True) -def add_or_invite_to_team(inviter, team, user=None, email=None): +def add_or_invite_to_team(inviter, team, user=None, email=None, requires_invite=True): # If the user is a member of the organization, then we simply add the # user directly to the team. Otherwise, an invite is created for the user/email. # We return None if the user was directly added and the invite object if the user was invited. - requires_invite = True - if user: + if user and requires_invite: orgname = team.organization.username # If the user is part of the organization (or a robot), then no invite is required. @@ -852,9 +848,9 @@ def change_invoice_email(user, invoice_email): user.save() -def update_email(user, new_email): +def update_email(user, new_email, auto_verify=False): user.email = new_email - user.verified = False + user.verified = auto_verify user.save() diff --git a/endpoints/api/repoemail.py b/endpoints/api/repoemail.py index 6585bbc49..4dbc845a7 100644 --- a/endpoints/api/repoemail.py +++ b/endpoints/api/repoemail.py @@ -3,7 +3,8 @@ import logging from flask import request, abort from endpoints.api import (resource, nickname, require_repo_admin, RepositoryParamResource, - log_action, validate_json_request, NotFound, internal_only) + log_action, validate_json_request, NotFound, internal_only, + show_if) from app import tf from data import model @@ -25,6 +26,7 @@ def record_view(record): @internal_only +@show_if(features.MAILING) @resource('/v1/repository//authorizedemail/') class RepositoryAuthorizedEmail(RepositoryParamResource): """ Resource for checking and authorizing e-mail addresses to receive repo notifications. """ diff --git a/endpoints/api/team.py b/endpoints/api/team.py index a1c687af9..a448cefc9 100644 --- a/endpoints/api/team.py +++ b/endpoints/api/team.py @@ -2,7 +2,7 @@ from flask import request from endpoints.api import (resource, nickname, ApiResource, validate_json_request, request_error, log_action, Unauthorized, NotFound, internal_only, require_scope, - query_param, truthy_bool, parse_args, require_user_admin) + query_param, truthy_bool, parse_args, require_user_admin, show_if) from auth.permissions import AdministerOrganizationPermission, ViewTeamPermission from auth.auth_context import get_authenticated_user from auth import scopes @@ -10,6 +10,8 @@ from data import model from util.useremails import send_org_invite_email from util.gravatar import compute_hash +import features + def try_accept_invite(code, user): (team, inviter) = model.confirm_team_invite(code, user) @@ -26,7 +28,8 @@ def try_accept_invite(code, user): def handle_addinvite_team(inviter, team, user=None, email=None): - invite = model.add_or_invite_to_team(inviter, team, user, email) + invite = model.add_or_invite_to_team(inviter, team, user, email, + requires_invite = features.MAILING) if not invite: # User was added to the team directly. return @@ -275,6 +278,7 @@ class TeamMember(ApiResource): @resource('/v1/organization//team//invite/') +@show_if(features.MAILING) class InviteTeamMember(ApiResource): """ Resource for inviting a team member via email address. """ @require_scope(scopes.ORG_ADMIN) @@ -331,6 +335,7 @@ class InviteTeamMember(ApiResource): @resource('/v1/teaminvite/') @internal_only +@show_if(features.MAILING) class TeamMemberInvite(ApiResource): """ Resource for managing invites to jon a team. """ @require_user_admin diff --git a/endpoints/api/user.py b/endpoints/api/user.py index f81e7ffba..7747addcc 100644 --- a/endpoints/api/user.py +++ b/endpoints/api/user.py @@ -168,7 +168,9 @@ class User(ApiResource): logger.debug('Changing password for user: %s', user.username) log_action('account_change_password', user.username) model.change_password(user, user_data['password']) - send_password_changed(user.username, user.email) + + if features.MAILING: + send_password_changed(user.username, user.email) if 'invoice_email' in user_data: logger.debug('Changing invoice_email for user: %s', user.username) @@ -180,10 +182,13 @@ class User(ApiResource): # Email already used. raise request_error(message='E-mail address already used') - logger.debug('Sending email to change email address for user: %s', - user.username) - code = model.create_confirm_email_code(user, new_email=new_email) - send_change_email(user.username, user_data['email'], code.code) + if features.MAILING: + logger.debug('Sending email to change email address for user: %s', + user.username) + code = model.create_confirm_email_code(user, new_email=new_email) + send_change_email(user.username, user_data['email'], code.code) + else: + model.update_email(user, new_email, auto_verify=not features.MAILING) except model.InvalidPasswordException, ex: raise request_error(exception=ex) @@ -207,9 +212,7 @@ class User(ApiResource): try: new_user = model.create_user(user_data['username'], user_data['password'], - user_data['email']) - code = model.create_confirm_email_code(new_user) - send_confirmation_email(new_user.username, new_user.email, code.code) + user_data['email'], auto_verify=not features.MAILING) # Handle any invite codes. parsed_invite = parse_single_urn(invite_code) @@ -221,7 +224,17 @@ class User(ApiResource): except model.DataModelException: pass - return 'Created', 201 + + if features.MAILING: + code = model.create_confirm_email_code(new_user) + send_confirmation_email(new_user.username, new_user.email, code.code) + return { + 'awaiting_verification': True + } + else: + common_login(new_user) + return user_view(new_user) + except model.TooManyUsersException as ex: raise license_error(exception=ex) except model.DataModelException as ex: @@ -441,6 +454,7 @@ class DetachExternal(ApiResource): @resource("/v1/recovery") +@show_if(features.MAILING) @internal_only class Recovery(ApiResource): """ Resource for requesting a password recovery email. """ diff --git a/endpoints/web.py b/endpoints/web.py index c0da52bdb..94c5583cd 100644 --- a/endpoints/web.py +++ b/endpoints/web.py @@ -223,6 +223,7 @@ def receipt(): @web.route('/authrepoemail', methods=['GET']) +@route_show_if(features.MAILING) def confirm_repo_email(): code = request.values['code'] record = None @@ -243,6 +244,7 @@ def confirm_repo_email(): @web.route('/confirm', methods=['GET']) +@route_show_if(features.MAILING) def confirm_email(): code = request.values['code'] user = None diff --git a/static/directives/team-view-add.html b/static/directives/team-view-add.html index 841e07a42..9129c7031 100644 --- a/static/directives/team-view-add.html +++ b/static/directives/team-view-add.html @@ -7,7 +7,7 @@ auto-clear="true" allowed-entities="['user', 'robot']" pull-right="true" - allow-emails="true" + allow-emails="allowEmail" email-message="Press enter to invite the entered e-mail address to this team" ng-show="!addingMember">
diff --git a/static/directives/user-setup.html b/static/directives/user-setup.html index 99687476c..a3ac4e3b8 100644 --- a/static/directives/user-setup.html +++ b/static/directives/user-setup.html @@ -28,7 +28,7 @@ -
+
-
+
+
-
+
Change e-mail address
diff --git a/test/testconfig.py b/test/testconfig.py index 35c96a803..46288de7e 100644 --- a/test/testconfig.py +++ b/test/testconfig.py @@ -34,6 +34,7 @@ class TestConfig(DefaultConfig): FEATURE_SUPER_USERS = True FEATURE_BILLING = True + FEATURE_MAILING = True SUPER_USERS = ['devtable'] LICENSE_USER_LIMIT = 500