Skip to content

Commit

Permalink
Move IP restrictions and hidden user block to contest configuration
Browse files Browse the repository at this point in the history
Also changed slightly their name to properly match their meaning,
added documentation for them and the IP based autologin, and added IP
based autologin to the options available when creating a new contest
(it was missing from the commit introducing IP autologin).
  • Loading branch information
stefano-maggiolo committed Jan 5, 2016
1 parent c425c65 commit d60c987
Show file tree
Hide file tree
Showing 12 changed files with 138 additions and 26 deletions.
2 changes: 0 additions & 2 deletions cms/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,6 @@ def __init__(self):
self.submit_local_copy_path = "%s/submissions/"
self.tests_local_copy = True
self.tests_local_copy_path = "%s/tests/"
self.ip_lock = True
self.block_hidden_users = False
self.is_proxy_used = False
self.max_submission_length = 100000
self.max_input_length = 5000000
Expand Down
2 changes: 1 addition & 1 deletion cms/db/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@

# Instantiate or import these objects.

version = 18
version = 19


engine = create_engine(config.database, echo=config.database_debug,
Expand Down
14 changes: 14 additions & 0 deletions cms/db/contest.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,20 @@ class Contest(Base):
nullable=False,
default=True)

# Whether to prevent hidden participations to log in.
block_hidden_participations = Column(
Boolean,
nullable=False,
default=False)

# Whether to enforce that the IP address of the request matches
# the IP address or subnet specified for the participation (if
# present).
ip_restriction = Column(
Boolean,
nullable=False,
default=True)

# Whether to automatically log in users connecting from an IP
# address specified in the ip field of a participation to this
# contest.
Expand Down
4 changes: 4 additions & 0 deletions cms/server/admin/handlers/contest.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ def post(self):
attrs["languages"] = self.get_arguments("languages")

self.get_bool(attrs, "submissions_download_allowed")
self.get_bool(attrs, "block_hidden_participations")
self.get_bool(attrs, "ip_restriction")
self.get_bool(attrs, "ip_autologin")

self.get_string(attrs, "token_mode")
Expand Down Expand Up @@ -128,6 +130,8 @@ def post(self, contest_id):
attrs["languages"] = self.get_arguments("languages")

self.get_bool(attrs, "submissions_download_allowed")
self.get_bool(attrs, "block_hidden_participations")
self.get_bool(attrs, "ip_restriction")
self.get_bool(attrs, "ip_autologin")

self.get_string(attrs, "token_mode")
Expand Down
30 changes: 24 additions & 6 deletions cms/server/admin/templates/add_contest.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,30 @@ <h1>New contest</h1>
{% end %}
</td>
</tr>
<tr>
<td><label for="submissions_download_allowed">Submissions download allowed</label></td>
<td>
<input type="checkbox" id="submissions_download_allowed" name="submissions_download_allowed" checked/>
</td>
</tr>
<tr>
<td><label for="block_hidden_participations">Block hidden participations</label></td>
<td>
<input type="checkbox" id="block_hidden_participations" name="block_hidden_participations"/>
</td>
</tr>
<tr>
<td><label for="ip_restriction">IP based login restriction</label></td>
<td>
<input type="checkbox" id="ip_restriction" name="ip_restriction" checked/>
</td>
</tr>
<tr>
<td><label for="ip_autologin">IP based autologin</label></td>
<td>
<input type="checkbox" id="ip_autologin" name="ip_autologin"/>
</td>
</tr>
<tr>
<td>Token mode</td>
<td>
Expand All @@ -42,12 +66,6 @@ <h1>New contest</h1>
</select>
</td>
</tr>
<tr>
<td><label for="submissions_download_allowed">Submissions download allowed</label></td>
<td>
<input type="checkbox" id="submissions_download_allowed" name="submissions_download_allowed" checked/>
</td>
</tr>
<tr>
<td>[only if finite] Maximum number of tokens a contestant can use</td>
<td><input type="text" name="token_max_number" size="3" value=""></td>
Expand Down
12 changes: 12 additions & 0 deletions cms/server/admin/templates/contest.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ <h1>Contest information</h1>
<input type="checkbox" id="submissions_download_allowed" name="submissions_download_allowed" {{ "checked" if contest.submissions_download_allowed else "" }}/>
</td>
</tr>
<tr>
<td><label for="block_hidden_participations">Block hidden participations</label></td>
<td>
<input type="checkbox" id="block_hidden_participations" name="block_hidden_participations" {{ "checked" if contest.block_hidden_participations else "" }}/>
</td>
</tr>
<tr>
<td><label for="ip_restriction">IP based login restriction</label></td>
<td>
<input type="checkbox" id="ip_restriction" name="ip_restriction" {{ "checked" if contest.ip_restriction else "" }}/>
</td>
</tr>
<tr>
<td><label for="ip_autologin">IP based autologin</label></td>
<td>
Expand Down
4 changes: 2 additions & 2 deletions cms/server/contest/handlers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,13 @@ def get_current_user(self):
return None

# Check if user is using the right IP (or is on the right subnet)
if config.ip_lock and participation.ip is not None \
if self.contest.ip_restriction and participation.ip is not None \
and not check_ip(self.request.remote_ip, participation.ip):
self.clear_cookie("login")
return None

# Check if user is hidden
if participation.hidden and config.block_hidden_users:
if participation.hidden and self.contest.block_hidden_participations:
self.clear_cookie("login")
return None

Expand Down
4 changes: 2 additions & 2 deletions cms/server/contest/handlers/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,14 @@ def post(self):
self.redirect("/?login_error=true")
return

if config.ip_lock and participation.ip is not None \
if self.contest.ip_restriction and participation.ip is not None \
and not check_ip(self.request.remote_ip, participation.ip):
logger.info("Unexpected IP: user=%s pass=%s remote_ip=%s.",
filtered_user, filtered_pass, self.request.remote_ip)
self.redirect("/?login_error=true")
return

if participation.hidden and config.block_hidden_users:
if participation.hidden and self.contest.block_hidden_participations:
logger.info("Hidden user login attempt: "
"user=%s pass=%s remote_ip=%s.",
filtered_user, filtered_pass, self.request.remote_ip)
Expand Down
48 changes: 48 additions & 0 deletions cmscontrib/updaters/update_19.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-

# Contest Management System - http://cms-dev.github.io/
# Copyright © 2015 Stefano Maggiolo <[email protected]>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

"""A class to update a dump created by CMS.
Used by ContestImporter and DumpUpdater.
This updater just adds the default values for the new fields
(block_hidden_participations and ip_restriction).
"""

from __future__ import absolute_import
from __future__ import unicode_literals
from __future__ import print_function


class Updater(object):

def __init__(self, data):
assert data["_version"] == 18
self.objs = data

def run(self):
for k, v in self.objs.iteritems():
if k.startswith("_"):
continue
if v["_class"] == "Contest":
v["block_hidden_participations"] = False
v["ip_restriction"] = True

return self.objs
3 changes: 2 additions & 1 deletion cmstestsuite/ReplayContest.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,10 @@ def run(self):
self.importer.run()
logger.info("Contest imported.")

logger.info("Please run CMS against the contest (with ip_lock=False).")
logger.info("Please run CMS against the contest.")
logger.info("Please ensure that:")
logger.info("- the contest is active (we are between start and stop);")
logger.info("- IP based login restrictions are disabled;")
logger.info("- the minimum interval for submissions and usertests ")
logger.info(" (contest- and task-wise) is None.")
logger.info("Then press enter to start.")
Expand Down
8 changes: 0 additions & 8 deletions config/cms.conf.sample
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,6 @@
"submit_local_copy": true,
"submit_local_copy_path": "%s/submissions/",

"_help": "If we allow users to log in only from their specified IP",
"_help": "address. Users with no IP set are anyway allowed to log in",
"_help": "from anywhere.",
"ip_lock": true,

"_help": "Whether hidden users are allowed to log in.",
"block_hidden_users": false,

"_help": "Whether the CWSs are behind a (trusted) proxy (e.g.,",
"_help": "nginx) that does load balancing. Used to know if it is",
"_help": "safe to assume that the real source IP address is the",
Expand Down
33 changes: 29 additions & 4 deletions docs/Configuring a contest.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Configuring a contest
*********************

In the following text "user" and "contestant" are used interchangeably.
In the following text "user" and "contestant" are used interchangeably. A "participation" is an instance of a user participating in a specific constest.

Configuration parameters will be referred to using their internal name, but it should always be easy to infer what fields control them in the AWS interface by using their label.

Expand Down Expand Up @@ -113,11 +113,36 @@ When CWS needs to show a timestamp to the user it first tries to show it accordi
User login
==========

Users log into CWS using a username and a password. These have to be specified, respectively, in the ``username`` and ``password`` fields (in cleartext!). These credentials need to be inserted by the admins (i.e. there's no way to have an automatic login, a "guest" session, etc.). The user needs to login again if they do not navigate the site for ``cookie_duration`` seconds (specified in the :file:`cms.conf` file).
Users log into CWS using their credentials (username and a password), or automatically, matching their IP address.

In fact, there are other reasons that can cause the login to fail. If the ``ip_lock`` option (in :file:`cms.conf`) is set to ``true`` then the login will fail if the IP address that attempted it doesn't match the address or subnet in the ``ip`` field of the specified user. If ``ip`` is not set then this check is skipped, even if ``ip_lock`` is ``true``. Note that if a reverse-proxy (like nginx) is in use then it is necessary to set ``is_proxy_used`` (in :file:`cms.conf`) to ``true`` and configure the proxy in order to properly pass the ``X-Forwarded-For``-style headers (see :ref:`running-cms_recommended-setup`).
Logging in with IP based autologin
----------------------------------

The login can also fail if ``block_hidden_users`` (in :file:`cms.conf`) is ``true`` and the user trying to login as has the ``hidden`` field set.
If the "IP based autologin" option in the contest configuration is set, CWS tries to find a user with the IP address of the request, and if it finds exactly one, the requester is automatically logged in as the user. If zero or more than one user match, CWS does not let the user in (and the incident is logged to allow troubleshooting).

.. warning::

If a reverse-proxy (like nginx) is in use then it is necessary to set ``is_proxy_used`` (in :file:`cms.conf`) to ``true`` and configure the proxy in order to properly pass the ``X-Forwarded-For``-style headers (see :ref:`running-cms_recommended-setup`).

Logging in with credentials
---------------------------

If the autologin is not enabled, users can log in with username and password, which have to be specified in the user configuration (in cleartext, for the moment). The password can also be overridden for a specific contest in the participation configuration. These credentials need to be inserted by the admins (i.e. there's no way to sign up, of log in as a "guest", etc.).

A successfully logged in user needs to reauthenticate after ``cookie_duration`` seconds (specified in the :file:`cms.conf` file) from when they last visited a page.

Even without autologin, it is possible to restrict the IP address or subnet that the user is using for accessing CWS, using the "IP based login restriction" option in the contest configuration (in which case, admins need to set ``is_proxy_used`` as before). If this is set, then the login will fail if the IP address that attempted it does not match the address or subnet in the IP specified for the specified participation. If the participation IP address is not set, then no restriction applies.

Failure to login
----------------

The following are some common reasons for login failures, all of them coming with some useful log message from CWS.

- IP address mismatch (with IP based autologin): if the participation has the wrong IP address, or if more than one participation has the same IP address, then the login fails. Note that if the user is using the IP address of a different user, CWS will happily log them in without noticing anything.

- IP address mismatch (using IP based login restrictions): the login fails if the participation has the wrong IP address or subnet.

- Blocked hidden participations: users whose participation is hidden cannot log in if "Block hidden participations" is set in the contest configuration.


.. _configuringacontest_usaco-like-contests:
Expand Down

0 comments on commit d60c987

Please sign in to comment.