From 4789f6b61ffe7be3a08ebfa28fa0cdc86823ab3d Mon Sep 17 00:00:00 2001 From: Rene Krenn Date: Mon, 1 Apr 2019 11:50:59 +0200 Subject: [PATCH] TT#56411 honor use_session => 0 for bcrypt auth WIP Change-Id: I549c4e6fc18b7cb738e99b3a6e254a47db3af0cf --- lib/NGCP/Panel.pm | 32 ++ .../Authentication/Store/RoleFromRealm.pm | 2 +- lib/NGCP/Panel/Controller/Login.pm | 8 +- lib/NGCP/Panel/Controller/Root.pm | 291 +++++++++--------- lib/NGCP/Panel/Utils/Admin.pm | 19 +- lib/NGCP/Panel/Utils/Message.pm | 6 +- 6 files changed, 200 insertions(+), 158 deletions(-) diff --git a/lib/NGCP/Panel.pm b/lib/NGCP/Panel.pm index 6b1a2a6228..0d106a3d6b 100644 --- a/lib/NGCP/Panel.pm +++ b/lib/NGCP/Panel.pm @@ -153,6 +153,38 @@ __PACKAGE__->config( use_userdata_from_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 => 1, + }, + 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 => 1, + }, + use_session => 0, + }, api_admin_cert => { # TODO: should be NoPassword, but it's not available in our catalyst version yet credential => { diff --git a/lib/NGCP/Panel/Authentication/Store/RoleFromRealm.pm b/lib/NGCP/Panel/Authentication/Store/RoleFromRealm.pm index 6574d6517b..d67236cb79 100644 --- a/lib/NGCP/Panel/Authentication/Store/RoleFromRealm.pm +++ b/lib/NGCP/Panel/Authentication/Store/RoleFromRealm.pm @@ -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/) { + for my $auth_type (qw/admin_bcrypt admin api_admin_cert api_admin_http api_admin api_admin_bcrypt/) { if ($auth_type eq $self->auth_realm) { $self->_user->is_superuser ? return "admin" : return "reseller"; diff --git a/lib/NGCP/Panel/Controller/Login.pm b/lib/NGCP/Panel/Controller/Login.pm index 09fba50ffd..72999a7c8d 100644 --- a/lib/NGCP/Panel/Controller/Login.pm +++ b/lib/NGCP/Panel/Controller/Login.pm @@ -12,7 +12,7 @@ use NGCP::Panel::Utils::Admin; sub login_index :Path Form { my ( $self, $c, $realm ) = @_; - $realm = 'subscriber' + $realm = 'subscriber' unless($realm && $realm eq 'admin'); my $posted = ($c->req->method eq 'POST'); @@ -30,7 +30,7 @@ sub login_index :Path Form { $c->log->debug("*** Login::index user=$user, pass=****, realm=$realm"); my $res; if($realm eq 'admin') { - $res = NGCP::Panel::Utils::Admin::perform_auth($c, $user, $pass); + $res = NGCP::Panel::Utils::Admin::perform_auth($c, $user, $pass, 'admin', 'admin_bcrypt'); } elsif($realm eq 'subscriber') { my ($u, $d, $t) = split /\@/, $user; if(defined $t) { @@ -52,12 +52,12 @@ sub login_index :Path Form { }); $res = $c->authenticate( { - webusername => $u, + webusername => $u, webpassword => $pass, 'dbix_class' => { resultset => $authrs } - }, + }, $realm); } diff --git a/lib/NGCP/Panel/Controller/Root.pm b/lib/NGCP/Panel/Controller/Root.pm index 682af2918a..612a20bebf 100644 --- a/lib/NGCP/Panel/Controller/Root.pm +++ b/lib/NGCP/Panel/Controller/Root.pm @@ -27,9 +27,12 @@ __PACKAGE__->config(namespace => ''); sub auto :Private { my($self, $c) = @_; + my $is_api_request = 0; + $c->log->debug("Path: " . $c->request->path); if ($c->request->path =~/^api\//i) { $c->log->debug("Root::auto: enable cache"); NGCP::Panel::Form::dont_use_cache(0); + $is_api_request = 1; } else { $c->log->debug("Root::auto: disable cache"); NGCP::Panel::Form::dont_use_cache(1); @@ -43,31 +46,35 @@ sub auto :Private { } if(exists $c->installed_languages->{$c->request->params->{lang}} || $c->request->params->{lang} eq "i-default") { - $c->session->{lang} = $c->request->params->{lang}; - $c->response->cookies->{ngcp_panel_lang} = { value => $c->request->params->{lang}, expires => '+3M', }; + unless ($is_api_request) { + $c->session->{lang} = $c->request->params->{lang}; + $c->response->cookies->{ngcp_panel_lang} = { value => $c->request->params->{lang}, expires => '+3M', }; + } $c->log->debug("Setting language to ". $c->request->params->{lang}); } # clear form cache (they need to be properly re-translated) NGCP::Panel::Form::clear_form_cache(); } - if (defined $c->session->{lang}) { - $c->languages([$c->session->{lang}, "i-default"]); - } elsif ( $c->req->cookie('ngcp_panel_lang') ) { - $c->session->{lang} = $c->req->cookie('ngcp_panel_lang')->value; - $c->languages([$c->session->{lang}, 'i-default']); - } else { # if language has not yet be set, set it from config or browser - if (defined $c->config->{appearance}{force_language}) { - $c->log->debug("lang set by config: " . $c->config->{appearance}{force_language}); - $c->languages([$c->config->{appearance}{force_language}, 'i-default']); - } else { - $c->languages([ map { s/^en.*$/i-default/r } @{ $c->languages } ]); + unless ($is_api_request) { + if (defined $c->session->{lang}) { + $c->languages([$c->session->{lang}, "i-default"]); + } elsif ( $c->req->cookie('ngcp_panel_lang') ) { + $c->session->{lang} = $c->req->cookie('ngcp_panel_lang')->value; + $c->languages([$c->session->{lang}, 'i-default']); + } else { # if language has not yet be set, set it from config or browser + if (defined $c->config->{appearance}{force_language}) { + $c->log->debug("lang set by config: " . $c->config->{appearance}{force_language}); + $c->languages([$c->config->{appearance}{force_language}, 'i-default']); + } else { + $c->languages([ map { s/^en.*$/i-default/r } @{ $c->languages } ]); + } + $c->session->{lang} = $c->language; + $c->log->debug("lang set by browser or config: " . $c->language); } - $c->session->{lang} = $c->language; - $c->log->debug("lang set by browser or config: " . $c->language); } ################################################### timezone retrieval - if ($c->user_exists) { + if (not $is_api_request and $c->user_exists) { if ($c->session->{user_tz}) { # nothing to do } elsif ($c->user->roles eq 'admin') { @@ -110,142 +117,144 @@ sub auto :Private { return 1; } - unless($c->user_exists) { - - if(index($c->controller->catalyst_component_name, 'NGCP::Panel::Controller::API') == 0) { - $c->log->debug("++++++ Root::auto unauthenticated API request"); - my $ssl_dn = $c->request->env->{SSL_CLIENT_M_DN} // ""; - my $ssl_sn = hex ($c->request->env->{SSL_CLIENT_M_SERIAL} // 0); - my $ngcp_api_realm = $c->request->env->{NGCP_API_REALM} // ""; - if($ssl_sn) { - $c->log->debug("++++++ Root::auto API request with client auth sn '$ssl_sn'"); - unless($ssl_dn eq "/CN=Sipwise NGCP API client certificate") { - $c->log->error("++++++ Root::auto API request with invalid client DN '$ssl_dn'"); - $c->res->status(403); - $c->res->body(JSON::to_json({ - message => "Invalid client certificate DN '$ssl_dn'", - code => 403, - })); - return; - } + #if($is_api_request or not $c->user_exists) { + + if(index($c->controller->catalyst_component_name, 'NGCP::Panel::Controller::API') == 0) { + $c->log->debug("++++++ Root::auto unauthenticated API request"); + my $ssl_dn = $c->request->env->{SSL_CLIENT_M_DN} // ""; + my $ssl_sn = hex ($c->request->env->{SSL_CLIENT_M_SERIAL} // 0); + my $ngcp_api_realm = $c->request->env->{NGCP_API_REALM} // ""; + if($ssl_sn) { + $c->log->debug("++++++ Root::auto API request with client auth sn '$ssl_sn'"); + unless($ssl_dn eq "/CN=Sipwise NGCP API client certificate") { + $c->log->error("++++++ Root::auto API request with invalid client DN '$ssl_dn'"); + $c->res->status(403); + $c->res->body(JSON::to_json({ + message => "Invalid client certificate DN '$ssl_dn'", + code => 403, + })); + return; + } - my $res = $c->authenticate({ - ssl_client_m_serial => $ssl_sn, - is_active => 1, # TODO: abused as password until NoPassword handler is available - }, 'api_admin_cert'); - unless($c->user_exists) { - $c->log->warn("invalid api login from '".$c->req->address."'"); - $c->detach(qw(API::Root invalid_user), [$ssl_sn]) unless $c->user_exists; - } else { - $c->log->debug("++++++ admin '".$c->user->login."' authenticated via api_admin_cert"); - } - if($c->user->read_only && $c->req->method eq "POST" && - $c->req->uri->path =~ m|^/api/admincerts/$|) { - $c->log->info("let read-only user '".$c->user->login."' generate admin cert for itself"); - } elsif($c->user->read_only && !($c->req->method =~ /^(GET|HEAD|OPTIONS)$/)) { - $c->log->error("invalid method '".$c->req->method."' for read-only user '".$c->user->login."', rejecting"); - $c->user->logout; - $c->log->error("++++ body data: " . $c->req->body_data); - $c->response->status(403); - $c->res->body(JSON::to_json({ - message => "Invalid HTTP method for read-only user", - code => 403, - })); - return; - } - $self->api_apply_fake_time($c); - return 1; - } elsif ($c->req->headers->header("NGCP-UserAgent") && - $c->req->headers->header("NGCP-UserAgent") eq "NGCP::API::Client") { - $c->log->debug("++++++ Root::auto API request with system auth"); - my $realm = "api_admin_system"; - my $res = $c->authenticate({}, $realm); - - unless ($c->user_exists) { - $c->log->debug("+++++ invalid api admin system login"); - $c->log->warn("invalid api system login from '".$c->req->address."'"); - } + my $res = $c->authenticate({ + ssl_client_m_serial => $ssl_sn, + is_active => 1, # TODO: abused as password until NoPassword handler is available + }, 'api_admin_cert'); + unless($c->user_exists) { + $c->log->warn("invalid api login from '".$c->req->address."'"); + $c->detach(qw(API::Root invalid_user), [$ssl_sn]) unless $c->user_exists; + } else { + $c->log->debug("++++++ admin '".$c->user->login."' authenticated via api_admin_cert"); + } + if($c->user->read_only && $c->req->method eq "POST" && + $c->req->uri->path =~ m|^/api/admincerts/$|) { + $c->log->info("let read-only user '".$c->user->login."' generate admin cert for itself"); + } elsif($c->user->read_only && !($c->req->method =~ /^(GET|HEAD|OPTIONS)$/)) { + $c->log->error("invalid method '".$c->req->method."' for read-only user '".$c->user->login."', rejecting"); + $c->user->logout; + $c->log->error("++++ body data: " . $c->req->body_data); + $c->response->status(403); + $c->res->body(JSON::to_json({ + message => "Invalid HTTP method for read-only user", + code => 403, + })); + return; + } + $self->api_apply_fake_time($c); + return 1; + } elsif ($c->req->headers->header("NGCP-UserAgent") && + $c->req->headers->header("NGCP-UserAgent") eq "NGCP::API::Client") { + $c->log->debug("++++++ Root::auto API request with system auth"); + my $realm = "api_admin_system"; + my $res = $c->authenticate({}, $realm); + + unless ($c->user_exists) { + $c->log->debug("+++++ invalid api admin system login"); + $c->log->warn("invalid api system login from '".$c->req->address."'"); + } - $self->api_apply_fake_time($c); - return 1; - } elsif ($c->req->headers->header("Authorization") && - $c->req->headers->header("Authorization") =~ m/^Bearer /) { - $c->log->debug("++++++ Root::auto API request with JWT"); - my $realm = "api_subscriber_jwt"; - my $res = $c->authenticate({}, $realm); - - unless ($c->user_exists) { - $c->log->debug("+++++ invalid api subscriber JWT login"); - # $c->log->warn("invalid api system login from '".$c->req->address."'"); - } + $self->api_apply_fake_time($c); + return 1; + } elsif ($c->req->headers->header("Authorization") && + $c->req->headers->header("Authorization") =~ m/^Bearer /) { + $c->log->debug("++++++ Root::auto API request with JWT"); + my $realm = "api_subscriber_jwt"; + my $res = $c->authenticate({}, $realm); + + unless ($c->user_exists) { + $c->log->debug("+++++ invalid api subscriber JWT login"); + # $c->log->warn("invalid api system login from '".$c->req->address."'"); + } - $self->api_apply_fake_time($c); - return 1; - } elsif ($ngcp_api_realm eq "subscriber") { - $c->log->debug("++++++ Root::auto API subscriber request with http auth"); - my $realm = "api_subscriber_http"; - my ($username,$password) = $c->req->headers->authorization_basic; - my ($u,$d) = split(/\@/,$username); - if ($d) { - $c->req->headers->authorization_basic($u,$password); - } - my $res = $c->authenticate({}, $realm); - - if($c->user_exists) { - $d //= $c->req->uri->host; - $c->log->debug("++++++ checking '".$c->user->domain->domain."' against '$d'"); - if ($c->user->domain->domain ne $d) { - $c->user->logout; - $c->log->debug("+++++ invalid api subscriber http login by '$username' (domain check failed)"); - $c->log->warn("invalid api http login from '".$c->req->address."' by '$username'"); - my $r = $c->get_auth_realm($realm); - $r->credential->authorization_required_response($c, $r); - return; - } - $c->log->debug("++++++ subscriber '$username' authenticated via api_subscriber_http"); - } else { - $c->user->logout if($c->user); - $c->log->debug("+++++ invalid api subscriber http login"); + $self->api_apply_fake_time($c); + return 1; + } elsif ($ngcp_api_realm eq "subscriber") { + $c->log->debug("++++++ Root::auto API subscriber request with http auth"); + my $realm = "api_subscriber_http"; + my ($username,$password) = $c->req->headers->authorization_basic; + my ($u,$d) = split(/\@/,$username); + if ($d) { + $c->req->headers->authorization_basic($u,$password); + } + my $res = $c->authenticate({}, $realm); + + if($c->user_exists) { + $d //= $c->req->uri->host; + $c->log->debug("++++++ checking '".$c->user->domain->domain."' against '$d'"); + if ($c->user->domain->domain ne $d) { + $c->user->logout; + $c->log->debug("+++++ invalid api subscriber http login by '$username' (domain check failed)"); $c->log->warn("invalid api http login from '".$c->req->address."' by '$username'"); my $r = $c->get_auth_realm($realm); $r->credential->authorization_required_response($c, $r); return; } - $self->api_apply_fake_time($c); - return 1; + $c->log->debug("++++++ subscriber '$username' authenticated via api_subscriber_http"); } else { - $c->log->debug("++++++ Root::auto API admin request with http auth"); - my $realm = "api_admin_http"; - my ($user, $pass) = $c->req->headers->authorization_basic; - my $res = NGCP::Panel::Utils::Admin::perform_auth($c, $user, $pass); - unless($c->user_exists && $c->user->is_active) { - $c->user->logout if($c->user); - $c->log->debug("+++++ invalid api admin http login"); - $c->log->warn("invalid api http login from '".$c->req->address."' by '$user'"); - my $r = $c->get_auth_realm($realm); - $r->credential->authorization_required_response($c, $r); - return; - } else { - $c->log->debug("++++++ admin '".$c->user->login."' authenticated via api_admin_http"); - } - if($c->user->read_only && $c->req->method eq "POST" && - $c->req->uri->path =~ m|^/api/admincerts/$|) { - $c->log->info("let read-only user '".$c->user->login."' generate admin cert for itself"); - } elsif($c->user->read_only && !($c->req->method =~ /^(GET|HEAD|OPTIONS)$/)) { - $c->log->error("invalid method '".$c->req->method."' for read-only user '".$c->user->login."', rejecting"); - $c->user->logout; - $c->log->error("++++ body data: " . $c->req->body_data); - $c->response->status(403); - $c->res->body(JSON::to_json({ - message => "Invalid HTTP method for read-only user", - code => 403, - })); - return; - } - $self->api_apply_fake_time($c); - return 1; + $c->user->logout if($c->user); + $c->log->debug("+++++ invalid api subscriber http login"); + $c->log->warn("invalid api http login from '".$c->req->address."' by '$username'"); + my $r = $c->get_auth_realm($realm); + $r->credential->authorization_required_response($c, $r); + return; + } + $self->api_apply_fake_time($c); + return 1; + } else { + $c->log->debug("++++++ Root::auto API admin request with http auth"); + my ($user, $pass) = $c->req->headers->authorization_basic; + #$c->log->debug("user: " . $user . " pass: " . $pass); + my $res = NGCP::Panel::Utils::Admin::perform_auth($c, $user, $pass, "api_admin" , "api_admin_bcrypt"); + if($res and $c->user_exists and $c->user->is_active) { + $c->log->debug("++++++ admin '".$c->user->login."' authenticated via api_admin_http"); + } else { + my $realm = 'api_admin_http'; + $c->user->logout if($c->user); + $c->log->debug("+++++ invalid api admin http login"); + $c->log->warn("invalid api http login from '".$c->req->address."' by '$user'"); + my $r = $c->get_auth_realm($realm); + $r->credential->authorization_required_response($c, $r); + return; + } + if($c->user->read_only && $c->req->method eq "POST" && + $c->req->uri->path =~ m|^/api/admincerts/$|) { + $c->log->info("let read-only user '".$c->user->login."' generate admin cert for itself"); + } elsif($c->user->read_only && !($c->req->method =~ /^(GET|HEAD|OPTIONS)$/)) { + $c->log->error("invalid method '".$c->req->method."' for read-only user '".$c->user->login."', rejecting"); + $c->user->logout; + $c->log->error("++++ body data: " . $c->req->body_data); + $c->response->status(403); + $c->res->body(JSON::to_json({ + message => "Invalid HTTP method for read-only user", + code => 403, + })); + return; } + $self->api_apply_fake_time($c); + #$c->log->debug("return 1"); + return 1; } + } elsif (not $c->user_exists) { # don't redirect to login page for ajax uris if($c->request->path =~ /\/ajax$/) { diff --git a/lib/NGCP/Panel/Utils/Admin.pm b/lib/NGCP/Panel/Utils/Admin.pm index 583f423bb5..9d1362973c 100644 --- a/lib/NGCP/Panel/Utils/Admin.pm +++ b/lib/NGCP/Panel/Utils/Admin.pm @@ -29,13 +29,14 @@ sub generate_salted_hash { } sub perform_auth { - my ($c, $user, $pass) = @_; + my ($c, $user, $pass, $realm, $bcrypt_realm) = @_; my $res; - my $dbadmin = $c->model('DB')->resultset('admins')->find({ + my $dbadmin; + $dbadmin = $c->model('DB')->resultset('admins')->find({ login => $user, is_active => 1, - }); + }) if $user; if(defined $dbadmin && defined $dbadmin->saltedpass) { $c->log->debug("login via bcrypt"); my ($db_b64salt, $db_b64hash) = split /\$/, $dbadmin->saltedpass; @@ -59,7 +60,7 @@ sub perform_auth { ], }], } - }, 'admin_bcrypt' + }, $bcrypt_realm ); } elsif(defined $dbadmin) { # we already know if the username is wrong, no need to check again @@ -77,7 +78,7 @@ sub perform_auth { ], }], } - }, 'admin'); + }, $realm); if($res) { # login ok, time to move user to bcrypt hashing @@ -107,7 +108,7 @@ sub generate_client_cert { return; } try { - $admin->update({ + $admin->update({ ssl_client_m_serial => $serial, ssl_client_certificate => undef, # not used anymore, clear it just in case }); @@ -224,7 +225,7 @@ sub toggle_openvpn { $error = cmd($c, undef, $systemctl_cmd, 'enable', $openvpn_service); if (!$error) { my $status_enabled = check_openvpn_status($c, { - no_check_availability => 1, + no_check_availability => 1, check_status => 'enabled', }, ); @@ -234,7 +235,7 @@ sub toggle_openvpn { $error = cmd($c, undef, $systemctl_cmd, 'start', $openvpn_service); if (!$error) { $status_out = check_openvpn_status($c, { - no_check_availability => 1, + no_check_availability => 1, }, ); if ($status_out->{active}) { @@ -255,7 +256,7 @@ sub toggle_openvpn { } if (!$error) { $status_out = check_openvpn_status($c, { - no_check_availability => 1, + no_check_availability => 1, }, ); if (!$status_out->{active}) { diff --git a/lib/NGCP/Panel/Utils/Message.pm b/lib/NGCP/Panel/Utils/Message.pm index 70a7ca6c41..2a782768b9 100644 --- a/lib/NGCP/Panel/Utils/Message.pm +++ b/lib/NGCP/Panel/Utils/Message.pm @@ -46,7 +46,7 @@ sub get_log_params { if ($c->user_exists) { if ($c->user->roles eq 'admin' || $c->user->roles eq 'reseller') { $r_user = $c->user->login; - } else { + } elsif ($c->user->roles eq 'subscriberadmin' || $c->user->roles eq 'subscriber') { $r_user = $c->user->webusername . '@' . $c->user->domain->domain; } } @@ -166,7 +166,7 @@ sub error { $msg = "$desc ($error)"; $usr_text = "$desc ($dup)"; } - elsif (my ($excerpt) = $error =~ /(Column \S+ cannot be null)/) + elsif (my ($excerpt) = $error =~ /(Column \S+ cannot be null)/) { $msg = "$desc ($error)"; $usr_text = "$desc ($excerpt)"; @@ -244,7 +244,7 @@ sub info { #flash is on by default if (!defined $params{flash} || $params{flash} ) { $c->flash(messages => [{ type => $usr_type, text => $usr_text }]); - } + } if ($params{stash} ) { my $messages = $c->stash->{messages} // []; push @$messages, { type => $usr_type, text => $usr_text };