TT#125902 add Login CSC v2 support

* Login CSC v2 button is shown on the subscriber's master
  data page if www_admin.http_csc.csc_js_enable == 1 or 2
* When the login is triggered an auth token
  is generated internally followed by a redirect to
  CSC as /?a=auth_token
* move generate_auth_token() into Utils/Auth
* improve generate_auth_token() arguments support
* add /api/authtokens error handling

Change-Id: Idd65400bf8ce6ce48979c736f6a199fb567ffaa4
mr10.0
Kirill Solomko 5 years ago
parent 62cf532c8e
commit 1cdae0b1e0

@ -8,9 +8,9 @@ use File::Basename;
use File::Find::Rule;
use HTTP::Headers qw();
use HTTP::Status qw(:constants);
use NGCP::Panel::Utils::Auth;
sub allowed_methods{
sub allowed_methods {
return [qw/POST OPTIONS/];
}
@ -25,15 +25,15 @@ sub query_params {
];
}
sub resource_name{
sub resource_name {
return 'authtokens';
}
sub dispatch_path{
sub dispatch_path {
return '/api/authtokens/';
}
sub relation{
sub relation {
return 'http://purl.org/sipwise/ngcp-api/#rel-authtokens';
}
@ -45,24 +45,37 @@ sub POST :Allow {
my ($self, $c) = @_;
my $resource = $self->get_valid_post_data(
c => $c,
c => $c,
media_type => 'application/json',
);
return unless $resource;
my $form = $self->get_form($c);
return unless $self->validate_form(
c => $c,
resource => $resource,
form => $form,
);
if($c->user->roles eq "reseller") {
if ($c->user->roles eq "reseller") {
$resource->{reseller_id} = $c->user->reseller_id;
}
my $res = {};
$res->{token} = $self->generate_auth_token($c, $resource);
$res->{token} = NGCP::Panel::Utils::Auth::generate_auth_token($self, $c,
$resource->{type},
$c->users->role,
$c->users->id,
$resource->{expires} // 10,
);
unless ($res->{token}) {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Could not generate auth token");
return;
}
$c->response->status(HTTP_CREATED);
$c->response->body(JSON::to_json($res));

@ -550,13 +550,21 @@ sub login_jwt :Chained('/') :PathPart('login_jwt') :Args(0) :Method('POST') {
my $role = $redis->hget("auth_token:$auth_token", "role");
my $user_id = $redis->hget("auth_token:$auth_token", "user_id");
$redis->del("auth_token:$auth_token") if ($type eq 'onetime');
unless ($type && $role && $user_id) {
$c->response->status(HTTP_FORBIDDEN);
$c->response->body(encode_json({ code => HTTP_FORBIDDEN,
message => "Forbidden!" })."\n");
$c->log->error("Unknown auth_token");
return;
}
$redis->del("auth_token:$auth_token") if $type eq 'onetime';
if ($ngcp_realm eq 'admin') {
unless (grep {$role eq $_} qw/admin reseller ccare ccareadmin/) {
$c->response->status(HTTP_FORBIDDEN);
$c->response->body(encode_json({ code => HTTP_FORBIDDEN,
message => "Forbidden!" })."\n");
message => "Forbidden!" })."\n");
$c->log->error("Wrong auth_token role");
return;
}

@ -12,6 +12,7 @@ use Data::Dumper;
use MIME::Base64 qw(encode_base64url decode_base64url);
use File::Slurp qw/read_file/;
use NGCP::Panel::Utils::Auth;
use NGCP::Panel::Utils::Navigation;
use NGCP::Panel::Utils::Contract;
use NGCP::Panel::Utils::Subscriber;
@ -3164,7 +3165,7 @@ sub edit_master :Chained('master') :PathPart('edit') :Args(0) :Does(ACL) :ACLDet
sub login_to_csc :Chained('master') :PathPart('login_to_csc') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) :AllowedRole(ccareadmin) :AllowedRole(ccare) {
my ($self, $c) = @_;
$c->detach('/denied_page') if($c->user->read_only);
$c->detach('/denied_page') if ($c->user->read_only);
my $subscriber = $c->stash->{subscriber};
my $prov_subscriber = $subscriber->provisioning_voip_subscriber;
@ -3184,6 +3185,39 @@ sub login_to_csc :Chained('master') :PathPart('login_to_csc') :Args(0) :Does(ACL
$c->res->redirect("https://" . $c->req->uri->host . "/");
}
sub login_to_csc_v2 :Chained('master') :PathPart('login_to_csc_v2') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) :AllowedRole(ccareadmin) :AllowedRole(ccare) {
my ($self, $c) = @_;
$c->detach('/denied_page') if ($c->user->read_only);
my $subscriber = $c->stash->{subscriber};
my $prov_subscriber = $subscriber->provisioning_voip_subscriber;
my $auth_token_type = 'onetime';
my $auth_token_role = 'subscriber';
my $auth_token_user_id = $prov_subscriber->id;
my $auth_token_expires = 60;
my $token = NGCP::Panel::Utils::Auth::generate_auth_token($self, $c,
$auth_token_type,
$auth_token_role,
$auth_token_user_id,
$auth_token_expires,
);
unless ($token) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => 'Internal error when generating the auth token',
desc => $c->loc('Failed to perform CSC V2 login'),
);
return;
}
#redirect to server's hostname
my $v2_prefix = $c->config->{general}{csc_js_enable} == 2 ? '/v2/' : '/';
$c->res->redirect("https://" . $c->req->uri->host . $v2_prefix . "?a=$token");
}
sub order_pbx_items :Chained('master') :PathPart('orderpbxitems') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) : AllowedRole(reseller) :AllowedRole(ccareadmin) :AllowedRole(ccare) :AllowedRole(subscriberadmin) {
my ($self, $c) = @_;

@ -1,35 +1,13 @@
package NGCP::Panel::Role::API::AuthTokens;
use Sipwise::Base;
use NGCP::Panel::Utils::Redis;
use parent 'NGCP::Panel::Role::API';
use Redis;
use UUID;
sub get_form {
my ($self, $c) = @_;
return NGCP::Panel::Form::get("NGCP::Panel::Form::AuthToken", $c);
}
sub generate_auth_token {
my ($self, $c, $resource) = @_;
my ($uuid_bin, $uuid_string);
UUID::generate($uuid_bin);
UUID::unparse($uuid_bin, $uuid_string);
#remove '-' from the token
$uuid_string =~ s/\-//g;
my $redis = NGCP::Panel::Utils::Redis::get_redis_connection($c, {database => $c->config->{'Plugin::Session'}->{redis_db}});
return unless $redis;
$redis->hset("auth_token:$uuid_string", 'type', $resource->{type});
$redis->hset("auth_token:$uuid_string", 'role', $c->user->roles);
$redis->hset("auth_token:$uuid_string", 'user_id', $c->user->id);
$redis->expire("auth_token:$uuid_string", $resource->{expires});
return $uuid_string;
}
1;
# vim: set tabstop=4 expandtab:

@ -7,6 +7,7 @@ use IO::Compress::Zip qw/zip/;
use IPC::System::Simple qw/capturex/;
use Redis;
use UUID;
use NGCP::Panel::Utils::Redis;
our $SALT_LENGTH = 128;
@ -430,4 +431,35 @@ sub initiate_password_reset {
return {success => 1};
}
sub generate_auth_token {
my ($self, $c, $type, $role, $user_id, $expires) = @_;
my ($uuid_bin, $uuid_string);
my $redis = NGCP::Panel::Utils::Redis::get_redis_connection($c, {database => $c->config->{'Plugin::Session'}->{redis_db}});
unless ($redis) {
$c->log->error("Could not generate auth token for user $user_id, no Redis connection available");
return;
}
$expires //= 10; # auto expire the token in 10 seconds if the value is not provided
my $expire_time = time+10;
UUID::generate($uuid_bin);
UUID::unparse($uuid_bin, $uuid_string);
#remove '-' from the token
$uuid_string =~ s/\-//g;
$redis->hset("auth_token:$uuid_string", 'type', $type);
$redis->hset("auth_token:$uuid_string", 'role', $role);
$redis->hset("auth_token:$uuid_string", 'user_id', $user_id);
$redis->hset("auth_token:$uuid_string", 'exp', $expire_time);
$redis->expire("auth_token:$uuid_string", $expires);
$c->log->debug(sprintf "Generated auth_token=%s type=%s role=%s user_id=%s expires=%d expire_time=%d",
$uuid_string, $type, $role, $user_id, $expires, $expire_time);
return $uuid_string;
}
1;

@ -56,9 +56,12 @@
<a class="btn btn-primary btn-large" href="[% href %]"><i class="icon-edit"></i> [% c.loc('Edit') %]</a>
[% IF (c.user.roles == "admin" || c.user.roles == "reseller" ||
c.user.roles == "ccareadmin" || c.user.roles == "ccare") -%]
[% IF (c.config.general.csc_js_enable == 0 || c.config.general.csc_js_enable == 2) -%]
[% IF c.config.general.csc_js_enable == 0 || c.config.general.csc_js_enable == 2 -%]
<a class="btn btn-primary btn-large" href="[% c.uri_for_action('/subscriber/login_to_csc', [ subscriber.id ]) %]" target="_blank"><i class="icon-user"></i> [% c.loc('Login to CSC') %]</a>
[% END -%]
[% IF c.config.general.csc_js_enable == 1 || c.config.general.csc_js_enable == 2 -%]
<a class="btn btn-primary btn-large" href="[% c.uri_for_action('/subscriber/login_to_csc_v2', [ subscriber.id ]) %]" target="_blank"><i class="icon-user"></i> [% c.loc('Login to New CSC') %]</a>
[% END -%]
[% END -%]
[% END -%]
[% IF subscriber.contract.passreset_email_template -%]

Loading…
Cancel
Save