Repository navigation
Benchmark PR 3 #13
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
base: cr-base-3
Are you sure you want to change the base?
Benchmark PR 3 #13
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -14,6 +14,7 @@ Discourse.CreateAccountController = Discourse.Controller.extend(Discourse.ModalF | |||||
| accountPasswordConfirm: 0, | ||||||
| accountChallenge: 0, | ||||||
| formSubmitted: false, | ||||||
| rejectedEmails: Em.A([]), | ||||||
|
|
||||||
| submitDisabled: function() { | ||||||
| if (this.get('formSubmitted')) return true; | ||||||
|
|
@@ -64,6 +65,14 @@ Discourse.CreateAccountController = Discourse.Controller.extend(Discourse.ModalF | |||||
| } | ||||||
|
|
||||||
| email = this.get("accountEmail"); | ||||||
|
|
||||||
| if (this.get('rejectedEmails').contains(email)) { | ||||||
| return Discourse.InputValidation.create({ | ||||||
| failed: true, | ||||||
| reason: I18n.t('user.email.invalid') | ||||||
| }); | ||||||
| } | ||||||
|
|
||||||
| if ((this.get('authOptions.email') === email) && this.get('authOptions.email_valid')) { | ||||||
| return Discourse.InputValidation.create({ | ||||||
| ok: true, | ||||||
|
|
@@ -84,7 +93,7 @@ Discourse.CreateAccountController = Discourse.Controller.extend(Discourse.ModalF | |||||
| failed: true, | ||||||
| reason: I18n.t('user.email.invalid') | ||||||
| }); | ||||||
| }.property('accountEmail'), | ||||||
| }.property('accountEmail', 'rejectedEmails.@each'), | ||||||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why: rejectedEmails.@each on line 96 does not observe Ember array membership changes; when a rejected email is added via pushObject on line 275, emailValidation is not recomputed. 🟠 Invalid Ember dependent key for array membership observation In Ember computed properties,
Suggested change
agent: |
||||||
|
|
||||||
| usernameMatch: function() { | ||||||
| if (this.usernameNeedsToBeValidatedWithEmail()) { | ||||||
|
|
@@ -262,6 +271,9 @@ Discourse.CreateAccountController = Discourse.Controller.extend(Discourse.ModalF | |||||
| createAccountController.set('complete', true); | ||||||
| } else { | ||||||
| createAccountController.flash(result.message || I18n.t('create_account.failed'), 'error'); | ||||||
| if (result.errors && result.errors.email && result.values) { | ||||||
| createAccountController.get('rejectedEmails').pushObject(result.values.email); | ||||||
| } | ||||||
| createAccountController.set('formSubmitted', false); | ||||||
| } | ||||||
| if (result.active) { | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| class BlockedEmail < ActiveRecord::Base | ||
|
|
||
| before_validation :set_defaults | ||
|
|
||
| validates :email, presence: true, uniqueness: true | ||
|
|
||
| def self.actions | ||
| @actions ||= Enum.new(:block, :do_nothing) | ||
| end | ||
|
|
||
| def self.should_block?(email) | ||
| record = BlockedEmail.where(email: email).first | ||
| if record | ||
| record.match_count += 1 | ||
| record.last_match_at = Time.zone.now | ||
| record.save | ||
| end | ||
| record && record.action_type == actions[:block] | ||
| end | ||
|
|
||
| def set_defaults | ||
| self.action_type ||= BlockedEmail.actions[:block] | ||
| end | ||
|
|
||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| class CreateBlockedEmails < ActiveRecord::Migration | ||
| def change | ||
| create_table :blocked_emails do |t| | ||
| t.string :email, null: false | ||
| t.integer :action_type, null: false | ||
| t.integer :match_count, null: false, default: 0 | ||
| t.datetime :last_match_at | ||
| t.timestamps | ||
| end | ||
| add_index :blocked_emails, :email, unique: true | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,24 @@ | ||||||||||||||||
| class EmailValidator < ActiveModel::EachValidator | ||||||||||||||||
|
|
||||||||||||||||
| def validate_each(record, attribute, value) | ||||||||||||||||
| if (setting = SiteSetting.email_domains_whitelist).present? | ||||||||||||||||
| unless email_in_restriction_setting?(setting, value) | ||||||||||||||||
| record.errors.add(attribute, I18n.t(:'user.email.not_allowed')) | ||||||||||||||||
| end | ||||||||||||||||
| elsif (setting = SiteSetting.email_domains_blacklist).present? | ||||||||||||||||
| if email_in_restriction_setting?(setting, value) | ||||||||||||||||
| record.errors.add(attribute, I18n.t(:'user.email.not_allowed')) | ||||||||||||||||
| end | ||||||||||||||||
| end | ||||||||||||||||
| if record.errors[attribute].blank? and BlockedEmail.should_block?(value) | ||||||||||||||||
| record.errors.add(attribute, I18n.t(:'user.email.blocked')) | ||||||||||||||||
| end | ||||||||||||||||
| end | ||||||||||||||||
|
|
||||||||||||||||
| def email_in_restriction_setting?(setting, value) | ||||||||||||||||
| domains = setting.gsub('.', '\.') | ||||||||||||||||
| regexp = Regexp.new("@(#{domains})", true) | ||||||||||||||||
| value =~ regexp | ||||||||||||||||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why: value can be nil when validating a record with no email on line 3; passed to email_in_restriction_setting? on line 5 or 9 and dereferenced with =~ on line 21, raising a NoMethodError exception. 🟠 NoMethodError when validating nil email value against domain settings When validating a user record whose email is Guard against
Suggested change
agent: |
||||||||||||||||
| end | ||||||||||||||||
|
|
||||||||||||||||
| end | ||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| require 'spec_helper' | ||
|
|
||
| describe EmailValidator do | ||
|
|
||
| let(:record) { Fabricate.build(:user, email: "bad@spamclub.com") } | ||
| let(:validator) { described_class.new({attributes: :email}) } | ||
| subject(:validate) { validator.validate_each(record,:email,record.email) } | ||
|
|
||
| context "blocked email" do | ||
| it "doesn't add an error when email doesn't match a blocked email" do | ||
| BlockedEmail.stubs(:should_block?).with(record.email).returns(false) | ||
| validate | ||
| record.errors[:email].should_not be_present | ||
| end | ||
|
|
||
| it "adds an error when email matches a blocked email" do | ||
| BlockedEmail.stubs(:should_block?).with(record.email).returns(true) | ||
| validate | ||
| record.errors[:email].should be_present | ||
| end | ||
| end | ||
|
|
||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| Fabricator(:blocked_email) do | ||
| email { sequence(:email) { |n| "bad#{n}@spammers.org" } } | ||
| action_type BlockedEmail.actions[:block] | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| require 'spec_helper' | ||
|
|
||
| describe BlockedEmail do | ||
|
|
||
| let(:email) { 'block@spamfromhome.org' } | ||
|
|
||
| describe "new record" do | ||
| it "sets a default action_type" do | ||
| BlockedEmail.create(email: email).action_type.should == BlockedEmail.actions[:block] | ||
| end | ||
|
|
||
| it "last_match_at is null" do | ||
| # If we manually load the table with some emails, we can see whether those emails | ||
| # have ever been blocked by looking at last_match_at. | ||
| BlockedEmail.create(email: email).last_match_at.should be_nil | ||
| end | ||
| end | ||
|
|
||
| describe "#should_block?" do | ||
| subject { BlockedEmail.should_block?(email) } | ||
|
|
||
| it "returns false if a record with the email doesn't exist" do | ||
| subject.should be_false | ||
| end | ||
|
|
||
| shared_examples "when a BlockedEmail record matches" do | ||
| it "updates statistics" do | ||
| Timecop.freeze(Time.zone.now) do | ||
| expect { subject }.to change { blocked_email.reload.match_count }.by(1) | ||
| blocked_email.last_match_at.should be_within_one_second_of(Time.zone.now) | ||
| end | ||
| end | ||
| end | ||
|
|
||
| context "action_type is :block" do | ||
| let!(:blocked_email) { Fabricate(:blocked_email, email: email, action_type: BlockedEmail.actions[:block]) } | ||
| it { should be_true } | ||
| include_examples "when a BlockedEmail record matches" | ||
| end | ||
|
|
||
| context "action_type is :do_nothing" do | ||
| let!(:blocked_email) { Fabricate(:blocked_email, email: email, action_type: BlockedEmail.actions[:do_nothing]) } | ||
| it { should be_false } | ||
| include_examples "when a BlockedEmail record matches" | ||
| end | ||
| end | ||
|
|
||
| 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.
Why: rejectedEmails is initialized as Em.A([]) on line 17 on the controller prototype; mutating it on line 275 shares rejected emails across all controller usages and modal re-openings.
🟡 Array initialized on Ember controller prototype shares state across usages
Defining
rejectedEmails: Em.A([])directly on the controller definition evaluatesEm.A([])once when the prototype is created. Any array mutations performed viapushObjectwill persist across modal re-opens and reset operations because all controller usages share the same array instance.agent:
defect· rule:defect.prototype-mutation· confidence: 0.90