diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 78020fbb6c5..170333146fd 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -96,9 +96,13 @@ def impersonate def invalidate_jwt @user = find_resource(:edit_users) @user.jwt_secret&.destroy - process_success( - :success_msg => _('Successfully invalidated JWTs for %s.') % @user.login - ) + unless editing_self? + process_success( + :success_msg => _('Successfully invalidated JWTs for %s.') % @user.login + ) + return + end + render :json => {}, :status => :ok end def stop_impersonation diff --git a/app/models/user.rb b/app/models/user.rb index a8ca2a1aed2..352cdd32663 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -463,7 +463,7 @@ def can_change_admin_flag? def editing_self?(options = {}) options[:controller].to_s == 'users' && - options[:action] =~ /edit|update/ && + options[:action] =~ /edit|update|invalidate_jwt/ && options[:id].to_i == id || options[:controller].to_s =~ /\Aapi\/v\d+\/users\Z/ && options[:action] =~ /show|update/ && diff --git a/app/views/jwt_tokens/_jwt_tokens_tab.html.erb b/app/views/jwt_tokens/_jwt_tokens_tab.html.erb new file mode 100644 index 00000000000..212031b2fa4 --- /dev/null +++ b/app/views/jwt_tokens/_jwt_tokens_tab.html.erb @@ -0,0 +1,5 @@ +
+ <%= react_component('JwtTokens', { + userId: @user.id, + }) %> +
diff --git a/app/views/users/_form.html.erb b/app/views/users/_form.html.erb index a952490995b..17311824c53 100644 --- a/app/views/users/_form.html.erb +++ b/app/views/users/_form.html.erb @@ -18,6 +18,9 @@ <% if @editing_self || (@user.persisted? && authorized_for(hash_for_api_user_personal_access_tokens_path(user_id: @user))) %>
  • <%= _('Personal Access Tokens') %>
  • <% end %> + <% if @editing_self %> +
  • <%= _('JWT Tokens') %>
  • + <% end %> <%= render_tab_header_for(:main_tabs, :subject => @user, :form => f) %> @@ -97,6 +100,10 @@ <%= render 'personal_access_tokens/personal_access_tokens_tab', :f => f, :user => @user %> <% end %> + <% if @editing_self %> + <%= render 'jwt_tokens/jwt_tokens_tab', :f => f, :user => @user %> + <% end %> +
    <% caption = @user.inherited_admin? ? _('Admin rights are currently inherited from a user group') : '' %> <%= checkbox_f f, :admin, help_block: caption if User.current.can_change_admin_flag? %> diff --git a/test/controllers/users_controller_test.rb b/test/controllers/users_controller_test.rb index 6b1a4bdae92..c2631f59069 100644 --- a/test/controllers/users_controller_test.rb +++ b/test/controllers/users_controller_test.rb @@ -162,16 +162,25 @@ class UsersControllerTest < ActionController::TestCase User.current = users(:admin) user = users(:two) FactoryBot.build(:jwt_secret, token: 'test_jwt_secret', user: user) - patch :invalidate_jwt, params: { :id => user.id } + patch :invalidate_jwt, params: { :id => user.id }, session: set_session_user(User.current) user.reload assert_nil user.jwt_secret end + test "User should be able to invalidate jwt for self" do + User.current = users(:one) + user = User.current + FactoryBot.create(:jwt_secret, token: 'test_jwt_secret', user: user) + patch :invalidate_jwt, params: { :id => user.id }, session: set_session_user(User.current) + user.reload + assert_response :success + end + test 'user with edit users permission should be able to invalidate jwt for another user' do User.current = setup_user "edit", "users" - user = users(:two) - FactoryBot.build(:jwt_secret, token: 'test_jwt_secret', user: user) - patch :invalidate_jwt, params: { :id => user.id } + user = users(:scoped) + FactoryBot.create(:jwt_secret, token: 'test_jwt_secret', user: user) + patch :invalidate_jwt, params: { :id => user.id }, session: set_session_user(User.current) user.reload assert_nil user.jwt_secret end @@ -179,8 +188,8 @@ class UsersControllerTest < ActionController::TestCase 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 } + FactoryBot.create(:jwt_secret, token: 'test_jwt_secret', user: user) + patch :invalidate_jwt, params: { :id => user.id }, session: set_session_user(User.current) assert_response :forbidden end diff --git a/webpack/assets/javascripts/react_app/components/componentRegistry.js b/webpack/assets/javascripts/react_app/components/componentRegistry.js index 5e8c9a509a7..2e5b8e66c4d 100644 --- a/webpack/assets/javascripts/react_app/components/componentRegistry.js +++ b/webpack/assets/javascripts/react_app/components/componentRegistry.js @@ -44,6 +44,7 @@ import LabelIcon from './common/LabelIcon'; import { WelcomeAuthSource } from './AuthSource/Welcome'; import { WelcomeConfigReports } from './ConfigReports/Welcome'; import { WelcomeArchitecture } from './Architectures/Welcome'; +import JwtTokens from './users/JwtTokens/JwtTokens'; const componentRegistry = { registry: forceSingleton('component_registry', () => ({})), @@ -142,6 +143,7 @@ const coreComponents = [ { name: 'SettingsTable', type: SettingsTable }, { name: 'SettingUpdateModal', type: SettingUpdateModal }, { name: 'PersonalAccessTokens', type: PersonalAccessTokens }, + { name: 'JwtTokens', type: JwtTokens }, { name: 'ClipboardCopy', type: ClipboardCopy }, { name: 'LabelIcon', type: LabelIcon }, { diff --git a/webpack/assets/javascripts/react_app/components/users/JwtTokens/JwtTokens.js b/webpack/assets/javascripts/react_app/components/users/JwtTokens/JwtTokens.js new file mode 100644 index 00000000000..1037e3b7e37 --- /dev/null +++ b/webpack/assets/javascripts/react_app/components/users/JwtTokens/JwtTokens.js @@ -0,0 +1,65 @@ +import React, { Fragment } from 'react'; +import { Button } from '@patternfly/react-core'; +import { KeyIcon } from '@patternfly/react-icons'; +import { useDispatch } from 'react-redux'; +import PropTypes from 'prop-types'; +import { openConfirmModal } from '../../ConfirmModal'; +import { APIActions } from '../../../redux/API'; +import { translate as __ } from '../../../common/I18n'; + +const JwtTokens = ({ userId }) => { + const dispatch = useDispatch(); + return ( + + + + + + + +
    +
    + +
    +

    {__('JWT Tokens')}

    +

    + {__( + 'By invalidating your JSON Web Tokens (JWTs), you will no longer be able to register hosts by using your existing JWTs.' + )} +

    + +
    +
    + ); +}; + +JwtTokens.propTypes = { + userId: PropTypes.string.isRequired, +}; + +export default JwtTokens;