From d5fd8679fe4348553c598de5d2541497ee1e6f74 Mon Sep 17 00:00:00 2001 From: Kirill Solomko Date: Mon, 10 Feb 2025 13:53:03 +0100 Subject: [PATCH] MT#62093 return 403 Banned for banned users * add banned user redirect for API requests * Urils/Auth move Redis composed keys into functions to increase consistency and avoid typos. Change-Id: I7a6f9b340ac53ffc2ee921410852e36a49587941 --- lib/NGCP/Panel/Controller/API/Root.pm | 11 +++++++ lib/NGCP/Panel/Controller/Root.pm | 12 +++++++- lib/NGCP/Panel/Utils/Auth.pm | 42 ++++++++++++++++++++++----- 3 files changed, 56 insertions(+), 9 deletions(-) diff --git a/lib/NGCP/Panel/Controller/API/Root.pm b/lib/NGCP/Panel/Controller/API/Root.pm index aa1d5e9b1c..78e6a90007 100644 --- a/lib/NGCP/Panel/Controller/API/Root.pm +++ b/lib/NGCP/Panel/Controller/API/Root.pm @@ -447,6 +447,17 @@ sub invalid_user : Private { return; } +sub banned_user : Private { + my ($self, $c, $user) = @_; + + my $log_user = "'$user'" // ''; + + $self->error($c, HTTP_FORBIDDEN, "Banned"); + $c->log->warn("banned user $log_user api login from '".$c->qs($c->req->address)."'"); + + return; +} + sub field_to_json : Private { my ($self, $field) = @_; diff --git a/lib/NGCP/Panel/Controller/Root.pm b/lib/NGCP/Panel/Controller/Root.pm index 56b17e364f..b90c8b9900 100644 --- a/lib/NGCP/Panel/Controller/Root.pm +++ b/lib/NGCP/Panel/Controller/Root.pm @@ -165,6 +165,7 @@ sub auto :Private { })); return; } + $self->api_apply_fake_time($c); return $self->check_user_access($c); } elsif ($c->req->headers->header("NGCP-UserAgent") && @@ -229,6 +230,10 @@ sub auto :Private { } my $res = NGCP::Panel::Utils::Auth::perform_subscriber_auth($c, $u, $d, $password); + if ($res && $res == -2) { + $c->detach(qw(API::Root banned_user), [$username]); + } + if($res && $c->user_exists) { $d //= $c->req->uri->host; $c->log->debug("checking '".$c->user->domain->domain."' against '$d'"); @@ -256,6 +261,11 @@ sub auto :Private { my ($user, $pass) = $c->req->headers->authorization_basic; #$c->log->debug("user: " . $user . " pass: " . $pass); my $res = NGCP::Panel::Utils::Auth::perform_auth($c, $user, $pass, "api_admin" , "api_admin_bcrypt"); + + if ($res && $res == -2) { + $c->detach(qw(API::Root banned_user), [$user]); + } + if($res and $c->user_exists and $c->user->is_active) { $c->log->debug("admin '".$c->user->login."' authenticated via api_admin_http"); } else { @@ -553,7 +563,7 @@ sub login_jwt :Chained('/') :PathPart('login_jwt') :Args(0) :Method('POST') { $c->response->status(HTTP_FORBIDDEN); $c->response->body(encode_json({ code => HTTP_FORBIDDEN, - message => "Forbidden!" })."\n"); + message => "Banned" })."\n"); $c->log->debug("Banned user=$log_user realm=$ngcp_realm ip=$ip login attempt"); return; } diff --git a/lib/NGCP/Panel/Utils/Auth.pm b/lib/NGCP/Panel/Utils/Auth.pm index 9d9c7b3fd7..02f7fb7b81 100644 --- a/lib/NGCP/Panel/Utils/Auth.pm +++ b/lib/NGCP/Panel/Utils/Auth.pm @@ -76,7 +76,7 @@ sub perform_auth { my $log_failed_login_attempt = 1; return $res if !check_password($pass); - return $res if user_is_banned($c, $user, 'admin'); + return -2 if user_is_banned($c, $user, 'admin'); my $dbadmin; $dbadmin = $c->model('DB')->resultset('admins')->find({ @@ -174,7 +174,7 @@ sub perform_subscriber_auth { } my $userdom = $domain ? $user . '@' . $domain : $user; - return $res if user_is_banned($c, $userdom, 'subscriber'); + return -2 if user_is_banned($c, $userdom, 'subscriber'); my $authrs = $c->model('DB')->resultset('provisioning_voip_subscribers')->search({ webusername => $user, @@ -553,6 +553,7 @@ sub user_is_banned { my ($p_user, $p_domain) = get_user_domain($c, $user); + my $key; my ($user_id, $reseller_id, $customer_id) = ('', '', ''); if ($realm eq 'admin') { @@ -563,7 +564,7 @@ sub user_is_banned { $user_id = $user_rs->id; $reseller_id = $user_rs->reseller_id; } - $key = "login:ban::user:${p_user}::domain:${p_domain}::realm:${realm}::ip:${ip}::admin_id:${user_id}::reseller_id:${reseller_id}"; + $key = login_ban_admin_key($p_user, $p_domain, $realm, $ip, $user_id, $reseller_id); } elsif ($realm eq 'subscriber') { my $user_rs = $c->model('DB')->resultset('provisioning_voip_subscribers')->search({ webusername => $p_user, @@ -575,7 +576,7 @@ sub user_is_banned { $user_id = $user_rs->voip_subscriber->id; $customer_id = $user_rs->account_id; } - $key = "login:ban::user:${p_user}::domain:${p_domain}::realm:${realm}::ip:${ip}::subscriber_id:${user_id}::customer_id:${customer_id}"; + $key = login_ban_subscriber_key($p_user, $p_domain, $realm, $ip, $user_id, $customer_id); } return $redis->exists($key) ? 1 : 0; @@ -593,7 +594,7 @@ sub log_failed_login_attempt { my ($p_user, $p_domain) = get_user_domain($c, $user); - my $key = "login:fail::user:${p_user}::domain:${p_domain}::realm:${realm}::ip:${ip}"; + my $key = login_fail_key($p_user, $p_domain, $realm, $ip); my $attempted = ($redis->hget($key, 'attempts') // 0) + 1; $attempted >= $max_attempts ? ban_user($c, $user, $realm) @@ -614,7 +615,7 @@ sub clear_failed_login_attempts { my ($p_user, $p_domain) = get_user_domain($c, $user); my $ip = $c->request->address; - my $key = "login:fail::user:${p_user}::domain:${p_domain}::realm:${realm}::ip:${ip}"; + my $key = login_fail_key($p_user, $p_domain, $realm, $ip); my $redis = $c->redis_get_connection({database => $c->config->{'Plugin::Session'}->{redis_db}}); @@ -677,7 +678,7 @@ sub ban_user { $user_id = $user_rs->id; $reseller_id = $user_rs->reseller_id; } - $key = "login:ban::user:${p_user}::domain:${p_domain}::realm:${realm}::ip:${ip}::admin_id:${user_id}::reseller_id:${reseller_id}"; + $key = login_ban_admin_key($p_user, $p_domain, $realm, $ip, $user_id, $reseller_id); } elsif ($realm eq 'subscriber') { $user_rs = $c->model('DB')->resultset('provisioning_voip_subscribers')->search({ webusername => $p_user, @@ -690,7 +691,7 @@ sub ban_user { $user_id = $user_rs->voip_subscriber->id; $customer_id = $user_rs->account_id; } - $key = "login:ban::user:${p_user}::domain:${p_domain}::realm:${realm}::ip:${ip}::subscriber_id:${user_id}::customer_id:${customer_id}"; + $key = login_ban_subscriber_key($p_user, $p_domain, $realm, $ip, $user_id, $customer_id); } if ($increment_stage >= 0) { @@ -703,8 +704,11 @@ sub ban_user { my $redis = $c->redis_get_connection({database => $c->config->{'Plugin::Session'}->{redis_db}}); + my $fail_key = login_fail_key($p_user, $p_domain, $realm, $ip); + $redis->hset($key, 'banned_at', time()); $redis->expire($key, $expire) if $expire; + $redis->del($fail_key); if ($increment_stage > 0 && $user_rs) { $user_rs->update({ban_increment_stage => $increment_stage}); @@ -759,4 +763,26 @@ sub check_max_age { return 1; } +sub login_fail_key { + my ($user, $domain, $realm, $ip) = @_; + return "login:fail::user:${user}::domain:${domain}::realm:${realm}::ip:${ip}"; +} + +sub login_ban_basic_key { + my ($user, $domain, $realm, $ip) = @_; + return "login:ban::user:${user}::domain:${domain}::realm:${realm}::ip:${ip}"; +} + +sub login_ban_admin_key { + my ($user, $domain, $realm, $ip, $admin_id, $reseller_id) = @_; + my $basic_key = login_ban_basic_key($user, $domain, $realm, $ip); + return "${basic_key}::admin_id:${admin_id}::reseller_id:${reseller_id}"; +} + +sub login_ban_subscriber_key { + my ($user, $domain, $realm, $ip, $subscriber_id, $customer_id) = @_; + my $basic_key = login_ban_basic_key($user, $domain, $realm, $ip); + return "${basic_key}::subscriber_id:${subscriber_id}::customer_id:${customer_id}"; +} + 1;