Commit 71f8fd60369e12848718d475fe99d6ed6d650eff

Authored by Evandro Junior
0 parents
Exists in master

1st commit

Gemfile 0 → 100644
  1 +++ a/Gemfile
... ... @@ -0,0 +1,3 @@
  1 +group :test do
  2 + gem 'webmock'
  3 +end
... ...
README.md 0 → 100644
  1 +++ a/README.md
... ... @@ -0,0 +1,7 @@
  1 +Sample config values:
  2 +
  3 +verify_uri 'http://captcha.servicoscorporativos.serpro.gov.br/captchavalidar/1.0.0/validar'
  4 +
  5 +serpro_client_id 'fdbcdc7a0b754ee7ae9d865fda740f17'
  6 +
  7 +See http://stdcs.supst.serpro/manual/html/#captcha/page/introducao.html for more details.
... ...
controllers/recaptcha_plugin_admin_controller.rb 0 → 100644
  1 +++ a/controllers/recaptcha_plugin_admin_controller.rb
... ... @@ -0,0 +1,17 @@
  1 +class RecaptchaPluginAdminController < PluginAdminController
  2 +
  3 + append_view_path File.join(File.dirname(__FILE__) + '/../views')
  4 +
  5 + def index
  6 + end
  7 +
  8 + def update
  9 + if @environment.update_attributes(params[:environment])
  10 + session[:notice] = _('Captcha configuration updated successfully.')
  11 + else
  12 + session[:notice] = _('Captcha configuration could not be saved.')
  13 + end
  14 + render :action => 'index'
  15 + end
  16 +
  17 +end
... ...
lib/ext/environment.rb 0 → 100644
  1 +++ a/lib/ext/environment.rb
... ... @@ -0,0 +1,49 @@
  1 +require_dependency 'environment'
  2 +
  3 +class Environment
  4 +
  5 + #reCAPTCHA settings
  6 + settings_items :recaptcha_plugin, :type => ActiveSupport::HashWithIndifferentAccess, :default => {}
  7 + attr_accessible :recaptcha_plugin_attributes, :recaptcha_version, :recaptcha_private_key, :recaptcha_site_key
  8 +
  9 + def recaptcha_plugin_attributes
  10 + self.recaptcha_plugin || {}
  11 + end
  12 +
  13 + def recaptcha_version= recaptcha_version
  14 + self.recaptcha_plugin = {} if self.recaptcha_plugin.blank?
  15 + self.recaptcha_plugin['recaptcha_version'] = recaptcha_version
  16 + end
  17 +
  18 + def recaptcha_version
  19 + self.recaptcha_plugin['recaptcha_version']
  20 + end
  21 +
  22 + def recaptcha_private_key= recaptcha_private_key
  23 + self.recaptcha_plugin = {} if self.recaptcha_plugin.blank?
  24 + self.recaptcha_plugin['recaptcha_private_key'] = recaptcha_private_key
  25 + end
  26 +
  27 + def recaptcha_private_key
  28 + self.recaptcha_plugin['recaptcha_private_key']
  29 + end
  30 +
  31 + def recaptcha_verify_uri= recaptcha_verify_uri
  32 + self.recaptcha_plugin = {} if self.recaptcha_plugin.blank?
  33 + self.recaptcha_plugin['recaptcha_verify_uri'] = recaptcha_verify_uri
  34 + end
  35 +
  36 + def recaptcha_verify_uri
  37 + self.recaptcha_plugin['recaptcha_verify_uri']
  38 + end
  39 +
  40 + def recaptcha_site_key= recaptcha_site_key
  41 + self.recaptcha_plugin = {} if self.recaptcha_plugin.blank?
  42 + self.recaptcha_plugin['recaptcha_site_key'] = recaptcha_site_key
  43 + end
  44 +
  45 + def recaptcha_site_key
  46 + self.recaptcha_plugin['recaptcha_site_key']
  47 + end
  48 +
  49 +end
... ...
lib/recaptcha_plugin.rb 0 → 100644
  1 +++ a/lib/recaptcha_plugin.rb
... ... @@ -0,0 +1,41 @@
  1 +class RecaptchaPlugin < Noosfero::Plugin
  2 +
  3 + def self.plugin_name
  4 + _('Google reCAPTCHA plugin')
  5 + end
  6 +
  7 + def self.plugin_description
  8 + _("Provides a plugin to Google reCAPTCHA.")
  9 + end
  10 +
  11 + def self.api_mount_points
  12 + [RecaptchaPlugin::API ]
  13 + end
  14 +
  15 + def test_captcha(*args)
  16 + remote_ip = args[0]
  17 + params = args[1]
  18 + environment = args[2]
  19 +
  20 + private_key = environment.recaptcha_private_key
  21 + version = environment.recaptcha_version
  22 +
  23 + msg_icve = _('Internal captcha validation error')
  24 + msg_esca = 'Environment recaptcha_plugin_attributes'
  25 +
  26 + return RecaptchaVerification.hash_error(msg_icve, s, nil, "#{msg_eacs} private_key not defined") if private_key.nil?
  27 + return RecaptchaVerification.hash_error(msg_icve, s, nil, "#{msg_eacs} version not defined") unless version == 1 || version == 2
  28 +
  29 + rv = RecaptchaVerification.new
  30 +
  31 + if version == 1
  32 + verify_uri = 'https://www.google.com/recaptcha/api/verify'
  33 + return rv.verify_recaptcha_v1(remote_ip, private_key, verify_uri, params[:recaptcha_challenge_field], params[:recaptcha_response_field])
  34 + end
  35 + if version == 2
  36 + verify_uri = 'https://www.google.com/recaptcha/api/siteverify'
  37 + return rv.verify_recaptcha_v2(remote_ip, private_key, verify_uri, params[:g_recaptcha_response])
  38 + end
  39 + end
  40 +
  41 +end
... ...
lib/recaptcha_verification.rb 0 → 100644
  1 +++ a/lib/recaptcha_verification.rb
... ... @@ -0,0 +1,85 @@
  1 +class RecaptchaVerification
  2 +
  3 + def self.hash_error(user_message, status, log_message=nil, javascript_console_message=nil)
  4 + {user_message: user_message, status: status, log_message: log_message, javascript_console_message: javascript_console_message}
  5 + end
  6 +
  7 + # return true or a hash with the error
  8 + # :user_message, :status, :log_message, :javascript_console_message
  9 + def verify_recaptcha_v1(remote_ip, private_key, api_recaptcha_verify_uri, recaptcha_challenge_field, recaptcha_response_field)
  10 + if recaptcha_challenge_field == nil || recaptcha_response_field == nil
  11 + return render_api_error!(_('Captcha validation error'), 500, nil, _('Missing captcha data'))
  12 + end
  13 +
  14 + verify_hash = {
  15 + "privatekey" => private_key,
  16 + "remoteip" => remote_ip,
  17 + "challenge" => recaptcha_challenge_field,
  18 + "response" => recaptcha_response_field
  19 + }
  20 + uri = URI(api_recaptcha_verify_uri)
  21 + https = Net::HTTP.new(uri.host, uri.port)
  22 + https.use_ssl = true
  23 + request = Net::HTTP::Post.new(uri.path)
  24 + request.set_form_data(verify_hash)
  25 + begin
  26 + result = https.request(request).body.split("\n")
  27 + rescue Exception => e
  28 + return render_api_error!(_('Internal captcha validation error'), 500, nil, "Error validating Googles' recaptcha version 1: #{e.message}")
  29 + end
  30 + return true if result[0] == "true"
  31 + return render_api_error!(_("Wrong captcha text, please try again"), 403, nil, "Error validating Googles' recaptcha version 1: #{result[1]}") if result[1] == "incorrect-captcha-sol"
  32 + #Catches all errors at the end
  33 + return render_api_error!(_("Internal recaptcha validation error"), 500, nil, "Error validating Googles' recaptcha version 1: #{result[1]}")
  34 + end
  35 +
  36 + # return true or a hash with the error
  37 + # :user_message, :status, :log_message, :javascript_console_message
  38 + def verify_recaptcha_v2(remote_ip, private_key, api_recaptcha_verify_uri, g_recaptcha_response)
  39 + return render_api_error!(_('Captcha validation error'), 500, nil, _('Missing captcha data')) if g_recaptcha_response == nil
  40 + verify_hash = {
  41 + "secret" => private_key,
  42 + "remoteip" => remote_ip,
  43 + "response" => g_recaptcha_response
  44 + }
  45 + uri = URI(api_recaptcha_verify_uri)
  46 + https = Net::HTTP.new(uri.host, uri.port)
  47 + https.use_ssl = true
  48 + request = Net::HTTP::Post.new(uri.path)
  49 + request.set_form_data(verify_hash)
  50 + begin
  51 + body = https.request(request).body
  52 + rescue Exception => e
  53 + return render_api_error!(_('Internal captcha validation error'), 500, nil, "recaptcha error: #{e.message}")
  54 + end
  55 + captcha_result = JSON.parse(body)
  56 + captcha_result["success"] ? true : captcha_result
  57 + end
  58 +
  59 + # return true or a hash with the error
  60 + # :user_message, :status, :log_message, :javascript_console_message
  61 + def verify_recaptcha(client_id, token, captcha_text, verify_uri)
  62 + msg_icve = _('Internal captcha validation error')
  63 + msg_esca = 'Environment recaptcha_plugin_attributes'
  64 + return hash_error(msg_icve, 500, nil, "#{msg_esca} verify_uri not defined") if verify_uri.nil?
  65 + return hash_error(msg_icve, 500, nil, "#{msg_esca} client_id not defined") if client_id.nil?
  66 + return hash_error(_("Error processing token validation"), 500, nil, _("Missing Serpro's Captcha token")) unless token
  67 + return hash_error(_('Captcha text has not been filled'), 403) unless captcha_text
  68 + uri = URI(verify_uri)
  69 + http = Net::HTTP.new(uri.host, uri.port)
  70 + request = Net::HTTP::Post.new(uri.path)
  71 + verify_string = "#{client_id}&#{token}&#{captcha_text}"
  72 + request.body = verify_string
  73 + body = http.request(request).body
  74 + return true if body == '1'
  75 + return hash_error(_("Internal captcha validation error"), 500, body, "Unable to reach Serpro's Captcha validation service") if body == "Activity timed out"
  76 + return hash_error(_("Wrong captcha text, please try again"), 403) if body == '0'
  77 + return hash_error(_("Serpro's captcha token not found"), 500) if body == '2'
  78 + return hash_error(_("No data sent to validation server or other serious problem"), 500) if body == -1
  79 + #Catches all errors at the end
  80 + return hash_error(_("Internal captcha validation error"), 500, nil, "Error validating Serpro's captcha service returned: #{body}")
  81 + end
  82 +
  83 +
  84 +
  85 +end
... ...
test/functional/account_controller_plugin_test.rb 0 → 100644
  1 +++ a/test/functional/account_controller_plugin_test.rb
... ... @@ -0,0 +1,16 @@
  1 +# Re-raise errors caught by the controller.
  2 +class AccountController; def rescue_action(e) raise e end; end
  3 +
  4 +class AccountControllerPluginTest < ActionController::TestCase
  5 +
  6 + def setup
  7 + @controller = AccountController.new
  8 + @request = ActionController::TestRequest.new
  9 + @response = ActionController::TestResponse.new
  10 +
  11 + @environment = Environment.default
  12 + @environment.enabled_plugins = ['RecaptchaPlugin']
  13 + @environment.save!
  14 + end
  15 +
  16 +end
... ...
test/test_helper.rb 0 → 100644
  1 +++ a/test/test_helper.rb
... ... @@ -0,0 +1,65 @@
  1 +require_relative "../../../lib/noosfero/api/helpers"
  2 +
  3 +class ActiveSupport::TestCase
  4 +
  5 + include Rack::Test::Methods
  6 +
  7 + def app
  8 + Noosfero::API::API
  9 + end
  10 +
  11 + def pass_captcha(mocked_url, captcha_verification_body)
  12 + stub_request(:post, mocked_url).
  13 + with(:body => captcha_verification_body,
  14 + :headers => {'Accept'=>'*/*', 'User-Agent'=>'Ruby'}).
  15 + to_return(:status => 200, :body => "1", :headers => {'Content-Length' => 1})
  16 + end
  17 +
  18 + def fail_captcha_text(mocked_url, captcha_verification_body)
  19 + stub_request(:post, mocked_url).
  20 + with(:body => captcha_verification_body,
  21 + :headers => {'Accept'=>'*/*', 'User-Agent'=>'Ruby'}).
  22 + to_return(:status => 200, :body => "0", :headers => {'Content-Length' => 1})
  23 + end
  24 +
  25 + def login_with_captcha
  26 + json = do_login_captcha_from_api
  27 + @private_token = json["private_token"]
  28 + @params = { "private_token" => @private_token}
  29 + json
  30 + end
  31 +
  32 + ## Performs a login using the session.rb but mocking the
  33 + ## real HTTP request to validate the captcha.
  34 + def do_login_captcha_from_api
  35 + post "/api/v1/login-captcha"
  36 + json = JSON.parse(last_response.body)
  37 + json
  38 + end
  39 +
  40 + def login_api
  41 + @environment = Environment.default
  42 + @user = User.create!(:login => 'testapi', :password => 'testapi', :password_confirmation => 'testapi', :email => 'test@test.org', :environment => @environment)
  43 + @user.activate
  44 + @person = @user.person
  45 +
  46 + post "/api/v1/login?login=testapi&password=testapi"
  47 + json = JSON.parse(last_response.body)
  48 + @private_token = json["private_token"]
  49 + unless @private_token
  50 + @user.generate_private_token!
  51 + @private_token = @user.private_token
  52 + end
  53 +
  54 + @params = {:private_token => @private_token}
  55 + end
  56 + attr_accessor :private_token, :user, :person, :params, :environment
  57 +
  58 + private
  59 +
  60 + def json_response_ids(kind)
  61 + json = JSON.parse(last_response.body)
  62 + json[kind.to_s].map {|c| c['id']}
  63 + end
  64 +
  65 +end
... ...
test/unit/recaptcha_verification_test.rb 0 → 100644
  1 +++ a/test/unit/recaptcha_verification_test.rb
... ... @@ -0,0 +1,110 @@
  1 +require 'webmock'
  2 +include WebMock::API
  3 +require File.dirname(__FILE__) + '/../../../../test/test_helper'
  4 +require_relative '../test_helper'
  5 +
  6 +class RecaptchaVerificationTest < ActiveSupport::TestCase
  7 +
  8 + def setup
  9 + @environment = Environment.default
  10 + @environment.enabled_plugins = ['RecaptchaPlugin']
  11 + @environment.recaptcha_verify_uri="http://www.google.com/validate" # do not correct!
  12 + @environment.recaptcha_version='2'
  13 + @environment.recaptcha_private_key = "private_key"
  14 + @environment.save!
  15 + @recaptcha_site_key = "64264643"
  16 + @captcha_text = "44641441"
  17 +# @captcha_verification_body = "#{@environment.recaptcha_client_id}&#{@captcha_token}&#{@captcha_text}"
  18 + end
  19 +
  20 + def login_with_captcha
  21 + store = Noosfero::API::SessionStore.create("captcha")
  22 + ## Initialize the data for the session store
  23 + store.data = []
  24 + ## Put it back in cache
  25 + store.store
  26 + { "private_token" => "#{store.private_token}" }
  27 + end
  28 +
  29 + def create_article(name)
  30 + person = fast_create(Person, :environment_id => @environment.id)
  31 + fast_create(Article, :profile_id => person.id, :name => name)
  32 + end
  33 +
  34 + should 'register a user when there are no enabled captcha pluging' do
  35 + @environment.enabled_plugins = []
  36 + @environment.save!
  37 + Environment.default.enable('skip_new_user_email_confirmation')
  38 + params = {:login => "newuserapi", :password => "newuserapi", :password_confirmation => "newuserapi", :email => "newuserapi@email.com" }
  39 + post "/api/v1/register?#{params.to_query}"
  40 + assert_equal 201, last_response.status
  41 + json = JSON.parse(last_response.body)
  42 + assert User['newuserapi'].activated?
  43 + assert json['user']['private_token'].present?
  44 + end
  45 +
  46 + should 'not register a user if captcha fails' do
  47 + fail_captcha_text @environment.recaptcha_verify_uri, @captcha_verification_body
  48 + Environment.default.enable('skip_new_user_email_confirmation')
  49 + params = {:login => "newuserapi", :password => "newuserapi", :password_confirmation => "newuserapi", :email => "newuserapi@email.com", :txtToken_captcha_serpro_gov_br => @captcha_token, :captcha_text => @captcha_text}
  50 + post "/api/v1/register?#{params.to_query}"
  51 + assert_equal 403, last_response.status
  52 + json = JSON.parse(last_response.body)
  53 + assert_equal json["message"], _("Wrong captcha text, please try again")
  54 + end
  55 +
  56 + should 'verify_recaptcha' do
  57 + pass_captcha @environment.recaptcha_verify_uri, @captcha_verification_body
  58 + scv = RecaptchaVerification.new
  59 + assert scv.verify_recaptcha(@environment.recaptcha_client_id, @captcha_token, @captcha_text, @environment.recaptcha_verify_uri)
  60 + end
  61 +
  62 + should 'fail captcha if user has not filled Serpro\' captcha text' do
  63 + pass_captcha @environment.recaptcha_verify_uri, @captcha_verification_body
  64 + scv = RecaptchaVerification.new
  65 + hash = scv.verify_recaptcha(@environment.recaptcha_client_id, @captcha_token, nil, @environment.recaptcha_verify_uri)
  66 + assert hash[:user_message], _('Captcha text has not been filled')
  67 + end
  68 +
  69 + should 'fail captcha if Serpro\' captcha token has not been sent' do
  70 + pass_captcha @environment.recaptcha_verify_uri, @captcha_verification_body
  71 + scv = RecaptchaVerification.new
  72 + hash = scv.verify_recaptcha(@environment.recaptcha_client_id, nil, @captcha_text, @environment.recaptcha_verify_uri)
  73 + assert hash[:javascript_console_message], _("Missing Serpro's Captcha token")
  74 + end
  75 +
  76 + should 'fail captcha text' do
  77 + fail_captcha_text @environment.recaptcha_verify_uri, @captcha_verification_body
  78 + scv = RecaptchaVerification.new
  79 + hash = scv.verify_recaptcha(@environment.recaptcha_client_id, nil, @captcha_text, @environment.recaptcha_verify_uri)
  80 + assert hash[:javascript_console_message], _("Wrong captcha text, please try again")
  81 + end
  82 +
  83 + should 'not perform a vote without authentication' do
  84 + article = create_article('Article 1')
  85 + params = {}
  86 + params[:value] = 1
  87 +
  88 + post "/api/v1/articles/#{article.id}/vote?#{params.to_query}"
  89 + json = JSON.parse(last_response.body)
  90 + assert_equal 401, last_response.status
  91 + end
  92 +
  93 + should 'perform a vote on an article identified by id' do
  94 + pass_captcha @environment.recaptcha_verify_uri, @captcha_verification_body
  95 + params = {}
  96 + params[:txtToken_captcha_serpro_gov_br]= @captcha_token
  97 + params[:captcha_text]= @captcha_text
  98 + post "/api/v1/login-captcha?#{params.to_query}"
  99 + json = JSON.parse(last_response.body)
  100 + article = create_article('Article 1')
  101 + params = {}
  102 + params[:private_token] = json['private_token']
  103 + params[:value] = 1
  104 + post "/api/v1/articles/#{article.id}/vote?#{params.to_query}"
  105 + json = JSON.parse(last_response.body)
  106 + assert_not_equal 401, last_response.status
  107 + assert_equal true, json['vote']
  108 + end
  109 +
  110 +end
... ...
views/recaptcha_plugin_admin/index.html.erb 0 → 100644
  1 +++ a/views/recaptcha_plugin_admin/index.html.erb
... ... @@ -0,0 +1,32 @@
  1 +<h1><%= _("reCaptcha Management") %> </h1>
  2 +
  3 +<%= labelled_form_for(:environment, :url => {:action => 'update'}) do |f| %>
  4 +
  5 +<table>
  6 + <tr>
  7 + <th><%= c_('Configuration') %></th>
  8 + <th><%= _('Value') %></th>
  9 + </tr>
  10 + <tr>
  11 + <td><%= _('Version (1 or 2)') %></td>
  12 + <td><%= text_field :environment, :recaptcha_version %></td>
  13 + </tr>
  14 + <tr>
  15 + <td><%= _('Site key') %></td>
  16 + <td><%= text_field :environment, :recaptcha_site_key %></td>
  17 + </tr>
  18 + <tr>
  19 + <td><%= _('Secret key') %></td>
  20 + <td><%= text_field :environment, :recaptcha_private_key %></td>
  21 + </tr>
  22 +</table>
  23 +
  24 +
  25 +<div>
  26 + <% button_bar do %>
  27 + <%= submit_button('save', c_('Save changes')) %>
  28 + <%= button :back, _('Back to plugins administration panel'), :controller => 'plugins' %>
  29 + <% end %>
  30 +</div>
  31 +
  32 +<% end %>
... ...