better version of API login, and filtered api calls
This commit is contained in:
		@@ -1,14 +1,17 @@
 | 
			
		||||
package App::Netdisco::Util::Web;
 | 
			
		||||
 | 
			
		||||
use strict;
 | 
			
		||||
use warnings;
 | 
			
		||||
use Dancer ':syntax';
 | 
			
		||||
 | 
			
		||||
use base 'Exporter';
 | 
			
		||||
use Time::Piece;
 | 
			
		||||
use Time::Seconds;
 | 
			
		||||
 | 
			
		||||
use base 'Exporter';
 | 
			
		||||
our @EXPORT = ();
 | 
			
		||||
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);
 | 
			
		||||
 | 
			
		||||
@@ -25,6 +28,18 @@ subroutines.
 | 
			
		||||
 | 
			
		||||
=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? )
 | 
			
		||||
 | 
			
		||||
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 Path::Class 'dir';
 | 
			
		||||
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::Static;
 | 
			
		||||
@@ -82,7 +83,7 @@ $swagger->{schemes} = ['http','https'];
 | 
			
		||||
$swagger->{consumes} = 'application/json';
 | 
			
		||||
$swagger->{produces} = 'application/json';
 | 
			
		||||
$swagger->{tags} = [
 | 
			
		||||
  {name => 'Global'},
 | 
			
		||||
  {name => 'General'},
 | 
			
		||||
  {name => 'Devices',
 | 
			
		||||
    description => 'Operations relating to Devices (switches, routers, etc)'},
 | 
			
		||||
  {name => 'Nodes',
 | 
			
		||||
@@ -94,7 +95,7 @@ $swagger->{securityDefinitions} = {
 | 
			
		||||
  APIKeyHeader =>
 | 
			
		||||
    { type => 'apiKey', name => 'Authorization', in => 'header' },
 | 
			
		||||
  BasicAuth =>
 | 
			
		||||
    { type => 'basic'  },
 | 
			
		||||
    { type => 'basic' },
 | 
			
		||||
};
 | 
			
		||||
$swagger->{security} = [ { APIKeyHeader => [] } ];
 | 
			
		||||
 | 
			
		||||
@@ -234,6 +235,9 @@ hook 'after' => sub {
 | 
			
		||||
 | 
			
		||||
# forward API calls to AJAX route handlers
 | 
			
		||||
any '/api/:type/:identifier/:method' => require_login sub {
 | 
			
		||||
    pass unless setting('api_enabled')
 | 
			
		||||
      ->{ params->{'type'} }->{ params->{'method'} };
 | 
			
		||||
 | 
			
		||||
    vars->{'is_api'} = 1;
 | 
			
		||||
    my $target =
 | 
			
		||||
      sprintf '/ajax/content/%s/%s', params->{'type'}, params->{'method'};
 | 
			
		||||
@@ -241,9 +245,15 @@ any '/api/:type/:identifier/:method' => require_login sub {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
any qr{.*} => sub {
 | 
			
		||||
    var('notfound' => true);
 | 
			
		||||
    status 'not_found';
 | 
			
		||||
    template 'index';
 | 
			
		||||
    if (request_is_api()) {
 | 
			
		||||
      status(404);
 | 
			
		||||
      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::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)/);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
hook 'before' => sub {
 | 
			
		||||
    params->{return_url} ||= ((request->path ne uri_for('/')->path)
 | 
			
		||||
      ? request->uri : uri_for('/inventory')->path);
 | 
			
		||||
@@ -22,7 +17,9 @@ hook 'before' => sub {
 | 
			
		||||
 | 
			
		||||
    if (! session('logged_in_user')
 | 
			
		||||
        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('/api/logout')->path
 | 
			
		||||
        and request->path ne uri_for('/swagger.json')->path
 | 
			
		||||
        and index(request->path, uri_for('/swagger-ui')->path) != 0) {
 | 
			
		||||
 | 
			
		||||
@@ -53,7 +50,7 @@ hook 'before' => sub {
 | 
			
		||||
            session(logged_in_user_realm => 'users');
 | 
			
		||||
        }
 | 
			
		||||
        elsif (request_is_api()
 | 
			
		||||
          and index(request->path, uri_for('/api')->path) == 0) {
 | 
			
		||||
          and request->header('Authorization')) {
 | 
			
		||||
 | 
			
		||||
            my $token = request->header('Authorization');
 | 
			
		||||
            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
 | 
			
		||||
swagger_path {
 | 
			
		||||
  description => 'Obtain an API Key using HTTP BasicAuth',
 | 
			
		||||
  tags => ['Global'],
 | 
			
		||||
  parameters => [],
 | 
			
		||||
  description => 'Obtain an API Key using BasicAuth or parameters',
 | 
			
		||||
  path => '/api/login',
 | 
			
		||||
  tags => ['General'],
 | 
			
		||||
  parameters => [
 | 
			
		||||
      { name => 'username', in => 'formData', required => false, type => 'string' },
 | 
			
		||||
      { name => 'password', in => 'formData', required => false, type => 'string' },
 | 
			
		||||
  ],
 | 
			
		||||
  responses => {
 | 
			
		||||
    default => {
 | 
			
		||||
      examples => {
 | 
			
		||||
        'application/json' => { api_key => 'cc9d5c02d8898e5728b7d7a0339c0785' } } },
 | 
			
		||||
  },
 | 
			
		||||
},
 | 
			
		||||
post '/login' => sub {
 | 
			
		||||
post qr{^/(?:api/)?login$} => sub {
 | 
			
		||||
    my $mode = (request_is_api() ? 'API' : 'WebUI');
 | 
			
		||||
 | 
			
		||||
    my $x = params; use DDP; p $x;
 | 
			
		||||
 | 
			
		||||
    # get authN data from request (HTTP BasicAuth or Form params)
 | 
			
		||||
    my $authheader = request->header('Authorization');
 | 
			
		||||
    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
 | 
			
		||||
# must be after the path is declared, above.
 | 
			
		||||
Dancer::Plugin::Swagger->instance->doc->{paths}->{'/login'}
 | 
			
		||||
  ->{post}->{security}->[0]->{BasicAuth} = [];
 | 
			
		||||
Dancer::Plugin::Swagger->instance->doc->{paths}->{'/api/login'}
 | 
			
		||||
  ->{post}->{security} = [ {}, {BasicAuth => []} ];
 | 
			
		||||
 | 
			
		||||
# 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' => {} } } },
 | 
			
		||||
},
 | 
			
		||||
 
 | 
			
		||||
@@ -90,6 +90,28 @@ web_plugins:
 | 
			
		||||
  - Device::Addresses
 | 
			
		||||
  - Device::Vlans
 | 
			
		||||
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:
 | 
			
		||||
  search_node:
 | 
			
		||||
    stamps:      { default: checked }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user