Add a feature flag for disabling all emails

This commit is contained in:
Joseph Schorr 2014-09-22 19:11:48 -04:00
parent dc685b2387
commit f3b03ebc34
10 changed files with 71 additions and 32 deletions

View file

@ -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'}],

View file

@ -93,7 +93,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)
@ -102,11 +102,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
@ -343,12 +340,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.
@ -833,9 +829,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()

View file

@ -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/<repopath:repository>/authorizedemail/<email>')
class RepositoryAuthorizedEmail(RepositoryParamResource):
""" Resource for checking and authorizing e-mail addresses to receive repo notifications. """

View file

@ -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/<orgname>/team/<teamname>/invite/<email>')
@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/<code>')
@internal_only
@show_if(features.MAILING)
class TeamMemberInvite(ApiResource):
""" Resource for managing invites to jon a team. """
@require_user_admin

View file

@ -168,6 +168,8 @@ 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'])
if features.MAILING:
send_password_changed(user.username, user.email)
if 'invoice_email' in user_data:
@ -180,10 +182,13 @@ class User(ApiResource):
# Email already used.
raise request_error(message='E-mail address already used')
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. """

View file

@ -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

View file

@ -28,7 +28,7 @@
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel panel-default" quay-show="Features.MAILING">
<div class="panel-heading">
<h6 class="panel-title accordion-title">
<a class="accordion-toggle" data-toggle="collapse" data-parent="#accordion" data-target="#collapseForgot">
@ -37,7 +37,8 @@
</h6>
</div>
<div id="collapseForgot" class="panel-collapse collapse out">
<div class="panel-body">
<div class="quay-spinner" ng-show="sendingRecovery"></div>
<div class="panel-body" ng-show="!sendingRecovery">
<form class="form-signin" ng-submit="sendRecovery();">
<input type="text" class="form-control input-lg" placeholder="Email" ng-model="recovery.email">
<button class="btn btn-lg btn-primary btn-block" type="submit">Send Recovery Email</button>

View file

@ -1273,7 +1273,7 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
return userService;
}]);
$provide.factory('ExternalNotificationData', ['Config', function(Config) {
$provide.factory('ExternalNotificationData', ['Config', 'Features', function(Config, Features) {
var externalNotificationData = {};
var events = [
@ -1327,7 +1327,8 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
'type': 'email',
'title': 'E-mail address'
}
]
],
'enabled': Features.MAILING
},
{
'id': 'webhook',
@ -1407,7 +1408,13 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
};
externalNotificationData.getSupportedMethods = function() {
return methods;
var filtered = [];
for (var i = 0; i < methods.length; ++i) {
if (methods[i].enabled !== false) {
filtered.push(methods[i]);
}
}
return filtered;
};
externalNotificationData.getEventInfo = function(event) {
@ -2598,14 +2605,18 @@ quayApp.directive('userSetup', function () {
},
controller: function($scope, $location, $timeout, ApiService, KeyService, UserService) {
$scope.sendRecovery = function() {
$scope.sendingRecovery = true;
ApiService.requestRecoveryEmail($scope.recovery).then(function() {
$scope.invalidRecovery = false;
$scope.errorMessage = '';
$scope.sent = true;
$scope.sendingRecovery = false;
}, function(result) {
$scope.invalidRecovery = true;
$scope.errorMessage = result.data;
$scope.sent = false;
$scope.sendingRecovery = false;
});
};
@ -2783,15 +2794,19 @@ quayApp.directive('signupForm', function () {
$scope.newUser['inviteCode'] = $scope.inviteCode;
}
ApiService.createNewUser($scope.newUser).then(function() {
ApiService.createNewUser($scope.newUser).then(function(resp) {
$scope.registering = false;
$scope.awaitingConfirmation = true;
$scope.awaitingConfirmation = !!resp['awaiting_verification'];
if (Config.MIXPANEL_KEY) {
mixpanel.alias($scope.newUser.username);
}
$scope.userRegistered({'username': $scope.newUser.username});
if (!$scope.awaitingConfirmation) {
document.location = '/';
}
}, function(result) {
$scope.registering = false;
UIService.showFormError('#signupButton', result);

View file

@ -122,7 +122,7 @@
</div>
</div>
<div class="panel" ng-show="!updatingUser" >
<div class="panel" ng-show="!updatingUser" quay-show="Features.MAILING">
<div class="panel-title">Change e-mail address</div>
<div class="panel-body">

View file

@ -34,6 +34,7 @@ class TestConfig(DefaultConfig):
FEATURE_SUPER_USERS = True
FEATURE_BILLING = True
FEATURE_MAILING = True
SUPER_USERS = ['devtable']
LICENSE_USER_LIMIT = 500