diff --git a/openwisp_radius/api/freeradius_views.py b/openwisp_radius/api/freeradius_views.py index 252a7d6b..dd90594d 100644 --- a/openwisp_radius/api/freeradius_views.py +++ b/openwisp_radius/api/freeradius_views.py @@ -304,14 +304,15 @@ def get_replies(self, user, organization_id): group_checks = get_group_checks(user_group.group) - for counter in app_settings.COUNTERS: - group_check = group_checks.get(counter.check_name) + for Counter in app_settings.COUNTERS: + group_check = group_checks.get(Counter.check_name) if not group_check: continue try: - remaining = counter( + counter = Counter( user=user, group=user_group.group, group_check=group_check - ).check() + ) + remaining = counter.check() except SkipCheck: continue # if max is reached send access rejected + reply message @@ -325,14 +326,15 @@ def get_replies(self, user, organization_id): continue if remaining is None: continue + reply_name = counter.reply_name # send remaining value in RADIUS reply, if needed. # This emulates the implementation of sqlcounter in freeradius # which sends the reply message only if the value is smaller # than what was defined to a previous reply message - if counter.reply_name not in data or remaining < self._get_reply_value( + if reply_name not in data or remaining < self._get_reply_value( data, counter ): - data[counter.reply_name] = remaining + data[reply_name] = remaining return data, self.accept_status diff --git a/openwisp_radius/api/serializers.py b/openwisp_radius/api/serializers.py index 0245d5ab..b8f24bcb 100644 --- a/openwisp_radius/api/serializers.py +++ b/openwisp_radius/api/serializers.py @@ -285,12 +285,15 @@ class Meta: def get_result(self, obj): try: - counter = app_settings.CHECK_ATTRIBUTE_COUNTERS_MAP[obj.attribute] - remaining = counter( + Counter = app_settings.CHECK_ATTRIBUTE_COUNTERS_MAP[obj.attribute] + counter = Counter( user=self.context['user'], group=self.context['group'], group_check=obj, - ).check() + ) + # Python can handle 64 bit numbers and + # hence we don't need to display Gigawords + remaining = counter.check(gigawords=False) return int(obj.value) - remaining except MaxQuotaReached: return int(obj.value) diff --git a/openwisp_radius/counters/base.py b/openwisp_radius/counters/base.py index 238c2a91..086a678f 100644 --- a/openwisp_radius/counters/base.py +++ b/openwisp_radius/counters/base.py @@ -43,6 +43,7 @@ def get_sql_params(self, start_time, end_time): # pragma: no cover # sqlcounter module, now we can translate it with gettext # or customize it (in new counter classes) if needed reply_message = _('Your maximum daily usage time has been reached') + gigawords = False def __init__(self, user, group, group_check): self.user = user @@ -92,7 +93,7 @@ def get_counter(self): # or if nothing is returned (no sessions present), return zero return row[0] or 0 - def check(self): + def check(self, gigawords=gigawords): if not self.group_check: raise SkipCheck( message=( diff --git a/openwisp_radius/radclient/dictionary b/openwisp_radius/radclient/dictionary index a3f186cc..92523463 100644 --- a/openwisp_radius/radclient/dictionary +++ b/openwisp_radius/radclient/dictionary @@ -143,43 +143,107 @@ VALUE NAS-Port-Type Cable 17 VALUE NAS-Port-Type Wireless-Other 18 VALUE NAS-Port-Type Wireless-802.11 19 -# -*- text -*- -# Copyright (C) 2019 The FreeRADIUS Server project and contributors -# This work is licensed under CC-BY version 4.0 https://creativecommons.org/licenses/by/4.0 -# -############################################################################## # -# ChilliSpot (and CoovaChilli) captive portal -# http://www.chillispot.org -# http://coova.org/wiki/index.php/CoovaChilli +# CoovaChilli captive portal +# http://coova.github.io/ # -# $Id: c086a0790f2151b24f0d64c3e466eba18643891e $ -# -############################################################################## - -VENDOR ChilliSpot 14559 - -BEGIN-VENDOR ChilliSpot - -ATTRIBUTE ChilliSpot-Max-Input-Octets 1 integer -ATTRIBUTE ChilliSpot-Max-Output-Octets 2 integer -ATTRIBUTE ChilliSpot-Max-Total-Octets 3 integer -ATTRIBUTE ChilliSpot-Bandwidth-Max-Up 4 integer -ATTRIBUTE ChilliSpot-Bandwidth-Max-Down 5 integer -ATTRIBUTE ChilliSpot-Config 6 string -ATTRIBUTE ChilliSpot-Lang 7 string -ATTRIBUTE ChilliSpot-Version 8 string -ATTRIBUTE ChilliSpot-OriginalURL 9 string -# Configuration management parameters (ChilliSpot Only) -ATTRIBUTE ChilliSpot-UAM-Allowed 100 string -ATTRIBUTE ChilliSpot-MAC-Allowed 101 string -ATTRIBUTE ChilliSpot-Interval 102 integer +VENDOR CoovaChilli 14559 + +BEGIN-VENDOR CoovaChilli + +ATTRIBUTE CoovaChilli-Max-Input-Octets 1 integer +ATTRIBUTE CoovaChilli-Max-Output-Octets 2 integer +ATTRIBUTE CoovaChilli-Max-Total-Octets 3 integer +ATTRIBUTE CoovaChilli-Bandwidth-Max-Up 4 integer +ATTRIBUTE CoovaChilli-Bandwidth-Max-Down 5 integer +ATTRIBUTE CoovaChilli-Config 6 string +ATTRIBUTE CoovaChilli-Lang 7 string +ATTRIBUTE CoovaChilli-Version 8 string +ATTRIBUTE CoovaChilli-OriginalURL 9 string +ATTRIBUTE CoovaChilli-Acct-View-Point 10 integer + +VALUE CoovaChilli-Acct-View-Point CoovaChilli-NAS-View-Point 1 +VALUE CoovaChilli-Acct-View-Point CoovaChilli-Client-View-Point 2 + +ATTRIBUTE CoovaChilli-Require-UAM 11 string +ATTRIBUTE CoovaChilli-Require-Splash 12 string +ATTRIBUTE CoovaChilli-Route-To-Interface 13 string +ATTRIBUTE CoovaChilli-Config-File 14 string + +ATTRIBUTE CoovaChilli-Session-State 15 integer + +VALUE CoovaChilli-Session-State Authorized 1 +VALUE CoovaChilli-Session-State NotAuthorized 2 +VALUE CoovaChilli-Session-State Started 3 +VALUE CoovaChilli-Session-State Stopped 4 +VALUE CoovaChilli-Session-State UserLogoutUrl 10 +VALUE CoovaChilli-Session-State IdleTimeoutReached 11 +VALUE CoovaChilli-Session-State TimeoutReached 12 +VALUE CoovaChilli-Session-State LogoutTimeReached 13 +VALUE CoovaChilli-Session-State InDataLimitReached 14 +VALUE CoovaChilli-Session-State OutDataLimitReached 15 +VALUE CoovaChilli-Session-State TotalDataLimitReached 16 +VALUE CoovaChilli-Session-State LocationChanged 17 + +ATTRIBUTE CoovaChilli-Session-Id 16 string +ATTRIBUTE CoovaChilli-AP-Session-Id 17 string +ATTRIBUTE CoovaChilli-User-Agent 18 string +ATTRIBUTE CoovaChilli-Accept-Language 19 string + +ATTRIBUTE CoovaChilli-Max-Input-Gigawords 21 integer +ATTRIBUTE CoovaChilli-Max-Output-Gigawords 22 integer +ATTRIBUTE CoovaChilli-Max-Total-Gigawords 23 integer + +ATTRIBUTE CoovaChilli-VLAN-Id 24 integer +ATTRIBUTE CoovaChilli-Location 25 string +ATTRIBUTE CoovaChilli-Old-Location 26 string +ATTRIBUTE CoovaChilli-Location-Change-Count 27 integer + +ATTRIBUTE CoovaChilli-Sys-Uptime 40 integer +ATTRIBUTE CoovaChilli-Sys-LoadAvg 41 string +ATTRIBUTE CoovaChilli-Sys-Memory 42 string + +ATTRIBUTE CoovaChilli-DHCP-Vendor-Class-Id 50 octets +ATTRIBUTE CoovaChilli-DHCP-Client-Id 51 octets +ATTRIBUTE CoovaChilli-DHCP-Options 52 octets +ATTRIBUTE CoovaChilli-DHCP-Filename 53 string +ATTRIBUTE CoovaChilli-DHCP-Hostname 54 string +ATTRIBUTE CoovaChilli-DHCP-Server-Name 55 string +ATTRIBUTE CoovaChilli-DHCP-Client-FQDN 56 string +ATTRIBUTE CoovaChilli-DHCP-Parameter-Request-List 57 octets + +ATTRIBUTE CoovaChilli-DHCP-IP-Address 60 ipaddr +ATTRIBUTE CoovaChilli-DHCP-Netmask 61 ipaddr +ATTRIBUTE CoovaChilli-DHCP-DNS1 62 ipaddr +ATTRIBUTE CoovaChilli-DHCP-DNS2 63 ipaddr +ATTRIBUTE CoovaChilli-DHCP-Gateway 64 ipaddr +ATTRIBUTE CoovaChilli-DHCP-Domain 65 string +ATTRIBUTE CoovaChilli-DHCP-Relay 66 ipaddr + +ATTRIBUTE CoovaChilli-Inject-URL 70 string + +ATTRIBUTE CoovaChilli-PostAuthProxy-Address 75 ipaddr +ATTRIBUTE CoovaChilli-PostAuthProxy-Port 76 integer + +ATTRIBUTE CoovaChilli-Garden-Input-Octets 80 integer +ATTRIBUTE CoovaChilli-Garden-Output-Octets 81 integer +ATTRIBUTE CoovaChilli-Garden-Input-Gigawords 82 integer +ATTRIBUTE CoovaChilli-Garden-Output-Gigawords 83 integer +ATTRIBUTE CoovaChilli-Other-Input-Octets 84 integer +ATTRIBUTE CoovaChilli-Other-Output-Octets 85 integer +ATTRIBUTE CoovaChilli-Other-Input-Gigawords 86 integer +ATTRIBUTE CoovaChilli-Other-Output-Gigawords 87 integer + +# Configuration management parameters (CoovaChilli Only) +ATTRIBUTE CoovaChilli-UAM-Allowed 100 string +ATTRIBUTE CoovaChilli-MAC-Allowed 101 string +ATTRIBUTE CoovaChilli-Interval 102 integer # Inline with RFC 2882 use of VSE-Authorize-Only for remote config # Note that 14559 = 0x38df is used as prefix for the VSE. # This is recognized as the best (but bad) way of doing VSEs. -# (ChilliSpot Only - CoovaChilli uses Service-Type = Administrative-User) -VALUE Service-Type ChilliSpot-Authorize-Only 0x38df0001 +# (CoovaChilli Only - CoovaChilli uses Service-Type = Administrative-User) +VALUE Service-Type CoovaChilli-Authorize-Only 0x38df0001 -END-VENDOR ChilliSpot +END-VENDOR CoovaChilli diff --git a/openwisp_radius/tasks.py b/openwisp_radius/tasks.py index aefa6e18..5423775f 100644 --- a/openwisp_radius/tasks.py +++ b/openwisp_radius/tasks.py @@ -12,6 +12,7 @@ from django.utils import timezone, translation from django.utils.translation import gettext_lazy as _ +from openwisp_radius.utils import get_group_checks, get_user_group from openwisp_utils.admin_theme.email import send_email from openwisp_utils.tasks import OpenwispCeleryTask @@ -141,18 +142,24 @@ def get_radsecret_from_radacct(rad_acct): f'Failed to parse NAS IP network for "{nas.id}" object. Skipping!' ) - def get_radius_reply_name(check): + def get_radius_reply_name_and_value(user, check): + Counter = app_settings.CHECK_ATTRIBUTE_COUNTERS_MAP[check.attribute] + counter = Counter(user=user, group=check.group, group_check=check) try: - return app_settings.CHECK_ATTRIBUTE_COUNTERS_MAP[check.attribute].reply_name + value = counter.check() + return counter.reply_name, value except KeyError: - return check.attribute + return check.attribute, check.value + except Exception as e: + logger.exception(f'Got {e} while CoA for counter {Counter}') - def get_radius_attributes(): + def get_radius_attributes(user): attributes = {} rad_group_checks = RadiusGroupCheck.objects.filter(group_id=new_group_id) if rad_group_checks: for check in rad_group_checks: - attributes[get_radius_reply_name(check)] = f'{check.value}' + reply_name, value = get_radius_reply_name_and_value(user, check) + attributes[reply_name] = f'{value}' elif ( not rad_group_checks and RadiusGroup.objects.filter(id=new_group_id).exists() @@ -161,7 +168,8 @@ def get_radius_attributes(): # Unset attributes set by the previous group. rad_group_checks = RadiusGroupCheck.objects.filter(group_id=old_group_id) for check in rad_group_checks: - attributes[get_radius_reply_name(check)] = '' + reply_name, _ = get_radius_reply_name_and_value(user, check) + attributes[reply_name] = '' return attributes try: @@ -171,7 +179,7 @@ def get_radius_attributes(): f'Failed to find user with "{user_id}" ID. Skipping CoA operation.' ) return - # Check is user has open RadiusAccounting sessions + # Check if user has open RadiusAccounting sessions open_sessions = RadiusAccounting.objects.filter( username=user.username, stop_time__isnull=True ).select_related('organization', 'organization__radius_settings') @@ -190,7 +198,7 @@ def get_radius_attributes(): ) return else: - attributes = get_radius_attributes() + attributes = get_radius_attributes(user) attributes['User-Name'] = user.username updated_sessions = []