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 #37936 - Invalidate jwt for any user or users(API) #10397

Merged
merged 1 commit into from
Jan 9, 2025

Conversation

girijaasoni
Copy link
Contributor

@girijaasoni girijaasoni commented Dec 4, 2024

Link to UI PR: #10357

@girijaasoni girijaasoni changed the title Fixes #37936 - As a user, I want to invalidate jwt for specific user(… Fixes #37936 - Invalidate jwt for any user or users(API) Dec 4, 2024
end
end

api :DELETE, '/users/:id/registration_tokens', N_("Invalidate all JSON Web Tokens (JWTs) for a specific user.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
api :DELETE, '/users/:id/registration_tokens', N_("Invalidate all JSON Web Tokens (JWTs) for a specific user.")
api :DELETE, '/users/:id/registration_tokens', N_("Invalidate all registration tokens for a specific user.")


class Api::V2::RegistrationTokensControllerTest < ActionController::TestCase
test 'user shall invalidate tokens for self' do
user = User.create :login => "foo", :mail => "foo@bar.com", :auth_source => auth_sources(:one)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use FactoryBot.create(:user) here.

class Api::V2::RegistrationTokensControllerTest < ActionController::TestCase
test 'user shall invalidate tokens for self' do
user = User.create :login => "foo", :mail => "foo@bar.com", :auth_source => auth_sources(:one)
FactoryBot.build(:jwt_secret, token: 'test_jwt_secret', user: user)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably want to create the token as part of the user creation, and use the create method - to make sure the token is saved before the action.

end

def find_resource(permission = :view_users)
editing_self? ? User.find(User.current.id) : User.authorized(permission).except_hidden.find(params[:id])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ekohl what do you think, should we block changes to hidden accounts here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was also wondering about that.

@nofaralfasi
Copy link
Contributor

@girijaasoni In the UI PR, we used the term JWT token for the phrasing. Why are we referring to it as a registration token here? Wouldn't that create confusion?

@girijaasoni
Copy link
Contributor Author

girijaasoni commented Dec 19, 2024

@girijaasoni In the UI PR, we used the term JWT token for the phrasing. Why are we referring to it as a registration token here? Wouldn't that create confusion?

@nofaralfasi , we can handle that in the documentation. Registration tokens make more sense from user POV(specially from API POV), as we are creating a new controller. For UI, we are trying to use the existing controller where jwt is used as phrasing.

end

api :DELETE, "/registration_tokens", N_("Invalidate all JSON Web Tokens (JWTs) for multiple users.")
param :search, String, :required => true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@girijaasoni could you please share an example for this query? What does the search param should contain?

Copy link
Contributor Author

@girijaasoni girijaasoni Dec 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

curl --user test-user1:changeme -X DELETE http://localhost:3000/api/registration_tokens/remove_multiple?search=id%20%5E%20%281%2C2%2C3%29 -H 'Content-Type: application/json'

this is the command I'm using for a scoped search query id ^ (1, 2, 3) you will need to URL encode in the search params as curl does not accept brackets and operators.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where does the remove_multiple come from? And why not to pass an array instead?

Copy link
Contributor Author

@girijaasoni girijaasoni Dec 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my bad, I meant curl --user test-user1:changeme -X DELETE http://localhost:3000/api/registration_tokens?search=id%20%5E%20%281%2C2%2C3%29 -H 'Content-Type: application/json we are using scoped search for this here so we can search with id or name or whatever. If you don't understand this we can sync up

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand how search_for works, but it still seems a bit unintuitive to me that the user needs to write queries in this format. I would have expected a simpler approach. At least, that's my perspective.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I must agree with @nofaralfasi here, for bulk actions that already exist (mostly in Katello), we provide ids param of the entities that are about to be changed/deleted.

search param is used mostly in index actions. Ideal approach is to use index action with search and then use the result in the second "destructive" call.

Although, I personally don't like REST or our? REST because of that (multiple calls instead of one), but at least we're consistent.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shweta83 and @ShimShtein, since you both approved, WDYT about ^ (#10397 (comment))? Did I miss something?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IDs are mostly hidden value, and definitely not a user-friendly one. I would go with the search approach, from what I know, people are using naming schemes for their users, so performing an action on a group would become much easier if search is implemented. I don't see a reason to "punish" the user that wants to perform such an action with a second call (index with search condition -> invalidation with ids).
We can add an ids parameter as a "syntactic sugar" here, but I don't think it's really needed in this iteration.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I won't block on that then.

It just seems weird we didn't do the same before for other endpoints.

@girijaasoni girijaasoni force-pushed the jwt-api branch 7 times, most recently from 8858b6c to 6d841de Compare December 24, 2024 13:45
@girijaasoni girijaasoni marked this pull request as draft December 24, 2024 14:14
@girijaasoni girijaasoni marked this pull request as ready for review December 24, 2024 14:24
raise ::Foreman::Exception.new(N_("No record found for %s"), params[:id])
end
@user.jwt_secret&.destroy
process_success _('Successfully invalidated JWTs for %s.' % @user.login)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@girijaasoni Can we add a newline here as well?

@shweta83
Copy link

@girijaasoni invalidate token for other users doesn't work for invalidating self token. Can we fix that too?

The users you specify will no longer be able to register hosts by using their JWTs.
DOC

def invalidate_tokens
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find the naming a bit inconsistent between the invalidate_jwt method and this one, invalidate_tokens. Would it make sense to align their names for clarity?

The users you specify will no longer be able to register hosts by using their JWTs.
DOC

def invalidate_tokens
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def invalidate_tokens
def invalidate_jwt_tokens

To be a bit more consistent with the naming.

end
end

api :DELETE, '/users/:id/registration_tokens', N_("Invalidate all registration Tokens for a specific user.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
api :DELETE, '/users/:id/registration_tokens', N_("Invalidate all registration Tokens for a specific user.")
api :DELETE, '/users/:id/registration_tokens', N_("Invalidate all registration tokens for a specific user.")

process_success _('Successfully invalidated JWTs for %s.' % @user.login)
end

api :DELETE, "/registration_tokens", N_("Invalidate all JSON Web Tokens (JWTs) for multiple users.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
api :DELETE, "/registration_tokens", N_("Invalidate all JSON Web Tokens (JWTs) for multiple users.")
api :DELETE, "/registration_tokens", N_("Invalidate all registration tokens for multiple users.")

DOC

def invalidate_tokens
raise ::Foreman::Exception.new(N_("Please provide search parameters")) if params[:search].blank?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
raise ::Foreman::Exception.new(N_("Please provide search parameters")) if params[:search].blank?
raise ::Foreman::Exception.new(N_("Please provide search parameter")) if params[:search].blank?

raise ::Foreman::Exception.new(N_("No record found %s"), params[:search])
end
JwtSecret.where(user_id: @users).destroy_all
process_success _("Successfully invalidated JWTs for %s.\n" % @users.pluck(:login).to_sentence)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
process_success _("Successfully invalidated JWTs for %s.\n" % @users.pluck(:login).to_sentence)
process_success _("Successfully invalidated registration tokens for %s.\n" % @users.pluck(:login).to_sentence)

process_success _("Successfully invalidated registration tokens for %s.\n" % @user.login)
end

api :DELETE, "/registration_tokens", N_("Invalidate all registration tokens for multiple users.")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why didn't we use /users here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to REST principles, you should state what resource you are acting upon. If you wan to use nested resources (as in users/:id/registration_token) that would work only for a specific user. There is no good way to specify a sub-object of multiple parent objects. So since registration token is an object by itself, we are addressing multiple registration token objects by a search condition.

ShimShtein
ShimShtein previously approved these changes Jan 5, 2025
Copy link

@shweta83 shweta83 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

end

api :DELETE, "/registration_tokens", N_("Invalidate all registration tokens for multiple users.")
param :search, String, :required => true
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Lennonka , could you please provide me with a description for the search parameters that I could use.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

URL-encoded search query that selects users for which registration tokens will be invalidated. Search query example: id ^ (2, 4, 6)

@Lennonka
Copy link

Lennonka commented Jan 6, 2025

Too bad we didn't use the same terminology in the UI in #10357. Is it too late to change it?

cc @girijaasoni @ShimShtein

@girijaasoni
Copy link
Contributor Author

Too bad we didn't use the same terminology in the UI in #10357. Is it too late to change it?

cc @girijaasoni @ShimShtein

It is not required in UI, we are using the correct terminology there, this is specific to API

@nofaralfasi nofaralfasi merged commit 29b3843 into theforeman:develop Jan 9, 2025
51 checks passed
@nofaralfasi
Copy link
Contributor

Thanks everyone!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants