You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
421 lines
14 KiB
421 lines
14 KiB
package NGCP::Panel;
|
|
|
|
use Moose;
|
|
use Catalyst::Runtime 5.80;
|
|
use File::Slurp qw();
|
|
use Config::General qw();
|
|
use IO::Socket::UNIX qw(SOCK_DGRAM);
|
|
|
|
# Set flags and add plugins for the application.
|
|
#
|
|
# Note that ORDERING IS IMPORTANT here as plugins are initialized in order,
|
|
# therefore you almost certainly want to keep ConfigLoader at the head of the
|
|
# list if you're using it.
|
|
#
|
|
# -Debug: activates the debug mode for very useful log messages
|
|
# ConfigLoader: will load the configuration from a Config::General file in the
|
|
# application's home directory
|
|
# Static::Simple: will serve static files from the application's root
|
|
# directory
|
|
|
|
use Catalyst qw/
|
|
ConfigLoader
|
|
Static::Simple
|
|
Authentication
|
|
Authorization::Roles
|
|
Session
|
|
Session::Store::Redis
|
|
NGCP::RealmCookie
|
|
NGCP::EscapeSensitiveValue
|
|
NGCP::EscapeJs
|
|
NGCP::EscapeURI
|
|
NGCP::License
|
|
NGCP::Redis
|
|
I18N
|
|
/;
|
|
use Log::Log4perl::Catalyst qw();
|
|
extends 'Catalyst';
|
|
|
|
our $VERSION = '0.01';
|
|
|
|
my $panel_config_file;
|
|
my $panel_config;
|
|
for my $path(qw#/etc/ngcp-panel/ngcp_panel.conf etc/ngcp_panel.conf ngcp_panel.conf#) {
|
|
if (-f $path) {
|
|
$panel_config_file = $path;
|
|
last;
|
|
}
|
|
}
|
|
$panel_config_file //= 'etc/ngcp_panel.conf';
|
|
$panel_config = get_panel_config() // {};
|
|
|
|
sub get_panel_config {
|
|
my $config;
|
|
if( -f $panel_config_file ){
|
|
my $catalyst_config = Config::General->new($panel_config_file);
|
|
my %config = $catalyst_config->getall();
|
|
$config = \%config;
|
|
}
|
|
return $config;
|
|
}
|
|
|
|
my $logger_config_file;
|
|
for my $path(qw#./logging.conf /etc/ngcp-panel/logging.conf#) {
|
|
if(-f $path) {
|
|
$logger_config_file = $path;
|
|
last;
|
|
}
|
|
}
|
|
$logger_config_file //= $panel_config_file;
|
|
|
|
sub handle_unicode_encoding_exception {
|
|
my ($self, $exception_context) = @_;
|
|
$self->log->debug(">> handle_unicode_encoding_exception: " . $exception_context->{error_msg});
|
|
return $exception_context->{param_value};
|
|
}
|
|
|
|
__PACKAGE__->config(
|
|
name => 'NGCP::Panel',
|
|
# Disable deprecated behavior needed by old applications
|
|
disable_component_resolution_regex_fallback => 1,
|
|
enable_catalyst_header => 1, # Send X-Catalyst header
|
|
encoding => 'UTF-8',
|
|
'Plugin::ConfigLoader' => {
|
|
file => $panel_config_file,
|
|
},
|
|
'View::HTML' => {
|
|
INCLUDE_PATH => [
|
|
'/usr/share/ngcp-panel/templates',
|
|
'/usr/share/ngcp-panel/layout',
|
|
'/usr/share/ngcp-panel/static',
|
|
__PACKAGE__->path_to('share', 'templates'),
|
|
__PACKAGE__->path_to('share', 'layout'),
|
|
__PACKAGE__->path_to('share', 'static'),
|
|
],
|
|
ABSOLUTE => 1,
|
|
EVAL_PERL => 1,
|
|
},
|
|
'View::JSON' => {
|
|
#Set the stash keys to be exposed to a JSON response
|
|
#(sEcho iTotalRecords iTotalDisplayRecords aaData) for datatables
|
|
expose_stash => [ qw(sEcho iTotalRecords iTotalDisplayRecords iTotalRecordCountClipped iTotalDisplayRecordCountClipped aaData dt_custom_footer widget_data timeline_data) ],
|
|
},
|
|
'View::TT' => {
|
|
INCLUDE_PATH => [
|
|
'/usr/share/ngcp-panel/templates',
|
|
'/usr/share/ngcp-panel/layout',
|
|
'/usr/share/ngcp-panel/static',
|
|
__PACKAGE__->path_to('share', 'templates'),
|
|
__PACKAGE__->path_to('share', 'layout'),
|
|
__PACKAGE__->path_to('share', 'static'),
|
|
],
|
|
ABSOLUTE => 1,
|
|
EVAL_PERL => 1,
|
|
},
|
|
|
|
'Plugin::Static::Simple' => {
|
|
include_path => [
|
|
'/usr/share/ngcp-panel/static',
|
|
__PACKAGE__->path_to('share', 'static'),
|
|
],
|
|
mime_types => {
|
|
woff => 'application/x-font-woff',
|
|
},
|
|
},
|
|
|
|
'Plugin::Session' => {
|
|
flash_to_stash => 1,
|
|
expires => 3600,
|
|
cookie_secure => 1,
|
|
cookie_name => 'ngcp-panel',
|
|
login_to_csc_session_expiry => 86400, #24 hours expiry for subscriber sessions created directly from Admin UI
|
|
},
|
|
|
|
'Plugin::Authentication' => {
|
|
default_realm => 'subscriber',
|
|
admin => {
|
|
credential => {
|
|
class => 'Password',
|
|
password_field => 'md5pass',
|
|
password_type => 'hashed',
|
|
password_hash_type => 'MD5'
|
|
},
|
|
store => {
|
|
class => 'DBIx::Class',
|
|
user_model => 'DB::admins',
|
|
id_field => 'id',
|
|
store_user_class => 'NGCP::Panel::Authentication::Store::RoleFromRealm',
|
|
use_userdata_from_session => 0,
|
|
}
|
|
},
|
|
admin_bcrypt => {
|
|
credential => {
|
|
class => 'Password',
|
|
password_field => 'saltedpass',
|
|
# we handle the salt and hash management manually in Login.pm
|
|
password_type => 'clear',
|
|
},
|
|
store => {
|
|
class => 'DBIx::Class',
|
|
user_model => 'DB::admins',
|
|
id_field => 'id',
|
|
store_user_class => 'NGCP::Panel::Authentication::Store::RoleFromRealm',
|
|
use_userdata_from_session => 0,
|
|
}
|
|
},
|
|
admin_jwt => {
|
|
credential => {
|
|
class => '+NGCP::Panel::Authentication::Credential::JWT',
|
|
username_jwt => 'username',
|
|
username_field => 'login',
|
|
id_jwt => 'id',
|
|
id_field => 'id',
|
|
jwt_key => _get_jwt_key(),
|
|
debug => 1,
|
|
alg => 'HS256',
|
|
},
|
|
store => {
|
|
class => 'DBIx::Class',
|
|
user_model => 'DB::admins',
|
|
store_user_class => 'NGCP::Panel::Authentication::Store::RoleFromRealm',
|
|
},
|
|
use_session => 1,
|
|
},
|
|
api_admin => {
|
|
credential => {
|
|
class => 'Password',
|
|
password_field => 'md5pass',
|
|
password_type => 'hashed',
|
|
password_hash_type => 'MD5'
|
|
},
|
|
store => {
|
|
class => 'DBIx::Class',
|
|
user_model => 'DB::admins',
|
|
id_field => 'id',
|
|
store_user_class => 'NGCP::Panel::Authentication::Store::RoleFromRealm',
|
|
use_userdata_from_session => 0,
|
|
},
|
|
use_session => 0,
|
|
},
|
|
api_admin_bcrypt => {
|
|
credential => {
|
|
class => 'Password',
|
|
password_field => 'saltedpass',
|
|
# we handle the salt and hash management manually in Login.pm
|
|
password_type => 'clear',
|
|
},
|
|
store => {
|
|
class => 'DBIx::Class',
|
|
user_model => 'DB::admins',
|
|
id_field => 'id',
|
|
store_user_class => 'NGCP::Panel::Authentication::Store::RoleFromRealm',
|
|
use_userdata_from_session => 0,
|
|
},
|
|
use_session => 0,
|
|
},
|
|
api_admin_cert => {
|
|
# TODO: should be NoPassword, but it's not available in our catalyst version yet
|
|
credential => {
|
|
class => 'Password',
|
|
password_field => 'is_active',
|
|
password_type => 'clear',
|
|
},
|
|
store => {
|
|
class => 'DBIx::Class',
|
|
user_model => 'DB::admins',
|
|
id_field => 'id',
|
|
store_user_class => 'NGCP::Panel::Authentication::Store::RoleFromRealm',
|
|
},
|
|
use_session => 0,
|
|
},
|
|
api_admin_http => {
|
|
credential => {
|
|
class => 'HTTP',
|
|
#type => 'digest',
|
|
type => 'basic',
|
|
username_field => 'login',
|
|
password_field => 'md5pass',
|
|
password_type => 'hashed',
|
|
password_hash_type => 'MD5'
|
|
},
|
|
store => {
|
|
class => 'DBIx::Class',
|
|
user_model => 'DB::admins',
|
|
id_field => 'id',
|
|
store_user_class => 'NGCP::Panel::Authentication::Store::RoleFromRealm',
|
|
},
|
|
use_session => 0,
|
|
},
|
|
api_subscriber_http => {
|
|
credential => {
|
|
class => 'HTTP',
|
|
#type => 'digest',
|
|
type => 'basic',
|
|
username_field => 'webusername',
|
|
password_field => 'webpassword',
|
|
password_type => 'clear',
|
|
},
|
|
store => {
|
|
class => 'DBIx::Class',
|
|
user_model => 'DB::provisioning_voip_subscribers',
|
|
store_user_class => 'NGCP::Panel::Authentication::Store::RoleFromRealm',
|
|
use_userdata_from_session => 0,
|
|
},
|
|
use_session => 0,
|
|
},
|
|
api_subscriber_jwt => {
|
|
credential => {
|
|
class => '+NGCP::Panel::Authentication::Credential::JWT',
|
|
username_jwt => 'username',
|
|
username_field => 'webusername',
|
|
id_jwt => 'subscriber_uuid',
|
|
id_field => 'uuid',
|
|
jwt_key => _get_jwt_key(),
|
|
debug => 1,
|
|
alg => 'HS256',
|
|
},
|
|
store => {
|
|
class => 'DBIx::Class',
|
|
user_model => 'DB::provisioning_voip_subscribers',
|
|
store_user_class => 'NGCP::Panel::Authentication::Store::RoleFromRealm',
|
|
},
|
|
use_session => 0,
|
|
},
|
|
api_admin_jwt => {
|
|
credential => {
|
|
class => '+NGCP::Panel::Authentication::Credential::JWT',
|
|
username_jwt => 'username',
|
|
username_field => 'login',
|
|
id_jwt => 'id',
|
|
id_field => 'id',
|
|
jwt_key => _get_jwt_key(),
|
|
debug => 1,
|
|
alg => 'HS256',
|
|
},
|
|
store => {
|
|
class => 'DBIx::Class',
|
|
user_model => 'DB::admins',
|
|
store_user_class => 'NGCP::Panel::Authentication::Store::RoleFromRealm',
|
|
},
|
|
use_session => 0,
|
|
},
|
|
api_admin_system => {
|
|
credential => {
|
|
class => 'HTTP',
|
|
type => 'basic',
|
|
username_field => 'login',
|
|
password_field => 'password',
|
|
password_type => 'clear',
|
|
},
|
|
store => {
|
|
class => '+NGCP::Panel::Authentication::Store::System',
|
|
file => '/etc/default/ngcp-api',
|
|
group => 'auth_system',
|
|
},
|
|
use_session => 0,
|
|
},
|
|
subscriber => {
|
|
credential => {
|
|
class => 'Password',
|
|
password_field => 'webpassword',
|
|
password_type => 'clear',
|
|
},
|
|
store => {
|
|
class => 'DBIx::Class',
|
|
user_model => 'DB::provisioning_voip_subscribers',
|
|
id_field => 'id',
|
|
store_user_class => 'NGCP::Panel::Authentication::Store::RoleFromRealm',
|
|
use_userdata_from_session => 0,
|
|
}
|
|
},
|
|
ngcp_admin_ui_jwt => {
|
|
credential => {
|
|
class => '+NGCP::Panel::Authentication::Credential::JWT',
|
|
username_jwt => 'username',
|
|
username_field => 'login',
|
|
id_jwt => 'id',
|
|
id_field => 'id',
|
|
jwt_key => _get_jwt_key(),
|
|
debug => 1,
|
|
alg => 'HS256',
|
|
},
|
|
store => {
|
|
class => 'DBIx::Class',
|
|
user_model => 'DB::admins',
|
|
store_user_class => 'NGCP::Panel::Authentication::Store::RoleFromRealm',
|
|
},
|
|
use_session => 1,
|
|
},
|
|
},
|
|
ngcp_version => get_ngcp_version(),
|
|
uploadtmp => $panel_config->{general}{tmpdir} // '/tmp',
|
|
);
|
|
__PACKAGE__->config( default_view => 'HTML' );
|
|
|
|
__PACKAGE__->log(Log::Log4perl::Catalyst->new($logger_config_file, autoflush => 1));
|
|
# autoflush => 1: since we are not writing directly to the log file but to syslog instead, let syslog/rsyslog take care of the buffering part, otherwise $c->log->* invokations get buffered and delayed until a request if fully completed and response is sent, on top of it all logs such dumped buffer to syslog will be logged with exact same time, which is not correct. As well as autoflush is enabled by default in Log4Perl.
|
|
|
|
# configure Data::HAL depending on our config
|
|
{
|
|
require Data::HAL;
|
|
*{Data::HAL::forcearray_policy} = sub {
|
|
my ($self, $root, $property_type, $relation, $property) = @_;
|
|
my $embedded = $root->embedded ? $root->embedded->[0] : undef;
|
|
if ($embedded
|
|
&& ( $property_type eq 'links' || $property_type eq 'embedded' )
|
|
&& $relation =~/^ngcp:[a-z0-9]+$/
|
|
) {
|
|
return 1;
|
|
}
|
|
if (!$embedded
|
|
&& ( $property_type eq 'links' )
|
|
&& $relation =~/^ngcp:[a-z0-9]+$/
|
|
) {
|
|
return 1;
|
|
}
|
|
};
|
|
}
|
|
|
|
after setup_finalize => sub {
|
|
|
|
my $app = shift;
|
|
|
|
if ($ENV{NOTIFY_SOCKET}) {
|
|
my $addr = $ENV{NOTIFY_SOCKET} =~ s/^@/\0/r;
|
|
my $client = IO::Socket::UNIX->new(
|
|
Type => SOCK_DGRAM(),
|
|
Peer => $addr,
|
|
) or warn("can't connect to socket $ENV{NOTIFY_SOCKET}: $!\n");
|
|
if ($client) {
|
|
$client->autoflush(1);
|
|
print $client "READY=1\n" or warn("can't send to socket $ENV{NOTIFY_SOCKET}: $!\n");
|
|
close $client;
|
|
}
|
|
} else {
|
|
warn("NOTIFY_SOCKET not set\n");
|
|
}
|
|
|
|
};
|
|
|
|
# Start the application
|
|
__PACKAGE__->setup();
|
|
|
|
sub get_ngcp_version {
|
|
my $content = File::Slurp::read_file("/etc/ngcp_version", err_mode => 'quiet');
|
|
$content //= '(unavailable)';
|
|
chomp($content);
|
|
return $content;
|
|
}
|
|
|
|
sub _get_jwt_key {
|
|
my $content = File::Slurp::read_file("/etc/ngcp-panel/jwt_secret", err_mode => 'quiet');
|
|
$content //= '';
|
|
$content =~ s/\n//; # remove newline before
|
|
chomp($content);
|
|
return $content;
|
|
}
|
|
|
|
1;
|
|
|
|
# vim: set tabstop=4 expandtab:
|