Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #38108 - As a user I want to invalidate tokens for self(UI) #10405

Merged
merged 1 commit into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions app/controllers/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,16 @@ 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
)
respond_to do |format|
format.html do
process_success(
:success_msg => _('Successfully invalidated registration tokens for %s.') % @user.login
)
end
format.json do
render :json => {}, :status => :ok
end
end
end

def stop_impersonation
Expand Down
2 changes: 1 addition & 1 deletion app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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/ &&
Expand Down
5 changes: 5 additions & 0 deletions app/views/jwt_tokens/_jwt_tokens_tab.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<div class="tab-pane" id="jwt_tokens">
<%= react_component('JwtTokens', {
userId: @user.id,
}) %>
</div>
7 changes: 7 additions & 0 deletions app/views/users/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
<% if @editing_self || (@user.persisted? && authorized_for(hash_for_api_user_personal_access_tokens_path(user_id: @user))) %>
<li><a href='#personal_access_tokens' data-toggle='tab'><%= _('Personal Access Tokens') %></a></li>
<% end %>
<% if @editing_self %>
<li><a href='#jwt_tokens' data-toggle='tab'><%= _('Registration Tokens') %></a></li>
<% end %>
<%= render_tab_header_for(:main_tabs, :subject => @user, :form => f) %>
</ul>

Expand Down Expand Up @@ -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 %>

<div class='tab-pane' id='roles'>
<% 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? %>
Expand Down
25 changes: 19 additions & 6 deletions test/controllers/users_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -162,26 +162,39 @@ 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
assert_response :redirect
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_nil user.jwt_secret
assert_response :redirect
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
ShimShtein marked this conversation as resolved.
Show resolved Hide resolved
assert_response :redirect
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 }
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
girijaasoni marked this conversation as resolved.
Show resolved Hide resolved
assert_not_nil user.jwt_secret
end

test "should modify session when locale is updated" do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => ({})),
Expand Down Expand Up @@ -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 },
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
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 }) => {
girijaasoni marked this conversation as resolved.
Show resolved Hide resolved
const dispatch = useDispatch();
return (
<Fragment>
<table className="table table-bordered table-striped table-hover table-fixed">
stejskalleos marked this conversation as resolved.
Show resolved Hide resolved
<tbody>
<tr>
<td className="blank-slate-pf">
<div className="blank-slate-pf-icon">
<KeyIcon type="fa" name="key" color="#9c9c9c" />
</div>
<h1>{__('JWT Tokens')}</h1>
<p>
{__(
'By invalidating your JSON Web Tokens (JWTs), you will no longer be able to register hosts by using your existing JWTs.'
)}
</p>
<Button
ouiaId="invalidate-jwt-token-button"
variant="primary"
isSmall
onClick={() =>
dispatch(
openConfirmModal({
isWarning: true,
title: __('Invalidate tokens for self?'),
confirmButtonText: __('Confirm'),
onConfirm: () =>
dispatch(
APIActions.patch({
url: `/users/${userId}/invalidate_jwt`,
key: `INVALIDATE-JWT`,
successToast: () =>
girijaasoni marked this conversation as resolved.
Show resolved Hide resolved
__(
'Successfully Invalidated registration tokens.'
),
errorToast: () => __('Unexpected error occurred.'),
})
),
})
)
}
>
{__('Invalidate JWTs')}
</Button>
</td>
</tr>
</tbody>
</table>
</Fragment>
);
};

JwtTokens.propTypes = {
userId: PropTypes.string.isRequired,
};

export default JwtTokens;
Loading