TT#80305 add JWT authentication for admin users

* /admin_login_jwt now returns a JWT token for admin
      users and also the JWT token is supported in the
      authorization process for the admin requests

Change-Id: I987640d46bd8a339a959a6b2efb65b6dce06bf8c
changes/31/39631/3
Kirill Solomko 5 years ago
parent 74bc82c4f5
commit 2c8a11029a

@ -256,6 +256,24 @@ __PACKAGE__->config(
},
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',

@ -46,7 +46,12 @@ sub authenticate {
my ($token) = $auth_header =~ m/Bearer\s+(.*)/;
return unless ($token);
$c->log->debug("Found token: $token") if $self->debug;
if ($token =~ /^a=(.+)$/) {
$c->log->debug("Found admin token: $token") if $self->debug;
$token = $1;
} else {
$c->log->debug("Found token: $token") if $self->debug;
}
my $jwt_data;
try {

@ -6,7 +6,7 @@ sub roles {
my ($self) = @_;
if ($self->auth_realm) {
for my $auth_type (qw/admin_bcrypt admin api_admin_cert api_admin_http api_admin api_admin_bcrypt/) {
for my $auth_type (qw/admin_bcrypt admin api_admin_cert api_admin_http api_admin api_admin_bcrypt api_admin_jwt/) {
if ($auth_type eq $self->auth_realm) {
if ($self->_user->is_ccare) {
$self->_user->is_superuser ? return "ccareadmin"

@ -181,6 +181,19 @@ sub auto :Private {
$c->log->warn("invalid api system login from '".$c->qs($c->req->address)."'");
}
$self->api_apply_fake_time($c);
return 1;
} elsif ($c->req->headers->header("Authorization") &&
$c->req->headers->header("Authorization") =~ m/^Bearer(\s+)a=/) {
$c->log->debug("++++++ Root::auto API request with admin JWT");
my $realm = "api_admin_jwt";
my $res = $c->authenticate({}, $realm);
unless ($c->user_exists) {
$c->log->debug("+++++ invalid api admin JWT login");
# $c->log->warn("invalid api system login from '".$c->qs($c->req->address)."'");
}
$self->api_apply_fake_time($c);
return 1;
} elsif ($c->req->headers->header("Authorization") &&
@ -497,6 +510,78 @@ sub login_jwt :Chained('/') :PathPart('login_jwt') :Args(0) :Method('POST') {
return;
}
sub admin_login_jwt :Chained('/') :PathPart('admin_login_jwt') :Args(0) :Method('POST') {
my ($self, $c) = @_;
use JSON qw/encode_json decode_json/;
use Crypt::JWT qw/encode_jwt/;
my $user = $c->req->body_data->{username} // '';
my $pass = $c->req->body_data->{password} // '';
my $key = $c->config->{'Plugin::Authentication'}{api_admin_jwt}{credential}{jwt_key};
my $relative_exp = $c->config->{'Plugin::Authentication'}{api_admin_jwt}{credential}{relative_exp};
my $alg = $c->config->{'Plugin::Authentication'}{api_admin_jwt}{credential}{alg};
$c->response->content_type('application/json');
unless ($key) {
$c->response->status(HTTP_INTERNAL_SERVER_ERROR);
$c->response->body(encode_json({ code => HTTP_INTERNAL_SERVER_ERROR,
message => "No JWT key has been configured" })."\n");
$c->log->error("No JWT key has been configured");
return;
}
my $raw_key = pack('H*', $key);
unless ($user && $pass) {
$c->response->status(HTTP_UNPROCESSABLE_ENTITY);
$c->response->body(encode_json({ code => HTTP_UNPROCESSABLE_ENTITY,
message => "No username or password given" })."\n");
$c->log->error("No username or password given");
return;
}
my $authrs = $c->model('DB')->resultset('admins')->search({
login => $user,
is_active => 1,
});
my $usr_salted_pass;
my $auth_user = $authrs->first;
my $result = {};
if ($auth_user && $auth_user->id) {
$usr_salted_pass = NGCP::Panel::Utils::Admin::get_usr_salted_pass($auth_user->saltedpass, $pass);
}
if ($usr_salted_pass && $usr_salted_pass eq $auth_user->saltedpass) {
my $jwt_data = {
id => $auth_user->id,
username => $auth_user->login,
};
$result->{jwt} = 'a='.encode_jwt(
payload => $jwt_data,
key => $raw_key,
alg => $alg,
$relative_exp ? (relative_exp => $relative_exp) : (),
);
$result->{id} = int($auth_user->id // 0);
} else {
$c->response->status(HTTP_FORBIDDEN);
$c->response->body(encode_json({ code => HTTP_FORBIDDEN,
message => "User not found" })."\n");
$c->log->error("User not found");
return;
}
$c->res->body(encode_json($result));
$c->res->code(HTTP_OK); # 200
return;
}
sub api_apply_fake_time :Private {
my ($self, $c) = @_;
my $allow_fake_client_time = 0;

@ -28,6 +28,18 @@ sub generate_salted_hash {
return $b64salt . '$' . $b64hash;
}
sub get_usr_salted_pass {
my ($saltedpass, $pass) = @_;
my ($db_b64salt, $db_b64hash) = split /\$/, $saltedpass;
my $salt = de_base64($db_b64salt);
my $usr_b64hash = en_base64(bcrypt_hash({
key_nul => 1,
cost => get_bcrypt_cost(),
salt => $salt,
}, $pass));
return $db_b64salt . '$' . $usr_b64hash;
}
sub perform_auth {
my ($c, $user, $pass, $realm, $bcrypt_realm) = @_;
my $res;
@ -39,19 +51,14 @@ sub perform_auth {
}) if $user;
if(defined $dbadmin && defined $dbadmin->saltedpass) {
$c->log->debug("login via bcrypt");
my ($db_b64salt, $db_b64hash) = split /\$/, $dbadmin->saltedpass;
my $salt = de_base64($db_b64salt);
my $usr_b64hash = en_base64(bcrypt_hash({
key_nul => 1,
cost => get_bcrypt_cost(),
salt => $salt,
}, $pass));
my $saltedpass = $dbadmin->saltedpass;
my $usr_salted_pass = get_usr_salted_pass($saltedpass, $pass);
# fetch again to load user into session etc (otherwise we could
# simply compare the two hashes here :(
$res = $c->authenticate(
{
login => $user,
saltedpass => $db_b64salt . '$' . $usr_b64hash,
saltedpass => $usr_salted_pass,
'dbix_class' => {
searchargs => [{
-and => [

Loading…
Cancel
Save