diff --git a/lib/NGCP/Panel/Controller/API/AdminCerts.pm b/lib/NGCP/Panel/Controller/API/AdminCerts.pm new file mode 100644 index 0000000000..831ff38c84 --- /dev/null +++ b/lib/NGCP/Panel/Controller/API/AdminCerts.pm @@ -0,0 +1,76 @@ +package NGCP::Panel::Controller::API::AdminCerts; + +use Sipwise::Base; +use parent qw/NGCP::Panel::Role::Entities NGCP::Panel::Role::API::AdminCerts/; + +use HTTP::Status qw(:constants); +use NGCP::Panel::Utils::Admin; + +__PACKAGE__->set_config(); + +sub allowed_roles { + return qw/admin reseller/; +} + +sub allowed_methods { + return [qw/POST OPTIONS HEAD/]; +} + +sub api_description { + return 'Creates a new SSL client certificate package in ZIP format containing a PEM and a P12 certificate.'; +} + +sub query_params { + return [ + {}, + ]; +} + +# avoid automatic creation of HAL response, since we're +# taking care of returning the body ourselves. +sub return_representation_post {} + +sub create_item { + my ($self, $c, $resource, $form, $process_extras) = @_; + + my $login = $resource->{login} // $c->user->login; + my $item_rs = $self->get_list($c); + my $admin = $item_rs->search({ + login => $login, + })->first; + + unless($admin) { + $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid login, administrator does not exist"); + return; + } + + # only allow to generate a certificate if + # a. you're doing it for yourself + # b. you're a master + # c. you're a superuser + unless($c->user->login eq $login || $c->user->is_master || $c->user->is_superuser) { + $self->error($c, HTTP_FORBIDDEN, "Insufficient privileges to create certificate for this administrator"); + return; + } + + my $err; + my $res = NGCP::Panel::Utils::Admin::generate_client_cert($c, $admin, sub { + my $e = shift; + $self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Failed to generate client certificate"); + $err = 1; + }); + return if $err; + + my $serial = $res->{serial}; + my $zipped_file = $res->{file}; + $c->res->headers(HTTP::Headers->new( + 'Content-Type' => 'application/zip', + 'Content-Disposition' => sprintf('attachment; filename=%s', "NGCP-API-client-certificate-$serial.zip") + )); + $c->res->body($zipped_file); + $c->response->status(HTTP_CREATED); +} + +1; + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Controller/Administrator.pm b/lib/NGCP/Panel/Controller/Administrator.pm index 8685cc1e0c..fc112365a0 100644 --- a/lib/NGCP/Panel/Controller/Administrator.pm +++ b/lib/NGCP/Panel/Controller/Administrator.pm @@ -3,7 +3,6 @@ use NGCP::Panel::Utils::Generic qw(:all); use Sipwise::Base; use parent 'Catalyst::Controller'; use HTTP::Headers qw(); -use IO::Compress::Zip qw/zip/; use NGCP::Panel::Form::Administrator::Reseller; use NGCP::Panel::Form::Administrator::Admin; use NGCP::Panel::Form::Administrator::APIGenerate qw(); @@ -259,54 +258,23 @@ sub api_key :Chained('base') :PathPart('api_key') :Args(0) { my $serial = $c->stash->{administrator}->ssl_client_m_serial; my ($pem, $p12); if ($c->req->body_parameters->{'gen.generate'}) { - my $updated; - while (!$updated) { - $serial = time; - try { - $pem = $c->model('CA')->make_client($c, $serial); - $p12 = $c->model('CA')->make_pkcs12($c, $serial, $pem, 'sipwise'); - } catch ($e) { - NGCP::Panel::Utils::Message::error( - c => $c, - error => $e, - data => { $c->stash->{administrator}->get_inflated_columns }, - desc => $c->loc("Failed to generate client certificate."), - ); - NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/administrator')); - } - try { - $c->stash->{administrator}->update({ - ssl_client_m_serial => $serial, - ssl_client_certificate => undef, # not used anymore, clear it just in case - }); - $updated = 1; - } catch(DBIx::Class::Exception $e where { "$_" =~ qr'Duplicate entry' }) { - $serial++; - } - } - - my $input = { - "NGCP-API-client-certificate-$serial.pem" => $pem, - "NGCP-API-client-certificate-$serial.p12" => $p12, - }; - my $zip_opts = { - AutoClose => 0, - Append => 0, - Name => "README.txt", - CanonicalName => 1, - Stream => 1, - }; - my $zipped_file; - my $zip = IO::Compress::Zip->new(\$zipped_file, %{ $zip_opts }); - $zip->write("Use the PEM file for programmatical clients like java, perl, php or curl, and the P12 file for browsers like Firefox or Chrome. The password for the P12 import is 'sipwise'. Handle this file with care, as it cannot be downloaded for a second time! Only a new certificate can be generated if the certificate is lost.\n"); - foreach my $k(keys %{ $input } ) { - $zip_opts->{Name} = $k; - $zip_opts->{Append} = 1; - $zip->newStream(%{ $zip_opts }); - $zip->write($input->{$k}); + my $err; + my $res = NGCP::Panel::Utils::Admin::generate_client_cert($c, $c->stash->{administrator}, sub { + my $e = shift; + NGCP::Panel::Utils::Message::error( + c => $c, + error => $e, + data => { $c->stash->{administrator}->get_inflated_columns }, + desc => $c->loc("Failed to generate client certificate."), + ); + $err = 1; + }); + if($err) { + NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/administrator')); } - $zip->close(); + $serial = $res->{serial}; + my $zipped_file = $res->{file}; $c->res->headers(HTTP::Headers->new( 'Content-Type' => 'application/zip', 'Content-Disposition' => sprintf('attachment; filename=%s', "NGCP-API-client-certificate-$serial.zip") diff --git a/lib/NGCP/Panel/Form/Administrator/APICert.pm b/lib/NGCP/Panel/Form/Administrator/APICert.pm new file mode 100644 index 0000000000..de5d48bc47 --- /dev/null +++ b/lib/NGCP/Panel/Form/Administrator/APICert.pm @@ -0,0 +1,9 @@ +package NGCP::Panel::Form::Administrator::APICert; +use HTML::FormHandler::Moose; +extends 'HTML::FormHandler'; + +has_field 'login' => (type => 'Text', required => 0, minlength => 5); + +1; + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Role/API/AdminCerts.pm b/lib/NGCP/Panel/Role/API/AdminCerts.pm new file mode 100644 index 0000000000..22c3c092be --- /dev/null +++ b/lib/NGCP/Panel/Role/API/AdminCerts.pm @@ -0,0 +1,50 @@ +package NGCP::Panel::Role::API::AdminCerts; + +use Sipwise::Base; + +use parent 'NGCP::Panel::Role::API'; + +use NGCP::Panel::Utils::DataHalLink qw(); +use HTTP::Status qw(:constants); +use NGCP::Panel::Form::Administrator::APICert; + +sub item_name { + return 'admincerts'; +} + +sub resource_name{ + return 'admincerts'; +} + +sub get_form { + my ($self, $c) = @_; + return NGCP::Panel::Form::Administrator::APICert->new(c => $c); +} + +sub hal_links { + my($self, $c, $item, $resource, $form) = @_; + return []; +} + +sub process_hal_resource { + my($self, $c, $item, $resource, $form) = @_; + return $resource; +} + +sub _item_rs { + my ($self, $c) = @_; + my $item_rs; + + if($c->user->roles eq "admin") { + $item_rs = $c->model('DB')->resultset('admins'); + } elsif($c->user->roles eq "reseller") { + $item_rs = $c->model('DB')->resultset('admins')->search({ + reseller_id => $c->user->reseller_id, + }); + } + + return $item_rs; +} + +1; +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Utils/Admin.pm b/lib/NGCP/Panel/Utils/Admin.pm index 84a39ae66a..d8de7ccd3d 100644 --- a/lib/NGCP/Panel/Utils/Admin.pm +++ b/lib/NGCP/Panel/Utils/Admin.pm @@ -3,6 +3,7 @@ package NGCP::Panel::Utils::Admin; use Sipwise::Base; use Crypt::Eksblowfish::Bcrypt qw/bcrypt_hash en_base64 de_base64/; use Data::Entropy::Algorithms qw/rand_bits/; +use IO::Compress::Zip qw/zip/; sub get_bcrypt_cost { return 13; @@ -85,4 +86,54 @@ sub perform_auth { return $res; } +sub generate_client_cert { + my ($c, $admin, $error_cb) = @_; + + my $updated; + my ($serial, $pem, $p12); + while (!$updated) { + $serial = time; + try { + $pem = $c->model('CA')->make_client($c, $serial); + $p12 = $c->model('CA')->make_pkcs12($c, $serial, $pem, 'sipwise'); + } catch ($e) { + $error_cb->($e); + return; + } + try { + $admin->update({ + ssl_client_m_serial => $serial, + ssl_client_certificate => undef, # not used anymore, clear it just in case + }); + $updated = 1; + } catch(DBIx::Class::Exception $e where { "$_" =~ qr'Duplicate entry' }) { + $serial++; + } + } + + my $input = { + "NGCP-API-client-certificate-$serial.pem" => $pem, + "NGCP-API-client-certificate-$serial.p12" => $p12, + }; + my $zip_opts = { + AutoClose => 0, + Append => 0, + Name => "README.txt", + CanonicalName => 1, + Stream => 1, + }; + my $zipped_file; + my $zip = IO::Compress::Zip->new(\$zipped_file, %{ $zip_opts }); + $zip->write("Use the PEM file for programmatical clients like java, perl, php or curl, and the P12 file for browsers like Firefox or Chrome. The password for the P12 import is 'sipwise'. Handle this file with care, as it cannot be downloaded for a second time! Only a new certificate can be generated if the certificate is lost.\n"); + foreach my $k(keys %{ $input } ) { + $zip_opts->{Name} = $k; + $zip_opts->{Append} = 1; + $zip->newStream(%{ $zip_opts }); + $zip->write($input->{$k}); + } + $zip->close(); + + return { serial => $serial, file => $zipped_file }; +} + 1;