mirror of https://github.com/JohnHammond/CTFd.git
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 #897selenium-screenshot-testing
parent
42fa8fe555
commit
4f7c4687d7
|
@ -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': {}
|
||||||
|
}
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -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>
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue