|
| 1 | +<?php |
| 2 | + |
| 3 | +namespace RESTAPI\ModelTraits; |
| 4 | + |
| 5 | +require_once 'RESTAPI/autoloader.inc'; |
| 6 | + |
| 7 | +use RESTAPI\Fields\IntegerField; |
| 8 | +use RESTAPI\Fields\StringField; |
| 9 | +use RESTAPI\Validators\IPAddressValidator; |
| 10 | +use RESTAPI\Validators\RegexValidator; |
| 11 | +use RESTAPI\Validators\URLValidator; |
| 12 | + |
| 13 | +/** |
| 14 | + * Defines a set of Field declarations and helpers that are shared between the FreeRADIUS user-style Models |
| 15 | + * (FreeRADIUSUser, FreeRADIUSMAC). Both pfSense FreeRADIUS user and authorized-MAC entries expose the same |
| 16 | + * set of RADIUS attribute fields (description, framed addressing, VLAN, time/quota, bandwidth, additional |
| 17 | + * RADIUS attributes, etc.) — the only difference is the internal pfSense config key prefix |
| 18 | + * (`varusers...` vs `varmacs...`) and a small naming quirk on the WISPr redirection URL field. |
| 19 | + */ |
| 20 | +trait FreeRADIUSCommonModelTraits { |
| 21 | + public StringField $description; |
| 22 | + public StringField $framed_ip_address; |
| 23 | + public StringField $framed_ip_netmask; |
| 24 | + public StringField $framed_route; |
| 25 | + public StringField $framed_ipv6_address; |
| 26 | + public StringField $framed_ipv6_route; |
| 27 | + public StringField $vlan_id; |
| 28 | + public StringField $wispr_redirection_url; |
| 29 | + public IntegerField $simultaneous_connect; |
| 30 | + public StringField $expiration; |
| 31 | + public IntegerField $session_timeout; |
| 32 | + public StringField $login_time; |
| 33 | + public IntegerField $amount_of_time; |
| 34 | + public StringField $point_of_time; |
| 35 | + public IntegerField $max_total_octets; |
| 36 | + public StringField $max_total_octets_time_range; |
| 37 | + public IntegerField $max_bandwidth_down; |
| 38 | + public IntegerField $max_bandwidth_up; |
| 39 | + public IntegerField $acct_interim_interval; |
| 40 | + public StringField $top_additional_options; |
| 41 | + public StringField $check_items_additional_options; |
| 42 | + public StringField $reply_items_additional_options; |
| 43 | + |
| 44 | + /** |
| 45 | + * Defines the set of Fields that are shared between the FreeRADIUSUser and FreeRADIUSMAC models. |
| 46 | + * |
| 47 | + * @param string $prefix The internal pfSense config key prefix used by the consuming Model |
| 48 | + * (e.g. `varusers` for users or `varmacs` for MAC entries). |
| 49 | + * @param string $url_field_suffix The suffix appended to $prefix to compute the internal_name |
| 50 | + * for the WISPr redirection URL field. The pfSense package uses `wisprredirectionurl` for |
| 51 | + * users and the (typo'd) `swisprredirectionurl` for MACs, so this is overridable. |
| 52 | + */ |
| 53 | + protected function init_freeradius_common_fields( |
| 54 | + string $prefix, |
| 55 | + string $url_field_suffix = 'wisprredirectionurl', |
| 56 | + ): void { |
| 57 | + $this->description = new StringField( |
| 58 | + required: false, |
| 59 | + default: '', |
| 60 | + allow_empty: true, |
| 61 | + verbose_name: 'Description', |
| 62 | + validators: [ |
| 63 | + new RegexValidator( |
| 64 | + pattern: '/^[a-zA-Z0-9 _,.;:+=()-]*$/', |
| 65 | + error_msg: 'Value contains invalid characters.', |
| 66 | + ), |
| 67 | + ], |
| 68 | + help_text: 'A description for this entry.', |
| 69 | + ); |
| 70 | + $this->framed_ip_address = new StringField( |
| 71 | + required: false, |
| 72 | + default: '', |
| 73 | + allow_empty: true, |
| 74 | + verbose_name: 'Framed IP Address', |
| 75 | + internal_name: "{$prefix}framedipaddress", |
| 76 | + validators: [new IPAddressValidator(allow_ipv4: true, allow_ipv6: false)], |
| 77 | + help_text: 'Framed-IP-Address MUST be supported by NAS. ' . |
| 78 | + 'If the OpenVPN server uses a subnet style Topology the RADIUS server MUST ' . |
| 79 | + 'also send back an appropriate Framed-IP-Netmask value matching the VPN Tunnel Network.', |
| 80 | + ); |
| 81 | + $this->framed_ip_netmask = new StringField( |
| 82 | + required: false, |
| 83 | + default: '', |
| 84 | + allow_empty: true, |
| 85 | + verbose_name: 'Framed IP Netmask', |
| 86 | + internal_name: "{$prefix}framedipnetmask", |
| 87 | + validators: [new IPAddressValidator(allow_ipv4: true, allow_ipv6: false)], |
| 88 | + help_text: 'Framed-IP-Netmask MUST be supported by NAS.', |
| 89 | + ); |
| 90 | + $this->framed_route = new StringField( |
| 91 | + required: false, |
| 92 | + default: '', |
| 93 | + allow_empty: true, |
| 94 | + verbose_name: 'IPv4 Gateway', |
| 95 | + internal_name: "{$prefix}framedroute", |
| 96 | + help_text: 'Framed-Route must be supported by NAS. Required format: ' . |
| 97 | + 'Subnet Gateway Metric(s) (e.g. 192.168.10.0/24 192.168.10.1 1).', |
| 98 | + ); |
| 99 | + $this->framed_ipv6_address = new StringField( |
| 100 | + required: false, |
| 101 | + default: '', |
| 102 | + allow_empty: true, |
| 103 | + verbose_name: 'IPv6 Address', |
| 104 | + internal_name: "{$prefix}framedip6address", |
| 105 | + help_text: 'When the IPv6 prefix part is empty it uses Framed-IPv6-Address. ' . |
| 106 | + 'When the prefix part is filled in, it uses Framed-IPv6-Prefix. ' . |
| 107 | + 'Example: 2001:db8:abab::5 or 2001:db8:abab::/64', |
| 108 | + ); |
| 109 | + $this->framed_ipv6_route = new StringField( |
| 110 | + required: false, |
| 111 | + default: '', |
| 112 | + allow_empty: true, |
| 113 | + verbose_name: 'IPv6 Gateway', |
| 114 | + internal_name: "{$prefix}framedip6route", |
| 115 | + help_text: 'Framed-IPv6-Route must be supported by NAS. Required format: ' . |
| 116 | + 'Prefix Gateway Metric(s) (e.g. 2001:db8:0:16::/64 2001:db8::16:a0:20ff:fe99:a998 1).', |
| 117 | + ); |
| 118 | + $this->vlan_id = new StringField( |
| 119 | + required: false, |
| 120 | + default: '', |
| 121 | + allow_empty: true, |
| 122 | + verbose_name: 'VLAN ID', |
| 123 | + internal_name: "{$prefix}vlanid", |
| 124 | + help_text: 'The VLAN ID (integer from 1-4095) or the VLAN name that this entry should be assigned to. ' . |
| 125 | + 'Must be supported by the NAS.', |
| 126 | + ); |
| 127 | + $this->wispr_redirection_url = new StringField( |
| 128 | + required: false, |
| 129 | + default: '', |
| 130 | + allow_empty: true, |
| 131 | + verbose_name: 'Redirection URL', |
| 132 | + internal_name: "{$prefix}{$url_field_suffix}", |
| 133 | + validators: [new URLValidator()], |
| 134 | + help_text: 'The URL the user should be redirected to after successful login. ' . |
| 135 | + 'Example: http://www.google.com', |
| 136 | + ); |
| 137 | + $this->simultaneous_connect = new IntegerField( |
| 138 | + required: false, |
| 139 | + default: null, |
| 140 | + allow_null: true, |
| 141 | + minimum: 0, |
| 142 | + verbose_name: 'Simultaneous Connections', |
| 143 | + internal_name: "{$prefix}simultaneousconnect", |
| 144 | + help_text: 'The maximum number of simultaneous connections with this entry. Leave null for no limit. ' . |
| 145 | + 'If using FreeRADIUS with Captive Portal you should leave this null.', |
| 146 | + ); |
| 147 | + $this->expiration = new StringField( |
| 148 | + required: false, |
| 149 | + default: '', |
| 150 | + allow_empty: true, |
| 151 | + verbose_name: 'Expiration Date', |
| 152 | + internal_name: "{$prefix}expiration", |
| 153 | + help_text: 'The date when this account should expire. Required format: Mmm dd yyyy (e.g. Jan 01 2030).', |
| 154 | + ); |
| 155 | + $this->session_timeout = new IntegerField( |
| 156 | + required: false, |
| 157 | + default: null, |
| 158 | + allow_null: true, |
| 159 | + minimum: 0, |
| 160 | + verbose_name: 'Session Timeout', |
| 161 | + internal_name: "{$prefix}sessiontimeout", |
| 162 | + help_text: 'The time this entry has until relogin (in seconds).', |
| 163 | + ); |
| 164 | + $this->login_time = new StringField( |
| 165 | + required: false, |
| 166 | + default: '', |
| 167 | + allow_empty: true, |
| 168 | + verbose_name: 'Possible Login Times', |
| 169 | + internal_name: "{$prefix}logintime", |
| 170 | + validators: [ |
| 171 | + new RegexValidator( |
| 172 | + pattern: '/^[a-zA-Z0-9,|-]*$/', |
| 173 | + error_msg: 'Value may only contain a-z, A-Z, 0-9, comma, vertical bar and hyphen.', |
| 174 | + ), |
| 175 | + ], |
| 176 | + help_text: 'The time when this entry should have access. Empty for no time restriction. ' . |
| 177 | + 'Example: Wk0855-2305,Sa|Su2230-0230 ' . |
| 178 | + '(weekdays after 8:55 AM and before 11:05 PM | any time on Saturday | ' . |
| 179 | + 'Sunday after 10:30 PM and before 02:30 AM).', |
| 180 | + ); |
| 181 | + $this->amount_of_time = new IntegerField( |
| 182 | + required: false, |
| 183 | + default: null, |
| 184 | + allow_null: true, |
| 185 | + minimum: 0, |
| 186 | + verbose_name: 'Amount of Time', |
| 187 | + internal_name: "{$prefix}amountoftime", |
| 188 | + help_text: 'The amount of time this entry is allowed (in minutes) within the configured time period.', |
| 189 | + ); |
| 190 | + $this->point_of_time = new StringField( |
| 191 | + required: false, |
| 192 | + default: 'Daily', |
| 193 | + choices: ['Daily', 'Weekly', 'Monthly', 'Forever'], |
| 194 | + verbose_name: 'Time Period', |
| 195 | + internal_name: "{$prefix}pointoftime", |
| 196 | + help_text: "The time period after which the 'Amount of Time' is reset.", |
| 197 | + ); |
| 198 | + $this->max_total_octets = new IntegerField( |
| 199 | + required: false, |
| 200 | + default: null, |
| 201 | + allow_null: true, |
| 202 | + minimum: 0, |
| 203 | + verbose_name: 'Max Total Octets', |
| 204 | + internal_name: "{$prefix}maxtotaloctets", |
| 205 | + help_text: 'The amount of download and upload traffic (summarized) in megabytes (MB) for this entry. ' . |
| 206 | + 'If using captive portal without periodic reauthentication enabled, this value must not exceed ' . |
| 207 | + '4095 due to protocol limitations.', |
| 208 | + ); |
| 209 | + $this->max_total_octets_time_range = new StringField( |
| 210 | + required: false, |
| 211 | + default: 'daily', |
| 212 | + choices: ['daily', 'weekly', 'monthly', 'forever'], |
| 213 | + verbose_name: 'Max Total Octets Time Range', |
| 214 | + internal_name: "{$prefix}maxtotaloctetstimerange", |
| 215 | + help_text: 'The time period for the amount of download and upload traffic. ' . |
| 216 | + 'This does not automatically reset the counter; you must configure a cronjob to reset it.', |
| 217 | + ); |
| 218 | + $this->max_bandwidth_down = new IntegerField( |
| 219 | + required: false, |
| 220 | + default: null, |
| 221 | + allow_null: true, |
| 222 | + minimum: 0, |
| 223 | + maximum: 4294967, |
| 224 | + verbose_name: 'Max Bandwidth Down', |
| 225 | + internal_name: "{$prefix}maxbandwidthdown", |
| 226 | + help_text: 'The maximum bandwidth for download in kilobits (1000 bits) per second (Kbit/s).', |
| 227 | + ); |
| 228 | + $this->max_bandwidth_up = new IntegerField( |
| 229 | + required: false, |
| 230 | + default: null, |
| 231 | + allow_null: true, |
| 232 | + minimum: 0, |
| 233 | + maximum: 4294967, |
| 234 | + verbose_name: 'Max Bandwidth Up', |
| 235 | + internal_name: "{$prefix}maxbandwidthup", |
| 236 | + help_text: 'The maximum bandwidth for upload in kilobits (1000 bits) per second (Kbit/s).', |
| 237 | + ); |
| 238 | + $this->acct_interim_interval = new IntegerField( |
| 239 | + required: false, |
| 240 | + default: null, |
| 241 | + allow_null: true, |
| 242 | + minimum: 61, |
| 243 | + verbose_name: 'Accounting Interim Interval', |
| 244 | + internal_name: "{$prefix}acctinteriminterval", |
| 245 | + help_text: 'The interval in seconds which should elapse between interim-updates. ' . |
| 246 | + 'Must be more than 60s and should not be less than 600s.', |
| 247 | + ); |
| 248 | + $this->top_additional_options = new StringField( |
| 249 | + required: false, |
| 250 | + default: [], |
| 251 | + allow_empty: true, |
| 252 | + many: true, |
| 253 | + delimiter: '|', |
| 254 | + verbose_name: 'Additional Top Options', |
| 255 | + internal_name: "{$prefix}topadditionaloptions", |
| 256 | + help_text: 'Additional RADIUS attributes placed at the TOP of this entry. ' . |
| 257 | + 'Each list entry becomes a separate line. For experts only.', |
| 258 | + ); |
| 259 | + $this->check_items_additional_options = new StringField( |
| 260 | + required: false, |
| 261 | + default: [], |
| 262 | + allow_empty: true, |
| 263 | + many: true, |
| 264 | + delimiter: '|', |
| 265 | + verbose_name: 'Additional Check-Item Options', |
| 266 | + internal_name: "{$prefix}checkitemsadditionaloptions", |
| 267 | + help_text: 'Additional RADIUS check-item attributes for this entry. ' . |
| 268 | + 'Each list entry becomes a separate attribute. For experts only.', |
| 269 | + ); |
| 270 | + $this->reply_items_additional_options = new StringField( |
| 271 | + required: false, |
| 272 | + default: [], |
| 273 | + allow_empty: true, |
| 274 | + many: true, |
| 275 | + delimiter: '|', |
| 276 | + verbose_name: 'Additional Reply-Item Options', |
| 277 | + internal_name: "{$prefix}replyitemsadditionaloptions", |
| 278 | + help_text: 'Additional RADIUS reply-item attributes for this entry. ' . |
| 279 | + 'Each list entry becomes a separate attribute. For experts only.', |
| 280 | + ); |
| 281 | + } |
| 282 | +} |
0 commit comments