better version of API login, and filtered api calls
This commit is contained in:
@@ -1,14 +1,17 @@
|
|||||||
package App::Netdisco::Util::Web;
|
package App::Netdisco::Util::Web;
|
||||||
|
|
||||||
use strict;
|
use Dancer ':syntax';
|
||||||
use warnings;
|
|
||||||
|
|
||||||
use base 'Exporter';
|
|
||||||
use Time::Piece;
|
use Time::Piece;
|
||||||
use Time::Seconds;
|
use Time::Seconds;
|
||||||
|
|
||||||
|
use base 'Exporter';
|
||||||
our @EXPORT = ();
|
our @EXPORT = ();
|
||||||
our @EXPORT_OK = qw/
|
our @EXPORT_OK = qw/
|
||||||
sort_port sort_modules interval_to_daterange sql_match
|
request_is_api
|
||||||
|
sort_port sort_modules
|
||||||
|
interval_to_daterange
|
||||||
|
sql_match
|
||||||
/;
|
/;
|
||||||
our %EXPORT_TAGS = (all => \@EXPORT_OK);
|
our %EXPORT_TAGS = (all => \@EXPORT_OK);
|
||||||
|
|
||||||
@@ -25,6 +28,18 @@ subroutines.
|
|||||||
|
|
||||||
=head1 EXPORT_OK
|
=head1 EXPORT_OK
|
||||||
|
|
||||||
|
=head2 request_is_api
|
||||||
|
|
||||||
|
Whether the request should be interpreted as an API call.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub request_is_api {
|
||||||
|
return (setting('api_token_lifetime')
|
||||||
|
and request->accept =~ m/(?:json|javascript)/
|
||||||
|
and index(request->path, uri_for('/api')->path) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
=head2 sql_match( $value, $exact? )
|
=head2 sql_match( $value, $exact? )
|
||||||
|
|
||||||
Convert wildcard characters "C<*>" and "C<?>" to "C<%>" and "C<_>"
|
Convert wildcard characters "C<*>" and "C<?>" to "C<%>" and "C<_>"
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ use HTML::Entities (); # to ensure dependency is met
|
|||||||
use URI::QueryParam (); # part of URI, to add helper methods
|
use URI::QueryParam (); # part of URI, to add helper methods
|
||||||
use Path::Class 'dir';
|
use Path::Class 'dir';
|
||||||
use Module::Load ();
|
use Module::Load ();
|
||||||
use App::Netdisco::Util::Web 'interval_to_daterange';
|
use App::Netdisco::Util::Web
|
||||||
|
qw/request_is_api interval_to_daterange/;
|
||||||
|
|
||||||
use App::Netdisco::Web::AuthN;
|
use App::Netdisco::Web::AuthN;
|
||||||
use App::Netdisco::Web::Static;
|
use App::Netdisco::Web::Static;
|
||||||
@@ -82,7 +83,7 @@ $swagger->{schemes} = ['http','https'];
|
|||||||
$swagger->{consumes} = 'application/json';
|
$swagger->{consumes} = 'application/json';
|
||||||
$swagger->{produces} = 'application/json';
|
$swagger->{produces} = 'application/json';
|
||||||
$swagger->{tags} = [
|
$swagger->{tags} = [
|
||||||
{name => 'Global'},
|
{name => 'General'},
|
||||||
{name => 'Devices',
|
{name => 'Devices',
|
||||||
description => 'Operations relating to Devices (switches, routers, etc)'},
|
description => 'Operations relating to Devices (switches, routers, etc)'},
|
||||||
{name => 'Nodes',
|
{name => 'Nodes',
|
||||||
@@ -94,7 +95,7 @@ $swagger->{securityDefinitions} = {
|
|||||||
APIKeyHeader =>
|
APIKeyHeader =>
|
||||||
{ type => 'apiKey', name => 'Authorization', in => 'header' },
|
{ type => 'apiKey', name => 'Authorization', in => 'header' },
|
||||||
BasicAuth =>
|
BasicAuth =>
|
||||||
{ type => 'basic' },
|
{ type => 'basic' },
|
||||||
};
|
};
|
||||||
$swagger->{security} = [ { APIKeyHeader => [] } ];
|
$swagger->{security} = [ { APIKeyHeader => [] } ];
|
||||||
|
|
||||||
@@ -234,6 +235,9 @@ hook 'after' => sub {
|
|||||||
|
|
||||||
# forward API calls to AJAX route handlers
|
# forward API calls to AJAX route handlers
|
||||||
any '/api/:type/:identifier/:method' => require_login sub {
|
any '/api/:type/:identifier/:method' => require_login sub {
|
||||||
|
pass unless setting('api_enabled')
|
||||||
|
->{ params->{'type'} }->{ params->{'method'} };
|
||||||
|
|
||||||
vars->{'is_api'} = 1;
|
vars->{'is_api'} = 1;
|
||||||
my $target =
|
my $target =
|
||||||
sprintf '/ajax/content/%s/%s', params->{'type'}, params->{'method'};
|
sprintf '/ajax/content/%s/%s', params->{'type'}, params->{'method'};
|
||||||
@@ -241,9 +245,15 @@ any '/api/:type/:identifier/:method' => require_login sub {
|
|||||||
};
|
};
|
||||||
|
|
||||||
any qr{.*} => sub {
|
any qr{.*} => sub {
|
||||||
var('notfound' => true);
|
if (request_is_api()) {
|
||||||
status 'not_found';
|
status(404);
|
||||||
template 'index';
|
return to_json { error => 'not found' };
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var('notfound' => true);
|
||||||
|
status 'not_found';
|
||||||
|
template 'index';
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,14 +5,9 @@ use Dancer::Plugin::DBIC;
|
|||||||
use Dancer::Plugin::Auth::Extensible;
|
use Dancer::Plugin::Auth::Extensible;
|
||||||
use Dancer::Plugin::Swagger;
|
use Dancer::Plugin::Swagger;
|
||||||
|
|
||||||
|
use App::Netdisco::Util::Web 'request_is_api';
|
||||||
use MIME::Base64;
|
use MIME::Base64;
|
||||||
|
|
||||||
sub request_is_api {
|
|
||||||
return (setting('api_token_lifetime')
|
|
||||||
and request->header('Authorization')
|
|
||||||
and request->accept =~ m/(?:json|javascript)/);
|
|
||||||
}
|
|
||||||
|
|
||||||
hook 'before' => sub {
|
hook 'before' => sub {
|
||||||
params->{return_url} ||= ((request->path ne uri_for('/')->path)
|
params->{return_url} ||= ((request->path ne uri_for('/')->path)
|
||||||
? request->uri : uri_for('/inventory')->path);
|
? request->uri : uri_for('/inventory')->path);
|
||||||
@@ -22,7 +17,9 @@ hook 'before' => sub {
|
|||||||
|
|
||||||
if (! session('logged_in_user')
|
if (! session('logged_in_user')
|
||||||
and request->path ne uri_for('/login')->path
|
and request->path ne uri_for('/login')->path
|
||||||
|
and request->path ne uri_for('/api/login')->path
|
||||||
and request->path ne uri_for('/logout')->path
|
and request->path ne uri_for('/logout')->path
|
||||||
|
and request->path ne uri_for('/api/logout')->path
|
||||||
and request->path ne uri_for('/swagger.json')->path
|
and request->path ne uri_for('/swagger.json')->path
|
||||||
and index(request->path, uri_for('/swagger-ui')->path) != 0) {
|
and index(request->path, uri_for('/swagger-ui')->path) != 0) {
|
||||||
|
|
||||||
@@ -53,7 +50,7 @@ hook 'before' => sub {
|
|||||||
session(logged_in_user_realm => 'users');
|
session(logged_in_user_realm => 'users');
|
||||||
}
|
}
|
||||||
elsif (request_is_api()
|
elsif (request_is_api()
|
||||||
and index(request->path, uri_for('/api')->path) == 0) {
|
and request->header('Authorization')) {
|
||||||
|
|
||||||
my $token = request->header('Authorization');
|
my $token = request->header('Authorization');
|
||||||
my $user = $provider->validate_api_token($token)
|
my $user = $provider->validate_api_token($token)
|
||||||
@@ -85,18 +82,24 @@ get qr{^/(?:login(?:/denied)?)?} => sub {
|
|||||||
|
|
||||||
# override default login_handler so we can log access in the database
|
# override default login_handler so we can log access in the database
|
||||||
swagger_path {
|
swagger_path {
|
||||||
description => 'Obtain an API Key using HTTP BasicAuth',
|
description => 'Obtain an API Key using BasicAuth or parameters',
|
||||||
tags => ['Global'],
|
path => '/api/login',
|
||||||
parameters => [],
|
tags => ['General'],
|
||||||
|
parameters => [
|
||||||
|
{ name => 'username', in => 'formData', required => false, type => 'string' },
|
||||||
|
{ name => 'password', in => 'formData', required => false, type => 'string' },
|
||||||
|
],
|
||||||
responses => {
|
responses => {
|
||||||
default => {
|
default => {
|
||||||
examples => {
|
examples => {
|
||||||
'application/json' => { api_key => 'cc9d5c02d8898e5728b7d7a0339c0785' } } },
|
'application/json' => { api_key => 'cc9d5c02d8898e5728b7d7a0339c0785' } } },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
post '/login' => sub {
|
post qr{^/(?:api/)?login$} => sub {
|
||||||
my $mode = (request_is_api() ? 'API' : 'WebUI');
|
my $mode = (request_is_api() ? 'API' : 'WebUI');
|
||||||
|
|
||||||
|
my $x = params; use DDP; p $x;
|
||||||
|
|
||||||
# get authN data from request (HTTP BasicAuth or Form params)
|
# get authN data from request (HTTP BasicAuth or Form params)
|
||||||
my $authheader = request->header('Authorization');
|
my $authheader = request->header('Authorization');
|
||||||
if (defined $authheader and $authheader =~ /^Basic (.*)$/i) {
|
if (defined $authheader and $authheader =~ /^Basic (.*)$/i) {
|
||||||
@@ -158,13 +161,13 @@ post '/login' => sub {
|
|||||||
|
|
||||||
# ugh, *puke*, but D::P::Swagger has no way to set this with swagger_path
|
# ugh, *puke*, but D::P::Swagger has no way to set this with swagger_path
|
||||||
# must be after the path is declared, above.
|
# must be after the path is declared, above.
|
||||||
Dancer::Plugin::Swagger->instance->doc->{paths}->{'/login'}
|
Dancer::Plugin::Swagger->instance->doc->{paths}->{'/api/login'}
|
||||||
->{post}->{security}->[0]->{BasicAuth} = [];
|
->{post}->{security} = [ {}, {BasicAuth => []} ];
|
||||||
|
|
||||||
# we override the default login_handler, so logout has to be handled as well
|
# we override the default login_handler, so logout has to be handled as well
|
||||||
swagger_path {
|
swagger_path {
|
||||||
description => 'Destroy user API Key and session cookie',
|
description => 'Destroy user API Key and session cookie',
|
||||||
tags => ['Global'],
|
tags => ['General'],
|
||||||
parameters => [],
|
parameters => [],
|
||||||
responses => { default => { examples => { 'application/json' => {} } } },
|
responses => { default => { examples => { 'application/json' => {} } } },
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -90,6 +90,28 @@ web_plugins:
|
|||||||
- Device::Addresses
|
- Device::Addresses
|
||||||
- Device::Vlans
|
- Device::Vlans
|
||||||
extra_web_plugins: []
|
extra_web_plugins: []
|
||||||
|
api_enabled:
|
||||||
|
device:
|
||||||
|
details: true
|
||||||
|
port: false
|
||||||
|
ports: false
|
||||||
|
modules: false
|
||||||
|
addresses: false
|
||||||
|
vlans: false
|
||||||
|
node: false
|
||||||
|
vlan: false
|
||||||
|
report: false
|
||||||
|
admin:
|
||||||
|
topology: false
|
||||||
|
pseudodevice: false
|
||||||
|
user: false
|
||||||
|
report:
|
||||||
|
slowdevices: false
|
||||||
|
performance: false
|
||||||
|
timedoutdevices: false
|
||||||
|
duplicaedevices: false
|
||||||
|
orphaned: false
|
||||||
|
userlog: false
|
||||||
sidebar_defaults:
|
sidebar_defaults:
|
||||||
search_node:
|
search_node:
|
||||||
stamps: { default: checked }
|
stamps: { default: checked }
|
||||||
|
|||||||
Reference in New Issue
Block a user