Skip to content

Commit

Permalink
Fixes #37936 - As a user, I want to invalidate jwt for specific user
Browse files Browse the repository at this point in the history
  • Loading branch information
girijaasoni committed Nov 25, 2024
1 parent 1feb571 commit 4ad8a04
Show file tree
Hide file tree
Showing 8 changed files with 92 additions and 4 deletions.
21 changes: 19 additions & 2 deletions app/controllers/api/v2/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ class UsersController < V2::BaseController
['compute_attributes']

before_action :find_optional_nested_object
before_action :setup_search_options, :only => [:invalidate_jwt]
skip_before_action :authorize, :only => [:extlogin]
before_action :authenticate, :only => [:extlogin]
before_action :authenticate, :only => [:extlogin, :invalidate_jwt]

api :GET, "/users/", N_("List all users")
api :GET, "/auth_source_ldaps/:auth_source_ldap_id/users", N_("List all users for LDAP authentication source")
Expand Down Expand Up @@ -96,7 +97,7 @@ def create
api :PUT, "/users/:id/", N_("Update a user")
description <<-DOC
Adds role 'Default role' to the user if it is not already present.
Only another admin can change the admin account attribute.
Only another admin can change the admin account attribute.
DOC
param :id, String, :required => true
param_group :user_update
Expand All @@ -111,6 +112,22 @@ def update
end
end

api :PATCH, "/users/invalidate_jwt", N_("Invalidate all JSON Web Tokens (JWTs) for a user or users.")
description <<-DOC
The users you specify will no longer be able to register hosts by using their JWTs.
DOC

def invalidate_jwt
process_resource_error if params[:search].blank?
@users = resource_scope_for_index(:permission => :edit_users)
if @users.empty?()
render :json => { :error => _("No record found for '%s'") % params[:search]}
else
JwtSecret.where(user_id: @users).destroy_all
process_success _('Successfully invalidated JWTs for %s.' % @users.pluck(:login).to_sentence)
end
end

api :DELETE, "/users/:id/", N_("Delete a user")
param :id, String, :required => true

Expand Down
8 changes: 8 additions & 0 deletions app/controllers/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@ def impersonate
end
end

def invalidate_jwt
@user = find_resource(:edit_users)
User.find_by(id: @user.id).jwt_secret&.destroy
process_success(
:success_msg => _('Successfully invalidated JWTs for %s.') % @user.login
)
end

def stop_impersonation
if session[:impersonated_by].present?
user = User.unscoped.find_by_id(session[:impersonated_by])
Expand Down
7 changes: 7 additions & 0 deletions app/helpers/users_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ def user_action_buttons(user, additional_actions = [])
:data => { :no_turbolink => true })
end

if user != User.current
additional_actions << display_link_if_authorized(_("Invalidate JWTs"),
hash_for_invalidate_jwt_user_path(:id => user.id).merge(:auth_object => user, :permission => "edit_users"),
:method => :patch, :id => user.id,
:data => { :confirm => _("Invalidate tokens for %s?") % user.name })
end

delete_btn = display_delete_if_authorized(
hash_for_user_path(:id => user).merge(:auth_object => user, :authorizer => authorizer),
:data => { :confirm => _("Delete %s?") % user.name })
Expand Down
4 changes: 2 additions & 2 deletions config/initializers/f_foreman_permissions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -562,8 +562,8 @@
:users => [:new, :create],
:"api/v2/users" => [:create]
map.permission :edit_users,
:users => [:edit, :update],
:"api/v2/users" => [:update]
:users => [:edit, :update, :invalidate_jwt],
:"api/v2/users" => [:update, :invalidate_jwt]
map.permission :destroy_users,
:users => [:destroy],
:"api/v2/users" => [:destroy]
Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@
end
member do
post 'impersonate'
patch 'invalidate_jwt'
end
resources :ssh_keys, only: [:new, :create, :destroy]
end
Expand Down
1 change: 1 addition & 0 deletions config/routes/api/v2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@
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
patch 'invalidate_jwt', :to => 'users#invalidate_jwt', :on => :collection
end
end

Expand Down
27 changes: 27 additions & 0 deletions test/controllers/api/v2/users_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,33 @@ def setup
assert mod_user.matching_password?("changeme")
end

test 'user with edit permission should be able to invalidate jwt for another user' do
setup_user 'edit', 'users'
user = users(:two)
FactoryBot.build(:jwt_secret, token: 'test_jwt_secret', user: user)
patch :invalidate_jwt, params: { :search => "id ^ (#{user.id})"}
user.reload
assert_response :success
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.build(:jwt_secret, token: 'test_jwt_secret', user: user)
patch :invalidate_jwt, params: { :search => "id ^ #{user.id}" }
user.reload
assert_response :forbidden
end

test 'invalidating jwt should fail without search params' do
setup_user 'edit', 'users'
user = users(:two)
FactoryBot.build(:jwt_secret, token: 'test_jwt_secret', user: user)
patch :invalidate_jwt
user.reload
assert_response :error
end

test "should delete different user" do
user = users(:one)

Expand Down
27 changes: 27 additions & 0 deletions test/controllers/users_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,33 @@ class UsersControllerTest < ActionController::TestCase
assert !User.exists?(user.id)
end

test "Admin should be able to invalidate jwt for any user" do
User.current = users(:admin)
user = users(:two)
FactoryBot.build(:jwt_secret, token: 'test_jwt_secret', user: user)
patch :invalidate_jwt, params: { :id => user.id }, session: set_session_user
user.reload
assert_response :found
end

test 'user with edit users permission should be able to invalidate jwt for another user' do
setup_user 'edit', 'users'
user = users(:two)
FactoryBot.build(:jwt_secret, token: 'test_jwt_secret', user: user)
patch :invalidate_jwt, params: { :id => user.id }, session: set_session_user
user.reload
assert_response :found
end

test 'user without edit users permission should not be able to invalidate jwt for another user' do
User.current = users(:one)
user = users(:two)
FactoryBot.build(:jwt_secret, token: 'test_jwt_secret', user: user)
patch :invalidate_jwt, params: { :id => user.id }
user.reload
assert_response :forbidden
end

test "should modify session when locale is updated" do
as_admin do
put :update, params: { :id => users(:admin).id, :user => { :locale => "cs" } }, session: set_session_user
Expand Down

0 comments on commit 4ad8a04

Please sign in to comment.