-
Notifications
You must be signed in to change notification settings - Fork 138
Conversation
We added a rule that all users `can :read User`; we can choose to not authorize those actions, or check the abilities anyway.
City field has a presence validation, but that was not visible in the view.
#979) For real github accounts, the github_id is not nil. So if the id is nil, it is a fake account or an input error. Updated seeds with github_id
Solved in cancancan Fixed in cancancan: ryanb/cancan#835 (comment)
This is ready for review 🙇 |
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.
Looks great to me. Thank you so much!
method = "#{resource}_params" | ||
params[resource] &&= send(method) if respond_to?(method, true) | ||
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.
🙌
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 heading in the right direction 🙌 🚀
Left some comments on things I think we should improve before merging 👍
app/views/community/index.html.slim
Outdated
@@ -31,8 +31,7 @@ table.users.table.table-striped.table-bordered.table-condensed.table-hover | |||
td.image = avatar_url(user, size: 40) | |||
td = link_to user.name_or_handle, user | |||
td = user.interested_in.map { |key| User::INTERESTS[key].presence }.compact.join(', ') | |||
- if signed_in? | |||
td = mail_to user.email unless user.hide_email? | |||
td = mail_to user.email if can?(:read, user.email) |
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 there's a bug here: this keeps the td
even if the can?
method returns false, which breaks the layout a bit. (Try it with a logged out or unconfirmed user.)
I think you need to change this back to be a 2-line conditional.
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.
Also, as pointed out below, I think the check should be can?(:read_email, 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.
Oiii, thanks for catching this. Turns out I was solving the wrong problem. Fixing the ability rules also fixed the layout and what I was trying to do with the new ability rule.
app/views/community/index.html.slim
Outdated
@@ -16,7 +16,7 @@ table.users.table.table-striped.table-bordered.table-condensed.table-hover | |||
th | |||
th = sortable :name | |||
th = sortable :interested_in | |||
- if signed_in? | |||
- if current_user&.confirmed? |
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 we should make this check be the same can?
check as you have below. What do you think?
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.
🤔To use the can?
helper , I'd need access to the single user, as I do have below. I'd think this hack is better than alternatives, like adding an extra @user ivar? Or...?
Neat it is not. 😢
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.
Hmm, good point. I wonder what people usually do in these circumstances - we are kinda duplicating authorization knowledge here.
Also this seems to be slightly different from what we have specified in the Ability class: from what I understand, confirmed users can: can :read_email, User, hide_email: false
, can't they? (I may be missing something here! I'm not too familiar with CanCan).
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 hope we'll have a nicer solution at the end of the Overhaul. And I'll keep looking for a better solution.
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.
Saw your recent change to Ability 👍 One thing that seems to work is - if can?(:read_email, nil)
.
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.
That didn't work for me, not as a twoliner, and not as a oneliner :-(
See next commits.
@@ -0,0 +1,5 @@ | |||
class DestroyNonExistingGitHubUsers < ActiveRecord::Migration[5.1] | |||
def up | |||
execute 'DELETE FROM users WHERE github_id IS NULL' |
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.
Some considerations 🤔:
First, I think we may want to prevent this from running on development to avoid the issue you pointed out on the PR description. I worry this may cause unexpected behavior for existing developers who update their local copy of the app.
Wrapping the execute
statement on a unless Rails.env.development?
should do it, I think 👍
Second point: do these users have any relationships with other models in the database? If they do, maybe we should consider doing this using Ruby so the destroy callbacks run. User.where(github_id: nil).destroy_all
. I don't have access to production data to see if this is the case or not.
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 like the ruby destroy better anyway.
- Are you sure about not running the migration in dev env? I'd think that we want the user objects in the local database not to be different from the test and production databases?
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.
Then maybe we could generate github_id
s for users without one in development to make them be compatible without deleting them?
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 intended to add a 'how-to' to the readme or troubleshooting guide. Or should I add it to the migration? Add a part to add gh_ids if env == dev?
Sorry, I missed this comment yesterday.
expect(response.body).not_to include user_opted_out.email | ||
# expect(response.body).to include user.email | ||
# This expectation ^ fails, but should this be tested in a controller test in | ||
# the first place? |
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'm confused 😕 What changed in this PR to make this expectation start failing?
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.
Ah, I think this really is an issue with your changes:
# Shouldn't this:
can?(:read, user.email)
# Be this:
can?(:read_email, 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.
Yes, it should! See other comment. 💃
spec/models/ability/guest_spec.rb
Outdated
@@ -15,6 +15,8 @@ | |||
|
|||
it_behaves_like 'has access to public features' | |||
|
|||
it { expect(subject).not_to be_able_to(:read, other_user.email) } |
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.
Heads up I believe this should be be_able_to(:read_email, other_user)
. Same below.
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 thought this is testing the desired outcome of the read_email
rule? No?
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'm not experienced with CanCan, but looking at how the rule is defined, using read_email
instead of read
, I would assume you need to use read_email
here as well. Also, the rule definition does not receive an email, right? It receives a User.
But it should be testable. I made these changes and the spec fails:
Using this makes it pass:
it { expect(subject).to be_able_to(:read_email, other_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.
You are right. I remember what I did here. The thing is, the read_email rule is not yet renewed yet. So it is hard to test it (and use it in the community view) the right way.
The one thing I wanted to guard is that a guest user can not read emails. I am 98% sure that the test is testing the right thing here. 😇
See my general comment for a proposal on how to handle this.
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.
Still think this is a false positive @F3PiX 😟 I wasn't able to make be_able_to(:read, other_user.email)
return true even for cases where it should return true, as I showed above.
This is the same error you did initially, where you were testing things in the view as can?(:read, user.email)
and it didn't work and we changed it to can?(:read_email, user)
. It makes sense that the same logic would apply here in the expectation.
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.
O yes. So sorry. You are right.
@pgaspar Back to you! |
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.
Thanks for your changes @F3PiX!
Added some more comments. Thanks for bearing with me 😅
app/views/community/index.html.slim
Outdated
@@ -16,7 +16,7 @@ table.users.table.table-striped.table-bordered.table-condensed.table-hover | |||
th | |||
th = sortable :name | |||
th = sortable :interested_in | |||
- if signed_in? | |||
- if current_user&.confirmed? |
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.
Saw your recent change to Ability 👍 One thing that seems to work is - if can?(:read_email, nil)
.
app/views/community/index.html.slim
Outdated
@@ -31,8 +31,7 @@ table.users.table.table-striped.table-bordered.table-condensed.table-hover | |||
td.image = avatar_url(user, size: 40) | |||
td = link_to user.name_or_handle, user | |||
td = user.interested_in.map { |key| User::INTERESTS[key].presence }.compact.join(', ') | |||
- if signed_in? | |||
td = mail_to user.email unless user.hide_email? | |||
td = mail_to user.email if can?(:read_email, 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.
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.
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.
Ah, that's a great point - I'm sorry I missed that!
I haven't tested this, but maybe something like this would work:
- if can?(:read_email, nil)
td = mail_to user.email if can?(:read_email, user)
The idea being that the first check will be false
for unconfirmed / logged out users, while being true for other users. It's not the prettiest thing every, but it may solve the issue without us needing to explicitly check if the user is confirmed (which duplicates knowledge that unconfirmed/logged out users should not see emails).
def up | ||
unless ENV['RAILS_ENV'] == 'development' | ||
User.find_each do |user| | ||
if user.github_id == nil |
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'm curious why you're doing this like that instead of filtering users upfront.
User.where(github_id: nil).find_each do |user|
puts "removing user #{user.id} from database"
user.destroy
end
It would avoid needing to go through all the users in the database.
User.find_each do |user| | ||
if user.github_id == nil | ||
puts "removing user #{user.id} from database" | ||
user.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.
This may not matter much, depending on the number of records we have, but I think this version leads to better performance:
User.where(github_id: nil).destroy_all.each do |user|
puts "removed user #{user.id} from database"
end
destroy_all
returns the records that were removed, so we can use those to print stuff.
PS: did a quick one-run test and for 5000 users this method ran in ~23 seconds while the inline user.destroy
method ran in ~41 seconds. That being said, this probably doesn't matter at all since we don't have a lot of records being destroyed! 😅
@@ -36,7 +36,8 @@ | |||
user_opted_out = create(:user, hide_email: true) | |||
get :index | |||
expect(response.body).to include user.email | |||
expect(response.body).not_to include user_opted_out.email | |||
# Should this be tested in a controller test? | |||
expect(response.body).not_to include user_opted_out.email # false negative; also passes with hide_email: false |
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.
@F3PiX I may be missing something here, but I think this may not be a false negative. Changing user_opted_out
to have hide_email: false
will make the test fail.
let!(:activity) { create(:status_update, :published, team: team1) } | ||
let!(:other_user) { create(:user) } | ||
let!(:activity) { create(:status_update, :published, team: team1) } | ||
let(:other_user) { create(:user, hide_email: false) } |
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.
Isn't this change unnecessary? Removing it does not make the spec fail (I assume hide_email
defaults to false
).
Are you doing it for clarity? Looks good if you are 👍
|
||
it 'nothing happens when clicking on resend link' do | ||
find_link('Click here to resend the email.').click | ||
# todo: expect_to happen: something visible |
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.
Maybe check that an email was enqueued here or something?
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 there is no visual feedback to the 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.
Yep, I understood that part 😅 I was just saying that maybe we should also check that an email was enqueued, because that's what happens, right? Or maybe it isn't? I'm a bit confused by the spec name nothing happens when clicking on resend link
.
spec/models/ability/guest_spec.rb
Outdated
@@ -15,6 +15,8 @@ | |||
|
|||
it_behaves_like 'has access to public features' | |||
|
|||
it { expect(subject).not_to be_able_to(:read, other_user.email) } |
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'm not experienced with CanCan, but looking at how the rule is defined, using read_email
instead of read
, I would assume you need to use read_email
here as well. Also, the rule definition does not receive an email, right? It receives a User.
But it should be testable. I made these changes and the spec fails:
Using this makes it pass:
it { expect(subject).to be_able_to(:read_email, other_user) }
@pgaspar Sorry for keeping you busy 🙏 |
Thank you @F3PiX 👍 And thanks for your patience. If you're willing to try one last solution, this should get rid of the empty column for guests: - if current_user&.confirmed?
th Email
/ ...
- if current_user&.confirmed?
td = mail_to user.email if can?(:read_email, user) Did a quick test and it seemed to work (and it should work, looking at the code). |
e6bd629
to
d48ea6b
Compare
@pgaspar I updated the specs; now they do test the |
Great work @F3PiX 👍 As for the |
d10b94c
to
62b49c4
Compare
users = User.all.select { |user| user.github_id.nil? } | ||
users.each_with_index do |user, index| | ||
user.update!(github_id: index + 10) | ||
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.
@F3PiX select
is doing this in ruby which is slow 😟 users = User.where(github_id: nil)
does the same thing but in the database, which is fast!
users.each_with_index
is not great for memory because it loads every record without a github id at the same time into memory.
Here's an alternative:
User.where(github_id: nil).find_each(batch_size: 200).with_index do |user, index|
user.update!(github_id: index + 10)
end
You can find a similar example in the docs here: http://api.rubyonrails.org/classes/ActiveRecord/Batches.html#method-i-find_each
@pgaspar I got distracted before I submitted my comment.
Do you want me to fix it anyway? I'll do that tomorrow then! Either way, I will stick to the |
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.
@F3PiX no, that’s fine 🙌 thank you for your patience! ❤️
Thanks so much @pgaspar for having my back! 🙇 Much appreciated! |
Related issue #991
On the way to separating authentication from authorisation:
Also:
Why? For accounts imported from GitHUb, the github_id is not nil. So if the id is nil, it is a fake
account or an input error. Related issue Add rake task to strip down production database dump #979
@klappradla I never used a migration for removing data before. I copied one of your examples, can you double double check please?
ALERT
github_id
< 100.rails db:migrate
andrails db:seed
Background info:
before_action: set_resource
for authorised actions. (Note that my previous description about this behaviour where not 100% accurate 😇 )Follow up: