Reimplement admin send mail to users (#903)

* Reimplement admin send mail to users as `/api/v1/users/<user_id>/email`
* Update form and related Javascript
* Write test for controller
* Closes #897
selenium-screenshot-testing
Kevin Chung 2019-03-17 18:54:44 -07:00 committed by GitHub
parent 42fa8fe555
commit 4f7c4687d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 145 additions and 4 deletions

View File

@ -4,9 +4,12 @@ from CTFd.models import db, Users, Solves, Awards, Fails, Tracking, Unlocks, Sub
from CTFd.utils.decorators import ( from CTFd.utils.decorators import (
authed_only, authed_only,
admins_only, admins_only,
authed authed,
ratelimit
) )
from CTFd.cache import cache, clear_standings from CTFd.cache import cache, clear_standings
from CTFd.utils.config import get_mail_provider
from CTFd.utils.email import sendmail
from CTFd.utils.user import get_current_user, is_admin from CTFd.utils.user import get_current_user, is_admin
from CTFd.utils.decorators.visibility import check_account_visibility, check_score_visibility from CTFd.utils.decorators.visibility import check_account_visibility, check_score_visibility
@ -280,3 +283,44 @@ class UserAwards(Resource):
'success': True, 'success': True,
'data': response.data 'data': response.data
} }
@users_namespace.route('/<int:user_id>/email')
@users_namespace.param('user_id', "User ID")
class UserEmails(Resource):
@admins_only
@ratelimit(method="POST", limit=10, interval=60)
def post(self, user_id):
req = request.get_json()
text = req.get('text', '').strip()
user = Users.query.filter_by(id=user_id).first_or_404()
if get_mail_provider() is None:
return {
'success': False,
'errors': {
"": [
"Email settings not configured"
]
}
}, 400
if not text:
return {
'success': False,
'errors': {
"text": [
"Email text cannot be empty"
]
}
}, 400
result, response = sendmail(
addr=user.email,
text=text
)
return {
'success': result,
'data': {}
}

View File

@ -64,6 +64,46 @@ $(document).ready(function () {
}); });
}); });
$('#user-mail-form').submit(function(e){
e.preventDefault();
var params = $('#user-mail-form').serializeJSON(true);
CTFd.fetch('/api/v1/users/'+USER_ID+'/email', {
method: 'POST',
credentials: 'same-origin',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(params)
}).then(function (response) {
return response.json();
}).then(function (response) {
if (response.success) {
$('#user-mail-form > #results').append(
ezbadge({
type: 'success',
body: 'E-Mail sent successfully!'
})
);
$('#user-mail-form').find("input[type=text], textarea").val("")
} else {
$('#user-mail-form > #results').empty();
Object.keys(response.errors).forEach(function (key, index) {
$('#user-mail-form > #results').append(
ezbadge({
type: 'error',
body: response.errors[key]
})
);
var i = $('#user-mail-form').find('input[name={0}], textarea[name={0}]'.format(key));
var input = $(i);
input.addClass('input-filled-invalid');
input.removeClass('input-filled-valid');
});
}
});
});
$('.delete-submission').click(function(e){ $('.delete-submission').click(function(e){
e.preventDefault(); e.preventDefault();
var submission_id = $(this).attr('submission-id'); var submission_id = $(this).attr('submission-id');

View File

@ -1,16 +1,16 @@
<form id="mail-form" method="POST"> <form id="user-mail-form" method="POST">
<div class="form-group"> <div class="form-group">
<label> <label>
Message Message
<br> <br>
<small></small> <small></small>
</label> </label>
<textarea class="form-control" name="msg" placeholder="" rows="15"></textarea> <textarea class="form-control" name="text" placeholder="" rows="15"></textarea>
</div> </div>
<div id="results"> <div id="results">
</div> </div>
<button type="button" class="btn btn-primary float-right"> <button type="submit" class="btn btn-primary float-right">
Send Send
</button> </button>
</form> </form>

View File

@ -569,3 +569,60 @@ def test_api_user_get_awards():
r = client.get('/api/v1/users/2/awards') r = client.get('/api/v1/users/2/awards')
assert r.status_code == 200 assert r.status_code == 200
destroy_ctfd(app) destroy_ctfd(app)
def test_api_user_send_email():
"""Can an admin post /api/v1/users/<user_id>/email"""
app = create_ctfd()
with app.app_context():
register_user(app)
with login_as_user(app) as client:
r = client.post('/api/v1/users/2/email', json={
'text': 'email should get rejected'
})
assert r.status_code == 403
with login_as_user(app, "admin") as admin:
r = admin.post('/api/v1/users/2/email', json={
'text': 'email should be accepted'
})
assert r.get_json() == {
'success': False,
'errors': {
"": [
"Email settings not configured"
]
}
}
assert r.status_code == 400
set_config('verify_emails', True)
set_config('mail_server', 'localhost')
set_config('mail_port', 25)
set_config('mail_useauth', True)
set_config('mail_username', 'username')
set_config('mail_password', 'password')
with login_as_user(app, "admin") as admin:
r = admin.post('/api/v1/users/2/email', json={
'text': ''
})
assert r.get_json() == {
'success': False,
'errors': {
"text": [
"Email text cannot be empty"
]
}
}
assert r.status_code == 400
with login_as_user(app, "admin") as admin:
r = admin.post('/api/v1/users/2/email', json={
'text': 'email should be accepted'
})
assert r.status_code == 200
destroy_ctfd(app)