parent
16f16e8a15
commit
31a8a0fba4
7 changed files with 76 additions and 11 deletions
|
@ -72,6 +72,9 @@ def __get_org_admin_users(org):
|
||||||
.where(Team.organization == org, TeamRole.name == 'admin', User.robot == False)
|
.where(Team.organization == org, TeamRole.name == 'admin', User.robot == False)
|
||||||
.distinct())
|
.distinct())
|
||||||
|
|
||||||
|
def get_admin_users(org):
|
||||||
|
""" Returns the owner users for the organization. """
|
||||||
|
return __get_org_admin_users(org)
|
||||||
|
|
||||||
def remove_organization_member(org, user_obj):
|
def remove_organization_member(org, user_obj):
|
||||||
org_admins = [u.username for u in __get_org_admin_users(org)]
|
org_admins = [u.username for u in __get_org_admin_users(org)]
|
||||||
|
|
|
@ -402,7 +402,7 @@ def create_reset_password_email_code(email):
|
||||||
try:
|
try:
|
||||||
user = User.get(User.email == email)
|
user = User.get(User.email == email)
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
raise InvalidEmailAddressException('Email address was not found.');
|
raise InvalidEmailAddressException('Email address was not found.')
|
||||||
|
|
||||||
if user.organization:
|
if user.organization:
|
||||||
raise InvalidEmailAddressException('Organizations can not have passwords.')
|
raise InvalidEmailAddressException('Organizations can not have passwords.')
|
||||||
|
|
20
emails/orgrecovery.html
Normal file
20
emails/orgrecovery.html
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h3>Organization {{ organization }} recovery</h3>
|
||||||
|
|
||||||
|
A user at {{ app_link() }} has attempted to recover organization {{ organization | user_reference }} via this email address.
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
Please login with one of the following user accounts to access this organization:
|
||||||
|
<ul>
|
||||||
|
{% for admin_user in admin_usernames %}
|
||||||
|
<li>{{ admin_user | user_reference }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
<br>
|
||||||
|
If you did not make this request, your organization has not been compromised and the user was
|
||||||
|
not given access. Please disregard this email.
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -27,7 +27,7 @@ from auth.permissions import (AdministerOrganizationPermission, CreateRepository
|
||||||
from auth.auth_context import get_authenticated_user
|
from auth.auth_context import get_authenticated_user
|
||||||
from auth import scopes
|
from auth import scopes
|
||||||
from util.useremails import (send_confirmation_email, send_recovery_email, send_change_email,
|
from util.useremails import (send_confirmation_email, send_recovery_email, send_change_email,
|
||||||
send_password_changed)
|
send_password_changed, send_org_recovery_email)
|
||||||
from util.names import parse_single_urn
|
from util.names import parse_single_urn
|
||||||
|
|
||||||
|
|
||||||
|
@ -647,10 +647,35 @@ class Recovery(ApiResource):
|
||||||
@validate_json_request('RequestRecovery')
|
@validate_json_request('RequestRecovery')
|
||||||
def post(self):
|
def post(self):
|
||||||
""" Request a password recovery email."""
|
""" Request a password recovery email."""
|
||||||
|
def redact(value):
|
||||||
|
threshold = max((len(value) / 3) - 1, 1)
|
||||||
|
v = ''
|
||||||
|
for i in range(0, len(value)):
|
||||||
|
if i < threshold or i >= len(value) - threshold:
|
||||||
|
v = v + value[i]
|
||||||
|
else:
|
||||||
|
v = v + u'\u2022'
|
||||||
|
|
||||||
|
return v
|
||||||
|
|
||||||
email = request.get_json()['email']
|
email = request.get_json()['email']
|
||||||
|
user = model.user.find_user_by_email(email)
|
||||||
|
if not user:
|
||||||
|
raise model.InvalidEmailAddressException('Email address was not found.')
|
||||||
|
|
||||||
|
if user.organization:
|
||||||
|
send_org_recovery_email(user, model.organization.get_admin_users(user))
|
||||||
|
return {
|
||||||
|
'status': 'org',
|
||||||
|
'orgemail': email,
|
||||||
|
'orgname': redact(user.username),
|
||||||
|
}
|
||||||
|
|
||||||
code = model.user.create_reset_password_email_code(email)
|
code = model.user.create_reset_password_email_code(email)
|
||||||
send_recovery_email(email, code.code)
|
send_recovery_email(email, code.code)
|
||||||
return 'Created', 201
|
return {
|
||||||
|
'status': 'sent',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@resource('/v1/user/notifications')
|
@resource('/v1/user/notifications')
|
||||||
|
|
|
@ -39,16 +39,24 @@
|
||||||
</h6>
|
</h6>
|
||||||
</div>
|
</div>
|
||||||
<div id="collapseForgot" class="panel-collapse collapse out">
|
<div id="collapseForgot" class="panel-collapse collapse out">
|
||||||
<div class="quay-spinner" ng-show="sendingRecovery"></div>
|
<div style="text-align: center" ng-show="sendingRecovery">
|
||||||
|
<div class="cor-loader-inline"></div>
|
||||||
|
</div>
|
||||||
<div class="panel-body" ng-show="!sendingRecovery">
|
<div class="panel-body" ng-show="!sendingRecovery">
|
||||||
<form class="form-signin" ng-submit="sendRecovery();">
|
<form class="form-signin" ng-submit="sendRecovery()" ng-show="!sent">
|
||||||
<input type="text" class="form-control input-lg" placeholder="Email" ng-model="recovery.email">
|
<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>
|
<button class="btn btn-lg btn-primary btn-block" type="submit">Send Recovery Email</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="alert alert-danger" ng-show="invalidRecovery">{{errorMessage}}</div>
|
<div class="co-alert co-alert-danger" ng-show="invalidRecovery">{{errorMessage}}</div>
|
||||||
|
<div class="co-alert co-alert-info" ng-show="sent.status == 'org'">
|
||||||
<div class="alert alert-success" ng-show="sent">Account recovery email was sent.</div>
|
The e-mail address <code>{{ sent.orgemail }}</code> is assigned to organization <code>{{ sent.orgname }}</code>.
|
||||||
|
To access that organization, an admin user must be used.
|
||||||
|
<br><br>
|
||||||
|
An e-mail has been sent to
|
||||||
|
<code>{{ sent.orgemail }}</code> with the full list of admin users.
|
||||||
|
</div>
|
||||||
|
<div class="co-alert co-alert-success" ng-show="sent.status == 'sent'">Account recovery email was sent.</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -24,15 +24,15 @@ angular.module('quay').directive('userSetup', function () {
|
||||||
$scope.sendRecovery = function() {
|
$scope.sendRecovery = function() {
|
||||||
$scope.sendingRecovery = true;
|
$scope.sendingRecovery = true;
|
||||||
|
|
||||||
ApiService.requestRecoveryEmail($scope.recovery).then(function() {
|
ApiService.requestRecoveryEmail($scope.recovery).then(function(resp) {
|
||||||
$scope.invalidRecovery = false;
|
$scope.invalidRecovery = false;
|
||||||
$scope.errorMessage = '';
|
$scope.errorMessage = '';
|
||||||
$scope.sent = true;
|
$scope.sent = resp;
|
||||||
$scope.sendingRecovery = false;
|
$scope.sendingRecovery = false;
|
||||||
}, function(resp) {
|
}, function(resp) {
|
||||||
$scope.invalidRecovery = true;
|
$scope.invalidRecovery = true;
|
||||||
$scope.errorMessage = ApiService.getErrorMessage(resp, 'Cannot send recovery email');
|
$scope.errorMessage = ApiService.getErrorMessage(resp, 'Cannot send recovery email');
|
||||||
$scope.sent = false;
|
$scope.sent = null;
|
||||||
$scope.sendingRecovery = false;
|
$scope.sendingRecovery = false;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -117,6 +117,15 @@ def send_repo_authorization_email(namespace, repository, email, token):
|
||||||
'token': token
|
'token': token
|
||||||
}, action=action)
|
}, action=action)
|
||||||
|
|
||||||
|
|
||||||
|
def send_org_recovery_email(org, admin_users):
|
||||||
|
subject = 'Organization %s recovery' % (org.username)
|
||||||
|
send_email(org.email, subject, 'orgrecovery', {
|
||||||
|
'organization': org.username,
|
||||||
|
'admin_usernames': [user.username for user in admin_users],
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
def send_recovery_email(email, token):
|
def send_recovery_email(email, token):
|
||||||
action = GmailAction.view('Recover Account', 'recovery?code=' + token,
|
action = GmailAction.view('Recover Account', 'recovery?code=' + token,
|
||||||
'Recovery of an account')
|
'Recovery of an account')
|
||||||
|
|
Reference in a new issue