[freeside-commits] branch master updated. a4c1077430ac3b053c30084dcf76c54be45dca08

Mark Wells mark at 420.am
Tue Dec 10 20:09:01 PST 2013


The branch, master has been updated
       via  a4c1077430ac3b053c30084dcf76c54be45dca08 (commit)
      from  c27f80ec10180391d00286bf50dfbf09a96c1b00 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit a4c1077430ac3b053c30084dcf76c54be45dca08
Author: Mark Wells <mark at freeside.biz>
Date:   Tue Dec 10 20:08:43 2013 -0800

    Designate forbidden address ranges, #25530

diff --git a/FS/FS/IP_Mixin.pm b/FS/FS/IP_Mixin.pm
index fdeb51d..b3c1052 100644
--- a/FS/FS/IP_Mixin.pm
+++ b/FS/FS/IP_Mixin.pm
@@ -200,12 +200,21 @@ sub check_ip_addr {
   return '' if $addr eq '';
   my $na = $self->NetAddr
     or return "Can't parse address '$addr'";
+  # if there's a chosen address block, check that the address is in it
   if ( my $block = $self->addr_block ) {
     if ( !$block->NetAddr->contains($na) ) {
       return "Address $addr not in block ".$block->cidr;
     }
   }
-  # this returns '' if the address is in use by $self.
+  # if the address is in any designated ranges, check that they don't 
+  # disallow use
+  foreach my $range (FS::addr_range->any_contains($addr)) {
+    if ( !$range->allow_use ) {
+      return "Address $addr is in ".$range->desc." range ".$range->as_string;
+    }
+  }
+  # check that nobody else is sitting on the address
+  # (this returns '' if the address is in use by $self)
   if ( my $dup = $self->is_used($self->ip_addr) ) {
     return "Address $addr in use by $dup";
   }
diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm
index fc25a86..fefa1bc 100644
--- a/FS/FS/Mason.pm
+++ b/FS/FS/Mason.pm
@@ -358,6 +358,7 @@ if ( -e $addl_handler_use_file ) {
   use FS::cable_provider;
   use FS::cust_credit_void;
   use FS::discount_class;
+  use FS::addr_range;
   # Sammath Naur
 
   if ( $FS::Mason::addl_handler_use ) {
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 6403782..647e2b1 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -4278,6 +4278,18 @@ sub tables_hashref {
                         ],
     },
 
+    'addr_range' => {
+      'columns' => [
+        'rangenum', 'serial', '', '', '', '',
+        'start',    'varchar', '', 15, '', '',
+        'length',   'int', '', '', '', '',
+        'status',   'varchar', 'NULL', 32, '', '',
+      ],
+      'primary_key' => 'rangenum',
+      'unique'      => [],
+      'index'       => [],
+    },
+
     'svc_broadband' => {
       'columns' => [
         'svcnum',                  'int',     '',        '', '', '', 
diff --git a/FS/FS/addr_range.pm b/FS/FS/addr_range.pm
new file mode 100644
index 0000000..5faa443
--- /dev/null
+++ b/FS/FS/addr_range.pm
@@ -0,0 +1,264 @@
+package FS::addr_range;
+
+use strict;
+use base qw( FS::Record );
+use vars qw( %status_desc
+             %status_allow_auto
+             %status_allow_use
+           );
+use FS::Record qw( qsearch qsearchs );
+use NetAddr::IP;
+
+# metadata about status strings:
+# how to describe them
+%status_desc = (
+  ''            => '',
+  'unavailable' => 'unavailable',
+);
+
+# whether addresses in this range are available for use
+%status_allow_use = (
+  ''            => 1,
+  'unavailable' => 0,
+);
+
+=head1 NAME
+
+FS::addr_range - Object methods for addr_range records
+
+=head1 SYNOPSIS
+
+  use FS::addr_range;
+
+  $record = new FS::addr_range \%hash;
+  $record = new FS::addr_range { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::addr_range object represents a contiguous range of IP 
+addresses assigned to a certain purpose.  Unlike L<FS::addr_block>,
+this isn't a routing block; the range doesn't have to be aligned on 
+a subnet boundary, and doesn't have a gateway or broadcast address.
+It's just a range.
+
+=over 4
+
+=item rangenum - primary key
+
+=item start - starting address of the range, as a dotted quad
+
+=item length - number of addresses in the range, including start
+
+=item status - what to do with the addresses in this range; currently can 
+only be "unavailable", which makes the addresses unavailable for assignment 
+to any kind of service.
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new range.  To add the example to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to.  You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+sub table { 'addr_range'; }
+
+=item insert
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+# the insert method can be inherited from FS::Record
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+# the delete method can be inherited from FS::Record
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# the replace method can be inherited from FS::Record
+
+=item check
+
+Checks all fields to make sure this is a valid example.  If there is
+an error, returns the error, otherwise returns false.  Called by the insert
+and replace methods.
+
+=cut
+
+# the check method should currently be supplied - FS::Record contains some
+# data checking routines
+
+sub check {
+  my $self = shift;
+
+  my $error = 
+    $self->ut_numbern('rangenum')
+    || $self->ut_ip('start')
+    || $self->ut_number('length')
+    || $self->ut_textn('status')
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=item end [ IPADDR ]
+
+Get/set the end IP address in the range.  This isn't actually part of the
+record but it's convenient.
+
+=cut
+
+sub end {
+  my $self = shift;
+  # if there's no start address, just return nothing
+  my $start = NetAddr::IP->new($self->start, 0) or return '';
+
+  my $new = shift;
+  if ( $new ) {
+    my $end = NetAddr::IP->new($new, 0)
+      or die "bad end address $new";
+    if ( $end < $start ) {
+      $self->set('start', $end);
+      ($end, $start) = ($start, $end);
+    }
+    $self->set('length', $end - $start + 1);
+    return $end->addr;
+  }
+  my $end = $start + $self->get('length') - 1;
+  $end->addr;
+}
+
+=item contains IPADDR
+
+Checks whether IPADDR (a dotted-quad IPv4 address) is within the range.
+
+=cut
+
+sub contains {
+  my $self = shift;
+  my $addr = shift;
+  $addr = NetAddr::IP->new($addr, 0)
+    unless ref($addr) and UNIVERSAL::isa($addr, 'NetAddr::IP');
+  return 0 unless $addr;
+
+  my $start = NetAddr::IP->new($self->start, 0);
+
+  return ($addr >= $start and $addr - $start < $self->length) ? 1 : 0;
+} 
+
+=item as_string
+
+Returns a readable string showing the address range.
+
+=cut
+
+sub as_string {
+  my $self = shift;
+  my $start = NetAddr::IP->new($self->start, 0);
+  my $end   = $start + $self->length;
+
+  if ( $self->length == 1 ) {
+    # then just the address
+    return $self->start;
+  } else { # we have to get tricksy
+    my @end_octets = split('\.', $end->addr);
+    $start = ($start->numeric)[0] + 0;
+    $end   = ($end->numeric)[0] + 0;
+    # which octets are different between start and end?
+    my $delta = $end ^ $start;
+    foreach (0xffffff, 0xffff, 0xff) {
+      if ( $delta <= $_ ) {
+      # then they are identical in the first 8/16/24 bits
+        shift @end_octets;
+      }
+    }
+    return $self->start . '-' . join('.', @end_octets);
+  }
+}
+
+=item desc
+
+Returns a semi-friendly description of the block status.
+
+=item allow_use
+
+Returns true if addresses in this range can be used by services, etc.
+
+=cut
+
+sub desc {
+  my $self = shift;
+  $status_desc{ $self->status };
+}
+
+sub allow_auto {
+  my $self = shift;
+  $status_allow_auto{ $self->status };
+}
+
+sub allow_use {
+  my $self = shift;
+  $status_allow_use{ $self->status };
+}
+
+=back
+
+=head1 CLASS METHODS
+
+=sub any_contains IPADDR
+
+Returns all address ranges that contain IPADDR.
+
+=cut
+
+sub any_contains {
+  my $self = shift;
+  my $addr = shift;
+  return grep { $_->contains($addr) } qsearch('addr_range', {});
+}
+
+=head1 DEVELOPER NOTE
+
+L<NetAddr::IP> objects have netmasks.  When using them to represent 
+range endpoints, be sure to set the netmask to I<zero> so that math on 
+the address doesn't stop at the subnet boundary.  (The default is /32, 
+which doesn't work very well.  Address ranges ignore subnet boundaries.
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::svc_IP_Mixin>, L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/MANIFEST b/FS/MANIFEST
index e635831..0b36e24 100644
--- a/FS/MANIFEST
+++ b/FS/MANIFEST
@@ -729,3 +729,5 @@ FS/cust_credit_void.pm
 t/cust_credit_void.t
 FS/discount_class.pm
 t/discount_class.t
+FS/addr_range.pm
+t/addr_range.t
diff --git a/FS/t/addr_range.t b/FS/t/addr_range.t
new file mode 100644
index 0000000..6747d67
--- /dev/null
+++ b/FS/t/addr_range.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::addr_range;
+$loaded=1;
+print "ok 1\n";
diff --git a/httemplate/browse/addr_range.html b/httemplate/browse/addr_range.html
new file mode 100644
index 0000000..d657f32
--- /dev/null
+++ b/httemplate/browse/addr_range.html
@@ -0,0 +1,66 @@
+<& elements/browse.html,
+  'title'         => 'Address Ranges',
+  'name_singular' => 'address range',
+  'html_init'     => $html_init,
+  'html_foot'     => $html_foot,
+  'query'         => { 'table'     => 'addr_range',
+                       'order_by'  => $order_by,
+                     },
+  'count_query'   => "SELECT count(*) from addr_range",
+  'header'        => [ 'From',
+                       '', # the dash
+                       'To',
+                       'Status',
+                       # would be nice to show whether any addresses in the 
+                       # range are assigned, but that's ugly
+                     ],
+  'fields'        => [ 'start',
+                       sub { '–' },
+                       'end',
+                       'desc',
+                     ],
+  'links'         => [
+                       [ '#' ],
+                       '',
+                       [ '#' ],
+                     ],
+  'link_onclicks' => [ $edit_link,
+                       '',
+                       $edit_link,
+                       '',
+                     ],
+&>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Broadband global configuration');
+# addr_ranges are ALWAYS global, else there will be chaos
+
+my $order_by = "ORDER BY inet(start)"; # Pg-ism
+# though we could also make the field itself inet-type...
+# this would simplify a lot of things.
+
+my $html_init = include('/elements/error.html');
+
+my $edit_link = sub {
+  my $addr_range = shift;
+  include('/elements/popup_link_onclick.html',
+    action      => $p.'edit/addr_range.html?rangenum='.
+                   $addr_range->rangenum,
+    actionlabel => 'Edit address range',
+    width       => 650,
+    height      => 420,
+  );
+};
+
+my $add_link = include('/elements/popup_link_onclick.html',
+    action      => $p.'edit/addr_range.html',
+    actionlabel => 'Edit address range',
+    width       => 650,
+    height      => 420,
+);
+
+my $html_foot = qq!<A HREF="#" onclick="$add_link">
+<I>Add a new address range</I></A>!;
+
+</%init>
diff --git a/httemplate/edit/addr_range.html b/httemplate/edit/addr_range.html
new file mode 100644
index 0000000..68efa5d
--- /dev/null
+++ b/httemplate/edit/addr_range.html
@@ -0,0 +1,27 @@
+<& elements/edit.html,
+  'name_singular' => 'address range',
+  'popup'  => 1,
+  'table'  => 'addr_range',
+  'labels' => { 'start'   => 'From',
+                'end'     => 'To',
+                'status'  => 'Status',
+                'rangenum'=> 'Range',
+              },  
+  'fields' => [ 'start',
+                'end',
+                { field => 'status',
+                  type  => 'select',
+                  labels  => \%FS::addr_range::status_desc,
+                  options => [ sort { $a cmp $b } 
+                               keys(%FS::addr_range::status_desc) ],
+                  disable_empty => 1,
+                },
+              ],
+  'delete_url' => $p.'misc/delete-addr_range.html',
+&>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Broadband global configuration');
+
+</%init>
diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html
index 9e27f2a..16d0817 100644
--- a/httemplate/edit/elements/edit.html
+++ b/httemplate/edit/elements/edit.html
@@ -108,6 +108,9 @@ Example:
     # overrides default popurl(1)."process/$table.html"
     'post_url' => popurl(1).'process/something', 
 
+    # optional link to delete this object; primary key will be appended
+    'delete_url' => $p.'misc/delete-something.html?',
+
     #we're in a popup (no title/menu/searchboxes)
     'popup' => 1,
 
@@ -211,6 +214,7 @@ Example:
 %     );
 %   }
 
+
   <% include('/elements/header'. ( $opt{popup} ? '-popup' : '' ). '.html',
                 $title,
                 include( '/elements/menubar.html', @menubar ),
@@ -773,6 +777,23 @@ Example:
                               : "Add ". ($opt{'name'} || $opt{'name_singular'})
                          %>"
       >
+%     if ( $opt{'delete_url'} and $object->get($pkey) ) {
+%       my $delete_msg = 'Delete this '.
+%          ($opt{'name_singular'} || $opt{'name'});
+%       my $delete_url = $opt{'delete_url'};
+%       $delete_url .= '?' unless $delete_url =~ /\?/;
+%       $delete_url .= $object->get($pkey);
+        <SCRIPT TYPE="text/javascript">
+        function confirm_delete() {
+          if(confirm(<% $delete_msg . '?' |js_string %>)) {
+            window.location.href = <% $delete_url |js_string %>;
+          }
+        }
+        </SCRIPT>
+        <INPUT TYPE     = "button"
+               VALUE    = "<% $delete_msg |h %>"
+               onclick  = "confirm_delete()">
+%     }
 %   }
 
   </FORM>
diff --git a/httemplate/edit/process/addr_range.html b/httemplate/edit/process/addr_range.html
new file mode 100644
index 0000000..6b05d23
--- /dev/null
+++ b/httemplate/edit/process/addr_range.html
@@ -0,0 +1,22 @@
+<& elements/process.html,
+  'table'           => 'addr_range',
+  'popup_reload'    => 'Address range changed',
+  'precheck_callback' => sub {
+    my ($cgi) = @_;
+    my $start = NetAddr::IP->new($cgi->param('start'), 0)
+      or return 'Illegal or empty (IP address) start: '.$cgi->param('start');
+    if ( length($cgi->param('end')) ) {
+      my $end = NetAddr::IP->new($cgi->param('end'), 0)
+        or return 'Illegal or empty (IP address) end: '.$cgi->param('end');
+      if ( $end < $start ) {
+        ($start, $end) = ($end, $start);
+        $cgi->param('end', $end->addr);
+        $cgi->param('start', $start->addr);
+      }
+      $cgi->param('length', $end - $start + 1);
+    } else {
+      $cgi->param('length', 1);
+    }
+    '';
+  },
+&>
diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html
index b4fff22..2ae216c 100644
--- a/httemplate/elements/menu.html
+++ b/httemplate/elements/menu.html
@@ -508,6 +508,10 @@ tie my %config_broadband, 'Tie::IxHash',
   'Routers'        => [ $fsurl.'browse/router.cgi', 'Broadband access routers' ],
   'Address blocks' => [ $fsurl.'browse/addr_block.cgi', 'Manage address blocks and block assignments to broadband routers' ],
 ;
+if ( $curuser->access_right('Broadband global configuration') ) {
+  $config_broadband{'Address ranges'} = 
+                      [ $fsurl.'browse/addr_range.html', 'Designate special address ranges' ];
+}
 
 tie my %config_phone, 'Tie::IxHash',
   'View/Edit phone device types' => [ $fsurl.'browse/part_device.html', 'Phone device types' ],
diff --git a/httemplate/misc/delete-addr_range.html b/httemplate/misc/delete-addr_range.html
new file mode 100644
index 0000000..c6310e9
--- /dev/null
+++ b/httemplate/misc/delete-addr_range.html
@@ -0,0 +1,21 @@
+% if ( $error ) {
+<& /elements/errorpage-popup.html, $error &>
+% } else {
+<& /elements/header-popup.html, "Address range deleted" &>
+  <SCRIPT TYPE="text/javascript">
+    window.top.location.reload();
+  </SCRIPT>
+</BODY>
+</HTML>
+% }
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Broadband global configuration');
+
+my ($rangenum) = $cgi->keywords;
+$rangenum =~ /^\d+$/ or die "bad rangenum '$rangenum'";
+my $addr_range = FS::addr_range->by_key($rangenum);
+die "unknown rangenum $rangenum" unless $addr_range;
+my $error = $addr_range->delete;
+</%init>

-----------------------------------------------------------------------

Summary of changes:
 FS/FS/IP_Mixin.pm                       |   11 ++-
 FS/FS/Mason.pm                          |    1 +
 FS/FS/Schema.pm                         |   12 ++
 FS/FS/addr_range.pm                     |  264 +++++++++++++++++++++++++++++++
 FS/MANIFEST                             |    2 +
 FS/t/{ConfItem.t => addr_range.t}       |    2 +-
 httemplate/browse/addr_range.html       |   66 ++++++++
 httemplate/edit/addr_range.html         |   27 +++
 httemplate/edit/elements/edit.html      |   21 +++
 httemplate/edit/process/addr_range.html |   22 +++
 httemplate/elements/menu.html           |    4 +
 httemplate/misc/delete-addr_range.html  |   21 +++
 12 files changed, 451 insertions(+), 2 deletions(-)
 create mode 100644 FS/FS/addr_range.pm
 copy FS/t/{ConfItem.t => addr_range.t} (83%)
 create mode 100644 httemplate/browse/addr_range.html
 create mode 100644 httemplate/edit/addr_range.html
 create mode 100644 httemplate/edit/process/addr_range.html
 create mode 100644 httemplate/misc/delete-addr_range.html




More information about the freeside-commits mailing list