Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge tag '3.0.2' into stable-releases

3.0.2 Release!
  • Loading branch information...
commit cf0c0af037f43f4eb40d4aaddf764c8a1ec29093 2 parents 7c3f7ab + e8d0871
@micolous authored
Showing with 2,206 additions and 538 deletions.
  1. +5 −2 README.md
  2. +30 −0 docs/changelog.rst
  3. +1 −1  docs/conf.py
  4. +125 −50 docs/deploy.rst
  5. +16 −0 docs/example/dnsmasq.d/tollgate.conf
  6. +29 −10 docs/example/fedora/dhcpd.conf
  7. +5 −0 docs/example/tollgate_dhcp_script.sh
  8. +15 −0 docs/hacking.rst
  9. +1 −0  docs/index.rst
  10. +86 −0 docs/permissions.rst
  11. +1 −1  platform/fedora/rpm/tollgate.spec
  12. +1 −1  setup.py
  13. +1 −1  tollgate/__init__.py
  14. +41 −6 tollgate/api/resources.py
  15. +3 −3 tollgate/api/urls.py
  16. +58 −15 tollgate/api/views.py
  17. +1 −1  tollgate/backend/iptables.py
  18. +71 −2 tollgate/frontend/__init__.py
  19. +45 −11 tollgate/frontend/admin.py
  20. +19 −4 tollgate/frontend/common.py
  21. +33 −10 tollgate/frontend/forms.py
  22. +82 −0 tollgate/frontend/management/commands/dhcp_script.py
  23. +6 −2 tollgate/frontend/management/commands/refresh_hosts.py
  24. +146 −0 tollgate/frontend/migrations/0004_auto__add_field_ip4portforward_label.py
  25. +156 −0 tollgate/frontend/migrations/0005_auto__add_field_userprofile_maximum_quota_signins__add_field_userprofi.py
  26. +55 −12 tollgate/frontend/models.py
  27. +5 −1 tollgate/frontend/platform/__init__.py
  28. +6 −1 tollgate/frontend/platform/common.py
  29. +4 −1 tollgate/frontend/platform/dummy.py
  30. +18 −10 tollgate/frontend/platform/linux.py
  31. +7 −0 tollgate/frontend/static/tollgate/cake.css
  32. +8 −1 tollgate/frontend/static/tollgate/terminal.css
  33. +5 −1 tollgate/frontend/templates/frontend/base-internet.html
  34. +26 −6 tollgate/frontend/templates/frontend/captive_landing.html
  35. +12 −3 tollgate/frontend/templates/frontend/index.html
  36. +11 −7 tollgate/frontend/templates/frontend/internet.html
  37. +2 −0  tollgate/frontend/templates/frontend/internet_login_here-failure.html
  38. +2 −0  tollgate/frontend/templates/frontend/ip4portforward_list.html
  39. +34 −19 tollgate/frontend/templates/frontend/quota.html
  40. +43 −20 tollgate/frontend/templates/frontend/usage-info.html
  41. +19 −3 tollgate/frontend/templates/frontend/usage.html
  42. +35 −13 tollgate/frontend/tollgate_controller_api.py
  43. +234 −58 tollgate/frontend/urls.py
  44. +528 −195 tollgate/frontend/views.py
  45. +5 −1 tollgate/scripts/management/commands/mysql_bigint_patch.py
  46. +10 −2 tollgate/scripts/management/commands/repair_permissions.py
  47. +48 −11 tollgate/scripts/management/commands/scraper.dat
  48. +65 −22 tollgate/scripts/management/commands/scraper.py
  49. +14 −3 tollgate/scripts/management/commands/setup_settings.py
  50. +33 −28 tollgate/settings/base.py
View
7 README.md
@@ -1,9 +1,12 @@
# tollgate README #
tollgate - A captive portal software for Linux for LAN parties.
+
Copyright 2008-2012 Michael Farrell <http://micolous.id.au>.
-Version 3.0.1 "Cavity Protection".
+Version 3.0.2 "Cavity Protection".
+
+Website: <http://tollgate.org.au/>
## Introduction ##
@@ -22,5 +25,5 @@ This software isn't based on any existing captive portal solution - it's entirel
Full documentation for the project is located in the `docs` folder and may be generated with Sphinx.
-Alternatively, it is available online at: http://tollgate.rtfd.org/
+Alternatively, it is available online at: <http://tollgate.rtfd.org/>
View
30 docs/changelog.rst
@@ -7,6 +7,36 @@ Changelog
All releases in the 3.x series are named after types of toothpaste.
+3.1.0 "Tartar Control" (?? July 2012)
+-------------------------------------
+
+This release is still in development.
+
+3.0.2 "Cavity Protection" (20 July 2012)
+----------------------------------------
+
+This release is still in development. This release includes security fixes, and is recommended for all users.
+
+* Commenced cleanup of PEP8 warnings, wrote style guide documentation.
+* ``docs``: Documented permissions.
+* ``frontend``: Added extra reason why "cannot find MAC" page would display on the page.
+* ``frontend``: Added labels to IPv4 port forwards. (`Issue #15`_)
+* ``frontend``: Added support for DHCP servers (dnsmasq and ISC) to notify when hosts come online / go offline. (`Issue #32`_)
+* ``frontend``: Default protocol of IPv4 port forwards is now TCP. (`Issue #17`_)
+* ``frontend``: Fix a bug where non-superusers could not sign in other users that are new, when they had the permission they would require.
+* ``frontend``: New permissions: ``can_revoke_access``, ``can_reset_own_quota``, ``can_toggle_internet``.
+* ``frontend``: New user profile flags to control the number of times a user may reset another user's quota, and the maximum amount of quota they may grant a user at sign-in.
+* ``frontend``: Permission names are now much shorter.
+* ``frontend``: **Security**: Fix a CSRF issue where a malicious user could trick an administrative user into toggling or revoking internet access for other users, toggling internet access for all users, and where it could trick a regular user into toggling their own internet access or (dis)owning hosts.
+* ``frontend``: Usage graph now shows usage in the local time of the user, rather than UTC.
+* ``scripts``: Added new OUI vendors, improved detection of Cisco. OUI scraper now grabs all vendors, even if it doesn't recognise them. Fixed some encoding issues when handling non-ASCII vendor names.
+
+
+.. _Issue #15: https://github.com/micolous/tollgate/issues/15
+.. _Issue #17: https://github.com/micolous/tollgate/issues/17
+.. _Issue #32: https://github.com/micolous/tollgate/issues/32
+
+
3.0.1 "Cavity Protection" (13th May 2012)
-----------------------------------------
View
2  docs/conf.py
@@ -50,7 +50,7 @@
# The short X.Y version.
version = '3.0'
# The full version, including alpha/beta/rc tags.
-release = '3.0.1'
+release = '3.0.2'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
View
175 docs/deploy.rst
@@ -7,42 +7,42 @@ Deploying tollgate into a Django project
The "proper" way to deploy tollgate is to install the software (using ``setup.py``), and then create a Django project with tollgate setup inside of it.
-This is achieveable fairly simply, however be aware that **tollgate only manages routing**, it does **not** manage things like DNS and DHCP which you'll need to make your network actually accept clients.
+This is achievable fairly simply, however be aware that **tollgate only manages routing**, it does **not** manage things like DNS and DHCP which you'll need to make your network actually accept clients.
This has the advantage of allowing you to easily customise configuration and templates. Be aware though that all modifications to tollgate **must** be made available, as well as the software itself, to all of your users, as a condition of the license.
-Pre-requisites
+Prerequisites
--------------
I'm assuming here that you have:
-* Installed and configured an apache2 server with mod_wsgi and mod_ssl.
+* Installed and configured an ``apache2`` server with ``mod_wsgi`` and ``mod_ssl``.
* Installed and configured a database server, for example, MySQL, as well as installed appropriate Python bindings to allow interaction.
* Installed everything else you need to make your network work -- that is, DHCP server, DNS server, multiple network interfaces in your tollgate machine (which will be your router / default gateway).
-* Installed and configured DBUS.
+* Installed and configured D-Bus.
* Installed other dependencies.
-Installation and configuration of those is outside of the scope of this document. If you're looking up HOWTO documents on the internet, do not do anything with `iptables`, as setting up a NAT and routing itself is part of tollgate.
+Installation and configuration of those is outside of the scope of this document. If you're looking up HOW-TO documents on the Internet, do not do anything with `iptables`, as setting up a NAT and routing itself is part of tollgate.
Install tollgate
----------------
-Install tollgate, either using an official stable build, git repository, or distribution package. You can install the latest `master` version of tollgate using `pip` with this command::
+Install tollgate, either using an official stable build, git repository, or distribution package. You can install the latest `master` version of tollgate using ``pip`` with this command::
$ sudo pip install git+https://github.com/micolous/tollgate.git
This **may** not work though, as the state of `git master` may be in flux.
-This will install the entire `tollgate` package into your Python path, and install the captivity and backend daemons.
+This will install the entire ``tollgate`` package into your Python path, and install the ``captivity`` and ``backend`` daemons.
-Configure DBUS
---------------
+Configure D-Bus
+---------------
-We need to add some configuration files for tollgate to DBUS' configuration in order to allow the web server process to use tollgate's backend.
+We need to add some configuration files for tollgate to D-Bus' configuration in order to allow the web server process to use tollgate's backend.
-In ``docs/example/dbus/system.d/tollgate.conf`` are some example configuration you can use with tollgate. Copy this to ``/etc/dbus-1/system.d/``, and modify with the appropriate username that the webserver uses (if it is not ``www-data``).
+In ``docs/example/dbus/system.d/tollgate.conf`` are some example configuration you can use with tollgate. Copy this to ``/etc/dbus-1/system.d/``, and modify with the appropriate username that the web server uses (if it is not ``www-data``).
-Then reload the DBUS configuration with ``/etc/init.d/dbus reload``.
+Then reload the D-Bus configuration with ``/etc/init.d/dbus reload``.
Create a project
----------------
@@ -101,7 +101,7 @@ You should also add the following extra settings for tollgate and configure appr
LOGIN_URL='/login/'
LOGOUT_URL='/logout/'
-The final setting to add is a URL where you are hosting the tollgate sources with your modifications, ``SOURCE_URL``. You should **never** link back to the official tollgate repository using this method (there is already a link to the official repo on the source page).
+The final setting to add is a URL where you are hosting the tollgate sources with your modifications, ``SOURCE_URL``. You should **never** link back to the official tollgate repository using this method (there is already a link to the official repository on the source page).
Not hosting the source code yourself may expose you to legal liability.
@@ -131,11 +131,11 @@ We won't start the daemons just yet, though.
Configure cron
--------------
-tollgate requires a periodic cronjob to refresh the list of hosts in it's database.
+tollgate requires a periodic cron job to refresh the list of hosts in it's database.
An example configuration is given in ``docs/example/tollgate.cron``. You will need to adapt it to point to the path of your Django project.
-Configure webserver
+Configure web server
-------------------
You'll need to now configure your web server.
@@ -148,7 +148,52 @@ There is an example apache2 configuration, including all vhosts, in ``docs/examp
You will need to modify the path of static items (like the WPAD and WFC vhosts, and aliases for static files) to the appropriate locations, and URLs.
-Included in the examples is how to configure a gitweb instance. You could also push code changes to an external repository, however it must be accessible to users at all times (ie: you should mark it as "unmetered").
+Included in the examples is how to configure a ``gitweb`` instance. You could also push code changes to an external repository, however it must be accessible to users at all times (ie: you should mark it as "unmetered").
+
+Configure DHCP server
+---------------------
+
+You require a small wrapper script in order to be used with the ``dhcp-script``, as follows::
+
+ #!/bin/sh
+ cd /var/tollgate_site; ./manage.py dhcp_script $*
+
+There is an example of this in ``/docs/example/tollgate_dhcp_script.sh``. You must also make the script executable.
+
+This script allows your DHCP server to notify tollgate when a system goes comes online or goes offline.
+
+dnsmasq
+^^^^^^^
+
+You can then use the ``dhcp-script`` parameter in ``dnsmasq.conf``::
+
+ dhcp-script=/usr/local/bin/tollgate_dhcp_script.sh
+
+ISC dhcpd
+^^^^^^^^^
+
+In order to handle events in ISC dhcpd, you require the following configuration::
+
+ on commit {
+ set clip = binary-to-ascii(10, 8, ".", leased-address);
+ set clhw = binary-to-ascii(16, 8, ":", substring(hardware, 1, 6));
+ set hname = pick-first-value(host-decl-name, option host-name, "");
+ execute("/usr/local/bin/tollgate_dhcp_script.sh", "add", clhw, clip, hname);
+ }
+
+ on release {
+ set clip = binary-to-ascii(10, 8, ".", leased-address);
+ set clhw = binary-to-ascii(16, 8, ":", substring(hardware, 1, 6));
+ set hname = pick-first-value(host-decl-name, option host-name, "");
+ execute("/usr/local/bin/tollgate_dhcp_script.sh", "del", clhw, clip, hname);
+ }
+
+ on expiry {
+ set clip = binary-to-ascii(10, 8, ".", leased-address);
+ set clhw = binary-to-ascii(16, 8, ":", substring(hardware, 1, 6));
+ set hname = pick-first-value(host-decl-name, option host-name, "");
+ execute("/usr/local/bin/tollgate_dhcp_script.sh", "del", clhw, clip, hname);
+ }
Start the daemons
-----------------
@@ -159,9 +204,9 @@ The first time you run you'll need to manually start the daemons. They will sta
Deploying tollgate in development
=================================
-In development, you can run and deploy ``tollgate`` from within a git clone of the repository. This is the "old" way of deploying tollgate in production, and has since been superceeded.
+In development, you can run and deploy ``tollgate`` from within a git clone of the repository. This is the "old" way of deploying tollgate in production, and has since been superseded.
-You can run tollgate in development either out of a WSGI-compatible webserver, or using Django's single-threaded development server.
+You can run tollgate in development either out of a WSGI-compatible web server, or using Django's single-threaded development server.
Useful Functions
----------------
@@ -193,14 +238,14 @@ tollgate's quota saving procedures are written in such a way that it will work w
However, there is a window (between ``refresh_hosts`` calls, normally every 10 minutes) where you can use all of your quota via one tollgate and still have it available on the other, because the counters aren't synchronised live (and doing so is quite expensive).
-In typical deployments however I haven't had this as a real problem, as it hasn't been possible to use more than 50% of the allocated quota in 10 minutes. Doing so would require quite fast internet access, and you're generally competing for that resource with other clients on the network.
+In typical deployments however I haven't had this as a real problem, as it hasn't been possible to use more than 50% of the allocated quota in 10 minutes. Doing so would require quite fast Internet access, and you're generally competing for that resource with other clients on the network.
Be sure when configuring your network infrastructure for redundancy that:
* Your two tollgate machines have different power sources. This could mean they're supplied via a different mains circuit, or one of them has a battery backup.
* You also provide redundancy for the switch, if you have one.
* You have either a multi-master database server setup, or a single database server with redundant power supplies or battery backup.
-* If running with one database server, make sure that if one half of your power goes down, that the database server is still accessible (ie: use two switches and two NICs in your database server).
+* If running with one database server, make sure that if one half of your power goes down, that the database server is still accessible (ie: use two switches and two network cards in your database server).
* Use protocols like Spanning Tree Protocol (STP) on your switches to break routing loops.
At the moment, tollgate doesn't support running multiple instances of itself managing *different* subnets. That's a plan for down the track.
@@ -246,7 +291,7 @@ You should keep those ratios if you adjust it, but gc_thresh needs to be able to
This will automatically set all three garbage collector thresholds appropriately according to the ratios above.
-You absolutely require this value to be set to the number of hosts in your subnet, with a little bit of leeway for your WAN ethernet interface. Which means if you have a ``/23`` (512 IPs) on your LAN side, and about 10 machines on your WAN side, you should set the value to about 530 (enough for both sides with some leeway)::
+You absolutely require this value to be set to the number of hosts in your subnet, with a little bit of leeway for your WAN Ethernet interface. Which means if you have a ``/23`` (512 IP addresses) on your LAN side, and about 10 machines on your WAN side, you should set the value to about 530 (enough for both sides with some leeway)::
arp_table_size = 530
@@ -381,9 +426,9 @@ After this, reload your Samba and DHCP daemon.
Mass-mailing Worms
------------------
-It's pretty much a given you will have problems with infected Windows hosts. One major thing you will want to consider is blocking external SMTP traffic to at least prevent your network from becoming a spam hub, and angering your ISP (as well as other internet users). You can do this with an entry in ``backend.ini``, under the section ``blacklist``::
+It's pretty much a given you will have problems with infected Windows hosts. One major thing you will want to consider is blocking external SMTP traffic to at least prevent your network from becoming a spam hub, and angering your ISP (as well as other Internet users). You can do this with an entry in ``backend.ini``, under the section ``blacklist``::
- externaldns = 0.0.0.0/25
+ externaldns = 0.0.0.0/0:25
Normally you only have to block port 25 traffic. SMTP over SSL is generally never used by such worms, and mail servers running on SSL generally also require authentication (which the spam bots won't have).
@@ -398,27 +443,27 @@ Nintendo Consoles / WFC
.. WARNING::
Nintendo DS and DS Lite, as well as any DS games on the DSi and 3DS will **only** connect to wireless networks that are either unencrypted or encrypted with WEP. Additionally, they will only connect to 2.4GHz 802.11b networks.
- Because of the additional radio bandwidth that 802.11b clients require, it is recommended that you run a seperate 802.11b-only network for those devices.
+ Because of the additional radio bandwidth that 802.11b clients require, it is recommended that you run a separate 802.11b-only network for those devices.
.. NOTE::
On the Nintendo DSi and 3DS, connection profiles 1 - 3 do not support WPA or WPA2 encryption (for compatibility with DS games), only the profiles 4 - 6 support it.
All of Nintendo's gaming consoles, with the exception of the Gamecube, will probe a site called ``conntest.nintendowifi.net`` during connection setup.
-If this site is inaccessible or does not return a "200 OK" response, the console will assume it cannot connect to the internet, and refuse to save the connection profile.
+If this site is inaccessible or does not return a "200 OK" response, the console will assume it cannot connect to the Internet, and refuse to save the connection profile.
Included in tollgate's source repository in ``/www/wpad/`` is a website you can host at ``conntest.nintendowifi.net``, with a DNS record pointing to your server. This must be accessible inside of your LAN.
-Playstation Portable (PSP)
+PlayStation Portable (PSP)
==========================
.. WARNING::
- Playstation Portable will only connect to 2.4GHz 802.11b networks, and does not support WPA2 encryption.
+ PlayStation Portable will only connect to 2.4GHz 802.11b networks, and does not support WPA2 encryption.
- Because of the additional radio bandwidth that 802.11b clients require, it is recommended that you run a seperate 802.11b-only network for those devices.
+ Because of the additional radio bandwidth that 802.11b clients require, it is recommended that you run a separate 802.11b-only network for those devices.
.. WARNING::
- Playstation Portable E-1000 does not have WiFi.
+ PlayStation Portable E-1000 does not have WiFi.
PSP System software v2.00 includes a web browser. Earlier versions of the system software do not include a web browser.
@@ -435,12 +480,12 @@ The general process for logging a system into tollgate when the device does not
#. Find the device in tollgate's `login other computers` screen, and sign it in.
#. Reattempt the connection test (this should succeed).
-After this, the device will be registered with that user's account. Whenever they are signed into the event they will automatically grant access to the internet for all of their devices.
+After this, the device will be registered with that user's account. Whenever they are signed into the event they will automatically grant access to the Internet for all of their devices.
Rogue DHCP / DNS Servers
========================
-There have been several instances at events your author has administed where Windows worms propegating on the network will send out rogue DHCP server responses, attempting to either route traffic through the infected machine, or replace DNS with a third-party server that will redirect traffic to popular websites through an attacker's server.
+There have been several instances at events your author has administered where Windows worms propagating on the network will send out rogue DHCP server responses, attempting to either route traffic through the infected machine, or replace DNS with a third-party server that will redirect traffic to popular websites through an attacker's server.
There are two major mitigation steps you should take:
@@ -449,9 +494,9 @@ Block external DNS servers
This can be done in ``backend.ini``, by adding a blacklist line like::
- externaldns = 0.0.0.0/53
+ externaldns = 0.0.0.0/0:53
-This will only allow your DNS server, and any whitelisted / unmetered servers to have DNS traffic passed through to them.
+This will only allow your DNS server, and any white-listed / unmetered servers to have DNS traffic passed through to them.
Use layer 3 managed switches with DHCP filtering
------------------------------------------------
@@ -470,12 +515,12 @@ Tollgate has a "quota reset" function whereby a user may gain their allocated qu
At present, tollgate has a hard-coded "one free quota reset" function, which is user accessible. This becomes available to a user once they have used 70% of their quota allocation.
-An administrator may reset a user's quota any number of times. However administrators are prevented from resetting their **own** quota more than once.
-
-There are two settings relating to this function:
+There are several settings relating to this function:
* ``RESET_EXCUSE_REQUIRED``: Toggles whether a user must provide a reason for having their quota reset.
-* ``RESET_PURCHASE``: Changes the language of the quota reset page to imply that a user may purchase additional data blocks. Be aware, generally ISPs will disallow selling internet access as part of a residential access plan, and may disallow it as part of a sponsorship agreement (if you have one). Use with caution.
+* ``RESET_PURCHASE``: Changes the language of the quota reset page to imply that a user may purchase additional data blocks. Be aware, generally ISPs will disallow selling Internet access as part of a residential access plan, and may disallow it as part of a sponsorship agreement (if you have one). Use with caution.
+* ``UserProfile.maximum_quota_resets``: Controls the number of times a user with ``can_reset_quota`` permission can reset another user's quota.
+* Permission ``can_reset_own_quota``: Controls whether a user with ``can_reset_quota`` permission can reset their own quota more than once.
As a result, you should generally allocate a user about half of the total amount of quota you want them to use. Your author has observed the following that makes these restrictions useful, and has some other notes:
@@ -487,35 +532,65 @@ As a result, you should generally allocate a user about half of the total amount
* Administrators will often also reset themselves numerous times without regard, and fall into the same trap. There is an "unmetered" function if it is really required to have unlimited access, however this is prone to abuse.
- As a result, tollgate prevents administrators from resetting their own quota more than once (no more than any other user).
+ As a result, tollgate prevents administrators from resetting their own quota more than once (no more than any other user), unless ``can_reset_own_quota`` has been granted.
* If you are tracking regular attendees, it is generally a good idea to lower the quota of non-regular attendees. Non-regulars more frequently try to exhaust as much quota as possible, often citing a right to use as much of the venue's bandwidth as possible. They will also often not be familiar with what kind of traffic their computers use.
Regular attendees are generally more respectful of the event and it's resources.
-* Most Windows-based traffic monitoring programs (like DU Meter, NetLimiter) do not accurately record internet usage. Generally, these programs will show lower amounts of traffic as to what is actually produced.
-
- NetLimiter in particular is notoriously bad at recording usage accurately, and will report several orders of magnitude low. [#nl1]_ [#nl2]_ [#nl3]_ [#nl4]_
+Reporting quota metering errors
+===============================
+
+So you think tollgate is counting your traffic wrong? I'm open to hear about it, and I want to fix it if there is a problem! However, please be aware of the following **before you report it as an issue**:
+
+Windows network accounting is broken
+------------------------------------
+
+Most Windows-based traffic monitoring programs (like DU Meter, NetLimiter) do not accurately record Internet usage. Generally, these programs will show lower amounts of traffic as to what is actually produced.
- The WinSock hooks that these software use in Windows are unreliable, and require that each packet be sent to a userspace program. If the program does not record the usage in a timely manner, it is possible for them to miss information about other packets.
+NetLimiter in particular is notoriously bad at recording usage accurately, and will report several orders of magnitude low. [#nl1]_ [#nl2]_ [#nl3]_ [#nl4]_
- It is also for this reason that at present tollgate will never be able to act as a router on Windows.
+The WinSock hooks that these software use in Windows are unreliable, and require that each packet be sent to a user space program. If the program does not record the usage in a timely manner, it is possible for them to miss information about other packets.
- Windows network byte counters are **optionally** provided by the network card driver. Irregularities may occur as a result between different network card chipsets.
+It is also for this reason that at present tollgate will never be able to act as a router on Windows.
- **TL;DR:** It is impossible to get accurate traffic information out of Windows operating systems, **ever**.
+Windows network byte counters are **optionally** provided by the network card driver. Irregularities may occur as a result between different network card chipsets.
-* Some programs that create "raw" packets may not be accounted for properly by the OS in either traffic counters or firewall quota records, nor might they be filtered by outbound rules. Tollgate will also count traffic that the firewall may have rejected or dropped -- it has no way to tell if the client is ignoring or using the traffic or not.
+**TL;DR:** It is impossible to get accurate traffic information out of Windows operating systems, **ever**.
+
+Raw packets
+-----------
+
+Some programs that create "raw" packets may not be accounted for properly by the OS in either traffic counters or firewall quota records, nor might they be filtered by outbound rules. Tollgate will also count traffic that the firewall may have rejected or dropped -- it has no way to tell if the client is ignoring or using the traffic or not.
+
+Blacklists and whitelists, traffic from other sources
+-----------------------------------------------------
- Additionally, these programs fail to take into account things like blacklisted and unmetered site access, as well as access from other sources (such as home internet use, or mobile broadband), which can cause them to read higher amounts of usage.
+Most accounting information will fail to take into account things like blacklisted and unmetered site access, as well as access from other sources (such as home Internet use, or mobile broadband), which can cause them to read higher amounts of usage.
+
+Binary gibibytes vs. metric/drivemaker's gigabytes
+--------------------------------------------------
+
+Tollgate reports all values in it's web interface either in bytes, or binary units.
+
+This means that 1 KiB == 1024 bytes.
+
+Other usage monitoring programs using tollgate's API may report this information differently -- quota values are provided in the API in bytes.
+
+Conclusion
+----------
- It is important when reporting irregularities to come up with solid evidence that proves it. I'm welcome to reproducable reports of these issues. Please include all details in your report, including tollgate versions, kernel versions, network hardware, packet captures, etc., enough so that I can try to reproduce the problem and verify that there is not an issue with your reporting device.
+It is important when reporting irregularities to come up with solid evidence that proves it. I'm welcome to **reproducible** reports of these issues.
+
+Please include all details in your report, including tollgate versions, kernel versions, network hardware, packet captures, database server, deployment steps, etc., enough so that I can try to reproduce the problem and verify that there is not an issue with your reporting device or something else.
+
+I have had issues in the past where tollgate has read quota usage low (or has stopped counting). These were due to integer overflow issues in ``backend`` and MySQL at 4 GiB. These have been fixed in later versions.
- **Any reports incorporating data from only Windows machines will be ignored for the above reasons. Incomplete, vague or non-reproducable reports will also be ignored.**
+**Any reports incorporating data from only Windows machines will be ignored for the above reasons. Incomplete, vague or non-reproducible reports will also be ignored.**
.. rubric:: Footnotes
.. [#nl1] http://whrl.pl/RbdgEC
.. [#nl2] http://whrl.pl/RbxbbZ
.. [#nl3] http://whrl.pl/RDrTP
-.. [#nl4] http://whrl.pl/RbN17d
+.. [#nl4] http://whrl.pl/RbN17d
View
16 docs/example/dnsmasq.d/tollgate.conf
@@ -0,0 +1,16 @@
+interface=eth0
+expand-hosts
+domain=example.tollgate.org.au,10.4.0.0/24
+dhcp-range=10.4.0.60,10.4.0.249,7d
+
+# lets tell windows not to be silly
+dhcp-option=19,0 # disable ip-forwarding
+dhcp-option=46,8 # netbios node type
+dhcp-option=vendor:MSFT,2,1i # tell windows to release the lease when shutting down
+
+# helpfully Windows ICS sets this as well.
+dhcp-authoritative
+
+dhcp-script=/usr/local/bin/tollgate_dhcp_script.sh
+
+
View
39 docs/example/fedora/dhcpd.conf
@@ -10,7 +10,7 @@ ddns-domainname "dhcp.example.lan.";
update-static-leases on;
option domain-name "example.lan";
-option domain-search "example.lan","dhcp.example.lan";
+option domain-search "example.lan", "dhcp.example.lan";
option domain-name-servers 10.4.0.1;
option dhcp-server-identifier 10.4.0.1;
@@ -26,20 +26,39 @@ server-identifier 10.4.0.1;
#This subnet should match the subnet of your LAN facing ethernet device.
subnet 10.4.0.0 netmask 255.255.255.0 {
- range 10.4.0.10 10.4.0.150;
- option routers 10.4.0.1;
+ range 10.4.0.10 10.4.0.150;
+ option routers 10.4.0.1;
zone dhcp.example.lan. {
# This should be the same IP as in the named.conf control statement. 127.0.0.1 is a safe value.
- primary 127.0.0.1;
- key "rndc-key";
+ primary 127.0.0.1;
+ key "rndc-key";
}
- zone 0.4.10.in-addr.arpa. {
- primary 127.0.0.1;
- key "rndc-key";
- }
-
+ zone 0.4.10.in-addr.arpa. {
+ primary 127.0.0.1;
+ key "rndc-key";
+ }
+
+
+ on commit {
+ set clip = binary-to-ascii(10, 8, ".", leased-address);
+ set clhw = binary-to-ascii(16, 8, ":", substring(hardware, 1, 6));
+ set hname = pick-first-value(host-decl-name, option host-name, "");
+ execute("/var/tollgate_site/manage.py", "dhcp_script", "add", clhw, clip, hname);
+ }
+ on release {
+ set clip = binary-to-ascii(10, 8, ".", leased-address);
+ set clhw = binary-to-ascii(16, 8, ":", substring(hardware, 1, 6));
+ set hname = pick-first-value(host-decl-name, option host-name, "");
+ execute("/var/tollgate_site/manage.py", "dhcp_script", "del", clhw, clip, hname);
+ }
+ on expiry {
+ set clip = binary-to-ascii(10, 8, ".", leased-address);
+ set clhw = binary-to-ascii(16, 8, ":", substring(hardware, 1, 6));
+ set hname = pick-first-value(host-decl-name, option host-name, "");
+ execute("/var/tollgate_site/manage.py", "dhcp_script", "del", clhw, clip, hname);
+ }
}
#If you have other servers, you can set static addresses with the following.
View
5 docs/example/tollgate_dhcp_script.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+# Example wrapper to allow dnsmasq to call dhcp_script in your tollgate
+# project correctly.
+cd /var/tollgate_site; ./manage.py dhcp_script $*
+
View
15 docs/hacking.rst
@@ -65,6 +65,21 @@ This is how a packet is handled inside tollgate when running on Linux.
1. If it's a new connection, it hits the NAT table rules.
+Submitting Patches / Coding Style Manual
+========================================
+
+All the patch management for the project is handled via the GitHub project. Please file a pull request using the GitHub interface.
+
+As for coding "style", we use mostly follow `PEP-8`_, with the exception that we use tabs instead of spaces. Anything that is thrown as an error by the `pep8` tool should be fixed.
+
+We target Python 2.6 and 2.7, and Django 1.2 - 1.4.
+
+Please make sure that any platform-specific (Linux) code you write has a fallback for development on non-Linux systems. There's a platform support module in `frontend` for this. `backend` doesn't matter -- that should only ever work on platforms that actually are supported.
+
+Never include any project files in the source tree, or any editor-specific garbage in the headers of files.
+
+.. _PEP-8: http://www.python.org/dev/peps/pep-0008/
+
Known Issues
============
View
1  docs/index.rst
@@ -16,6 +16,7 @@ Contents:
introduction
deploy
fedora-deploy
+ permissions
hacking
credits
changelog
View
86 docs/permissions.rst
@@ -0,0 +1,86 @@
+.. _permissions:
+
+***********************
+Permissions in tollgate
+***********************
+
+There exists some permissions that are used inside of tollgate. This documents what they do.
+
+``EventAttendance.can_register_attendance``: Register event attendance
+====================================================================
+
+Allows the user to sign in users to the event. This is done with the "sign in" view.
+
+This will also allow the user to create new users through the sign-in system.
+
+In conjunction with this, there is a UserProfile field called ``maximum_quota_signins`` which controls the maximum amount of quota a user may set when signing in another user.
+
+If set to 0, it will not restrict the quota that may be granted.
+
+If set to greater values, this is the maximum number of megabytes that a user may grant during the sign-in process, and disables use of the unlimited quota option.
+
+
+``EventAttendance.can_view_quota``: View quota
+==============================================
+
+Allows access to the "quota management" view.
+
+This lets the user see a report of all the internet quota used by each user at the event, as well as the overall total for the event.
+
+This also lets the user view all resets performed for a user.
+
+
+``EventAttendance.can_reset_quota``: Reset quota
+================================================
+
+This permission controls the ability to reset another user's quota.
+
+You cannot reset your own quota more than once -- you must get another user to do it for you.
+
+The UI to access this permission also requires you grant ``EventAttendance.can_view_quota``.
+
+In conjuction with this, there is a UserProfile field called ``maximum_quota_resets`` which controls the number of times a user may reset another user's quota.
+
+If set to 0, it will allow the user to reset another user's quota an unlimited number of times. If set to 1, it will only allow them to perform the regular "one free reset".
+
+
+``EventAttendance.can_reset_own_quota``: Reset own quota multiple times
+=======================================================================
+
+Setting this permission allows a user to reset their own quota multiple times through the quota management interface, so long as it is not in conflict with the ``maximum_quota_resets`` setting.
+
+The UI to access this permission also requires you grant ``EventAttendance.can_view_quota``.
+
+
+``EventAttendance.can_revoke_access``: Revoke internet access for a user
+========================================================================
+
+Allows the user to revoke internet access rights for another user.
+
+The UI to access this permission also requires you grant ``EventAttendance.can_view_quota``.
+
+
+``EventAttendance.can_change_coffee``: Coffee request access change
+===================================================================
+
+This ACL is deprecated and will be removed in a future version.
+
+This controls access to being able to change the ``coffee`` flag for a user. The UI to set this flag has been removed, so this ACL does nothing.
+
+This is a seperate ACL because of Dasman and Ravenge. ;-)
+
+
+``IP4PortForward.can_ip4portforward``: Manage IPv4 port forwarding
+==================================================================
+
+Allows access to the IPv4 port forwarding interface.
+
+
+``UserProfile.can_toggle_internet``: Toggle internet access for users
+=====================================================================
+
+This permission controls the ability to use the "internet switch" for other users.
+
+The UI to access this permission also requires you grant ``EventAttendance.can_view_quota``.
+
+
View
2  platform/fedora/rpm/tollgate.spec
@@ -2,7 +2,7 @@
#%global eggpath $RPM_BUILD_ROOT%{_prefix}/lib/python2.7/site-packages/
Name: tollgate
-Version: 3.0.1
+Version: 3.0.2
Release: 1%{?dist}
Summary: Django based captive internet portal
View
2  setup.py
@@ -4,7 +4,7 @@
setup(
name="tollgate",
- version="3.0.1",
+ version="3.0.2",
description="Python/Django-based captive portal for LAN parties.",
author="Michael Farrell",
author_email="micolous@gmail.com",
View
2  tollgate/__init__.py
@@ -1 +1 @@
-__version__ = '3.0.1'
+__version__ = '3.0.2'
View
47 tollgate/api/resources.py
@@ -1,16 +1,42 @@
+"""tollgate api resources
+Copyright 2008-2012 Michael Farrell <http://micolous.id.au/>
+
+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/>.
+"""
from djangorestframework.resources import ModelResource
-from tollgate.frontend.models import NetworkHost, UserProfile, EventAttendance, NetworkUsageDataPoint
+from tollgate.frontend.models import \
+ NetworkHost, UserProfile, EventAttendance, NetworkUsageDataPoint
from django.core.urlresolvers import reverse
class NetworkHostResource(ModelResource):
model = NetworkHost
- fields = ('mac_address', 'ip_address', 'first_connection', 'online', 'vendor', 'is_console')
+ fields = (
+ 'mac_address',
+ 'ip_address',
+ 'first_connection',
+ 'online',
+ 'vendor',
+ 'is_console'
+ )
ordering = ('mac_address')
+
class PermissiveUserProfileResource(ModelResource):
"""
- Permissive version of UserProfileResource, which provides the user's real name.
+ Permissive version of UserProfileResource, which provides the user's real
+ name.
"""
model = UserProfile
fields = ('internet_on', 'username', 'user_id', 'first_name', 'last_name')
@@ -18,17 +44,26 @@ class PermissiveUserProfileResource(ModelResource):
class UserProfileResource(ModelResource):
"""
- Restricted UserProfile resource, which doesn't provide the user's real name.
+ Restricted UserProfile resource, which doesn't provide the user's real
+ name.
"""
model = UserProfile
fields = ('internet_on', 'username', 'user_id')
-
+
+
class EventAttendanceResource(ModelResource):
"""
Event attendance object.
"""
model = EventAttendance
- fields = ('quota_amount', 'reset_count', 'quota_unmetered', 'quota_used', 'quota_remaining', )
+ fields = (
+ 'quota_amount',
+ 'reset_count',
+ 'quota_unmetered',
+ 'quota_used',
+ 'quota_remaining'
+ )
+
class NetworkUsageDataPointResource(ModelResource):
"""
View
6 tollgate/api/urls.py
@@ -1,5 +1,5 @@
"""tollgate api urls
-Copyright 2008-2012 Michael Farrell
+Copyright 2008-2012 Michael Farrell <http://micolous.id.au/>
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
@@ -23,7 +23,6 @@
urlpatterns = patterns('tollgate.api.views',
-
# Gets information about a network host by IP.
# Equivalent to the old whatis_ip() API call.
url(
@@ -48,7 +47,8 @@
# Gets information about a user by IP.
# Equivalent to the old whois_ip() API call.
url(
- r'^user/by-ip/(?P<networkhost__ip_address>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/$',
+ r'^user/by-ip/(?P<networkhost__ip_address>\d{1,3}\.\d{1,3}\.\d{1,3}' +\
+ r'\.\d{1,3})/$',
ReadOnlyInstanceModelView.as_view(resource=UserProfileResource),
dict(networkhost__online=True),
name='api_whois_ip'
View
73 tollgate/api/views.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python
"""tollgate frontend api views
-Copyright 2008-2010 Michael Farrell <http://micolous.id.au/>
+Copyright 2008-2012 Michael Farrell <http://micolous.id.au/>
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
@@ -23,12 +23,15 @@
from djangorestframework import status
from djangorestframework.response import ErrorResponse
from django.core.urlresolvers import reverse
+from django.utils.translation import ugettext as _
+
class ReadOnlyInstanceModelView(ReadModelMixin, ModelView):
"""
A read-only InstanceModelView, that only allows GET requests.
"""
- _suffix="ReadOnlyInstance"
+ _suffix = "ReadOnlyInstance"
+
class NetworkHostRootView(View):
"""
@@ -42,7 +45,10 @@ def get(self, request):
"""
hosts = NetworkHost.objects.filter(online=True).only('ip_address')
- return [reverse('api_whatis_ip', kwargs={'ip_address': h.ip_address}) for h in hosts]
+ return [reverse(
+ 'api_whatis_ip',
+ kwargs={'ip_address': h.ip_address}
+ ) for h in hosts]
class UserProfileRootView(View):
@@ -59,9 +65,13 @@ def get(self, request):
return dict(
me=reverse('api_whoami'),
- all_users=[reverse('api_whois_ip', kwargs={'networkhost__ip_address': h.ip_address}) for h in hosts]
+ all_users=[reverse(
+ 'api_whois_ip',
+ kwargs={'networkhost__ip_address': h.ip_address}
+ ) for h in hosts]
)
-
+
+
class MyUserProfileModelView(ModelView):
"""
Reads the current NetworkHost record for this user.
@@ -100,13 +110,18 @@ def get(self, request, *args, **kwargs):
# other error
raise ErrorResponse(status.HTTP_500_INTERNAL_SERVER_ERROR)
+
class MyEventAttendanceModelView(MyUserProfileModelView):
"""
Reads the current EventAttendance for the user.
"""
def get(self, request, *args, **kwargs):
# gets the user_profile associated with the request.
- user_profile = super(MyEventAttendanceModelView, self).get(request, *args, **kwargs)
+ user_profile = super(MyEventAttendanceModelView, self).get(
+ request,
+ *args,
+ **kwargs
+ )
# now look up their attendance.
try:
@@ -117,17 +132,26 @@ def get(self, request, *args, **kwargs):
# now return the attendance.
return attendance
-
+
+
class MyNetworkUsageDataPointsView(MyEventAttendanceModelView):
"""
- Reads the NetworkUsageDataPoints associated with the user's attendance at the current event.
+ Reads the NetworkUsageDataPoints associated with the user's attendance at
+ the current event.
"""
def get(self, request, *args, **kwargs):
# gets the attendance associated with the request.
- attendance = super(MyNetworkUsageDataPointsView, self).get(request, *args, **kwargs)
+ attendance = super(MyNetworkUsageDataPointsView, self).get(
+ request,
+ *args,
+ **kwargs
+ )
# now lookup their usages in the last 36 hours
- return attendance.networkusagedatapoint_set.filter(when__gte=utcnow()-timedelta(hours=36)).order_by('when')
+ return attendance.networkusagedatapoint_set.filter(
+ when__gte=utcnow() - timedelta(hours=36)
+ ).order_by('when')
+
class TollgateAPIView(View):
"""
@@ -138,9 +162,28 @@ def get(self, request):
tollgate_api_version=1,
tollgate_version=tollgate.__version__,
methods=[
- dict(name='networkhost_root', description='Get information about a NetworkHost', url=reverse('api_networkhost_root')),
- dict(name='user_root', description='Get owner and user information', url=reverse('api_user_root')),
- dict(name='usage', description='Get your usage information', url=reverse('api_usage')),
- dict(name='usage_history', description='Get your usage history', url=reverse('api_usage_history')),
+ dict(
+ name='networkhost_root',
+ description=_('Get information about a NetworkHost'),
+ url=reverse('api_networkhost_root')
+ ),
+
+ dict(
+ name='user_root',
+ description=_('Get owner and user information'),
+ url=reverse('api_user_root')
+ ),
+
+ dict(
+ name='usage',
+ description=_('Get your usage information'),
+ url=reverse('api_usage')
+ ),
+
+ dict(
+ name='usage_history',
+ description=_('Get your usage history'),
+ url=reverse('api_usage_history')
+ ),
]
- )
+ )
View
2  tollgate/backend/iptables.py
@@ -557,7 +557,7 @@ def setup_dbus():
DBusGMainLoop(set_as_default=True)
system_bus = dbus.SystemBus()
name = dbus.service.BusName(DBUS_SERVICE, bus=system_bus)
- return name
+ return name
def boot_dbus(daemonise, name, pid_file=None):
PortalBackendAPI(name)
View
73 tollgate/frontend/__init__.py
@@ -1,7 +1,76 @@
+#!/usr/bin/env python
+"""tollgate frontend application
+Copyright 2008-2012 Michael Farrell <http://micolous.id.au>
+
+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/>.
+"""
+
from django.utils.translation import ugettext as _
+from south.signals import post_migrate
THEME_CHOICES = (
- ('cake', _('cake: The new (white) default theme, from version 2.6.2')),
+ ('cake', _('cake: The new (white) theme, from v2.6.2')),
# ('platinum', 'platinum: A grey theme (in development)'),
- ('terminal', _('terminal: The old (green) default theme, in versions 2.6.1 and earlier')),
+ ('terminal', _('terminal: The old (green) theme, in v2.6.1 and earlier')),
)
+
+
+def update_permissions_after_migration(app, **kwargs):
+ """
+ Update app permission just after every migration.
+ This is based on app django_extensions update_permissions management command.
+
+ """
+
+ from django.conf import settings
+ from django.db.models import get_app, get_models
+ from django.contrib.auth.management import create_permissions, \
+ _get_all_permissions
+ from django.contrib.auth import models as auth_app
+ from django.contrib.contenttypes.models import ContentType
+
+ app = get_app(app)
+ create_permissions(app, get_models(), 2 if settings.DEBUG else 0)
+
+ # create_permissions doesn't update labels.
+ # We need to do this ourself!
+ app_models = get_models(app)
+ ctypes, searched_perms = set(), set()
+
+ # lookup custom permissions and models
+ pyperms = {}
+ for klass in app_models:
+ ctype = ContentType.objects.get_for_model(klass)
+ ctypes.add(ctype)
+ for code, label in klass._meta.permissions:
+ pyperms[(klass._meta.object_name.lower(), code)] = label
+
+ # Iterate through existing permissions, find ones with different labels
+ # and update them.
+ for perm in auth_app.Permission.objects.filter(content_type__in=ctypes):
+ # lookup perm in class
+ if (perm.content_type.model, perm.codename) in pyperms:
+ pyperm_label = pyperms[(perm.content_type.model, perm.codename)]
+
+ if perm.name != pyperm_label:
+ print _("Updating permission label for %s.%s...") % \
+ (perm.content_type.model, perm.codename)
+ perm.name = pyperm_label
+ perm.save()
+
+ # done!
+
+post_migrate.connect(update_permissions_after_migration)
+
+
View
56 tollgate/frontend/admin.py
@@ -1,5 +1,7 @@
-"""tollgate frontend admin hooks
-Copyright 2008-2010 Michael Farrell
+#!/usr/bin/env python
+"""
+tollgate frontend admin hooks
+Copyright 2008-2012 Michael Farrell
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
@@ -18,36 +20,68 @@
from django.contrib import admin
from tollgate.frontend.models import *
+
class EventAttendanceAdmin(admin.ModelAdmin):
- list_display = ("event", "user_profile", "quota_used", "quota_multiplier", "quota_amount", "quota_unmetered", "coffee")
+ list_display = (
+ 'event',
+ 'user_profile',
+ 'quota_used',
+ 'quota_multiplier',
+ 'quota_amount',
+ 'quota_unmetered',
+ 'coffee'
+ )
+
class EventAdmin(admin.ModelAdmin):
- list_display = ("name", "start", "end", "is_active")
+ list_display = ('name', 'start', 'end', 'is_active')
+
class NetworkHostAdmin(admin.ModelAdmin):
- list_display = ("mac_address", "ip_address", "computer_name", "user_profile", "online", "vendor")
+ list_display = (
+ 'mac_address',
+ 'ip_address',
+ 'computer_name',
+ 'user_profile',
+ 'online',
+ 'vendor'
+ )
+
class UserProfileAdmin(admin.ModelAdmin):
- list_display = ("user", "internet_on")
+ list_display = ('user', 'internet_on')
+
class QuotaResetEventAdmin(admin.ModelAdmin):
- list_display = ("when", "event_attendance", "performer", "excuse")
+ list_display = ('when', 'event_attendance', 'performer', 'excuse')
+
class NetworkHostOwnerChangeEventAdmin(admin.ModelAdmin):
- list_display = ("when", "old_owner", "new_owner", "network_host")
+ list_display = ('when', 'old_owner', 'new_owner', 'network_host')
+
class NetworkUsageDataPointAdmin(admin.ModelAdmin):
- list_display = ("when", "event_attendance", "bytes")
+ list_display = ('when', 'event_attendance', 'bytes')
+
class OuiAdmin(admin.ModelAdmin):
- list_display = ("hex", "full_name", "slug", "is_console")
+ list_display = ('hex', 'full_name', 'slug', 'is_console')
list_filter = ('is_console', 'slug')
+
class IP4ProtocolAdmin(admin.ModelAdmin):
list_display = ('name', 'description', 'has_port')
+
class IP4PortForwardAdmin(admin.ModelAdmin):
- list_display = ('host', 'protocol', 'port', 'creator', 'created', 'enabled')
+ list_display = (
+ 'host',
+ 'protocol',
+ 'port',
+ 'creator',
+ 'created',
+ 'enabled'
+ )
list_filter = ('enabled', 'host')
mapairs = (
View
23 tollgate/frontend/common.py
@@ -1,5 +1,7 @@
-"""tollgate frontend middleware
-Copyright 2008-2011 Michael Farrell <http://micolous.id.au/>
+#!/usr/bin/env python
+"""
+tollgate frontend middleware
+Copyright 2008-2012 Michael Farrell <http://micolous.id.au/>
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
@@ -22,14 +24,27 @@
from django.conf import settings
from base64 import b32decode, b64decode
from django.core.validators import URLValidator
+from django.utils.translation import ugettext as _
import sys
+
class TollgateMiddleware:
def process_exception(self, request, exception):
if sys.exc_type is NotAConsoleException:
- return render_to_response('frontend/not-a-console.html', context_instance=RequestContext(request))
+ return render_to_response(
+ 'frontend/not-a-console.html',
+ context_instance=RequestContext(request)
+ )
else:
- return render_to_response('frontend/error.html', {'error_message': 'An unhandled error occured.', 'excinfo': "%s: %s" % (sys.exc_type, sys.exc_value), 'traceback': extract_tb(sys.exc_traceback)}, context_instance=RequestContext(request))
+ return render_to_response(
+ 'frontend/error.html',
+ {
+ 'error_message': _('An unhandled error occured.'),
+ 'excinfo': "%s: %s" % (sys.exc_type, sys.exc_value),
+ 'traceback': extract_tb(sys.exc_traceback)
+ },
+ context_instance=RequestContext(request)
+ )
def process_response(self, request, response):
# Guvf pbqr vf yvtugyl boshfpngrq gb nibvq pyhrovrf terccvat sbe vg.
View
43 tollgate/frontend/forms.py
@@ -1,5 +1,5 @@
"""tollgate frontend forms
-Copyright 2008-2010 Michael Farrell
+Copyright 2008-2012 Michael Farrell <http://micolous.id.au/>
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
@@ -20,6 +20,7 @@
from django.conf import settings
from django.utils.translation import ugettext as _
+
# forms
class LoginForm(forms.Form):
username = forms.CharField(
@@ -37,10 +38,21 @@ class LoginForm(forms.Form):
required=False
)
+
class ResetLectureForm(forms.Form):
+ # This used to have a "comprehension test" about the reset. However, this
+ # was not recieved well among users, and failed to achieve the objective of
+ # them actually going through and fixing things. Users didn't answer the
+ # questions accurately, or asked for help filling in the answers, and
+ # became frustrated and agressive.
+
+ # Yes, do as I say!
q1 = forms.CharField(
- label=_('Enter the confirmation text in the image above. Remember to include punctuation as it appears.')
- ) # Yes, do as I say!
+ label=_("""\
+Enter the confirmation text in the image above. Remember to include
+punctuation exactly as it appears.
+ """)
+ )
excuse = forms.CharField(
label=_('Why did you exceed your quota usage?'),
@@ -50,13 +62,17 @@ class ResetLectureForm(forms.Form):
)
def check_answers(self):
- if not self.is_bound: return False
- if not self.is_valid(): return False
+ if not self.is_bound:
+ return False
+ if not self.is_valid():
+ return False
- if self.cleaned_data['q1'].lower() != u'yes, do as i say!': return False
+ if self.cleaned_data['q1'].lower() != u'yes, do as i say!':
+ return False
return True
+
class ResetExcuseForm(forms.Form):
excuse = forms.CharField(
label=_('Excuse for reset:'),
@@ -64,6 +80,7 @@ class ResetExcuseForm(forms.Form):
max_length=256
)
+
class CoffeeForm(forms.Form):
coffee = forms.BooleanField(
label=_('Unlimited Coffee?'),
@@ -71,6 +88,7 @@ class CoffeeForm(forms.Form):
required=False
)
+
class SignInForm1(forms.Form):
username = forms.CharField(
label=_('Username'),
@@ -78,6 +96,7 @@ class SignInForm1(forms.Form):
max_length=30
)
+
class SignInForm2(SignInForm1):
first_name = forms.CharField(
label=_('First name'),
@@ -104,22 +123,26 @@ class SignInForm3(forms.Form):
quota_unlimited = forms.BooleanField(
label=_("Unlimited Quota"),
- help_text=_('Usage information will still be recorded for the user, but no limits will be imposed on the user\'s traffic.'),
+ help_text=_("""\
+Usage information will still be recorded for the user, but no limits will be
+imposed on the user's traffic.
+ """),
required=False,
initial=False
)
+
class ThemeChangeForm(forms.Form):
theme = forms.ChoiceField(
label=_('Theme'),
- choices = THEME_CHOICES
+ choices=THEME_CHOICES
)
+
class IP4PortForwardForm(forms.ModelForm):
class Meta:
model = IP4PortForward
- fields = ('host', 'protocol', 'port', 'external_port')
-
+ fields = ('label', 'host', 'protocol', 'port', 'external_port')
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
View
82 tollgate/frontend/management/commands/dhcp_script.py
@@ -0,0 +1,82 @@
+"""
+tollgate management command: dhcp notify script
+Copyright 2008-2012 Michael Farrell
+
+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/>.
+"""
+
+from django.core.management.base import BaseCommand, CommandError
+from tollgate.frontend.models import sync_user_connections, NetworkHost, utcnow
+from django.core.exceptions import ObjectDoesNotExist
+
+
+class Command(BaseCommand):
+ args = '<add|del> <mac address> <ip> [hostname]'
+ help = """
+ Recieves notifications of changes to DHCP servers.
+
+ Accepts options in the same format as dnsmasq's dhcp-script option.
+
+ Also can be used with ISC DHCPd with some tricks.
+ """
+
+ def handle(self, *args, **options):
+ if len(args) < 3:
+ print "Error: you must supply at least three arguments."
+ return
+
+ action = args[0].strip().lower()
+ mac = ''
+ ip = args[2].strip()
+ hostname = args[3].strip() if len(args) >= 4 else ''
+
+ for c in args[1].strip().lower():
+ # remove non-hex characters.
+ if c in '0123456789abcdef':
+ mac += c
+
+ if len(mac) != 12:
+ # not the right number of characters.
+ print "Error: MAC address must be 6 base16 encoded bytes (12 bytes" + \
+ " total)."
+ print mac
+ return
+
+ # TODO: Validate IP address
+
+ if action in ('add', 'del'):
+ try:
+ # see if host exists
+ host = NetworkHost.objects.get(mac_address__iexact=mac)
+ host.ip_address = ip
+ host.online = action == 'add'
+ host.computer_name = hostname
+ host.save()
+ except ObjectDoesNotExist:
+ # create new host entry
+ host = NetworkHost.objects.create(
+ mac_address=mac,
+ computer_name=hostname,
+ first_connection=utcnow(),
+ online=action == 'add',
+ ip_address=ip
+ )
+ # now refresh the owner's hosts, if any.
+ if host.user_profile != None:
+ sync_user_connections(host.user_profile)
+ else:
+ print "Error: unknown action %r" % action
+ return
+
+
View
8 tollgate/frontend/management/commands/refresh_hosts.py
@@ -17,11 +17,15 @@
"""
from django.core.management.base import BaseCommand, CommandError
-from tollgate.frontend.models import get_portalapi, refresh_networkhost, apply_ip4portforwards, refresh_all_quota_usage
+from tollgate.frontend.models import get_portalapi, refresh_networkhost, \
+ apply_ip4portforwards, refresh_all_quota_usage
+
class Command(BaseCommand):
args = ''
- help = 'Refreshes data about hosts on the network and accounting data. Run this from crontab every 10 minutes.'
+ help = 'Refreshes data about hosts on the network and accounting data. ' +\
+ 'Run this from crontab every 10 minutes.'
+
def handle(self, *args, **options):
# refresh information about networkhosts and quota for crontab.
portal = get_portalapi()
View
146 tollgate/frontend/migrations/0004_auto__add_field_ip4portforward_label.py
@@ -0,0 +1,146 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding field 'IP4PortForward.label'
+ db.add_column('frontend_ip4portforward', 'label',
+ self.gf('django.db.models.fields.CharField')(default='', max_length=64, blank=True),
+ keep_default=False)
+
+ def backwards(self, orm):
+ # Deleting field 'IP4PortForward.label'
+ db.delete_column('frontend_ip4portforward', 'label')
+
+ models = {
+ 'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'frontend.event': {
+ 'Meta': {'ordering': "['start']", 'object_name': 'Event'},
+ 'end': ('django.db.models.fields.DateTimeField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '50'}),
+ 'start': ('django.db.models.fields.DateTimeField', [], {})
+ },
+ 'frontend.eventattendance': {
+ 'Meta': {'ordering': "['event', 'user_profile']", 'object_name': 'EventAttendance'},
+ 'coffee': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'event': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['frontend.Event']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'quota_amount': ('django.db.models.fields.BigIntegerField', [], {'default': '157286400L'}),
+ 'quota_multiplier': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
+ 'quota_unmetered': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'quota_used': ('django.db.models.fields.BigIntegerField', [], {'default': '0'}),
+ 'registered_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'registered_by'", 'null': 'True', 'to': "orm['frontend.UserProfile']"}),
+ 'registered_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'user_profile': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['frontend.UserProfile']"})
+ },
+ 'frontend.ip4portforward': {
+ 'Meta': {'object_name': 'IP4PortForward'},
+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'creator': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['frontend.UserProfile']"}),
+ 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'external_port': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'host': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['frontend.NetworkHost']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'label': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '64', 'blank': 'True'}),
+ 'port': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'protocol': ('django.db.models.fields.related.ForeignKey', [], {'default': '6', 'to': "orm['frontend.IP4Protocol']"})
+ },
+ 'frontend.ip4protocol': {
+ 'Meta': {'ordering': "['name']", 'object_name': 'IP4Protocol'},
+ 'description': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
+ 'has_port': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '16'})
+ },
+ 'frontend.networkhost': {
+ 'Meta': {'ordering': "['mac_address']", 'object_name': 'NetworkHost'},
+ 'computer_name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'first_connection': ('django.db.models.fields.DateTimeField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'}),
+ 'mac_address': ('django.db.models.fields.CharField', [], {'max_length': '12'}),
+ 'online': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'user_profile': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['frontend.UserProfile']", 'null': 'True', 'blank': 'True'})
+ },
+ 'frontend.networkhostownerchangeevent': {
+ 'Meta': {'ordering': "['when']", 'object_name': 'NetworkHostOwnerChangeEvent'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'network_host': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['frontend.NetworkHost']"}),
+ 'new_owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'new_owner'", 'null': 'True', 'to': "orm['frontend.UserProfile']"}),
+ 'old_owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'old_owner'", 'null': 'True', 'to': "orm['frontend.UserProfile']"}),
+ 'when': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
+ },
+ 'frontend.networkusagedatapoint': {
+ 'Meta': {'ordering': "['event_attendance', 'when']", 'object_name': 'NetworkUsageDataPoint'},
+ 'bytes': ('django.db.models.fields.BigIntegerField', [], {}),
+ 'event_attendance': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['frontend.EventAttendance']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'when': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
+ },
+ 'frontend.oui': {
+ 'Meta': {'ordering': "['hex']", 'object_name': 'Oui'},
+ 'full_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'hex': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '6'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_console': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'})
+ },
+ 'frontend.quotaresetevent': {
+ 'Meta': {'ordering': "['when']", 'object_name': 'QuotaResetEvent'},
+ 'event_attendance': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['frontend.EventAttendance']"}),
+ 'excuse': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'performer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'performer'", 'to': "orm['frontend.UserProfile']"}),
+ 'when': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
+ },
+ 'frontend.userprofile': {
+ 'Meta': {'ordering': "['user__username']", 'object_name': 'UserProfile'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'internet_on': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'theme': ('django.db.models.fields.CharField', [], {'default': "'cake'", 'max_length': '30'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user'", 'unique': 'True', 'to': "orm['auth.User']"})
+ }
+ }
+
+ complete_apps = ['frontend']
View
156 tollgate/frontend/migrations/0005_auto__add_field_userprofile_maximum_quota_signins__add_field_userprofi.py
@@ -0,0 +1,156 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding field 'UserProfile.maximum_quota_signins'
+ db.add_column('frontend_userprofile', 'maximum_quota_signins',
+ self.gf('django.db.models.fields.PositiveIntegerField')(default=0),
+ keep_default=False)
+
+ # Adding field 'UserProfile.maximum_quota_resets'
+ db.add_column('frontend_userprofile', 'maximum_quota_resets',
+ self.gf('django.db.models.fields.PositiveIntegerField')(default=0),
+ keep_default=False)
+
+ def backwards(self, orm):
+ # Deleting field 'UserProfile.maximum_quota_signins'
+ db.delete_column('frontend_userprofile', 'maximum_quota_signins')
+
+ # Deleting field 'UserProfile.maximum_quota_resets'
+ db.delete_column('frontend_userprofile', 'maximum_quota_resets')
+
+ models = {
+ 'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'frontend.event': {
+ 'Meta': {'ordering': "['start']", 'object_name': 'Event'},
+ 'end': ('django.db.models.fields.DateTimeField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '50'}),
+ 'start': ('django.db.models.fields.DateTimeField', [], {})
+ },
+ 'frontend.eventattendance': {
+ 'Meta': {'ordering': "['event', 'user_profile']", 'object_name': 'EventAttendance'},
+ 'coffee': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'event': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['frontend.Event']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'quota_amount': ('django.db.models.fields.BigIntegerField', [], {'default': '157286400L'}),
+ 'quota_multiplier': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
+ 'quota_unmetered': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'quota_used': ('django.db.models.fields.BigIntegerField', [], {'default': '0'}),
+ 'registered_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'registered_by'", 'null': 'True', 'to': "orm['frontend.UserProfile']"}),
+ 'registered_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'user_profile': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['frontend.UserProfile']"})
+ },
+ 'frontend.ip4portforward': {
+ 'Meta': {'object_name': 'IP4PortForward'},
+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'creator': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['frontend.UserProfile']"}),
+ 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'external_port': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'host': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['frontend.NetworkHost']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'label': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '64', 'blank': 'True'}),
+ 'port': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'protocol': ('django.db.models.fields.related.ForeignKey', [], {'default': '6', 'to': "orm['frontend.IP4Protocol']"})
+ },
+ 'frontend.ip4protocol': {
+ 'Meta': {'ordering': "['name']", 'object_name': 'IP4Protocol'},
+ 'description': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
+ 'has_port': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '16'})
+ },
+ 'frontend.networkhost': {
+ 'Meta': {'ordering': "['mac_address']", 'object_name': 'NetworkHost'},
+ 'computer_name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'first_connection': ('django.db.models.fields.DateTimeField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'}),
+ 'mac_address': ('django.db.models.fields.CharField', [], {'max_length': '12'}),
+ 'online': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'user_profile': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['frontend.UserProfile']", 'null': 'True', 'blank': 'True'})
+ },
+ 'frontend.networkhostownerchangeevent': {
+ 'Meta': {'ordering': "['when']", 'object_name': 'NetworkHostOwnerChangeEvent'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'network_host': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['frontend.NetworkHost']"}),
+ 'new_owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'new_owner'", 'null': 'True', 'to': "orm['frontend.UserProfile']"}),
+ 'old_owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'old_owner'", 'null': 'True', 'to': "orm['frontend.UserProfile']"}),
+ 'when': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
+ },
+ 'frontend.networkusagedatapoint': {
+ 'Meta': {'ordering': "['event_attendance', 'when']", 'object_name': 'NetworkUsageDataPoint'},
+ 'bytes': ('django.db.models.fields.BigIntegerField', [], {}),
+ 'event_attendance': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['frontend.EventAttendance']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'when': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
+ },
+ 'frontend.oui': {
+ 'Meta': {'ordering': "['hex']", 'object_name': 'Oui'},
+ 'full_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'hex': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '6'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_console': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'})
+ },
+ 'frontend.quotaresetevent': {
+ 'Meta': {'ordering': "['when']", 'object_name': 'QuotaResetEvent'},
+ 'event_attendance': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['frontend.EventAttendance']"}),
+ 'excuse': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'performer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'performer'", 'to': "orm['frontend.UserProfile']"}),
+ 'when': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
+ },
+ 'frontend.userprofile': {
+ 'Meta': {'ordering': "['user__username']", 'object_name': 'UserProfile'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'internet_on': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'maximum_quota_resets': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'maximum_quota_signins': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'theme': ('django.db.models.fields.CharField', [], {'default': "'cake'", 'max_length': '30'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user'", 'unique': 'True', 'to': "orm['auth.User']"})
+ }
+ }
+
+ complete_apps = ['frontend']
View
67 tollgate/frontend/models.py
@@ -71,11 +71,40 @@ def unsigned_validator(value):
class UserProfile(Model):
class Meta:
ordering = ['user__username']
+ permissions = (
+ ('can_toggle_internet', 'Toggle internet access for users'),
+ )
user = ForeignKey(User, unique=True, related_name="user")
internet_on = BooleanField(default=True)
theme = CharField(default='cake', max_length=30, choices=THEME_CHOICES)
+ maximum_quota_signins = PositiveIntegerField(
+ default=0,
+ help_text=_("""\
+ Sets the maximum amount of quota in MiB that the user may grant to
+ users they sign in. If set to 0, they will be able to set any quota
+ amount. Setting this value disallows setting unlimited quota.
+
+ This will only have effect if the user has been granted permission
+ to sign in users. Otherwise, they will not be able to sign in users.
+ """)
+ )
+
+ maximum_quota_resets = PositiveIntegerField(
+ default=0,
+ help_text=_("""\
+ Sets the maximum amount of quota resets that the user may perform on
+ other users. If set to 0, they will be able to reset quota an
+ unlimited number of times. If set to 1, this will mean that the user
+ can only perform the "one free reset" on behalf of another user.
+
+ This will only have effect if the user has been granted permission
+ to reset user quota for other users. Otherwise, they will not be able
+ to reset quota for other users at all.
+ """)
+ )
+
def get_hosts(self):
return NetworkHost.objects.filter(user_profile=self)
@@ -103,9 +132,9 @@ class NetworkHost(Model):
class Meta:
ordering = ['mac_address']
permissions = (
- ("can_view_ownership", "May see who owns a specific computer."),
+ ("can_view_ownership", "View ownership of host"),
)
- mac_address = CharField(max_length=12)
+ mac_address = CharField(max_length=12, unique=True)
ip_address = IPAddressField()
computer_name = CharField(max_length=128)
first_connection = DateTimeField('first connection')
@@ -172,10 +201,12 @@ class EventAttendance(Model):
class Meta:
ordering = ['event', 'user_profile']
permissions = (
- ("can_register_attendance", "May use the event attendance registration system."),
- ("can_view_quota", "May view quota usage summaries."),
- ("can_reset_quota", "May reset quota usage."),
- ("can_change_coffee", "May change who has access to send coffee requests."), # this is a seperate ACL because of ravenge and dasman
+ ("can_register_attendance", "Register event attendance"),
+ ("can_view_quota", "View quota"),
+ ("can_reset_quota", "Reset quota"),
+ ("can_change_coffee", "Coffee request access change"), # this is a seperate ACL because of ravenge and dasman
+ ('can_reset_own_quota', 'Reset own quota multiple times'),
+ ('can_revoke_access', 'Revoke internet access for a user'),
)
event = ForeignKey(Event)
user_profile = ForeignKey(UserProfile)
@@ -346,15 +377,16 @@ class IP4PortForward(Model):
class Meta:
verbose_name = 'IP4 Port Forward'
permissions = (
- ('can_ip4portforward', 'Can manage IPv4 port forwarding'),
+ ('can_ip4portforward', 'Manage IPv4 port forwarding'),
)
host = ForeignKey(NetworkHost)
- protocol = ForeignKey(IP4Protocol, help_text='The IPv4 protocol that this service uses. If you don\'t know, try TCP or UDP.')
+ protocol = ForeignKey(IP4Protocol, default=6, help_text='The IPv4 protocol that this service uses. If you don\'t know, try TCP or UDP.')
port = PositiveIntegerField(default=0, help_text='The internal port that the service is running on. This has no effect if the protocol does not have port numbers.')
external_port = PositiveIntegerField(default=0, help_text='The external port that this service should show as running on. This has no effect if the protocol does not have port numbers.')
creator = ForeignKey(UserProfile)
created = DateTimeField(auto_now_add=True)
enabled = BooleanField(blank=True, default=True)
+ label = CharField(max_length=64, help_text="Optional description of the port forward", blank=True, default="")
def __unicode__(self):
return u'%s %s/%s->%s' % (self.host, self.protocol.name, self.external_port, self.port)
@@ -494,10 +526,13 @@ def sync_user_connections(profile, portal=None):
if portal == None:
portal = get_portalapi()
- portal.flush(user_id)
-
# find all the user's computers.
- hosts = NetworkHost.objects.filter(user_profile__exact=profile, online__exact=True)
+ hosts = list(NetworkHost.objects.filter(
+ user_profile__exact=profile, online__exact=True
+ ).only('mac_address', 'ip_address'))
+
+ # flush the
+ portal.flush(user_id)
for host in hosts:
if in_lan_subnet(host.ip_address) and (not settings.ONLY_CONSOLE or host.is_console()):
@@ -506,7 +541,13 @@ def sync_user_connections(profile, portal=None):
def refresh_quota_usage(event_attendance, portal=None):
if portal == None:
portal = get_portalapi()
-
+
+ event = get_current_event()
+
+ if event != event_attendance.event:
+ # not part of this event
+ return False
+
r = portal.get_quota(event_attendance.user_profile.user.id)
if r != None:
# now set counters
@@ -518,6 +559,8 @@ def refresh_quota_usage(event_attendance, portal=None):
event_attendance = event_attendance,
bytes = event_attendance.quota_used
)
+
+ return True
def refresh_all_quota_usage(portal=None):
if portal == None:
View
6 tollgate/frontend/platform/__init__.py
@@ -19,11 +19,15 @@
from platform import system
import warnings
+from django.utils.translation import ugettext as _
system = system().lower()
from tollgate.frontend.platform.common import *
if system == 'linux':
from tollgate.frontend.platform.linux import *
else:
- warnings.warn('Platform %r is unsupported. Some OS-specific functionality will not work.' % system, UserWarning)
+ warnings.warn(_("""\
+Platform %(platform)s is unsupported. Some OS-specific functionality will not work.
+""") % dict(platform=system), UserWarning)
from tollgate.frontend.platform.dummy import *
+
View
7 tollgate/frontend/platform/common.py
@@ -16,20 +16,25 @@
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/>.
"""
+
from django.conf import settings
+
+
try:
from iplib import CIDR
except ImportError:
CIDR = None
import IPy
-
+
if CIDR:
LAN_CIDR = CIDR(settings.LAN_SUBNET)
else:
LAN_CIDR = IPy.IP(settings.LAN_SUBNET)
+
def in_lan_subnet(ip):
if CIDR:
return LAN_CIDR.is_valid_ip(ip)
else:
return IPy.IP(ip) in LAN_CIDR
+
View
5 tollgate/frontend/platform/dummy.py
@@ -17,12 +17,15 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
+
def get_arp_cache():
return {}
+
def get_ip_address(mac):
return None
-
+
+
def get_mac_address(ip):
return None
View
28 tollgate/frontend/platform/linux.py
@@ -19,6 +19,7 @@
from tollgate.frontend.platform.common import *
+
def get_ip_address(mac):
fh = open('/proc/net/arp', 'r')
# skip header
@@ -29,7 +30,8 @@ def get_ip_address(mac):
# try active entries first
for l in d:
a = l.split()
- if a[3] == mac and a[5] == settings.LAN_IFACE and in_lan_subnet(a[0]) and a[2] == "0x2":
+ if a[3] == mac and a[5] == settings.LAN_IFACE and in_lan_subnet(a[0]) \
+ and a[2] == "0x2":
return a[0]
# try expired ones.
@@ -39,6 +41,7 @@ def get_ip_address(mac):
return a[0]
return None
+
def get_arp_cache():
fh = open('/proc/net/arp', 'r')
# skip header
@@ -52,19 +55,22 @@ def get_arp_cache():
# expired entries first, so they get overwritten by active ones.
for l in d:
a = l.split()
- if a[3] != '00:00:00:00:00:00' and a[5] == settings.LAN_IFACE and in_lan_subnet(a[0]) and a[2] == "0x0":
- mac = a[3].replace(":","")
+ if a[3] != '00:00:00:00:00:00' and a[5] == settings.LAN_IFACE and \
+ in_lan_subnet(a[0]) and a[2] == "0x0":
+ mac = a[3].replace(":", "")
o[a[0]] = mac
# active entries overwrite expired ones.
for l in d:
a = l.split()
- if a[3] != '00:00:00:00:00:00' and a[5] == settings.LAN_IFACE and in_lan_subnet(a[0]) and a[2] == "0x2":