Merge pull request #1371 from CTFd/ip-address-admin-modal

* IP Tracking cache returns the IPs used by the user in the last hour. This way we can track the "Last Seen" value better for GET requests. 
* Moves IP addresses in the Admin Panel for Users and Teams into a modal
* Closes #1146
2.4.0-dev
Kevin Chung 2020-05-01 02:44:33 -04:00 committed by GitHub
commit fe97385f69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 129 additions and 80 deletions

View File

@ -46,10 +46,10 @@ def clear_pages():
cache.delete_memoized(get_page)
def clear_user_ips(user_id):
from CTFd.utils.user import get_user_ips
def clear_user_recent_ips(user_id):
from CTFd.utils.user import get_user_recent_ips
cache.delete_memoized(get_user_ips, user_id=user_id)
cache.delete_memoized(get_user_recent_ips, user_id=user_id)
def clear_user_session(user_id):

View File

@ -221,6 +221,10 @@ $(() => {
$("#team-award-modal").modal("toggle");
});
$(".addresses-team").click(function(event) {
$("#team-addresses-modal").modal("toggle");
});
$("#user-award-form").submit(function(e) {
e.preventDefault();
const params = $("#user-award-form").serializeJSON(true);

View File

@ -419,6 +419,10 @@ $(() => {
$("#user-email-modal").modal("toggle");
});
$(".addresses-user").click(function(event) {
$("#user-addresses-modal").modal("toggle");
});
$("#user-mail-form").submit(emailUser);
$(".delete-submission").click(deleteUserSubmission);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,28 @@
<div class="row">
<div class="col-md-12">
<table class="table table-striped">
<thead>
<tr>
<td class="text-center"><b>User</b></td>
<td class="text-center"><b>IP Address</b></td>
<td class="text-center"><b>Last Seen</b></td>
</tr>
</thead>
<tbody>
{% for addr in addrs %}
<tr>
<td class="text-center">
<a href="{{ url_for("admin.users_detail", user_id=addr.user_id) }}">
{{ addr.user.name }}
</a>
</td>
<td class="text-center">{{ addr.ip }}</td>
<td class="text-center solve-time">
<span data-time="{{ addr.date | isoformat }}"></span>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>

View File

@ -0,0 +1,22 @@
<div class="row">
<div class="col-md-12">
<table class="table table-striped">
<thead>
<tr>
<td class="text-center"><b>IP Address</b></td>
<td class="text-center"><b>Last Seen</b></td>
</tr>
</thead>
<tbody>
{% for addr in addrs %}
<tr>
<td class="text-center">{{ addr.ip }}</td>
<td class="text-center solve-time">
<span data-time="{{ addr.date | isoformat }}"></span>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>

View File

@ -61,6 +61,22 @@
</div>
</div>
<div id="team-addresses-modal" class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-action text-center w-100">IP Addresses</h2>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body clearfix">
{% include "admin/modals/teams/addresses.html" %}
</div>
</div>
</div>
</div>
<div class="jumbotron">
<div class="container">
<h1 id="team-id" class="text-center">{{ team.name }}</h1>
@ -109,6 +125,7 @@
<small>points</small>
{% endif %}
</h3>
<hr class="w-50">
<div class="pt-3">
<a class="edit-team">
<i class="btn-fa fas fa-pencil-alt fa-2x px-2" data-toggle="tooltip" data-placement="top"
@ -126,6 +143,11 @@
title="Delete Team"></i>
</a>
</div>
<div class="pt-3">
<a class="addresses-team">
<i class="btn-fa fas fa-network-wired fa-2x px-2" data-toggle="tooltip" data-placement="top" title="IP Addresses"></i>
</a>
</div>
</div>
</div>
@ -356,37 +378,6 @@
</div>
</div>
</div>
<div class="row pt-5">
<div class="col-md-12">
<table class="table table-striped">
<h3 class="text-center">IP Addresses</h3>
<thead>
<tr>
<td class="text-center"><b>User</b></td>
<td class="text-center"><b>IP Address</b></td>
<td class="text-center"><b>Last Seen</b></td>
</tr>
</thead>
<tbody>
{% for addr in addrs %}
<tr>
<td class="text-center">
<a href="{{ url_for("admin.users_detail", user_id=addr.user_id) }}">
{{ addr.user.name }}
</a>
</td>
<td class="text-center">{{ addr.ip }}</td>
<td class="text-center solve-time">
<span data-time="{{ addr.date | isoformat }}"></span>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}

View File

@ -52,6 +52,22 @@
</div>
</div>
<div id="user-addresses-modal" class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-action text-center w-100">IP Addresses</h2>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body clearfix">
{% include "admin/modals/users/addresses.html" %}
</div>
</div>
</div>
</div>
<div class="jumbotron">
<div class="container">
<h1 id="team-id" class="text-center p-0 m-0">{{ user.name }}</h1>
@ -112,6 +128,7 @@
<small>points</small>
{% endif %}
</h3>
<hr class="w-50">
<div class="pt-3">
<a class="edit-user">
<i class="btn-fa fas fa-user-edit fa-2x px-2" data-toggle="tooltip" data-placement="top" title="Edit User"></i>
@ -126,6 +143,11 @@
<i class="btn-fa fas fa-trash-alt fa-2x px-2" data-toggle="tooltip" data-placement="top" title="Delete User"></i>
</a>
</div>
<div class="pt-3">
<a class="addresses-user">
<i class="btn-fa fas fa-network-wired fa-2x px-2" data-toggle="tooltip" data-placement="top" title="IP Addresses"></i>
</a>
</div>
</div>
</div>
@ -335,31 +357,6 @@
</div>
</div>
</div>
<div class="row pt-5">
<div class="col-md-12">
<table class="table table-striped">
<h3 class="text-center">IP Addresses</h3>
<thead>
<tr>
<td class="text-center"><b>IP Address</b></td>
<td class="text-center"><b>Last Seen</b></td>
</tr>
</thead>
<tbody>
{% for addr in addrs %}
<tr>
<td class="text-center">{{ addr.ip }}</td>
<td class="text-center solve-time">
<span data-time="{{ addr.date | isoformat }}"></span>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}

View File

@ -7,7 +7,7 @@ from flask import abort, redirect, render_template, request, session, url_for
from sqlalchemy.exc import IntegrityError, InvalidRequestError
from werkzeug.wsgi import DispatcherMiddleware
from CTFd.cache import clear_user_ips
from CTFd.cache import clear_user_recent_ips
from CTFd.exceptions import UserNotFoundException, UserTokenExpiredException
from CTFd.models import Tracking, db
from CTFd.utils import config, get_config, markdown
@ -42,7 +42,7 @@ from CTFd.utils.security.csrf import generate_nonce
from CTFd.utils.user import (
authed,
get_current_user_attrs,
get_current_user_ips,
get_current_user_recent_ips,
get_current_team_attrs,
get_ip,
is_admin,
@ -181,18 +181,20 @@ def init_request_processors(app):
return
if authed():
user_ips = get_current_user_ips()
user_ips = get_current_user_recent_ips()
ip = get_ip()
track = None
if ip not in user_ips:
track = Tracking(ip=get_ip(), user_id=session["id"])
db.session.add(track)
else:
if request.method != "GET":
track = Tracking.query.filter_by(
ip=get_ip(), user_id=session["id"]
).first()
if (ip not in user_ips) or (request.method != "GET"):
track = Tracking.query.filter_by(
ip=get_ip(), user_id=session["id"]
).first()
if track:
track.date = datetime.datetime.utcnow()
else:
track = Tracking(ip=get_ip(), user_id=session["id"])
db.session.add(track)
if track:
try:
@ -200,7 +202,7 @@ def init_request_processors(app):
except (InvalidRequestError, IntegrityError):
db.session.rollback()
logout_user()
clear_user_ips(user_id=session["id"])
clear_user_recent_ips(user_id=session["id"])
@app.before_request
def banned():

View File

@ -120,21 +120,22 @@ def get_ip(req=None):
return remote_addr
def get_current_user_ips():
def get_current_user_recent_ips():
if authed():
return get_user_ips(user_id=session["id"])
return get_user_recent_ips(user_id=session["id"])
else:
return None
@cache.memoize(timeout=60)
def get_user_ips(user_id):
def get_user_recent_ips(user_id):
hour_ago = datetime.datetime.now() - datetime.timedelta(hours=1)
addrs = (
Tracking.query.with_entities(Tracking.ip.distinct())
.filter_by(user_id=user_id)
.filter(Tracking.user_id == user_id, Tracking.date >= hour_ago)
.all()
)
return [ip for ip, in addrs]
return set([ip for (ip,) in addrs])
def get_wrong_submissions_per_minute(account_id):