From 9f1c978f114de452c1e6d56d7905221a984277ae Mon Sep 17 00:00:00 2001 From: Charles Oppenheimer Date: Wed, 6 Feb 2013 11:47:39 -0800 Subject: [PATCH 1/7] initial rake db:migrate was failing if hstore was already set --- db/migrate/20120921162512_add_meta_data_to_forum_threads.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrate/20120921162512_add_meta_data_to_forum_threads.rb b/db/migrate/20120921162512_add_meta_data_to_forum_threads.rb index 7f313de0f9d..9bc65aa1e36 100644 --- a/db/migrate/20120921162512_add_meta_data_to_forum_threads.rb +++ b/db/migrate/20120921162512_add_meta_data_to_forum_threads.rb @@ -1,6 +1,6 @@ class AddMetaDataToForumThreads < ActiveRecord::Migration def change - execute "CREATE EXTENSION hstore" + execute "CREATE EXTENSION IF NOT EXITS hstore" add_column :forum_threads, :meta_data, :hstore end end From c18a23f63c0ab7fc18521bde645d1cf38d36bd45 Mon Sep 17 00:00:00 2001 From: Charles Oppenheimer Date: Wed, 6 Feb 2013 13:37:38 -0800 Subject: [PATCH 2/7] Fixed typo in EXISTS sql --- db/migrate/20120921162512_add_meta_data_to_forum_threads.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrate/20120921162512_add_meta_data_to_forum_threads.rb b/db/migrate/20120921162512_add_meta_data_to_forum_threads.rb index 9bc65aa1e36..99d646ae78e 100644 --- a/db/migrate/20120921162512_add_meta_data_to_forum_threads.rb +++ b/db/migrate/20120921162512_add_meta_data_to_forum_threads.rb @@ -1,6 +1,6 @@ class AddMetaDataToForumThreads < ActiveRecord::Migration def change - execute "CREATE EXTENSION IF NOT EXITS hstore" + execute "CREATE EXTENSION IF NOT EXISTS hstore" add_column :forum_threads, :meta_data, :hstore end end From 653c146a4b74e6a7a393fc6af0429581e3da73dd Mon Sep 17 00:00:00 2001 From: Steve Klabnik Date: Wed, 6 Feb 2013 15:59:25 -0700 Subject: [PATCH 3/7] Fix typo on AUTHORS.md Just one character. --- AUTHORS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS.md b/AUTHORS.md index 7a9c8fec28a..efa85d26dca 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -14,6 +14,6 @@ * Nick Sahler - UI Implementation, supplemental -* Don Petersen - Ruby developmer, installation scripts +* Don Petersen - Ruby developer, installation scripts *For a more detailed list of the many individuals that contributed to the design and development of Discourse outside of GitHub, please refer to the official Discourse website.* From 19a0efee311a3e9c3aea083dee62962d62269af7 Mon Sep 17 00:00:00 2001 From: Sam Date: Thu, 7 Feb 2013 10:00:45 +1100 Subject: [PATCH 4/7] Update README.md --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d51a68981ff..77a16ffff46 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,17 @@ Whenever you need ... If you're interested in helping us develop Discourse, please start with our **[Discourse Developer Install Guide](https://github.com/discourse/discourse/blob/master/DEVELOPMENT.md)**, which includes instructions to get up and running in a development environment. -We also have a **[Discourse "Quick-and-Dirty" Install Guide](https://github.com/discourse/discourse/blob/master/INSTALL.md)**. +### The quick and easy setup + +``` +git clone git@github.com:discourse/discourse.git +cd discourse +rake db:create +rake db:migrate +rake db:seed_fu +redis-cli flushall +thin start +``` ## Vision From f79f0e740a28f0a7a14d7efb2933ce398d7c876b Mon Sep 17 00:00:00 2001 From: Sam Saffron Date: Thu, 7 Feb 2013 10:45:58 +1100 Subject: [PATCH 5/7] Don't allow users to double flag stuff Correct seed fu to match model --- app/models/post_action.rb | 11 +++++++++++ app/models/post_action_type.rb | 1 + config/application.rb | 4 ---- db/fixtures/post_action_types.rb | 7 +++++++ spec/models/post_action_spec.rb | 7 +++++++ 5 files changed, 26 insertions(+), 4 deletions(-) diff --git a/app/models/post_action.rb b/app/models/post_action.rb index 5c62edd7da2..089dc0d5fd5 100644 --- a/app/models/post_action.rb +++ b/app/models/post_action.rb @@ -2,6 +2,8 @@ require_dependency 'rate_limiter' require_dependency 'system_message' class PostAction < ActiveRecord::Base + class AlreadyFlagged < StandardError; end + include RateLimiter::OnCreateRecord attr_accessible :deleted_at, :post_action_type_id, :post_id, :user_id, :post, :user, :post_action_type, :message @@ -115,6 +117,15 @@ class PostAction < ActiveRecord::Base end end + before_create do + if is_flag? + if PostAction.where('user_id = ? and post_id = ? and post_action_type_id in (?) and deleted_at is null', + self.user_id, self.post_id, PostActionType.FlagTypes).exists? + raise AlreadyFlagged + end + end + end + after_save do # Update denormalized counts diff --git a/app/models/post_action_type.rb b/app/models/post_action_type.rb index 39b58a2a9f6..a347c4fba32 100644 --- a/app/models/post_action_type.rb +++ b/app/models/post_action_type.rb @@ -1,4 +1,5 @@ class PostActionType < ActiveRecord::Base + attr_accessible :id, :is_flag, :name_key, :icon def self.ordered diff --git a/config/application.rb b/config/application.rb index 0f3b40e2edf..85723bf2180 100644 --- a/config/application.rb +++ b/config/application.rb @@ -93,10 +93,6 @@ module Discourse # So open id logs somewhere sane config.after_initialize do OpenID::Util.logger = Rails.logger - - # latest possible so earliest in the stack - # require 'rack/message_bus' - # config.middleware.insert(0, Rack::MessageBus) end end end diff --git a/db/fixtures/post_action_types.rb b/db/fixtures/post_action_types.rb index 863fc8c3c51..820b05f76f6 100644 --- a/db/fixtures/post_action_types.rb +++ b/db/fixtures/post_action_types.rb @@ -27,6 +27,13 @@ PostActionType.seed do |s| s.position = 4 end +PostActionType.seed do |s| + s.id = PostActionType.Types[:vote] + s.name_key = 'vote' + s.is_flag = false + s.position = 5 +end + PostActionType.seed do |s| s.id = PostActionType.Types[:spam] s.name_key = 'spam' diff --git a/spec/models/post_action_spec.rb b/spec/models/post_action_spec.rb index 10c63990000..6eb78277288 100644 --- a/spec/models/post_action_spec.rb +++ b/spec/models/post_action_spec.rb @@ -111,6 +111,13 @@ describe PostAction do describe 'flagging' do + it 'does not allow you to flag stuff with 2 reasons' do + post = Fabricate(:post) + u1 = Fabricate(:evil_trout) + PostAction.act(u1, post, PostActionType.Types[:spam]) + lambda { PostAction.act(u1, post, PostActionType.Types[:off_topic]) }.should raise_error(PostAction::AlreadyFlagged) + end + it 'should update counts when you clear flags' do post = Fabricate(:post) u1 = Fabricate(:evil_trout) From 471c61fd69c98c1f51f2aa03feb861ef9146ac33 Mon Sep 17 00:00:00 2001 From: Neil Lalonde Date: Wed, 6 Feb 2013 19:25:21 -0500 Subject: [PATCH 6/7] Add honeypot and challenge to signup form --- .../discourse/models/user.js.coffee | 4 +- .../modal/create_account.js.handlebars | 8 ++++ .../views/modal/create_account_view.js.coffee | 15 ++++++- .../stylesheets/application/modal.css.scss | 3 ++ app/controllers/users_controller.rb | 18 ++++++++ config/routes.rb | 1 + spec/controllers/users_controller_spec.rb | 41 ++++++++++++++++++- 7 files changed, 86 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/discourse/models/user.js.coffee b/app/assets/javascripts/discourse/models/user.js.coffee index 681ac2fc91c..b90c1ea3aac 100644 --- a/app/assets/javascripts/discourse/models/user.js.coffee +++ b/app/assets/javascripts/discourse/models/user.js.coffee @@ -190,9 +190,9 @@ window.Discourse.User.reopenClass error: (xhr) -> promise.reject(xhr) promise - createAccount: (name, email, password, username) -> + createAccount: (name, email, password, username, passwordConfirm, challenge) -> $.ajax url: '/users' dataType: 'json' - data: {name: name, email: email, password: password, username: username} + data: {name: name, email: email, password: password, username: username, password_confirmation: passwordConfirm, challenge: challenge} type: 'POST' diff --git a/app/assets/javascripts/discourse/templates/modal/create_account.js.handlebars b/app/assets/javascripts/discourse/templates/modal/create_account.js.handlebars index 737db276fa0..71735338033 100644 --- a/app/assets/javascripts/discourse/templates/modal/create_account.js.handlebars +++ b/app/assets/javascripts/discourse/templates/modal/create_account.js.handlebars @@ -49,6 +49,14 @@ {{/if}} + + + + {{view Ember.TextField valueBinding="view.accountPasswordConfirm" type="password" id="new-account-password-confirmation"}} + {{view Ember.TextField valueBinding="view.accountChallenge" id="new-account-challenge"}} + + + diff --git a/app/assets/javascripts/discourse/views/modal/create_account_view.js.coffee b/app/assets/javascripts/discourse/views/modal/create_account_view.js.coffee index 7f6d5098dd0..a0f17695609 100644 --- a/app/assets/javascripts/discourse/views/modal/create_account_view.js.coffee +++ b/app/assets/javascripts/discourse/views/modal/create_account_view.js.coffee @@ -3,6 +3,8 @@ window.Discourse.CreateAccountView = window.Discourse.ModalBodyView.extend Disco title: Em.String.i18n('create_account.title') uniqueUsernameValidation: null complete: false + accountPasswordConfirm: 0 + accountChallenge: 0 submitDisabled: (-> @@ -22,6 +24,8 @@ window.Discourse.CreateAccountView = window.Discourse.ModalBodyView.extend Disco # If blank, fail without a reason return Discourse.InputValidation.create(failed: true) if @blank('accountName') + @fetchConfirmationValue() if @get('accountPasswordConfirm') == 0 + # If too short return Discourse.InputValidation.create(failed: true, reason: Em.String.i18n('user.name.too_short')) if @get('accountName').length < 3 @@ -120,13 +124,22 @@ window.Discourse.CreateAccountView = window.Discourse.ModalBodyView.extend Disco ).property('accountPassword') + fetchConfirmationValue: -> + $.ajax + url: '/users/hp.json', + success: (json) => + @set('accountPasswordConfirm', json.value) + @set('accountChallenge', json.challenge.split("").reverse().join("")) + createAccount: -> name = @get('accountName') email = @get('accountEmail') password = @get('accountPassword') username = @get('accountUsername') + passwordConfirm = @get('accountPasswordConfirm') + challenge = @get('accountChallenge') - Discourse.User.createAccount(name, email, password, username).then (result) => + Discourse.User.createAccount(name, email, password, username, passwordConfirm, challenge).then (result) => if result.success @flash(result.message) diff --git a/app/assets/stylesheets/application/modal.css.scss b/app/assets/stylesheets/application/modal.css.scss index 2f628966200..7361893f99a 100644 --- a/app/assets/stylesheets/application/modal.css.scss +++ b/app/assets/stylesheets/application/modal.css.scss @@ -152,6 +152,9 @@ margin-bottom: 20px; } } + .password-confirmation { + display: none; + } } #move-selected { diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 877470553c5..82138519c5f 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -123,6 +123,12 @@ class UsersController < ApplicationController end def create + + if params[:password_confirmation] != honeypot_value or params[:challenge] != challenge_value.try(:reverse) + # Don't give any indication that we caught you in the honeypot + return render(:json => {success: true, active: false, message: I18n.t("login.activate_email", email: params[:email]) }) + end + user = User.new user.name = params[:name] user.email = params[:email] @@ -183,6 +189,10 @@ class UsersController < ApplicationController render json: {errors: [I18n.t("mothership.access_token_problem")]} end + def get_honeypot_value + render json: {value: honeypot_value, challenge: challenge_value} + end + # all avatars are funneled through here def avatar @@ -320,6 +330,14 @@ class UsersController < ApplicationController private + def honeypot_value + Digest::SHA1::hexdigest("#{Discourse.current_hostname}:#{Discourse::Application.config.secret_token}")[0,15] + end + + def challenge_value + '3019774c067cc2b' + end + def fetch_user_from_params username_lower = params[:username].downcase username_lower.gsub!(/\.json$/, '') diff --git a/config/routes.rb b/config/routes.rb index 3331be7f080..ce4e76db22b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -80,6 +80,7 @@ Discourse::Application.routes.draw do put 'users/password-reset/:token' => 'users#password_reset' get 'users/activate-account/:token' => 'users#activate_account' get 'users/authorize-email/:token' => 'users#authorize_email' + get 'users/hp' => 'users#get_honeypot_value' get 'user_preferences' => 'users#user_preferences_redirect' get 'users/:username/private-messages' => 'user_actions#private_messages', :format => false, :constraints => {:username => USERNAME_ROUTE_FORMAT} diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index ac076be5ef1..b67f6f7af2e 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -2,6 +2,11 @@ require 'spec_helper' describe UsersController do + before do + UsersController.any_instance.stubs(:honeypot_value).returns(nil) + UsersController.any_instance.stubs(:challenge_value).returns(nil) + end + describe '.show' do let!(:user) { log_in } @@ -339,7 +344,41 @@ describe UsersController do User.where(username: @user.username).first.active.should be_false end end - + + shared_examples_for 'honeypot fails' do + it 'should not create a new user' do + expect { + xhr :post, :create, create_params + }.to_not change { User.count } + end + + it 'should not send an email' do + User.any_instance.expects(:enqueue_welcome_message).never + xhr :post, :create, create_params + end + + it 'should say it was successful' do + xhr :post, :create, create_params + json = JSON::parse(response.body) + json["success"].should be_true + end + end + + context 'when honeypot value is wrong' do + before do + UsersController.any_instance.stubs(:honeypot_value).returns('abc') + end + let(:create_params) { {:name => @user.name, :username => @user.username, :password => "strongpassword", :email => @user.email, :password_confirmation => 'wrong'} } + it_should_behave_like 'honeypot fails' + end + + context 'when challenge answer is wrong' do + before do + UsersController.any_instance.stubs(:challenge_value).returns('abc') + end + let(:create_params) { {:name => @user.name, :username => @user.username, :password => "strongpassword", :email => @user.email, :challenge => 'abc'} } + it_should_behave_like 'honeypot fails' + end end context '.username' do From 1c29b040c5b92157fdc1a601f8a2567967d08e64 Mon Sep 17 00:00:00 2001 From: Aaron Chambers Date: Thu, 7 Feb 2013 00:46:57 +0000 Subject: [PATCH 7/7] A tiny typo fix. It all adds up... --- lib/site_setting_extension.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/site_setting_extension.rb b/lib/site_setting_extension.rb index 8a1bd859997..89e49d9aebd 100644 --- a/lib/site_setting_extension.rb +++ b/lib/site_setting_extension.rb @@ -31,8 +31,8 @@ module SiteSettingExtension end # just like a setting, except that it is available in javascript via DiscourseSession - def client_setting(name, defualt = nil, type = nil) - setting(name,defualt,type) + def client_setting(name, default = nil, type = nil) + setting(name,default,type) @@client_settings ||= [] @@client_settings << name end