From dff26abc5cb622996a7d3675751edd0be7bcd611 Mon Sep 17 00:00:00 2001 From: Oliver Gorwits Date: Wed, 15 Apr 2020 21:15:52 +0100 Subject: [PATCH] API implementation (#712) * initial v0 creator * working json api for generic reports * add require login * move report swagger into plugin, and set new default layout of noop * require proper role and also use new util func * start to tidy authn * some work on cleaning up web authn * clean up the authN checks * fix bug * fix the auth for api * fixes to json handling * set swagger sort order * enable most reports for api endpoints * fix doc * add paramters to reports * add missed report * allow api_parameters in reports config * reorganise api * add vlan search * add port search * make sure to enable layout processing * add device search * add v1 to api paths * add Node Search * support api_responses * add device object search; fix spurious ports field in device result class * handle some plugins just returning undef if search fails * errors from api seamlessley * fix error in date range default * more sensible default for prefix * change order of endpoints in swagger-ui * all db row classes can now TO_JSON * add device_port api endpoint * add device ports endpoint * do not expand docs * add swagger ui json tree formatter * add all relations from Device table * add port relations * add nodes retrieve on device or vlan * rename to GetAPIKey * update config for previous commit --- Build.PL | 2 +- lib/App/Netdisco/DB/Result.pm | 20 ++ lib/App/Netdisco/DB/Result/Admin.pm | 2 +- lib/App/Netdisco/DB/Result/Community.pm | 2 +- lib/App/Netdisco/DB/Result/Device.pm | 5 +- lib/App/Netdisco/DB/Result/DeviceIp.pm | 2 +- lib/App/Netdisco/DB/Result/DeviceModule.pm | 2 +- lib/App/Netdisco/DB/Result/DevicePort.pm | 2 +- lib/App/Netdisco/DB/Result/DevicePortLog.pm | 2 +- lib/App/Netdisco/DB/Result/DevicePortPower.pm | 2 +- .../DB/Result/DevicePortProperties.pm | 2 +- lib/App/Netdisco/DB/Result/DevicePortSsid.pm | 2 +- lib/App/Netdisco/DB/Result/DevicePortVlan.pm | 2 +- .../Netdisco/DB/Result/DevicePortWireless.pm | 2 +- lib/App/Netdisco/DB/Result/DevicePower.pm | 2 +- lib/App/Netdisco/DB/Result/DeviceSkip.pm | 2 +- lib/App/Netdisco/DB/Result/DeviceVlan.pm | 2 +- lib/App/Netdisco/DB/Result/Log.pm | 2 +- lib/App/Netdisco/DB/Result/NetmapPositions.pm | 2 +- lib/App/Netdisco/DB/Result/Node.pm | 4 +- lib/App/Netdisco/DB/Result/NodeIp.pm | 2 +- lib/App/Netdisco/DB/Result/NodeMonitor.pm | 2 +- lib/App/Netdisco/DB/Result/NodeNbt.pm | 2 +- lib/App/Netdisco/DB/Result/NodeWireless.pm | 2 +- lib/App/Netdisco/DB/Result/Oui.pm | 2 +- lib/App/Netdisco/DB/Result/Process.pm | 2 +- lib/App/Netdisco/DB/Result/Session.pm | 2 +- lib/App/Netdisco/DB/Result/Statistics.pm | 2 +- lib/App/Netdisco/DB/Result/Subnet.pm | 2 +- lib/App/Netdisco/DB/Result/Topology.pm | 2 +- lib/App/Netdisco/DB/Result/User.pm | 2 +- lib/App/Netdisco/DB/Result/UserLog.pm | 2 +- .../Netdisco/DB/Result/Virtual/UserRole.pm | 1 + lib/App/Netdisco/DB/ResultSet/Device.pm | 4 + lib/App/Netdisco/Util/Web.pm | 57 +++++- lib/App/Netdisco/Web.pm | 80 ++++++-- lib/App/Netdisco/Web/API/Objects.pm | 176 ++++++++++++++++++ lib/App/Netdisco/Web/AdminTask.pm | 4 +- lib/App/Netdisco/Web/Auth/Provider/DBIC.pm | 3 +- lib/App/Netdisco/Web/AuthN.pm | 171 +++++++++-------- lib/App/Netdisco/Web/Device.pm | 2 +- lib/App/Netdisco/Web/GenericReport.pm | 9 +- lib/App/Netdisco/Web/Password.pm | 4 +- lib/App/Netdisco/Web/Plugin.pm | 30 +++ lib/App/Netdisco/Web/Plugin/Inventory.pm | 2 +- .../Web/Plugin/Report/ApChannelDist.pm | 7 +- .../Netdisco/Web/Plugin/Report/ApClients.pm | 7 +- .../Web/Plugin/Report/ApRadioChannelPower.pm | 7 +- .../Web/Plugin/Report/DeviceAddrNoDNS.pm | 7 +- .../Web/Plugin/Report/DeviceByLocation.pm | 7 +- .../Web/Plugin/Report/DeviceDnsMismatch.pm | 7 +- .../Web/Plugin/Report/DevicePoeStatus.pm | 6 +- .../Web/Plugin/Report/DuplexMismatch.pm | 7 +- .../Netdisco/Web/Plugin/Report/HalfDuplex.pm | 8 +- .../Netdisco/Web/Plugin/Report/IpInventory.pm | 38 +++- .../Web/Plugin/Report/NodeMultiIPs.pm | 7 +- .../Web/Plugin/Report/NodesDiscovered.pm | 30 ++- .../Web/Plugin/Report/PortAdminDown.pm | 7 +- .../Web/Plugin/Report/PortBlocking.pm | 7 +- .../Web/Plugin/Report/PortMultiNodes.pm | 13 +- .../Netdisco/Web/Plugin/Report/PortSsid.pm | 12 +- .../Web/Plugin/Report/PortUtilization.pm | 19 +- .../Web/Plugin/Report/PortVLANMismatch.pm | 7 +- .../Web/Plugin/Report/SsidInventory.pm | 7 +- .../Web/Plugin/Report/SubnetUtilization.pm | 29 ++- .../Web/Plugin/Report/VlanInventory.pm | 7 +- lib/App/Netdisco/Web/Plugin/Search/Device.pm | 54 +++++- lib/App/Netdisco/Web/Plugin/Search/Node.pm | 52 +++++- lib/App/Netdisco/Web/Plugin/Search/Port.pm | 34 +++- lib/App/Netdisco/Web/Plugin/Search/VLAN.pm | 19 +- lib/App/Netdisco/Web/Report.pm | 2 +- lib/App/Netdisco/Web/Search.pm | 2 +- .../Plugin/{SetUserToken.pm => GetAPIKey.pm} | 4 +- share/config.yml | 4 +- share/swagger-ui/index.html | 8 +- .../swagger-ui/swagger-ui-json-tree-plugin.js | 15 ++ .../swagger-ui-json-tree-plugin.js.map | 1 + share/views/layouts/noop.tt | 1 + 78 files changed, 815 insertions(+), 257 deletions(-) create mode 100644 lib/App/Netdisco/DB/Result.pm create mode 100644 lib/App/Netdisco/Web/API/Objects.pm rename lib/App/Netdisco/Worker/Plugin/{SetUserToken.pm => GetAPIKey.pm} (87%) create mode 100644 share/swagger-ui/swagger-ui-json-tree-plugin.js create mode 100644 share/swagger-ui/swagger-ui-json-tree-plugin.js.map create mode 100644 share/views/layouts/noop.tt diff --git a/Build.PL b/Build.PL index 8bbba8d0..43f1a851 100644 --- a/Build.PL +++ b/Build.PL @@ -35,8 +35,8 @@ Module::Build->new( 'DBIx::Class::Helpers' => '2.033004', 'Daemon::Control' => '0.001006', 'Dancer' => '1.3132', - 'Dancer::Plugin::DBIC' => '0.2001', 'Dancer::Plugin::Auth::Extensible' => '0.30', + 'Dancer::Plugin::DBIC' => '0.2001', 'Dancer::Plugin::Passphrase' => '2.0.1', 'Dancer::Plugin::Swagger' => '0', 'Dancer::Session::Cookie' => '0.27', diff --git a/lib/App/Netdisco/DB/Result.pm b/lib/App/Netdisco/DB/Result.pm new file mode 100644 index 00000000..042e9fb9 --- /dev/null +++ b/lib/App/Netdisco/DB/Result.pm @@ -0,0 +1,20 @@ +package App::Netdisco::DB::Result; + +use strict; +use warnings; + +use base 'DBIx::Class::Core'; + +__PACKAGE__->load_components(qw{Helper::Row::ToJSON}); + +# for DBIx::Class::Helper::Row::ToJSON +# to allow text columns to be included in results + +sub unserializable_data_types { + return { + blob => 1, + ntext => 1, + }; +} + +1; diff --git a/lib/App/Netdisco/DB/Result/Admin.pm b/lib/App/Netdisco/DB/Result/Admin.pm index 3a9156fe..3091d99d 100644 --- a/lib/App/Netdisco/DB/Result/Admin.pm +++ b/lib/App/Netdisco/DB/Result/Admin.pm @@ -5,7 +5,7 @@ package App::Netdisco::DB::Result::Admin; use strict; use warnings; -use base 'DBIx::Class::Core'; +use base 'App::Netdisco::DB::Result'; __PACKAGE__->table("admin"); __PACKAGE__->add_columns( "job", diff --git a/lib/App/Netdisco/DB/Result/Community.pm b/lib/App/Netdisco/DB/Result/Community.pm index 6351c3e0..82b2eee8 100644 --- a/lib/App/Netdisco/DB/Result/Community.pm +++ b/lib/App/Netdisco/DB/Result/Community.pm @@ -4,7 +4,7 @@ package App::Netdisco::DB::Result::Community; use strict; use warnings; -use base 'DBIx::Class::Core'; +use base 'App::Netdisco::DB::Result'; __PACKAGE__->table("community"); __PACKAGE__->add_columns( "ip", diff --git a/lib/App/Netdisco/DB/Result/Device.pm b/lib/App/Netdisco/DB/Result/Device.pm index 5177c552..6fc6544f 100644 --- a/lib/App/Netdisco/DB/Result/Device.pm +++ b/lib/App/Netdisco/DB/Result/Device.pm @@ -1,7 +1,6 @@ use utf8; package App::Netdisco::DB::Result::Device; - use strict; use warnings; @@ -10,7 +9,7 @@ use App::Netdisco::Util::DNS 'hostname_from_ip'; use overload '""' => sub { shift->ip }, fallback => 1; -use base 'DBIx::Class::Core'; +use base 'App::Netdisco::DB::Result'; __PACKAGE__->table("device"); __PACKAGE__->add_columns( "ip", @@ -36,8 +35,6 @@ __PACKAGE__->add_columns( { data_type => "text", is_nullable => 1 }, "layers", { data_type => "varchar", is_nullable => 1, size => 8 }, - "ports", - { data_type => "integer", is_nullable => 1 }, "mac", { data_type => "macaddr", is_nullable => 1 }, "serial", diff --git a/lib/App/Netdisco/DB/Result/DeviceIp.pm b/lib/App/Netdisco/DB/Result/DeviceIp.pm index e5b0c4dc..b30fa2df 100644 --- a/lib/App/Netdisco/DB/Result/DeviceIp.pm +++ b/lib/App/Netdisco/DB/Result/DeviceIp.pm @@ -5,7 +5,7 @@ package App::Netdisco::DB::Result::DeviceIp; use strict; use warnings; -use base 'DBIx::Class::Core'; +use base 'App::Netdisco::DB::Result'; __PACKAGE__->table("device_ip"); __PACKAGE__->add_columns( "ip", diff --git a/lib/App/Netdisco/DB/Result/DeviceModule.pm b/lib/App/Netdisco/DB/Result/DeviceModule.pm index cc5bbcb6..7e43324e 100644 --- a/lib/App/Netdisco/DB/Result/DeviceModule.pm +++ b/lib/App/Netdisco/DB/Result/DeviceModule.pm @@ -5,7 +5,7 @@ package App::Netdisco::DB::Result::DeviceModule; use strict; use warnings; -use base 'DBIx::Class::Core'; +use base 'App::Netdisco::DB::Result'; __PACKAGE__->table("device_module"); __PACKAGE__->add_columns( "ip", diff --git a/lib/App/Netdisco/DB/Result/DevicePort.pm b/lib/App/Netdisco/DB/Result/DevicePort.pm index e7181523..5b2c3b39 100644 --- a/lib/App/Netdisco/DB/Result/DevicePort.pm +++ b/lib/App/Netdisco/DB/Result/DevicePort.pm @@ -9,7 +9,7 @@ use NetAddr::MAC; use MIME::Base64 'encode_base64url'; -use base 'DBIx::Class::Core'; +use base 'App::Netdisco::DB::Result'; __PACKAGE__->table("device_port"); __PACKAGE__->add_columns( "ip", diff --git a/lib/App/Netdisco/DB/Result/DevicePortLog.pm b/lib/App/Netdisco/DB/Result/DevicePortLog.pm index e56a70b5..666adb10 100644 --- a/lib/App/Netdisco/DB/Result/DevicePortLog.pm +++ b/lib/App/Netdisco/DB/Result/DevicePortLog.pm @@ -5,7 +5,7 @@ package App::Netdisco::DB::Result::DevicePortLog; use strict; use warnings; -use base 'DBIx::Class::Core'; +use base 'App::Netdisco::DB::Result'; __PACKAGE__->table("device_port_log"); __PACKAGE__->add_columns( "id", diff --git a/lib/App/Netdisco/DB/Result/DevicePortPower.pm b/lib/App/Netdisco/DB/Result/DevicePortPower.pm index 110ccd3f..1a8a694e 100644 --- a/lib/App/Netdisco/DB/Result/DevicePortPower.pm +++ b/lib/App/Netdisco/DB/Result/DevicePortPower.pm @@ -5,7 +5,7 @@ package App::Netdisco::DB::Result::DevicePortPower; use strict; use warnings; -use base 'DBIx::Class::Core'; +use base 'App::Netdisco::DB::Result'; __PACKAGE__->table("device_port_power"); __PACKAGE__->add_columns( "ip", diff --git a/lib/App/Netdisco/DB/Result/DevicePortProperties.pm b/lib/App/Netdisco/DB/Result/DevicePortProperties.pm index deff2181..26175eb5 100644 --- a/lib/App/Netdisco/DB/Result/DevicePortProperties.pm +++ b/lib/App/Netdisco/DB/Result/DevicePortProperties.pm @@ -4,7 +4,7 @@ package App::Netdisco::DB::Result::DevicePortProperties; use strict; use warnings; -use base 'DBIx::Class::Core'; +use base 'App::Netdisco::DB::Result'; __PACKAGE__->table("device_port_properties"); __PACKAGE__->add_columns( "ip", diff --git a/lib/App/Netdisco/DB/Result/DevicePortSsid.pm b/lib/App/Netdisco/DB/Result/DevicePortSsid.pm index ea465f34..539ad782 100644 --- a/lib/App/Netdisco/DB/Result/DevicePortSsid.pm +++ b/lib/App/Netdisco/DB/Result/DevicePortSsid.pm @@ -5,7 +5,7 @@ package App::Netdisco::DB::Result::DevicePortSsid; use strict; use warnings; -use base 'DBIx::Class::Core'; +use base 'App::Netdisco::DB::Result'; __PACKAGE__->table("device_port_ssid"); __PACKAGE__->add_columns( "ip", diff --git a/lib/App/Netdisco/DB/Result/DevicePortVlan.pm b/lib/App/Netdisco/DB/Result/DevicePortVlan.pm index 2de88ac6..a63e6d31 100644 --- a/lib/App/Netdisco/DB/Result/DevicePortVlan.pm +++ b/lib/App/Netdisco/DB/Result/DevicePortVlan.pm @@ -5,7 +5,7 @@ package App::Netdisco::DB::Result::DevicePortVlan; use strict; use warnings; -use base 'DBIx::Class::Core'; +use base 'App::Netdisco::DB::Result'; __PACKAGE__->table("device_port_vlan"); __PACKAGE__->add_columns( "ip", diff --git a/lib/App/Netdisco/DB/Result/DevicePortWireless.pm b/lib/App/Netdisco/DB/Result/DevicePortWireless.pm index 5e1caa59..6f3039a2 100644 --- a/lib/App/Netdisco/DB/Result/DevicePortWireless.pm +++ b/lib/App/Netdisco/DB/Result/DevicePortWireless.pm @@ -5,7 +5,7 @@ package App::Netdisco::DB::Result::DevicePortWireless; use strict; use warnings; -use base 'DBIx::Class::Core'; +use base 'App::Netdisco::DB::Result'; __PACKAGE__->table("device_port_wireless"); __PACKAGE__->add_columns( "ip", diff --git a/lib/App/Netdisco/DB/Result/DevicePower.pm b/lib/App/Netdisco/DB/Result/DevicePower.pm index aab07147..c81ee4b8 100644 --- a/lib/App/Netdisco/DB/Result/DevicePower.pm +++ b/lib/App/Netdisco/DB/Result/DevicePower.pm @@ -5,7 +5,7 @@ package App::Netdisco::DB::Result::DevicePower; use strict; use warnings; -use base 'DBIx::Class::Core'; +use base 'App::Netdisco::DB::Result'; __PACKAGE__->table("device_power"); __PACKAGE__->add_columns( "ip", diff --git a/lib/App/Netdisco/DB/Result/DeviceSkip.pm b/lib/App/Netdisco/DB/Result/DeviceSkip.pm index 4bb64d93..e9b41e53 100644 --- a/lib/App/Netdisco/DB/Result/DeviceSkip.pm +++ b/lib/App/Netdisco/DB/Result/DeviceSkip.pm @@ -6,7 +6,7 @@ use warnings; use List::MoreUtils (); -use base 'DBIx::Class::Core'; +use base 'App::Netdisco::DB::Result'; __PACKAGE__->table("device_skip"); __PACKAGE__->add_columns( "backend", diff --git a/lib/App/Netdisco/DB/Result/DeviceVlan.pm b/lib/App/Netdisco/DB/Result/DeviceVlan.pm index 6c64e57e..9e9797b1 100644 --- a/lib/App/Netdisco/DB/Result/DeviceVlan.pm +++ b/lib/App/Netdisco/DB/Result/DeviceVlan.pm @@ -5,7 +5,7 @@ package App::Netdisco::DB::Result::DeviceVlan; use strict; use warnings; -use base 'DBIx::Class::Core'; +use base 'App::Netdisco::DB::Result'; __PACKAGE__->table("device_vlan"); __PACKAGE__->add_columns( "ip", diff --git a/lib/App/Netdisco/DB/Result/Log.pm b/lib/App/Netdisco/DB/Result/Log.pm index 4bce50e8..579d6033 100644 --- a/lib/App/Netdisco/DB/Result/Log.pm +++ b/lib/App/Netdisco/DB/Result/Log.pm @@ -5,7 +5,7 @@ package App::Netdisco::DB::Result::Log; use strict; use warnings; -use base 'DBIx::Class::Core'; +use base 'App::Netdisco::DB::Result'; __PACKAGE__->table("log"); __PACKAGE__->add_columns( "id", diff --git a/lib/App/Netdisco/DB/Result/NetmapPositions.pm b/lib/App/Netdisco/DB/Result/NetmapPositions.pm index 0d2b7e2c..f5b467fa 100644 --- a/lib/App/Netdisco/DB/Result/NetmapPositions.pm +++ b/lib/App/Netdisco/DB/Result/NetmapPositions.pm @@ -4,7 +4,7 @@ package App::Netdisco::DB::Result::NetmapPositions; use strict; use warnings; -use base 'DBIx::Class::Core'; +use base 'App::Netdisco::DB::Result'; __PACKAGE__->table("netmap_positions"); __PACKAGE__->add_columns( "id", diff --git a/lib/App/Netdisco/DB/Result/Node.pm b/lib/App/Netdisco/DB/Result/Node.pm index d4c4eb59..8f610d57 100644 --- a/lib/App/Netdisco/DB/Result/Node.pm +++ b/lib/App/Netdisco/DB/Result/Node.pm @@ -7,7 +7,7 @@ use warnings; use NetAddr::MAC; -use base 'DBIx::Class::Core'; +use base 'App::Netdisco::DB::Result'; __PACKAGE__->table("node"); __PACKAGE__->add_columns( "mac", @@ -19,7 +19,7 @@ __PACKAGE__->add_columns( "active", { data_type => "boolean", is_nullable => 1 }, "oui", - { data_type => "varchar", is_nullable => 1, size => 8 }, + { data_type => "varchar", is_nullable => 1, is_serializable => 0, size => 8 }, "time_first", { data_type => "timestamp", diff --git a/lib/App/Netdisco/DB/Result/NodeIp.pm b/lib/App/Netdisco/DB/Result/NodeIp.pm index 9f0bb852..57c40438 100644 --- a/lib/App/Netdisco/DB/Result/NodeIp.pm +++ b/lib/App/Netdisco/DB/Result/NodeIp.pm @@ -7,7 +7,7 @@ use warnings; use NetAddr::MAC; -use base 'DBIx::Class::Core'; +use base 'App::Netdisco::DB::Result'; __PACKAGE__->table("node_ip"); __PACKAGE__->add_columns( "mac", diff --git a/lib/App/Netdisco/DB/Result/NodeMonitor.pm b/lib/App/Netdisco/DB/Result/NodeMonitor.pm index 5aa7ca0c..2eb01103 100644 --- a/lib/App/Netdisco/DB/Result/NodeMonitor.pm +++ b/lib/App/Netdisco/DB/Result/NodeMonitor.pm @@ -5,7 +5,7 @@ package App::Netdisco::DB::Result::NodeMonitor; use strict; use warnings; -use base 'DBIx::Class::Core'; +use base 'App::Netdisco::DB::Result'; __PACKAGE__->table("node_monitor"); __PACKAGE__->add_columns( "mac", diff --git a/lib/App/Netdisco/DB/Result/NodeNbt.pm b/lib/App/Netdisco/DB/Result/NodeNbt.pm index 5866095a..abff6225 100644 --- a/lib/App/Netdisco/DB/Result/NodeNbt.pm +++ b/lib/App/Netdisco/DB/Result/NodeNbt.pm @@ -7,7 +7,7 @@ use warnings; use NetAddr::MAC; -use base 'DBIx::Class::Core'; +use base 'App::Netdisco::DB::Result'; __PACKAGE__->table("node_nbt"); __PACKAGE__->add_columns( "mac", diff --git a/lib/App/Netdisco/DB/Result/NodeWireless.pm b/lib/App/Netdisco/DB/Result/NodeWireless.pm index 5833cb38..5c593f20 100644 --- a/lib/App/Netdisco/DB/Result/NodeWireless.pm +++ b/lib/App/Netdisco/DB/Result/NodeWireless.pm @@ -7,7 +7,7 @@ use warnings; use NetAddr::MAC; -use base 'DBIx::Class::Core'; +use base 'App::Netdisco::DB::Result'; __PACKAGE__->table("node_wireless"); __PACKAGE__->add_columns( "mac", diff --git a/lib/App/Netdisco/DB/Result/Oui.pm b/lib/App/Netdisco/DB/Result/Oui.pm index 13273a10..eb052c69 100644 --- a/lib/App/Netdisco/DB/Result/Oui.pm +++ b/lib/App/Netdisco/DB/Result/Oui.pm @@ -5,7 +5,7 @@ package App::Netdisco::DB::Result::Oui; use strict; use warnings; -use base 'DBIx::Class::Core'; +use base 'App::Netdisco::DB::Result'; __PACKAGE__->table("oui"); __PACKAGE__->add_columns( "oui", diff --git a/lib/App/Netdisco/DB/Result/Process.pm b/lib/App/Netdisco/DB/Result/Process.pm index 17b51b11..93f33225 100644 --- a/lib/App/Netdisco/DB/Result/Process.pm +++ b/lib/App/Netdisco/DB/Result/Process.pm @@ -5,7 +5,7 @@ package App::Netdisco::DB::Result::Process; use strict; use warnings; -use base 'DBIx::Class::Core'; +use base 'App::Netdisco::DB::Result'; __PACKAGE__->table("process"); __PACKAGE__->add_columns( "controller", diff --git a/lib/App/Netdisco/DB/Result/Session.pm b/lib/App/Netdisco/DB/Result/Session.pm index 2a6e1950..4347b222 100644 --- a/lib/App/Netdisco/DB/Result/Session.pm +++ b/lib/App/Netdisco/DB/Result/Session.pm @@ -5,7 +5,7 @@ package App::Netdisco::DB::Result::Session; use strict; use warnings; -use base 'DBIx::Class::Core'; +use base 'App::Netdisco::DB::Result'; __PACKAGE__->table("sessions"); __PACKAGE__->add_columns( "id", diff --git a/lib/App/Netdisco/DB/Result/Statistics.pm b/lib/App/Netdisco/DB/Result/Statistics.pm index 41207d37..b7f140a6 100644 --- a/lib/App/Netdisco/DB/Result/Statistics.pm +++ b/lib/App/Netdisco/DB/Result/Statistics.pm @@ -4,7 +4,7 @@ package App::Netdisco::DB::Result::Statistics; use strict; use warnings; -use base 'DBIx::Class::Core'; +use base 'App::Netdisco::DB::Result'; __PACKAGE__->table("statistics"); __PACKAGE__->add_columns( "day", diff --git a/lib/App/Netdisco/DB/Result/Subnet.pm b/lib/App/Netdisco/DB/Result/Subnet.pm index a19ed78c..dafedb7b 100644 --- a/lib/App/Netdisco/DB/Result/Subnet.pm +++ b/lib/App/Netdisco/DB/Result/Subnet.pm @@ -5,7 +5,7 @@ package App::Netdisco::DB::Result::Subnet; use strict; use warnings; -use base 'DBIx::Class::Core'; +use base 'App::Netdisco::DB::Result'; __PACKAGE__->table("subnets"); __PACKAGE__->add_columns( "net", diff --git a/lib/App/Netdisco/DB/Result/Topology.pm b/lib/App/Netdisco/DB/Result/Topology.pm index 65a9187f..46eff226 100644 --- a/lib/App/Netdisco/DB/Result/Topology.pm +++ b/lib/App/Netdisco/DB/Result/Topology.pm @@ -4,7 +4,7 @@ package App::Netdisco::DB::Result::Topology; use strict; use warnings; -use base 'DBIx::Class::Core'; +use base 'App::Netdisco::DB::Result'; __PACKAGE__->table("topology"); diff --git a/lib/App/Netdisco/DB/Result/User.pm b/lib/App/Netdisco/DB/Result/User.pm index b1de5921..f3ee60e4 100644 --- a/lib/App/Netdisco/DB/Result/User.pm +++ b/lib/App/Netdisco/DB/Result/User.pm @@ -5,7 +5,7 @@ package App::Netdisco::DB::Result::User; use strict; use warnings; -use base 'DBIx::Class::Core'; +use base 'App::Netdisco::DB::Result'; __PACKAGE__->table("users"); __PACKAGE__->add_columns( "username", diff --git a/lib/App/Netdisco/DB/Result/UserLog.pm b/lib/App/Netdisco/DB/Result/UserLog.pm index bad5ab25..eb9d6de9 100644 --- a/lib/App/Netdisco/DB/Result/UserLog.pm +++ b/lib/App/Netdisco/DB/Result/UserLog.pm @@ -5,7 +5,7 @@ package App::Netdisco::DB::Result::UserLog; use strict; use warnings; -use base 'DBIx::Class::Core'; +use base 'App::Netdisco::DB::Result'; __PACKAGE__->table("user_log"); __PACKAGE__->add_columns( "entry", diff --git a/lib/App/Netdisco/DB/Result/Virtual/UserRole.pm b/lib/App/Netdisco/DB/Result/Virtual/UserRole.pm index 16ca8420..ab146de2 100644 --- a/lib/App/Netdisco/DB/Result/Virtual/UserRole.pm +++ b/lib/App/Netdisco/DB/Result/Virtual/UserRole.pm @@ -28,6 +28,7 @@ __PACKAGE__->result_source_instance->view_definition(< (EXTRACT(EPOCH FROM now()) - ?) ENDSQL ); diff --git a/lib/App/Netdisco/DB/ResultSet/Device.pm b/lib/App/Netdisco/DB/ResultSet/Device.pm index 37584d3f..806bb6a5 100644 --- a/lib/App/Netdisco/DB/ResultSet/Device.pm +++ b/lib/App/Netdisco/DB/ResultSet/Device.pm @@ -206,6 +206,10 @@ Can match any of the Device IP address aliases as a substring. Can be a string IP or a NetAddr::IP object, either way being treated as an IPv4 or IPv6 prefix within which the device must have one IP address alias. +=item layers + +OSI Layers which the device must support. + =back =cut diff --git a/lib/App/Netdisco/Util/Web.pm b/lib/App/Netdisco/Util/Web.pm index f4d9d70a..043781a3 100644 --- a/lib/App/Netdisco/Util/Web.pm +++ b/lib/App/Netdisco/Util/Web.pm @@ -3,12 +3,20 @@ package App::Netdisco::Util::Web; use strict; use warnings; -use base 'Exporter'; +use Dancer ':syntax'; + use Time::Piece; use Time::Seconds; + +use base 'Exporter'; our @EXPORT = (); our @EXPORT_OK = qw/ - sort_port sort_modules interval_to_daterange sql_match + sort_port sort_modules + interval_to_daterange + sql_match + request_is_api + request_is_api_report + request_is_api_search /; our %EXPORT_TAGS = (all => \@EXPORT_OK); @@ -25,6 +33,51 @@ subroutines. =head1 EXPORT_OK +=head2 request_is_api + +Client has requested JSON format data and an endpoint under C. + +=cut + +sub request_is_api { + return ((request->accept =~ m/(?:json|javascript)/) and ( + index(request->path, uri_for('/api/')->path) == 0 + or + (param('return_url') + and index(param('return_url'), uri_for('/api/')->path) == 0) + )); +} + +=head2 request_is_api_report + +Same as C but also requires path to start "C". + +=cut + +sub request_is_api_report { + return (request_is_api and ( + index(request->path, uri_for('/api/v1/report/')->path) == 0 + or + (param('return_url') + and index(param('return_url'), uri_for('/api/v1/report/')->path) == 0) + )); +} + +=head2 request_is_api_search + +Same as C but also requires path to start "C". + +=cut + +sub request_is_api_search { + return (request_is_api and ( + index(request->path, uri_for('/api/v1/search/')->path) == 0 + or + (param('return_url') + and index(param('return_url'), uri_for('/api/v1/search/')->path) == 0) + )); +} + =head2 sql_match( $value, $exact? ) Convert wildcard characters "C<*>" and "C" to "C<%>" and "C<_>" diff --git a/lib/App/Netdisco/Web.pm b/lib/App/Netdisco/Web.pm index 961e0570..e50d167e 100644 --- a/lib/App/Netdisco/Web.pm +++ b/lib/App/Netdisco/Web.pm @@ -7,19 +7,55 @@ use Dancer::Plugin::DBIC; use Dancer::Plugin::Auth::Extensible; use Dancer::Plugin::Swagger; +use Dancer::Error; +use Dancer::Continuation::Route::ErrorSent; + use URI (); use Socket6 (); # to ensure dependency is met use HTML::Entities (); # to ensure dependency is met use URI::QueryParam (); # part of URI, to add helper methods use Path::Class 'dir'; use Module::Load (); -use App::Netdisco::Util::Web 'interval_to_daterange'; +use App::Netdisco::Util::Web qw/ + interval_to_daterange + request_is_api + request_is_api_report + request_is_api_search +/; + +BEGIN { + # https://github.com/PerlDancer/Dancer/issues/967 + no warnings 'redefine'; + *Dancer::_redirect = sub { + my ($destination, $status) = @_; + my $response = Dancer::SharedData->response; + $response->status($status || 302); + $response->headers('Location' => $destination); + }; + # neater than using Dancer::Plugin::Res to handle JSON differently + *Dancer::send_error = sub { + my ($body, $status) = @_; + if (request_is_api) { + status $status || 400; + $body = '' unless defined $body; + Dancer::Continuation::Route::ErrorSent->new( + return_value => to_json { error => $body, return_url => param('return_url') } + )->throw; + } + Dancer::Continuation::Route::ErrorSent->new( + return_value => Dancer::Error->new( + message => $body, + code => $status || 500)->render() + )->throw; + }; +} use App::Netdisco::Web::AuthN; use App::Netdisco::Web::Static; use App::Netdisco::Web::Search; use App::Netdisco::Web::Device; use App::Netdisco::Web::Report; +use App::Netdisco::Web::API::Objects; use App::Netdisco::Web::AdminTask; use App::Netdisco::Web::TypeAhead; use App::Netdisco::Web::PortControl; @@ -82,13 +118,14 @@ $swagger->{schemes} = ['http','https']; $swagger->{consumes} = 'application/json'; $swagger->{produces} = 'application/json'; $swagger->{tags} = [ - {name => 'Global'}, - {name => 'Devices', - description => 'Operations relating to Devices (switches, routers, etc)'}, - {name => 'Nodes', - description => 'Operations relating to Nodes (end-stations such as printers)'}, - {name => 'NodeIPs', - description => 'Operations relating to MAC-IP mappings (IPv4 ARP and IPv6 Neighbors)'}, + {name => 'General', + description => 'Log in and Log out'}, + {name => 'Search', + description => 'Search Operations'}, + {name => 'Objects', + description => 'Retrieve Device, Port, and associated Node Data'}, + {name => 'Reports', + description => 'Canned and Custom Reports'}, ]; $swagger->{securityDefinitions} = { APIKeyHeader => @@ -229,6 +266,15 @@ hook 'after_template_render' => sub { # debug $template_engine->{config}->{AUTO_FILTER}; }; +# support for report api which is basic table result in json +hook before_layout_render => sub { + my ($tokens, $html_ref) = @_; + return unless request_is_api_report or request_is_api_search; + + ${ $html_ref } = + $tokens->{results} ? (to_json $tokens->{results}) : {}; +}; + # workaround for Swagger plugin weird response body hook 'after' => sub { my $r = shift; # a Dancer::Response @@ -237,6 +283,13 @@ hook 'after' => sub { $r->content( to_json( $r->content ) ); header('Content-Type' => 'application/json'); } + + # instead of setting serialiser + # and also to handle some plugins just returning undef if search fails + if (request_is_api) { + header('Content-Type' => 'application/json'); + $r->content( $r->content || '[]' ); + } }; # remove empty lines from CSV response @@ -262,15 +315,4 @@ any qr{.*} => sub { template 'index'; }; -{ - # https://github.com/PerlDancer/Dancer/issues/967 - no warnings 'redefine'; - *Dancer::_redirect = sub { - my ($destination, $status) = @_; - my $response = Dancer::SharedData->response; - $response->status($status || 302); - $response->headers('Location' => $destination); - }; -} - true; diff --git a/lib/App/Netdisco/Web/API/Objects.pm b/lib/App/Netdisco/Web/API/Objects.pm new file mode 100644 index 00000000..341520f6 --- /dev/null +++ b/lib/App/Netdisco/Web/API/Objects.pm @@ -0,0 +1,176 @@ +package App::Netdisco::Web::API::Objects; + +use Dancer ':syntax'; +use Dancer::Plugin::DBIC; +use Dancer::Plugin::Swagger; +use Dancer::Plugin::Auth::Extensible; + +use Try::Tiny; + +swagger_path { + tags => ['Objects'], + description => 'Returns a row from the device table', + parameters => [ + ip => { + description => 'Canonical IP of the Device. Use Search methods to find this.', + required => 1, + in => 'path', + }, + ], + responses => { default => {} }, +}, get '/api/v1/object/device/:ip' => require_role api => sub { + my $device = try { schema('netdisco')->resultset('Device') + ->find( params->{ip} ) } or send_error('Bad Device', 404); + return to_json $device->TO_JSON; +}; + +foreach my $rel (qw/device_ips vlans ports modules port_vlans wireless_ports ssids powered_ports/) { + swagger_path { + tags => ['Objects'], + description => "Returns $rel rows for a given device", + parameters => [ + ip => { + description => 'Canonical IP of the Device. Use Search methods to find this.', + required => 1, + in => 'path', + }, + ], + responses => { default => {} }, + }, get "/api/v1/object/device/:ip/$rel" => require_role api => sub { + my $rows = try { schema('netdisco')->resultset('Device') + ->find( params->{ip} )->$rel } or send_error('Bad Device', 404); + return to_json [ map {$_->TO_JSON} $rows->all ]; + }; +} + +swagger_path { + tags => ['Objects'], + description => 'Returns a row from the device_port table', + path => '/api/v1/object/device/{ip}/port/{port}', + parameters => [ + ip => { + description => 'Canonical IP of the Device. Use Search methods to find this.', + required => 1, + in => 'path', + }, + port => { + description => 'Name of the port. Use the ".../device/{ip}/ports" method to find these.', + required => 1, + in => 'path', + }, + ], + responses => { default => {} }, +}, get qr{/api/v1/object/device/(?[^/]+)/port/(?[^/]+)$} => require_role api => sub { + my $params = captures; + my $port = try { schema('netdisco')->resultset('DevicePort') + ->find( $$params{port}, $$params{ip} ) } + or send_error('Bad Device or Port', 404); + return to_json $port->TO_JSON; +}; + +foreach my $rel (qw/nodes active_nodes nodes_with_age active_nodes_with_age vlans logs/) { + swagger_path { + tags => ['Objects'], + description => "Returns $rel rows for a given port", + path => "/api/v1/object/device/{ip}/port/{port}/$rel", + parameters => [ + ip => { + description => 'Canonical IP of the Device. Use Search methods to find this.', + required => 1, + in => 'path', + }, + port => { + description => 'Name of the port. Use the ".../device/{ip}/ports" method to find these.', + required => 1, + in => 'path', + }, + ], + responses => { default => {} }, + }, get qq{/api/v1/object/device/(?[^/]+)/port/(?[^/]+)/$rel} => require_role api => sub { + my $params = captures; + my $rows = try { schema('netdisco')->resultset('DevicePort') + ->find( $$params{port}, $$params{ip} )->$rel } + or send_error('Bad Device or Port', 404); + return to_json [ map {$_->TO_JSON} $rows->all ]; + }; +} + +foreach my $rel (qw/power properties ssid wireless agg_master neighbor last_node/) { + swagger_path { + tags => ['Objects'], + description => "Returns the related $rel table entry for a given port", + path => "/api/v1/object/device/{ip}/port/{port}/$rel", + parameters => [ + ip => { + description => 'Canonical IP of the Device. Use Search methods to find this.', + required => 1, + in => 'path', + }, + port => { + description => 'Name of the port. Use the ".../device/{ip}/ports" method to find these.', + required => 1, + in => 'path', + }, + ], + responses => { default => {} }, + }, get qq{/api/v1/object/device/(?[^/]+)/port/(?[^/]+)/$rel} => require_role api => sub { + my $params = captures; + my $row = try { schema('netdisco')->resultset('DevicePort') + ->find( $$params{port}, $$params{ip} )->$rel } + or send_error('Bad Device or Port', 404); + return to_json $row->TO_JSON; + }; +} + +swagger_path { + tags => ['Objects'], + description => "Returns the nodes found on a given Device", + parameters => [ + ip => { + description => 'Canonical IP of the Device. Use Search methods to find this.', + required => 1, + in => 'path', + }, + active_only => { + description => 'Restrict results to active Nodes only', + type => 'boolean', + default => 'true', + in => 'query', + }, + ], + responses => { default => {} }, +}, get '/api/v1/object/device/:ip/nodes' => require_role api => sub { + my $active = (params->{active_only} and ('true' eq params->{active_only})) ? 1 : 0; + my $rows = try { schema('netdisco')->resultset('Node') + ->search({ switch => params->{ip}, ($active ? (-bool => 'active') : ()) }) } + or send_error('Bad Device', 404); + return to_json [ map {$_->TO_JSON} $rows->all ]; +}; + +swagger_path { + tags => ['Objects'], + description => "Returns the nodes found in a given VLAN", + parameters => [ + vlan => { + description => 'VLAN number', + type => 'integer', + required => 1, + in => 'path', + }, + active_only => { + description => 'Restrict results to active Nodes only', + type => 'boolean', + default => 'true', + in => 'query', + }, + ], + responses => { default => {} }, +}, get '/api/v1/object/vlan/:vlan/nodes' => require_role api => sub { + my $active = (params->{active_only} and ('true' eq params->{active_only})) ? 1 : 0; + my $rows = try { schema('netdisco')->resultset('Node') + ->search({ vlan => params->{vlan}, ($active ? (-bool => 'active') : ()) }) } + or send_error('Bad VLAN', 404); + return to_json [ map {$_->TO_JSON} $rows->all ]; +}; + +true; diff --git a/lib/App/Netdisco/Web/AdminTask.pm b/lib/App/Netdisco/Web/AdminTask.pm index f5b73bf7..479a1a31 100644 --- a/lib/App/Netdisco/Web/AdminTask.pm +++ b/lib/App/Netdisco/Web/AdminTask.pm @@ -66,12 +66,12 @@ get '/admin/*' => require_role admin => sub { var(nav => 'admin'); template 'admintask', { task => setting('_admin_tasks')->{ $tag }, - }; + }, { layout => 'main' }; } else { var('notfound' => true); status 'not_found'; - template 'index'; + template 'index', {}, { layout => 'main' }; } }; diff --git a/lib/App/Netdisco/Web/Auth/Provider/DBIC.pm b/lib/App/Netdisco/Web/Auth/Provider/DBIC.pm index af777b1d..24a160c3 100644 --- a/lib/App/Netdisco/Web/Auth/Provider/DBIC.pm +++ b/lib/App/Netdisco/Web/Auth/Provider/DBIC.pm @@ -94,7 +94,8 @@ sub get_user_roles { my $role_column = $settings->{role_column} || 'role'; return [ try { - $user->$roles->get_column( $role_column )->all; + $user->$roles->search({}, { bind => [setting('api_token_lifetime')] }) + ->get_column( $role_column )->all; } ]; } diff --git a/lib/App/Netdisco/Web/AuthN.pm b/lib/App/Netdisco/Web/AuthN.pm index 44ee6bc2..e8b0b7d8 100644 --- a/lib/App/Netdisco/Web/AuthN.pm +++ b/lib/App/Netdisco/Web/AuthN.pm @@ -5,99 +5,95 @@ use Dancer::Plugin::DBIC; use Dancer::Plugin::Auth::Extensible; use Dancer::Plugin::Swagger; +use App::Netdisco::Util::Web 'request_is_api'; use MIME::Base64; -sub request_is_api { - return (setting('api_token_lifetime') - and request->header('Authorization') - and request->accept =~ m/(?:json|javascript)/); -} - +# ensure that regardless of where the user is redirected, we have a link +# back to the page they requested. hook 'before' => sub { params->{return_url} ||= ((request->path ne uri_for('/')->path) ? request->uri : uri_for(setting('web_home'))->path); +}; + +# Dancer will create a session if it sees its own cookie. For the API and also +# various auto login options we need to bootstrap the session instead. If no +# auth data passed, then the hook simply returns, no session is set, and the +# user is redirected to login page. +hook 'before' => sub { + # return if request is for endpoints not requiring a session + return if ( + request->path eq uri_for('/login')->path + or request->path eq uri_for('/logout')->path + or request->path eq uri_for('/swagger.json')->path + or index(request->path, uri_for('/swagger-ui')->path) == 0 + ); # from the internals of Dancer::Plugin::Auth::Extensible my $provider = Dancer::Plugin::Auth::Extensible::auth_provider('users'); - if (! session('logged_in_user') - and request->path ne uri_for('/login')->path - and request->path ne uri_for('/logout')->path - and request->path ne uri_for('/swagger.json')->path - and index(request->path, uri_for('/swagger-ui')->path) != 0) { + # API calls must conform strictly to path and header requirements + if (request_is_api) { + # Dancer will issue a cookie to the client which could be returned and + # cause API calls to succeed without passing token. Kill the session. + session->destroy; - if (setting('trust_x_remote_user') - and scalar request->header('X-REMOTE_USER') - and length scalar request->header('X-REMOTE_USER')) { + my $token = request->header('Authorization'); + my $user = $provider->validate_api_token($token) + or return; - (my $user = scalar request->header('X-REMOTE_USER')) =~ s/@[^@]*$//; - return if setting('validate_remote_user') - and not $provider->get_user_details($user); - - session(logged_in_user => $user); - session(logged_in_user_realm => 'users'); - } - elsif (setting('trust_remote_user') - and defined $ENV{REMOTE_USER} - and length $ENV{REMOTE_USER}) { - - (my $user = $ENV{REMOTE_USER}) =~ s/@[^@]*$//; - return if setting('validate_remote_user') - and not $provider->get_user_details($user); - - session(logged_in_user => $user); - session(logged_in_user_realm => 'users'); - } - elsif (setting('no_auth')) { - session(logged_in_user => 'guest'); - session(logged_in_user_realm => 'users'); - } - elsif (request_is_api() - and index(request->path, uri_for('/api')->path) == 0) { - - my $token = request->header('Authorization'); - my $user = $provider->validate_api_token($token) - or return; - - session(logged_in_user => $user); - session(logged_in_user_realm => 'users'); - } - else { - # user has no AuthN - force to handler for '/' - request->path_info('/'); - } + session(logged_in_user => $user); + session(logged_in_user_realm => 'users'); + return; } -}; -# user redirected here (POST -> GET) when login fails -get qr{^/(?:login(?:/denied)?)?} => sub { - if (request_is_api()) { - status('unauthorized'); - return to_json { - error => 'not authorized', - return_url => param('return_url'), - }; + # after checking API, we can short circuit if Dancer reads its cookie OK + return if session('logged_in_user'); + + if (setting('trust_x_remote_user') + and scalar request->header('X-REMOTE_USER') + and length scalar request->header('X-REMOTE_USER')) { + + (my $user = scalar request->header('X-REMOTE_USER')) =~ s/@[^@]*$//; + return if setting('validate_remote_user') + and not $provider->get_user_details($user); + + session(logged_in_user => $user); + session(logged_in_user_realm => 'users'); + } + elsif (setting('trust_remote_user') + and defined $ENV{REMOTE_USER} + and length $ENV{REMOTE_USER}) { + + (my $user = $ENV{REMOTE_USER}) =~ s/@[^@]*$//; + return if setting('validate_remote_user') + and not $provider->get_user_details($user); + + session(logged_in_user => $user); + session(logged_in_user_realm => 'users'); + } + elsif (setting('no_auth')) { + session(logged_in_user => 'guest'); + session(logged_in_user_realm => 'users'); } else { - template 'index', { return_url => param('return_url') }; + # user has no AuthN - force to handler for '/' + request->path_info('/'); } }; # override default login_handler so we can log access in the database swagger_path { - description => 'Obtain an API Key using HTTP BasicAuth', - tags => ['Global'], + description => 'Obtain an API Key', + tags => ['General'], parameters => [], - responses => { - default => { - examples => { - 'application/json' => { api_key => 'cc9d5c02d8898e5728b7d7a0339c0785' } } }, + responses => { default => { examples => { + 'application/json' => { api_key => 'cc9d5c02d8898e5728b7d7a0339c0785' } } }, }, }, post '/login' => sub { - my $mode = (request_is_api() ? 'API' : 'WebUI'); + my $api = ((request->accept =~ m/(?:json|javascript)/) ? true : false); - # get authN data from request (HTTP BasicAuth or Form params) + # get authN data from BasicAuth header used by API, put into params my $authheader = request->header('Authorization'); if (defined $authheader and $authheader =~ /^Basic (.*)$/i) { my ($u, $p) = split(m/:/, (MIME::Base64::decode($1) || ":")); @@ -119,12 +115,13 @@ post '/login' => sub { schema('netdisco')->resultset('UserLog')->create({ username => session('logged_in_user'), userip => request->remote_address, - event => "Login ($mode)", + event => (sprintf 'Login (%s)', ($api ? 'API' : 'WebUI')), details => param('return_url'), }); $user->update({ last_on => \'now()' }); - if ($mode eq 'API') { + if ($api) { + header('Content-Type' => 'application/json'); $user->update({ token_from => time, token => \'md5(random()::text)', @@ -135,16 +132,18 @@ post '/login' => sub { redirect param('return_url'); } else { + # invalidate session cookie session->destroy; schema('netdisco')->resultset('UserLog')->create({ username => param('username'), userip => request->remote_address, - event => "Login Failure ($mode)", + event => (sprintf 'Login Failure (%s)', ($api ? 'API' : 'WebUI')), details => param('return_url'), }); - if ($mode eq 'API') { + if ($api) { + header('Content-Type' => 'application/json'); status('unauthorized'); return to_json { error => 'authentication failed' }; } @@ -164,12 +163,12 @@ Dancer::Plugin::Swagger->instance->doc->{paths}->{'/login'} # we override the default login_handler, so logout has to be handled as well swagger_path { description => 'Destroy user API Key and session cookie', - tags => ['Global'], + tags => ['General'], parameters => [], responses => { default => { examples => { 'application/json' => {} } } }, }, get '/logout' => sub { - my $mode = (request_is_api() ? 'API' : 'WebUI'); + my $api = ((request->accept =~ m/(?:json|javascript)/) ? true : false); # clear out API token my $user = schema('netdisco')->resultset('User') @@ -183,15 +182,35 @@ get '/logout' => sub { schema('netdisco')->resultset('UserLog')->create({ username => session('logged_in_user'), userip => request->remote_address, - event => "Logout ($mode)", + event => (sprintf 'Logout (%s)', ($api ? 'API' : 'WebUI')), details => '', }); - if ($mode eq 'API') { + if ($api) { + header('Content-Type' => 'application/json'); return to_json {}; } redirect uri_for(setting('web_home'))->path; }; +# user redirected here (POST -> GET) when login fails +get qr{^/(?:login(?:/denied)?)?} => sub { + my $api = ((request->accept =~ m/(?:json|javascript)/) ? true : false); + + if ($api) { + header('Content-Type' => 'application/json'); + status('unauthorized'); + return to_json { + error => 'not authorized', + return_url => param('return_url'), + }; + } + else { + template 'index', { + return_url => param('return_url') + }, { layout => 'main' }; + } +}; + true; diff --git a/lib/App/Netdisco/Web/Device.pm b/lib/App/Netdisco/Web/Device.pm index 010ed550..56ca283a 100644 --- a/lib/App/Netdisco/Web/Device.pm +++ b/lib/App/Netdisco/Web/Device.pm @@ -85,7 +85,7 @@ get '/device' => require_login sub { lgroup_list => [ schema('netdisco')->resultset('Device')->get_distinct_col('location') ], hgroup_list => setting('host_group_displaynames'), device => params->{'tab'}, - }; + }, { layout => 'main' }; }; true; diff --git a/lib/App/Netdisco/Web/GenericReport.pm b/lib/App/Netdisco/Web/GenericReport.pm index 097ae6d8..59f7420a 100644 --- a/lib/App/Netdisco/Web/GenericReport.pm +++ b/lib/App/Netdisco/Web/GenericReport.pm @@ -21,6 +21,9 @@ foreach my $report (@{setting('reports')}) { category => ($report->{category} || 'My Reports'), ($report->{hidden} ? (hidden => true) : ()), provides_csv => true, + api_endpoint => true, + bind_params => $report->{bind_params}, + api_parameters => $report->{api_parameters}, }); get "/ajax/content/report/$r" => require_login sub { @@ -76,16 +79,14 @@ foreach my $report (@{setting('reports')}) { is_custom_report => true, column_options => \%column_config, headings => [map {$column_config{$_}->{displayname}} @column_order], - columns => [@column_order] }, - { layout => undef }; + columns => [@column_order] }; } else { header( 'Content-Type' => 'text/comma-separated-values' ); template 'ajax/report/generic_report_csv.tt', { results => \@results, headings => [map {$column_config{$_}->{displayname}} @column_order], - columns => [@column_order] }, - { layout => undef }; + columns => [@column_order] }; } }; } diff --git a/lib/App/Netdisco/Web/Password.pm b/lib/App/Netdisco/Web/Password.pm index 8992e6e8..e6caf462 100644 --- a/lib/App/Netdisco/Web/Password.pm +++ b/lib/App/Netdisco/Web/Password.pm @@ -19,7 +19,7 @@ sub _make_password { sub _bail { var('passchange_failed' => 1); - return template 'password.tt'; + return template 'password.tt', {}, { layout => 'main' }; } any ['get', 'post'] => '/password' => require_login sub { @@ -45,7 +45,7 @@ any ['get', 'post'] => '/password' => require_login sub { var('passchange_ok' => 1); } - template 'password.tt'; + template 'password.tt', {}, { layout => 'main' }; }; true; diff --git a/lib/App/Netdisco/Web/Plugin.pm b/lib/App/Netdisco/Web/Plugin.pm index 642ba757..e14ec2ae 100644 --- a/lib/App/Netdisco/Web/Plugin.pm +++ b/lib/App/Netdisco/Web/Plugin.pm @@ -2,6 +2,8 @@ package App::Netdisco::Web::Plugin; use Dancer ':syntax'; use Dancer::Plugin; +use Dancer::Plugin::Swagger; +use Dancer::Plugin::Auth::Extensible; use Path::Class 'dir'; @@ -150,6 +152,19 @@ sub _register_tab { register 'register_search_tab' => sub { my ($self, $config) = plugin_args(@_); _register_tab('search', $config); + + if ($config->{api_endpoint}) { + my $tag = $config->{tag}; + swagger_path { + tags => ['Search'], + description => $config->{label} .' Search', + parameters => $config->{api_parameters}, + responses => + ($config->{api_responses} || { default => {} }), + }, get "/api/v1/search/$tag" => require_role api => sub { + forward "/ajax/content/search/$tag"; + }; + } }; register 'register_device_tab' => sub { @@ -178,6 +193,21 @@ register 'register_report' => sub { if ($config->{tag} eq $tag) { setting('_reports')->{$tag} = $config; + if ($config->{api_endpoint}) { + (my $category_path = lc $config->{category}) =~ s/ /-/g; + swagger_path { + tags => ['Reports'], + description => $config->{label} .' Report', + parameters => + ($config->{api_parameters} || + ($config->{bind_params} ? [map { $_ => {} } @{ $config->{bind_params} }] : [])), + responses => + ($config->{api_responses} || { default => {} }), + }, get "/api/v1/report/$category_path/$tag" => require_role api => sub { + forward "/ajax/content/report/$tag"; + }; + } + foreach my $rconfig (@{setting('reports')}) { if ($rconfig->{tag} eq $tag) { setting('_reports')->{$tag}->{'rconfig'} = $rconfig; diff --git a/lib/App/Netdisco/Web/Plugin/Inventory.pm b/lib/App/Netdisco/Web/Plugin/Inventory.pm index 125c6e53..0146323e 100644 --- a/lib/App/Netdisco/Web/Plugin/Inventory.pm +++ b/lib/App/Netdisco/Web/Plugin/Inventory.pm @@ -29,7 +29,7 @@ get '/inventory' => require_login sub { template 'inventory', { platforms => [ $platforms->hri->all ], releases => [ @release_list ], - }; + }, { layout => 'main' }; }; true; diff --git a/lib/App/Netdisco/Web/Plugin/Report/ApChannelDist.pm b/lib/App/Netdisco/Web/Plugin/Report/ApChannelDist.pm index 851c1edc..2e22f127 100644 --- a/lib/App/Netdisco/Web/Plugin/Report/ApChannelDist.pm +++ b/lib/App/Netdisco/Web/Plugin/Report/ApChannelDist.pm @@ -11,6 +11,7 @@ register_report( tag => 'apchanneldist', label => 'Access Point Channel Distribution', provides_csv => 1, + api_endpoint => 1, } ); @@ -28,13 +29,11 @@ get '/ajax/content/report/apchanneldist' => require_login sub { if ( request->is_ajax ) { my $json = to_json( \@results ); - template 'ajax/report/apchanneldist.tt', { results => $json }, - { layout => undef }; + template 'ajax/report/apchanneldist.tt', { results => $json }; } else { header( 'Content-Type' => 'text/comma-separated-values' ); - template 'ajax/report/apchanneldist_csv.tt', { results => \@results }, - { layout => undef }; + template 'ajax/report/apchanneldist_csv.tt', { results => \@results }; } }; diff --git a/lib/App/Netdisco/Web/Plugin/Report/ApClients.pm b/lib/App/Netdisco/Web/Plugin/Report/ApClients.pm index 6bd929e7..fcfb874a 100644 --- a/lib/App/Netdisco/Web/Plugin/Report/ApClients.pm +++ b/lib/App/Netdisco/Web/Plugin/Report/ApClients.pm @@ -11,6 +11,7 @@ register_report( tag => 'apclients', label => 'Access Point Client Count', provides_csv => 1, + api_endpoint => 1, } ); @@ -37,14 +38,12 @@ get '/ajax/content/report/apclients' => require_login sub { if ( request->is_ajax ) { my $json = to_json( \@results ); - template 'ajax/report/apclients.tt', { results => $json }, - { layout => undef }; + template 'ajax/report/apclients.tt', { results => $json }; } else { header( 'Content-Type' => 'text/comma-separated-values' ); template 'ajax/report/apclients_csv.tt', - { results => \@results }, - { layout => undef }; + { results => \@results }; } }; diff --git a/lib/App/Netdisco/Web/Plugin/Report/ApRadioChannelPower.pm b/lib/App/Netdisco/Web/Plugin/Report/ApRadioChannelPower.pm index 8ab46374..1389e6d9 100644 --- a/lib/App/Netdisco/Web/Plugin/Report/ApRadioChannelPower.pm +++ b/lib/App/Netdisco/Web/Plugin/Report/ApRadioChannelPower.pm @@ -12,6 +12,7 @@ register_report( tag => 'apradiochannelpower', label => 'Access Point Radios Channel and Power', provides_csv => 1, + api_endpoint => 1, } ); @@ -38,8 +39,7 @@ get '/ajax/content/report/apradiochannelpower/data' => require_login sub { get '/ajax/content/report/apradiochannelpower' => require_login sub { if ( request->is_ajax ) { - template 'ajax/report/apradiochannelpower.tt', {}, - { layout => undef }; + template 'ajax/report/apradiochannelpower.tt'; } else { my @results @@ -50,8 +50,7 @@ get '/ajax/content/report/apradiochannelpower' => require_login sub { header( 'Content-Type' => 'text/comma-separated-values' ); template 'ajax/report/apradiochannelpower_csv.tt', - { results => \@results, }, - { layout => undef }; + { results => \@results, }; } }; diff --git a/lib/App/Netdisco/Web/Plugin/Report/DeviceAddrNoDNS.pm b/lib/App/Netdisco/Web/Plugin/Report/DeviceAddrNoDNS.pm index 880a5567..73c4885a 100644 --- a/lib/App/Netdisco/Web/Plugin/Report/DeviceAddrNoDNS.pm +++ b/lib/App/Netdisco/Web/Plugin/Report/DeviceAddrNoDNS.pm @@ -11,6 +11,7 @@ register_report( tag => 'deviceaddrnodns', label => 'Addresses without DNS Entries', provides_csv => 1, + api_endpoint => 1, } ); @@ -28,14 +29,12 @@ get '/ajax/content/report/deviceaddrnodns' => require_login sub { if ( request->is_ajax ) { my $json = to_json (\@results); - template 'ajax/report/deviceaddrnodns.tt', { results => $json }, - { layout => undef }; + template 'ajax/report/deviceaddrnodns.tt', { results => $json }; } else { header( 'Content-Type' => 'text/comma-separated-values' ); template 'ajax/report/deviceaddrnodns_csv.tt', - { results => \@results, }, - { layout => undef }; + { results => \@results, }; } }; diff --git a/lib/App/Netdisco/Web/Plugin/Report/DeviceByLocation.pm b/lib/App/Netdisco/Web/Plugin/Report/DeviceByLocation.pm index 32c724f2..ff03469a 100644 --- a/lib/App/Netdisco/Web/Plugin/Report/DeviceByLocation.pm +++ b/lib/App/Netdisco/Web/Plugin/Report/DeviceByLocation.pm @@ -11,6 +11,7 @@ register_report( tag => 'devicebylocation', label => 'By Location', provides_csv => 1, + api_endpoint => 1, } ); @@ -24,14 +25,12 @@ get '/ajax/content/report/devicebylocation' => require_login sub { if ( request->is_ajax ) { my $json = to_json( \@results ); - template 'ajax/report/devicebylocation.tt', { results => $json }, - { layout => undef }; + template 'ajax/report/devicebylocation.tt', { results => $json }; } else { header( 'Content-Type' => 'text/comma-separated-values' ); template 'ajax/report/devicebylocation_csv.tt', - { results => \@results }, - { layout => undef }; + { results => \@results }; } }; diff --git a/lib/App/Netdisco/Web/Plugin/Report/DeviceDnsMismatch.pm b/lib/App/Netdisco/Web/Plugin/Report/DeviceDnsMismatch.pm index 02e04e65..66bd2591 100644 --- a/lib/App/Netdisco/Web/Plugin/Report/DeviceDnsMismatch.pm +++ b/lib/App/Netdisco/Web/Plugin/Report/DeviceDnsMismatch.pm @@ -11,6 +11,7 @@ register_report( tag => 'devicednsmismatch', label => 'Device Name / DNS Mismatches', provides_csv => 1, + api_endpoint => 1, } ); @@ -27,14 +28,12 @@ get '/ajax/content/report/devicednsmismatch' => require_login sub { if ( request->is_ajax ) { my $json = to_json( \@results ); - template 'ajax/report/devicednsmismatch.tt', { results => $json }, - { layout => undef }; + template 'ajax/report/devicednsmismatch.tt', { results => $json }; } else { header( 'Content-Type' => 'text/comma-separated-values' ); template 'ajax/report/devicednsmismatch_csv.tt', - { results => \@results }, - { layout => undef }; + { results => \@results }; } }; diff --git a/lib/App/Netdisco/Web/Plugin/Report/DevicePoeStatus.pm b/lib/App/Netdisco/Web/Plugin/Report/DevicePoeStatus.pm index 08f2a95d..097ba532 100644 --- a/lib/App/Netdisco/Web/Plugin/Report/DevicePoeStatus.pm +++ b/lib/App/Netdisco/Web/Plugin/Report/DevicePoeStatus.pm @@ -12,6 +12,7 @@ register_report( tag => 'devicepoestatus', label => 'Power over Ethernet (PoE) Status', provides_csv => 1, + api_endpoint => 1, } ); @@ -39,7 +40,7 @@ get '/ajax/content/report/devicepoestatus/data' => require_login sub { get '/ajax/content/report/devicepoestatus' => require_login sub { if ( request->is_ajax ) { - template 'ajax/report/devicepoestatus.tt', {}, { layout => undef }; + template 'ajax/report/devicepoestatus.tt'; } else { my @results @@ -50,8 +51,7 @@ get '/ajax/content/report/devicepoestatus' => require_login sub { header( 'Content-Type' => 'text/comma-separated-values' ); template 'ajax/report/devicepoestatus_csv.tt', - { results => \@results, }, - { layout => undef }; + { results => \@results, }; } }; diff --git a/lib/App/Netdisco/Web/Plugin/Report/DuplexMismatch.pm b/lib/App/Netdisco/Web/Plugin/Report/DuplexMismatch.pm index dbb92405..1d4d85ff 100644 --- a/lib/App/Netdisco/Web/Plugin/Report/DuplexMismatch.pm +++ b/lib/App/Netdisco/Web/Plugin/Report/DuplexMismatch.pm @@ -11,6 +11,7 @@ register_report( tag => 'duplexmismatch', label => 'Duplex Mismatches Between Devices', provides_csv => 1, + api_endpoint => 1, } ); @@ -22,14 +23,12 @@ get '/ajax/content/report/duplexmismatch' => require_login sub { if ( request->is_ajax ) { my $json = to_json( \@results ); - template 'ajax/report/duplexmismatch.tt', { results => $json, }, - { layout => undef }; + template 'ajax/report/duplexmismatch.tt', { results => $json, }; } else { header( 'Content-Type' => 'text/comma-separated-values' ); template 'ajax/report/duplexmismatch_csv.tt', - { results => \@results, }, - { layout => undef }; + { results => \@results, }; } }; diff --git a/lib/App/Netdisco/Web/Plugin/Report/HalfDuplex.pm b/lib/App/Netdisco/Web/Plugin/Report/HalfDuplex.pm index 8b424355..f98856c3 100644 --- a/lib/App/Netdisco/Web/Plugin/Report/HalfDuplex.pm +++ b/lib/App/Netdisco/Web/Plugin/Report/HalfDuplex.pm @@ -11,11 +11,11 @@ register_report( tag => 'halfduplex', label => 'Ports in Half Duplex Mode', provides_csv => 1, + api_endpoint => 1, } ); get '/ajax/content/report/halfduplex' => require_login sub { - my $format = param('format'); my @results = schema('netdisco')->resultset('DevicePort') ->columns( [qw/ ip port name duplex /] )->search( @@ -30,14 +30,12 @@ get '/ajax/content/report/halfduplex' => require_login sub { if ( request->is_ajax ) { my $json = to_json( \@results ); - template 'ajax/report/halfduplex.tt', { results => $json }, - { layout => undef }; + template 'ajax/report/halfduplex.tt', { results => $json }; } else { header( 'Content-Type' => 'text/comma-separated-values' ); template 'ajax/report/halfduplex_csv.tt', - { results => \@results }, - { layout => undef }; + { results => \@results }; } }; diff --git a/lib/App/Netdisco/Web/Plugin/Report/IpInventory.pm b/lib/App/Netdisco/Web/Plugin/Report/IpInventory.pm index 82c59422..0a9e6267 100644 --- a/lib/App/Netdisco/Web/Plugin/Report/IpInventory.pm +++ b/lib/App/Netdisco/Web/Plugin/Report/IpInventory.pm @@ -6,12 +6,39 @@ use Dancer::Plugin::Auth::Extensible; use App::Netdisco::Web::Plugin; use NetAddr::IP::Lite ':lower'; +use POSIX qw/strftime/; register_report( { category => 'IP', tag => 'ipinventory', label => 'IP Inventory', provides_csv => 1, + api_endpoint => 1, + api_parameters => [ + subnet => { + description => 'IP Prefix to search', + required => 1, + }, + daterange => { + description => 'Date range to search', + default => ('1970-01-01 to '. strftime('%Y-%m-%d', gmtime)), + }, + age_invert => { + description => 'Results should NOT be within daterange', + type => 'boolean', + default => 'false', + }, + limit => { + description => 'Maximum number of historical records', + enum => [qw/32 64 128 256 512 1024 2048 4096 8192/], + default => '256', + }, + never => { + description => 'Include in the report IPs never seen', + type => 'boolean', + default => 'false', + }, + ], } ); @@ -25,7 +52,10 @@ get '/ajax/content/report/ipinventory' => require_login sub { if (! $subnet) or ($subnet->addr eq '0.0.0.0'); my $agenot = param('age_invert') || '0'; - my ( $start, $end ) = param('daterange') =~ /(\d+-\d+-\d+)/gmx; + + my $daterange = param('daterange') + || ('1970-01-01 to '. strftime('%Y-%m-%d', gmtime)); + my ( $start, $end ) = $daterange =~ /(\d+-\d+-\d+)/gmx; my $limit = param('limit') || 256; my $never = param('never') || '0'; @@ -155,13 +185,11 @@ get '/ajax/content/report/ipinventory' => require_login sub { if ( request->is_ajax ) { my $json = to_json( \@results ); - template 'ajax/report/ipinventory.tt', { results => $json }, - { layout => undef }; + template 'ajax/report/ipinventory.tt', { results => $json }; } else { header( 'Content-Type' => 'text/comma-separated-values' ); - template 'ajax/report/ipinventory_csv.tt', { results => \@results, }, - { layout => undef }; + template 'ajax/report/ipinventory_csv.tt', { results => \@results, }; } }; diff --git a/lib/App/Netdisco/Web/Plugin/Report/NodeMultiIPs.pm b/lib/App/Netdisco/Web/Plugin/Report/NodeMultiIPs.pm index d295a69d..94e54c46 100644 --- a/lib/App/Netdisco/Web/Plugin/Report/NodeMultiIPs.pm +++ b/lib/App/Netdisco/Web/Plugin/Report/NodeMultiIPs.pm @@ -11,6 +11,7 @@ register_report( tag => 'nodemultiips', label => 'Nodes with multiple active IP addresses', provides_csv => 1, + api_endpoint => 1, } ); @@ -37,14 +38,12 @@ get '/ajax/content/report/nodemultiips' => require_login sub { if ( request->is_ajax ) { my $json = to_json( \@results ); - template 'ajax/report/nodemultiips.tt', { results => $json }, - { layout => undef }; + template 'ajax/report/nodemultiips.tt', { results => $json }; } else { header( 'Content-Type' => 'text/comma-separated-values' ); template 'ajax/report/nodemultiips_csv.tt', - { results => \@results }, - { layout => undef }; + { results => \@results }; } }; diff --git a/lib/App/Netdisco/Web/Plugin/Report/NodesDiscovered.pm b/lib/App/Netdisco/Web/Plugin/Report/NodesDiscovered.pm index b5a697a2..46a02483 100644 --- a/lib/App/Netdisco/Web/Plugin/Report/NodesDiscovered.pm +++ b/lib/App/Netdisco/Web/Plugin/Report/NodesDiscovered.pm @@ -12,6 +12,30 @@ register_report( tag => 'nodesdiscovered', label => 'Nodes discovered through LLDP/CDP', provides_csv => 1, + api_endpoint => 1, + api_parameters => [ + remote_id => { + description => 'Host Name reported', + }, + remote_type => { + description => 'Platform reported', + }, + aps => { + description => 'Include Wireless APs in the report', + type => 'boolean', + default => 'false', + }, + phones => { + description => 'Include IP Phones in the report', + type => 'boolean', + default => 'false', + }, + matchall => { + description => 'Match all parameters (true) or any (false)', + type => 'boolean', + default => 'false', + }, + ], } ); @@ -40,14 +64,12 @@ get '/ajax/content/report/nodesdiscovered' => require_login sub { if ( request->is_ajax ) { my $json = to_json( \@results ); - template 'ajax/report/nodesdiscovered.tt', { results => $json }, - { layout => undef }; + template 'ajax/report/nodesdiscovered.tt', { results => $json }; } else { header( 'Content-Type' => 'text/comma-separated-values' ); template 'ajax/report/nodesdiscovered_csv.tt', - { results => \@results }, - { layout => undef }; + { results => \@results }; } }; diff --git a/lib/App/Netdisco/Web/Plugin/Report/PortAdminDown.pm b/lib/App/Netdisco/Web/Plugin/Report/PortAdminDown.pm index 8c9c4eea..020c5998 100644 --- a/lib/App/Netdisco/Web/Plugin/Report/PortAdminDown.pm +++ b/lib/App/Netdisco/Web/Plugin/Report/PortAdminDown.pm @@ -11,6 +11,7 @@ register_report( tag => 'portadmindown', label => 'Ports administratively disabled', provides_csv => 1, + api_endpoint => 1, } ); @@ -31,14 +32,12 @@ get '/ajax/content/report/portadmindown' => require_login sub { if ( request->is_ajax ) { my $json = to_json (\@results); - template 'ajax/report/portadmindown.tt', { results => $json }, - { layout => undef }; + template 'ajax/report/portadmindown.tt', { results => $json }; } else { header( 'Content-Type' => 'text/comma-separated-values' ); template 'ajax/report/portadmindown_csv.tt', - { results => \@results, }, - { layout => undef }; + { results => \@results, }; } }; diff --git a/lib/App/Netdisco/Web/Plugin/Report/PortBlocking.pm b/lib/App/Netdisco/Web/Plugin/Report/PortBlocking.pm index 23e17127..7f44a73a 100644 --- a/lib/App/Netdisco/Web/Plugin/Report/PortBlocking.pm +++ b/lib/App/Netdisco/Web/Plugin/Report/PortBlocking.pm @@ -11,6 +11,7 @@ register_report( tag => 'portblocking', label => 'Ports that are blocking', provides_csv => 1, + api_endpoint => 1, } ); @@ -31,14 +32,12 @@ get '/ajax/content/report/portblocking' => require_login sub { if ( request->is_ajax ) { my $json = to_json (\@results); - template 'ajax/report/portblocking.tt', { results => $json }, - { layout => undef }; + template 'ajax/report/portblocking.tt', { results => $json }; } else { header( 'Content-Type' => 'text/comma-separated-values' ); template 'ajax/report/portblocking_csv.tt', - { results => \@results, }, - { layout => undef }; + { results => \@results, }; } }; diff --git a/lib/App/Netdisco/Web/Plugin/Report/PortMultiNodes.pm b/lib/App/Netdisco/Web/Plugin/Report/PortMultiNodes.pm index 483a86ae..fd17266d 100644 --- a/lib/App/Netdisco/Web/Plugin/Report/PortMultiNodes.pm +++ b/lib/App/Netdisco/Web/Plugin/Report/PortMultiNodes.pm @@ -11,6 +11,13 @@ register_report( tag => 'portmultinodes', label => 'Ports with multiple nodes attached', provides_csv => 1, + api_endpoint => 1, + api_parameters => [ + vlan => { + description => 'Filter by VLAN', + type => 'integer', + }, + ], } ); @@ -39,14 +46,12 @@ get '/ajax/content/report/portmultinodes' => require_login sub { if ( request->is_ajax ) { my $json = to_json (\@results); - template 'ajax/report/portmultinodes.tt', { results => $json }, - { layout => undef }; + template 'ajax/report/portmultinodes.tt', { results => $json }; } else { header( 'Content-Type' => 'text/comma-separated-values' ); template 'ajax/report/portmultinodes_csv.tt', - { results => \@results, }, - { layout => undef }; + { results => \@results, }; } }; diff --git a/lib/App/Netdisco/Web/Plugin/Report/PortSsid.pm b/lib/App/Netdisco/Web/Plugin/Report/PortSsid.pm index 36f27168..29bee987 100644 --- a/lib/App/Netdisco/Web/Plugin/Report/PortSsid.pm +++ b/lib/App/Netdisco/Web/Plugin/Report/PortSsid.pm @@ -11,6 +11,12 @@ register_report( tag => 'portssid', label => 'Port SSID Inventory', provides_csv => 1, + api_endpoint => 1, + api_parameters => [ + ssid => { + description => 'Get details for this SSID', + }, + ], } ); @@ -65,14 +71,12 @@ get '/ajax/content/report/portssid' => require_login sub { if ( request->is_ajax ) { my $json = to_json( \@results ); template 'ajax/report/portssid.tt', - { results => $json, opt => $ssid }, - { layout => undef }; + { results => $json, opt => $ssid }; } else { header( 'Content-Type' => 'text/comma-separated-values' ); template 'ajax/report/portssid_csv.tt', - { results => \@results, opt => $ssid }, - { layout => undef }; + { results => \@results, opt => $ssid }; } }; diff --git a/lib/App/Netdisco/Web/Plugin/Report/PortUtilization.pm b/lib/App/Netdisco/Web/Plugin/Report/PortUtilization.pm index ad06f3e7..432e5539 100644 --- a/lib/App/Netdisco/Web/Plugin/Report/PortUtilization.pm +++ b/lib/App/Netdisco/Web/Plugin/Report/PortUtilization.pm @@ -11,6 +11,19 @@ register_report( tag => 'portutilization', label => 'Port Utilization', provides_csv => 1, + api_endpoint => 1, + api_parameters => [ + age_num => { + description => 'Mark as Free if down for (quantity)', + enum => [1 .. 31], + default => '3', + }, + age_unit => { + description => 'Mark as Free if down for (period)', + enum => [qw/days weeks months years/], + default => 'months', + }, + ], } ); @@ -24,13 +37,11 @@ get '/ajax/content/report/portutilization' => require_login sub { if (request->is_ajax) { my $json = to_json (\@results); - template 'ajax/report/portutilization.tt', { results => $json }, - { layout => undef }; + template 'ajax/report/portutilization.tt', { results => $json }; } else { header( 'Content-Type' => 'text/comma-separated-values' ); - template 'ajax/report/portutilization_csv.tt', { results => \@results, }, - { layout => undef }; + template 'ajax/report/portutilization_csv.tt', { results => \@results, }; } }; diff --git a/lib/App/Netdisco/Web/Plugin/Report/PortVLANMismatch.pm b/lib/App/Netdisco/Web/Plugin/Report/PortVLANMismatch.pm index 53ef47b9..0286d140 100644 --- a/lib/App/Netdisco/Web/Plugin/Report/PortVLANMismatch.pm +++ b/lib/App/Netdisco/Web/Plugin/Report/PortVLANMismatch.pm @@ -11,6 +11,7 @@ register_report( tag => 'portvlanmismatch', label => 'Port VLAN Mismatches', provides_csv => 1, + api_endpoint => 1, } ); @@ -20,13 +21,11 @@ get '/ajax/content/report/portvlanmismatch' => require_login sub { if (request->is_ajax) { my $json = to_json (\@results); - template 'ajax/report/portvlanmismatch.tt', { results => $json }, - { layout => undef }; + template 'ajax/report/portvlanmismatch.tt', { results => $json }; } else { header( 'Content-Type' => 'text/comma-separated-values' ); - template 'ajax/report/portvlanmismatch_csv.tt', { results => \@results, }, - { layout => undef }; + template 'ajax/report/portvlanmismatch_csv.tt', { results => \@results, }; } }; diff --git a/lib/App/Netdisco/Web/Plugin/Report/SsidInventory.pm b/lib/App/Netdisco/Web/Plugin/Report/SsidInventory.pm index 910bdddd..ca45199d 100644 --- a/lib/App/Netdisco/Web/Plugin/Report/SsidInventory.pm +++ b/lib/App/Netdisco/Web/Plugin/Report/SsidInventory.pm @@ -11,6 +11,7 @@ register_report( tag => 'ssidinventory', label => 'SSID Inventory', provides_csv => 1, + api_endpoint => 1, } ); @@ -22,13 +23,11 @@ get '/ajax/content/report/ssidinventory' => require_login sub { if ( request->is_ajax ) { my $json = to_json( \@results ); - template 'ajax/report/portssid.tt', { results => $json }, - { layout => undef }; + template 'ajax/report/portssid.tt', { results => $json }; } else { header( 'Content-Type' => 'text/comma-separated-values' ); - template 'ajax/report/portssid_csv.tt', { results => \@results }, - { layout => undef }; + template 'ajax/report/portssid_csv.tt', { results => \@results }; } }; diff --git a/lib/App/Netdisco/Web/Plugin/Report/SubnetUtilization.pm b/lib/App/Netdisco/Web/Plugin/Report/SubnetUtilization.pm index aa4232e4..857f1ad9 100644 --- a/lib/App/Netdisco/Web/Plugin/Report/SubnetUtilization.pm +++ b/lib/App/Netdisco/Web/Plugin/Report/SubnetUtilization.pm @@ -5,19 +5,38 @@ use Dancer::Plugin::DBIC; use Dancer::Plugin::Auth::Extensible; use App::Netdisco::Web::Plugin; +use POSIX qw/strftime/; register_report({ category => 'IP', tag => 'subnets', label => 'Subnet Utilization', - provides_csv => 1, + provides_csv => 1, + api_endpoint => 1, + api_parameters => [ + subnet => { + description => 'IP Prefix to search', + default => '0.0.0.0/32', + }, + daterange => { + description => 'Date range to search', + default => ('1970-01-01 to '. strftime('%Y-%m-%d', gmtime)), + }, + age_invert => { + description => 'Results should NOT be within daterange', + type => 'boolean', + default => 'false', + }, + ], }); get '/ajax/content/report/subnets' => require_login sub { my $subnet = param('subnet') || '0.0.0.0/32'; my $agenot = param('age_invert') || '0'; - my ( $start, $end ) = param('daterange') =~ /(\d+-\d+-\d+)/gmx; + my $daterange = param('daterange') + || ('1970-01-01 to '. strftime('%Y-%m-%d', gmtime)); + my ( $start, $end ) = $daterange =~ /(\d+-\d+-\d+)/gmx; $start = $start . ' 00:00:00'; $end = $end . ' 23:59:59'; @@ -29,13 +48,11 @@ get '/ajax/content/report/subnets' => require_login sub { return unless scalar @results; if ( request->is_ajax ) { - template 'ajax/report/subnets.tt', { results => \@results }, - { layout => undef }; + template 'ajax/report/subnets.tt', { results => \@results }; } else { header( 'Content-Type' => 'text/comma-separated-values' ); - template 'ajax/report/subnets_csv.tt', { results => \@results }, - { layout => undef }; + template 'ajax/report/subnets_csv.tt', { results => \@results }; } }; diff --git a/lib/App/Netdisco/Web/Plugin/Report/VlanInventory.pm b/lib/App/Netdisco/Web/Plugin/Report/VlanInventory.pm index 866753fe..41975969 100644 --- a/lib/App/Netdisco/Web/Plugin/Report/VlanInventory.pm +++ b/lib/App/Netdisco/Web/Plugin/Report/VlanInventory.pm @@ -11,6 +11,7 @@ register_report( tag => 'vlaninventory', label => 'VLAN Inventory', provides_csv => 1, + api_endpoint => 1, } ); @@ -36,13 +37,11 @@ get '/ajax/content/report/vlaninventory' => require_login sub { if ( request->is_ajax ) { my $json = to_json (\@results); - template 'ajax/report/vlaninventory.tt', { results => $json }, - { layout => undef }; + template 'ajax/report/vlaninventory.tt', { results => $json }; } else { header( 'Content-Type' => 'text/comma-separated-values' ); - template 'ajax/report/vlaninventory_csv.tt', { results => \@results }, - { layout => undef }; + template 'ajax/report/vlaninventory_csv.tt', { results => \@results }; } }; diff --git a/lib/App/Netdisco/Web/Plugin/Search/Device.pm b/lib/App/Netdisco/Web/Plugin/Search/Device.pm index 96173f41..d7fc878d 100644 --- a/lib/App/Netdisco/Web/Plugin/Search/Device.pm +++ b/lib/App/Netdisco/Web/Plugin/Search/Device.pm @@ -8,8 +8,52 @@ use List::MoreUtils (); use App::Netdisco::Web::Plugin; -register_search_tab( - { tag => 'device', label => 'Device', provides_csv => 1 } ); +register_search_tab({ + tag => 'device', + label => 'Device', + provides_csv => 1, + api_endpoint => 1, + api_parameters => [ + q => { + description => 'Partial match of Device contact, serial, module serials, location, name, description, dns, or any IP alias', + }, + name => { + description => 'Partial match of the Device name', + }, + location => { + description => 'Partial match of the Device location', + }, + dns => { + description => 'Partial match of any of the Device IP aliases', + }, + ip => { + description => 'IP or IP Prefix within which the Device must have an interface address', + }, + description => { + description => 'Partial match of the Device description', + }, + model => { + description => 'Exact match of the Device model', + }, + os => { + description => 'Exact match of the Device operating system', + }, + os_ver => { + description => 'Exact match of the Device operating system version', + }, + vendor => { + description => 'Exact match of the Device vendor', + }, + layers => { + description => 'OSI Layer which the device must support', + }, + matchall => { + description => 'If true, all fields (except "q") must match the Device', + type => 'boolean', + default => 'false', + }, + ], +}); # device with various properties or a default match-all get '/ajax/content/search/device' => require_login sub { @@ -40,13 +84,11 @@ get '/ajax/content/search/device' => require_login sub { if ( request->is_ajax ) { my $json = to_json( \@results ); - template 'ajax/search/device.tt', { results => $json }, - { layout => undef }; + template 'ajax/search/device.tt', { results => $json }; } else { header( 'Content-Type' => 'text/comma-separated-values' ); - template 'ajax/search/device_csv.tt', { results => \@results, }, - { layout => undef }; + template 'ajax/search/device_csv.tt', { results => \@results, }; } }; diff --git a/lib/App/Netdisco/Web/Plugin/Search/Node.pm b/lib/App/Netdisco/Web/Plugin/Search/Node.pm index 0df12490..1a34d6b7 100644 --- a/lib/App/Netdisco/Web/Plugin/Search/Node.pm +++ b/lib/App/Netdisco/Web/Plugin/Search/Node.pm @@ -12,7 +12,51 @@ use NetAddr::MAC (); use App::Netdisco::Web::Plugin; use App::Netdisco::Util::Web 'sql_match'; -register_search_tab({ tag => 'node', label => 'Node' }); +register_search_tab({ + tag => 'node', + label => 'Node', + api_endpoint => 1, + api_parameters => [ + q => { + description => 'MAC Address or IP Address or Hostname (without Domain Suffix) of a Node (supports SQL or "*" wildcards)', + required => 1, + }, + partial => { + description => 'Partially match the "q" parameter (wildcard characters not required)', + type => 'boolean', + default => 'false', + }, + deviceports => { + description => 'MAC Address search will include Device Port MACs', + type => 'boolean', + default => 'true', + }, + show_vendor => { + description => 'Include interface Vendor in results', + type => 'boolean', + default => 'false', + }, + archived => { + description => 'Include archived records in results', + type => 'boolean', + default => 'false', + }, + daterange => { + description => 'Date Range in format "YYYY-MM-DD to YYYY-MM-DD"', + }, + age_invert => { + description => 'Date Range is NOT within the supplied range', + type => 'boolean', + default => 'false', + }, + # mac_format is used only in the template (will be IEEE) in results + #mac_format => { + #}, + # stamps param is used only in the template (they will be included) + #stamps => { + #}, + ], +}); # nodes matching the param as an IP or DNS hostname or MAC ajax '/ajax/content/search/node' => require_login sub { @@ -144,7 +188,7 @@ ajax '/ajax/content/search/node' => require_login sub { ports => $ports, wireless => $wireless, netbios => $netbios, - }, { layout => undef }; + }; } else { my $ports = param('deviceports') @@ -157,7 +201,7 @@ ajax '/ajax/content/search/node' => require_login sub { ports => $ports, wireless => $wireless, netbios => $netbios, - }, { layout => undef }; + }; } } @@ -200,7 +244,7 @@ ajax '/ajax/content/search/node' => require_login sub { template 'ajax/search/node_by_ip.tt', { macs => $set, archive_filter => {@active}, - }, { layout => undef }; + }; }; true; diff --git a/lib/App/Netdisco/Web/Plugin/Search/Port.pm b/lib/App/Netdisco/Web/Plugin/Search/Port.pm index 9bc43f22..63c73a7b 100644 --- a/lib/App/Netdisco/Web/Plugin/Search/Port.pm +++ b/lib/App/Netdisco/Web/Plugin/Search/Port.pm @@ -7,7 +7,33 @@ use Dancer::Plugin::Auth::Extensible; use App::Netdisco::Web::Plugin; use App::Netdisco::Util::Web 'sql_match'; -register_search_tab( { tag => 'port', label => 'Port', provides_csv => 1 } ); +register_search_tab({ + tag => 'port', + label => 'Port', + provides_csv => 1, + api_endpoint => 1, + api_parameters => [ + q => { + description => 'Port name or VLAN or MAC address', + required => 1, + }, + partial => { + description => 'Search for a partial match on parameter "q"', + type => 'boolean', + default => 'true', + }, + uplink => { + description => 'Include uplinks in results', + type => 'boolean', + default => 'false', + }, + ethernet => { + description => 'Only Ethernet type interfaces in results', + type => 'boolean', + default => 'true', + }, + ], +}); # device ports with a description (er, name) matching get '/ajax/content/search/port' => require_login sub { @@ -67,13 +93,11 @@ get '/ajax/content/search/port' => require_login sub { if ( request->is_ajax ) { my $json = to_json( \@results ); - template 'ajax/search/port.tt', { results => $json }, - { layout => undef }; + template 'ajax/search/port.tt', { results => $json }; } else { header( 'Content-Type' => 'text/comma-separated-values' ); - template 'ajax/search/port_csv.tt', { results => \@results }, - { layout => undef }; + template 'ajax/search/port_csv.tt', { results => \@results }; } }; diff --git a/lib/App/Netdisco/Web/Plugin/Search/VLAN.pm b/lib/App/Netdisco/Web/Plugin/Search/VLAN.pm index 315b4228..ac7f6aa4 100644 --- a/lib/App/Netdisco/Web/Plugin/Search/VLAN.pm +++ b/lib/App/Netdisco/Web/Plugin/Search/VLAN.pm @@ -6,7 +6,18 @@ use Dancer::Plugin::Auth::Extensible; use App::Netdisco::Web::Plugin; -register_search_tab( { tag => 'vlan', label => 'VLAN', provides_csv => 1 } ); +register_search_tab({ + tag => 'vlan', + label => 'VLAN', + provides_csv => 1, + api_endpoint => 1, + api_parameters => [ + q => { + description => 'VLAN name or number', + required => 1, + }, + ], +}); # devices carrying vlan xxx get '/ajax/content/search/vlan' => require_login sub { @@ -28,13 +39,11 @@ get '/ajax/content/search/vlan' => require_login sub { if (request->is_ajax) { my $json = to_json( \@results ); - template 'ajax/search/vlan.tt', { results => $json }, - { layout => undef }; + template 'ajax/search/vlan.tt', { results => $json }; } else { header( 'Content-Type' => 'text/comma-separated-values' ); - template 'ajax/search/vlan_csv.tt', { results => \@results }, - { layout => undef }; + template 'ajax/search/vlan_csv.tt', { results => \@results }; } }; diff --git a/lib/App/Netdisco/Web/Report.pm b/lib/App/Netdisco/Web/Report.pm index 069068db..77fbeacc 100644 --- a/lib/App/Netdisco/Web/Report.pm +++ b/lib/App/Netdisco/Web/Report.pm @@ -51,7 +51,7 @@ get '/report/*' => require_login sub { ssid_list => $ssid_list, type_list => $type_list, vendor_list => $vendor_list, - }; + }, { layout => 'main' }; }; true; diff --git a/lib/App/Netdisco/Web/Search.pm b/lib/App/Netdisco/Web/Search.pm index 60028857..a9b25918 100644 --- a/lib/App/Netdisco/Web/Search.pm +++ b/lib/App/Netdisco/Web/Search.pm @@ -95,7 +95,7 @@ get '/search' => require_login sub { os_list => $os_list, os_ver_list => $os_ver_list, vendor_list => $vendor_list, - }; + }, { layout => 'main' }; }; true; diff --git a/lib/App/Netdisco/Worker/Plugin/SetUserToken.pm b/lib/App/Netdisco/Worker/Plugin/GetAPIKey.pm similarity index 87% rename from lib/App/Netdisco/Worker/Plugin/SetUserToken.pm rename to lib/App/Netdisco/Worker/Plugin/GetAPIKey.pm index 1458b133..bef03ee6 100644 --- a/lib/App/Netdisco/Worker/Plugin/SetUserToken.pm +++ b/lib/App/Netdisco/Worker/Plugin/GetAPIKey.pm @@ -1,4 +1,4 @@ -package App::Netdisco::Worker::Plugin::SetUserToken; +package App::Netdisco::Worker::Plugin::GetAPIKey; use Dancer ':syntax'; use Dancer::Plugin::DBIC 'schema'; @@ -9,7 +9,7 @@ use aliased 'App::Netdisco::Worker::Status'; register_worker({ phase => 'check' }, sub { return Status->error('Missing user (-e).') unless shift->extra; - return Status->done('SetUserToken is able to run'); + return Status->done('GetAPIKey is able to run'); }); register_worker({ phase => 'main' }, sub { diff --git a/share/config.yml b/share/config.yml index 49038cbd..db6fb71d 100644 --- a/share/config.yml +++ b/share/config.yml @@ -416,7 +416,7 @@ worker_plugins: - 'Power' - 'Psql' - 'Renumber' - - 'SetUserToken' + - 'GetAPIKey' - 'Show' - 'Stats' - 'Vlan' @@ -494,7 +494,7 @@ engines: PRE_CHOMP: 1 INCLUDE_PATH: [] AUTO_FILTER: 'html_entity' -layout: 'main' +layout: 'noop' plugins: Swagger: main_api_module: 'App::Netdisco' diff --git a/share/swagger-ui/index.html b/share/swagger-ui/index.html index 32169e36..f09aa56e 100644 --- a/share/swagger-ui/index.html +++ b/share/swagger-ui/index.html @@ -4,6 +4,7 @@ Swagger UI + @@ -37,6 +38,8 @@