From 8f69cb234835ccdbf7038f46a383941a4b7a39d8 Mon Sep 17 00:00:00 2001 From: Girija Soni Date: Tue, 7 Jan 2025 15:03:06 +0530 Subject: [PATCH] Fixes #37936 - Invalidate jwt for any user or users(API) --- .../api/v2/registration_tokens_controller.rb | 58 +++++++++++++++++++ app/models/user.rb | 5 +- config/initializers/f_foreman_permissions.rb | 3 +- config/routes/api/v2.rb | 7 +++ .../v2/registration_tokens_controller_test.rb | 46 +++++++++++++++ 5 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 app/controllers/api/v2/registration_tokens_controller.rb create mode 100644 test/controllers/api/v2/registration_tokens_controller_test.rb diff --git a/app/controllers/api/v2/registration_tokens_controller.rb b/app/controllers/api/v2/registration_tokens_controller.rb new file mode 100644 index 00000000000..e4e0ef569cc --- /dev/null +++ b/app/controllers/api/v2/registration_tokens_controller.rb @@ -0,0 +1,58 @@ +module Api + module V2 + class RegistrationTokensController < V2::BaseController + include Foreman::Controller::UsersMixin + + include Foreman::Controller::Parameters::User + include Foreman::Controller::AutoCompleteSearch + before_action :authenticate, :only => [:invalidate_jwt_tokens, :invalidate_jwt] + + def resource_class + User + end + + def find_resource(permission = :view_users) + editing_self? ? User.find(User.current.id) : User.authorized(permission).except_hidden.find(params[:id]) + end + + def action_permission + case params[:action] + when 'invalidate_jwt_tokens', 'invalidate_jwt' + 'edit' + else + super + end + end + + api :DELETE, '/users/:id/registration_tokens', N_("Invalidate all registration tokens for a specific user.") + description <<-DOC + The user you specify will no longer be able to register hosts by using their JWTs. + DOC + param :id, String, :desc => N_("ID of the user"), :required => true + + def invalidate_jwt + @user = find_resource(:edit_users) + unless @user + raise ::Foreman::Exception.new(N_("No record found for %s"), params[:id]) + end + @user.jwt_secret&.destroy + process_success _("Successfully invalidated registration tokens for %s.\n" % @user.login) + end + + api :DELETE, "/registration_tokens", N_("Invalidate all registration tokens for multiple users.") + param :search, String, :desc => N_("URL-encoded search query that selects users for which registration tokens will be invalidated. Search query example: id ^ (2, 4, 6)"), :required => true + description <<-DOC + The users you specify will no longer be able to register hosts by using their JWTs. + DOC + + def invalidate_jwt_tokens + raise ::Foreman::Exception.new(N_("Please provide search parameter")) if params[:search].blank? + @users = resource_scope_for_index(:permission => :edit_users).except_hidden.uniq + if @users.blank? + raise ::Foreman::Exception.new(N_("No record found for search '%s'"), params[:search]) end + JwtSecret.where(user_id: @users).destroy_all + process_success _("Successfully invalidated registration tokens for %s.\n" % @users.pluck(:login).to_sentence) + end + end + end +end diff --git a/app/models/user.rb b/app/models/user.rb index a8ca2a1aed2..b3acec595b0 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -476,7 +476,10 @@ def editing_self?(options = {}) options[:user_id].to_i == id || options[:controller].to_s == 'api/v2/personal_access_tokens' && options[:action] =~ /show|destroy|index|create/ && - options[:user_id].to_i == id + options[:user_id].to_i == id || + options[:controller].to_s == 'api/v2/registration_tokens' && + options[:action] =~ /invalidate_jwt/ && + options[:id].to_i == id end def taxonomy_foreign_conditions diff --git a/config/initializers/f_foreman_permissions.rb b/config/initializers/f_foreman_permissions.rb index 15e469c150c..f5bbda18c5b 100644 --- a/config/initializers/f_foreman_permissions.rb +++ b/config/initializers/f_foreman_permissions.rb @@ -563,7 +563,8 @@ :"api/v2/users" => [:create] map.permission :edit_users, :users => [:edit, :update, :invalidate_jwt], - :"api/v2/users" => [:update] + :"api/v2/users" => [:update], + :"api/v2/registration_tokens" => [:invalidate_jwt_tokens, :invalidate_jwt] map.permission :destroy_users, :users => [:destroy], :"api/v2/users" => [:destroy] diff --git a/config/routes/api/v2.rb b/config/routes/api/v2.rb index 3eca161f873..f465215c223 100644 --- a/config/routes/api/v2.rb +++ b/config/routes/api/v2.rb @@ -218,6 +218,13 @@ resources :mail_notifications, :only => [:create, :destroy, :update] get 'mail_notifications', :to => 'mail_notifications#user_mail_notifications', :on => :member get 'extlogin', :to => 'users#extlogin', :on => :collection + delete 'registration_tokens', :to => 'registration_tokens#invalidate_jwt', :on => :member + end + end + + resources :registration_tokens, :only => [:invalidate_jwt_tokens] do + collection do + delete '/', :action => :invalidate_jwt_tokens end end diff --git a/test/controllers/api/v2/registration_tokens_controller_test.rb b/test/controllers/api/v2/registration_tokens_controller_test.rb new file mode 100644 index 00000000000..8cfd6b7c678 --- /dev/null +++ b/test/controllers/api/v2/registration_tokens_controller_test.rb @@ -0,0 +1,46 @@ +require 'test_helper' + +class Api::V2::RegistrationTokensControllerTest < ActionController::TestCase + test 'user shall invalidate tokens for self' do + user = users(:one) + FactoryBot.create(:jwt_secret, token: 'test_jwt_secret', user: user) + delete :invalidate_jwt, params: { :id => user.id.to_s}, session: set_session_user(user) + user.reload + assert_response :success + assert_nil user.jwt_secret + end + + test 'user with edit permission should be able to invalidate jwt for another user' do + setup_user 'edit', 'users' + user = users(:one) + FactoryBot.create(:jwt_secret, token: 'test_jwt_secret', user: user) + delete :invalidate_jwt_tokens, params: { :search => "id ^ (#{user.id})"}, session: set_session_user(User.current) + user.reload + assert_response :success + assert_nil user.jwt_secret + end + + test 'user without edit permission should not be able to invalidate jwt for another user' do + User.current = users(:one) + user = users(:two) + FactoryBot.create(:jwt_secret, token: 'test_jwt_secret', user: user) + delete :invalidate_jwt_tokens, params: { :search => "id ^ (#{user.id})"}, session: set_session_user(User.current) + user.reload + assert_response :forbidden + assert_not_nil user.jwt_secret + response = JSON.parse(@response.body) + assert_equal "Missing one of the required permissions: edit_users", response['error']['details'] + end + + test 'invalidating jwt should fail without search params' do + setup_user 'edit', 'users' + user = users(:two) + FactoryBot.create(:jwt_secret, token: 'test_jwt_secret', user: user) + delete :invalidate_jwt_tokens, session: set_session_user(User.current) + user.reload + assert_response :error + assert_not_nil user.jwt_secret + response = JSON.parse(@response.body) + assert_equal "ERF42-7534 [Foreman::Exception]: Please provide search parameter", response['error']['message'] + end +end