-
Notifications
You must be signed in to change notification settings - Fork 991
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 #37936 - As a user, I want to invalidate jwt for specific user #10357
base: develop
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leaving the comment for the the proper review, this can be easily missed but I believe is important to get it right.
render :json => { :error => _("User %s does not exist.") % @user.login} | ||
elsif [email protected]?(:edit_users) | ||
deny_access N_("User %s does not have permissions to invalidate" % @user.login) | ||
else @user.can?(:edit_users) && @user.invalidate_jwt.blank? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is not enough. edit_users
permission may have specific conditions set. This would allow any user that can edit one specific user's profile to invalidate token for all users, introducing a privilege escalation.
If you look at can?
definition you can see it also accepts subject
which should the permission be verified on. We need to be checking that user can edit_users
on the specific @user
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ack, I'll look into updating the subject
app/helpers/users_helper.rb
Outdated
@@ -33,14 +33,22 @@ def user_action_buttons(user, additional_actions = []) | |||
:method => :post, | |||
:data => { :no_turbolink => true }) | |||
end | |||
|
|||
if user != User.current | |||
additional_actions << display_link_if_authorized(_("Invalidate JWT"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the exact same problem like with can?
above, the auth object needs to be specified. If this is used on some users listing page, to avoid N+1 issue, an authorized object needs to be used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am already adding :auth_object
here, is this what you're talking about?
37cd91b
to
eeb34c3
Compare
@@ -111,6 +111,23 @@ def update | |||
end | |||
end | |||
|
|||
api :PATCH, "/users/:id/invalidate_jwt", N_("Get vm attributes of host") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copy-paste leftovers?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, my bad
@@ -111,6 +111,23 @@ def update | |||
end | |||
end | |||
|
|||
api :PATCH, "/users/:id/invalidate_jwt", N_("Get vm attributes of host") | |||
param :id, String, :required => true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose you would want to have an array parameter here. To be able to invalidate tokens for multiple users.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
currently in the scope, (atleast UI wise) we can invalidate tokens for a specific user or all users because otherwise I would have to add a select action in the UI. I was thinking, I can add another endpoint to invalidate all at once. Allowing multiple (but not all) users token invalidation can take some time to integrate
DOC | ||
|
||
def invalidate_jwt | ||
@user = User.find(params[:id]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@user = User.find(params[:id]) | |
@user = User.where(id: params[:id]).authorized(:edit_user) |
This will filter only the users that edit_user
permission applies to them, including custom filters.
Now you will have a set of users that you can safely delete the tokens for:
JwtSecret.where(user_id: @users).destroy_all
@@ -12,8 +12,8 @@ class UsersController < V2::BaseController | |||
['compute_attributes'] | |||
|
|||
before_action :find_optional_nested_object | |||
skip_before_action :authorize, :only => [:extlogin] | |||
before_action :authenticate, :only => [:extlogin] | |||
skip_before_action :authorize, :only => [:extlogin, :invalidate_jwt] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you need to skip authorization for invalidate_jwt
action?
You can specify the action in the security block:
:"api/v2/users" => [:update] |
This will make sure the current user has the
edit_users
permission before accessing the endpoint.
app/controllers/users_controller.rb
Outdated
@@ -8,7 +8,7 @@ class UsersController < ApplicationController | |||
rescue_from ActionController::InvalidAuthenticityToken, with: :login_token_reload | |||
skip_before_action :require_mail, :only => [:edit, :update, :logout, :stop_impersonation] | |||
skip_before_action :require_login, :check_user_enabled, :authorize, :session_expiry, :update_activity_time, :set_taxonomy, :set_gettext_locale_db, :only => [:login, :logout, :extlogout] | |||
skip_before_action :authorize, :only => [:extlogin, :impersonate, :stop_impersonation] | |||
skip_before_action :authorize, :only => [:extlogin, :impersonate, :stop_impersonation, :invalidate_jwt] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same as for the API endpoint. You can specify the new action in the security block:
:users => [:edit, :update], |
Tested and does as needed, one small thing that acts weird to me is that a user cant invalidate their own jwt, so for example admin doesnt have the invalidate button, but a user with edit_users permission can invalidate admins tokens. |
It's because there will be a different button in the user details page for this, from UI Perspective, for a user to invalidate tokens for themselves, they will have it in the profile page |
@girijaasoni I think it looks a bit weird in the UI that other users have the invalidate button, but not the current (admin) user: |
|
bf39e67
to
7e62bf0
Compare
Test failures seem unrelated |
@@ -111,6 +112,25 @@ def update | |||
end | |||
end | |||
|
|||
api :PATCH, "/users/:id/invalidate_jwt", N_("Invalidate JWT for a specific userS") | |||
description <<-DOC |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Lennonka review please of the text & labels
@@ -111,6 +112,25 @@ def update | |||
end | |||
end | |||
|
|||
api :PATCH, "/users/:id/invalidate_jwt", N_("Invalidate JWT for a specific userS") | |||
description <<-DOC | |||
Invalidate JWT for a specific users |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the description is one-liner, then I would go with description 'Invalidate JWT for a specific users'
@@ -93,6 +93,16 @@ def impersonate | |||
end | |||
end | |||
|
|||
def invalidate_jwt | |||
@user = find_resource(:edit_users) | |||
User.find_by(id: @user.id).jwt_secret&.destroy |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why? You already have the @user
object
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
its to coordinate with the permissions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand. You are checking permissions on the controller action, right?
If users don't have edit_users
permission, they cannot use the invalidate_jwt
action.
So why to recheck it here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the only place I'm checking permissions in. No other place
def invalidate_jwt | ||
@user = find_resource(:edit_users) | ||
User.find_by(id: @user.id).jwt_secret&.destroy | ||
if @user.jwt_secret.blank? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have to have this if statement?
How can it happen that the jwt_secret
IS NOT blank?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean just like a safety check
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean just like a safety check
Yes, but is it really needed?
How the User.find_by(id: @user.id).jwt_secret&.destroy
can result in @user.jwt_secret
not being blank, that's what I'm asking.
if params[:search].blank? | ||
process_resource_error | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if params[:search].blank? | |
process_resource_error | |
end | |
process_resource_error if params[:search].blank? | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ack
render :json => { :error => _("No record found for '%s'") % params[:search]} | ||
else | ||
response = JwtSecret.where(user_id: @users).destroy_all | ||
if response.blank? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the if response.blank?
needed? In which cases is it not blank?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean it's optional, just like for safety. I'm open to remove it on vote? what do you think @ShimShtein
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, I would like to see the reasoning behind the check.
I want to know which scenario the response is not blank in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think there would be a scenario as such.
I can remove the if condition
Failing tests are fixed in #10377 and fog/fog-libvirt#163 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My suggestions for the microcopy. It would be great to get the singulars and plurals right.
@@ -111,6 +112,26 @@ def update | |||
end | |||
end | |||
|
|||
api :PATCH, "/users/:id/invalidate_jwt", N_("Invalidate JWT for a specific users") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
api :PATCH, "/users/:id/invalidate_jwt", N_("Invalidate JWT for a specific users") | |
api :PATCH, "/users/:id/invalidate_jwt", N_("Invalidate all JWTs for the user") |
We're invalidating all JWTs for a single user, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for API, we are doing it for multiple users
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But the user is identified by an ID that must be unique for each user. So how is it for multiple users?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for API, we are using the scoped search approach, so the search params in the curl command would be something like {"seach" : "id ^ (1,2,3)"}
translating to search for id in 1, 2 ,3.
@@ -111,6 +112,26 @@ def update | |||
end | |||
end | |||
|
|||
api :PATCH, "/users/:id/invalidate_jwt", N_("Invalidate JWT for a specific users") | |||
description <<-DOC | |||
Invalidate JWT for a specific users |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Invalidate JWT for a specific users | |
Invalidate all JSON Web Tokens (JWT) for this user. | |
The user will no longer be able to register hosts by using their JWTs. |
User.find_by(id: @user.id).jwt_secret&.destroy | ||
if @user.jwt_secret.blank? | ||
process_success( | ||
:success_msg => _('Successfully invalidated JWT for %s.') % @user.login |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:success_msg => _('Successfully invalidated JWT for %s.') % @user.login | |
:success_msg => _('Successfully invalidated JWTs for %s.') % @user.login |
We're invalidating all of user's JWTs, right?
@@ -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 JWT"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
additional_actions << display_link_if_authorized(_("Invalidate JWT"), | |
additional_actions << display_link_if_authorized(_("Invalidate JWTs"), |
else | ||
response = JwtSecret.where(user_id: @users).destroy_all | ||
if response.blank? | ||
process_success _('Successfully invalidated JWT for %s.' % @users.pluck(:login).to_sentence) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
process_success _('Successfully invalidated JWT for %s.' % @users.pluck(:login).to_sentence) | |
process_success _('Successfully invalidated JWTs for %s.' % @users.pluck(:login).to_sentence) |
No description provided.