diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..6b9ce46 --- /dev/null +++ b/Gemfile @@ -0,0 +1 @@ +gem 'webmock' diff --git a/README.md b/README.md new file mode 100644 index 0000000..308fb79 --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +Sample config values: + +verify_uri 'http://captcha.servicoscorporativos.serpro.gov.br/captchavalidar/1.0.0/validar' +serpro_client_id 'fdbcdc7a0b754ee7ae9d865fda740f17' + +See http://stdcs.supst.serpro/manual/html/#captcha/page/introducao.html for more details diff --git a/controllers/serpro_captcha_plugin_admin_controller.rb b/controllers/serpro_captcha_plugin_admin_controller.rb new file mode 100644 index 0000000..a8dddab --- /dev/null +++ b/controllers/serpro_captcha_plugin_admin_controller.rb @@ -0,0 +1,17 @@ +class SerproCaptchaPluginAdminController < PluginAdminController + + append_view_path File.join(File.dirname(__FILE__) + '/../views') + + def index + end + + def update + if @environment.update_attributes(params[:environment]) + session[:notice] = _('Captcha configuration updated successfully.') + else + session[:notice] = _('Captcha configuration could not be saved.') + end + render :action => 'index' + end + +end diff --git a/lib/ext/environment.rb b/lib/ext/environment.rb new file mode 100644 index 0000000..6b1f346 --- /dev/null +++ b/lib/ext/environment.rb @@ -0,0 +1,35 @@ +require_dependency 'environment' + +class Environment + + #Captcha settings + settings_items :serpro_captcha_plugin, :type => ActiveSupport::HashWithIndifferentAccess, :default => {} + +# settings_items :verify_uri, :type => :string, :default => 'http://captcha.servicoscorporativos.serpro.gov.br/captchavalidar/1.0.0/validar' +# settings_items :serpro_client_id, :type => :string, :default => 'fdbcdc7a0b754ee7ae9d865fda740f17' + + attr_accessible :serpro_captcha_plugin_attributes, :serpro_captcha_verify_uri, :serpro_captcha_client_id + + def serpro_captcha_plugin_attributes + self.serpro_captcha_plugin || {} + end + + def serpro_captcha_verify_uri= verify_uri + self.serpro_captcha_plugin = {} if self.serpro_captcha_plugin.blank? + self.serpro_captcha_plugin['serpro_captcha_verify_uri'] = verify_uri + end + + def serpro_captcha_verify_uri + self.serpro_captcha_plugin['serpro_captcha_verify_uri'] + end + + def serpro_captcha_client_id= client_id + self.serpro_captcha_plugin = {} if self.serpro_captcha_plugin.blank? + self.serpro_captcha_plugin['serpro_captcha_client_id'] = client_id + end + + def serpro_captcha_client_id + self.serpro_captcha_plugin['serpro_captcha_client_id'] + end + +end diff --git a/lib/serpro_captcha_plugin.rb b/lib/serpro_captcha_plugin.rb new file mode 100644 index 0000000..6b7fd7b --- /dev/null +++ b/lib/serpro_captcha_plugin.rb @@ -0,0 +1,20 @@ +class SerproCaptchaPlugin < Noosfero::Plugin + + def self.plugin_name + _('Serpro\'s captcha plugin') + end + + def self.plugin_description + _("Provides a plugin to Serpro's captcha infrastructure.") + end + + def self.api_mount_points + [SerproCaptchaPlugin::API ] + end + + def test_captcha(remote_ip, params, environment) + scv = SerproCaptchaVerification.new + return scv.verify_serpro_captcha(environment.serpro_captcha_client_id, params[:txtToken_captcha_serpro_gov_br], params[:captcha_text], environment.serpro_captcha_verify_uri) + end + +end diff --git a/lib/serpro_captcha_verification.rb b/lib/serpro_captcha_verification.rb new file mode 100644 index 0000000..1af51f5 --- /dev/null +++ b/lib/serpro_captcha_verification.rb @@ -0,0 +1,31 @@ +class SerproCaptchaVerification + + # return true or a hash with the error + # :user_message, :status, :log_message, :javascript_console_message + def verify_serpro_captcha(client_id, token, captcha_text, verify_uri) + msg_icve = _('Internal captcha validation error') + msg_esca = 'Environment serpro_captcha_plugin_attributes' + return hash_error(msg_icve, 500, nil, "#{msg_esca} verify_uri not defined") if verify_uri.nil? + return hash_error(msg_icve, 500, nil, "#{msg_esca} client_id not defined") if client_id.nil? + return hash_error(_("Error processing token validation"), 500, nil, _("Missing Serpro's Captcha token")) unless token + return hash_error(_('Captcha text has not been filled'), 403) unless captcha_text + uri = URI(verify_uri) + http = Net::HTTP.new(uri.host, uri.port) + request = Net::HTTP::Post.new(uri.path) + verify_string = "#{client_id}{token}{captcha_text}" + request.body = verify_string + body = http.request(request).body + return true if body == '1' + return hash_error(_("Internal captcha validation error"), 500, body, "Unable to reach Serpro's Captcha validation service") if body == "Activity timed out" + return hash_error(_("Wrong captcha text, please try again"), 403) if body == '0' + return hash_error(_("Serpro's captcha token not found"), 500) if body == '2' + return hash_error(_("No data sent to validation server or other serious problem"), 500) if body == -1 + #Catches all errors at the end + return hash_error(_("Internal captcha validation error"), 500, nil, "Error validating Serpro's captcha service returned: #{body}") + end + + def hash_error(user_message, status, log_message=nil, javascript_console_message=nil) + {user_message: user_message, status: status, log_message: log_message, javascript_console_message: javascript_console_message} + end + +end diff --git a/test/functional/account_controller_plugin_test.rb b/test/functional/account_controller_plugin_test.rb new file mode 100644 index 0000000..4b0e63a --- /dev/null +++ b/test/functional/account_controller_plugin_test.rb @@ -0,0 +1,18 @@ +require File.dirname(__FILE__) + '/../test_helper' + +# Re-raise errors caught by the controller. +class AccountController; def rescue_action(e) raise e end; end + +class AccountControllerPluginTest < ActionController::TestCase + + def setup + @controller = AccountController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + + @environment = Environment.default + @environment.enabled_plugins = ['SerproCaptchaPlugin'] + @environment.save! + end + +end diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 0000000..c4539b5 --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,65 @@ +require "#{Rails.root}/lib/noosfero/api/helpers" + +class ActiveSupport::TestCase + + include Rack::Test::Methods + + def app + Noosfero::API::API + end + + def pass_captcha(mocked_url, captcha_verification_body) + stub_request(:post, mocked_url). + with(:body => captcha_verification_body, + :headers => {'Accept'=>'*/*', 'User-Agent'=>'Ruby'}). + to_return(:status => 200, :body => "1", :headers => {'Content-Length' => 1}) + end + + def fail_captcha_text(mocked_url, captcha_verification_body) + stub_request(:post, mocked_url). + with(:body => captcha_verification_body, + :headers => {'Accept'=>'*/*', 'User-Agent'=>'Ruby'}). + to_return(:status => 200, :body => "0", :headers => {'Content-Length' => 1}) + end + + def login_with_captcha + json = do_login_captcha_from_api + @private_token = json["private_token"] + @params = { "private_token" => @private_token} + json + end + + ## Performs a login using the session.rb but mocking the + ## real HTTP request to validate the captcha. + def do_login_captcha_from_api + post "/api/v1/login-captcha" + json = JSON.parse(last_response.body) + json + end + + def login_api + @environment = Environment.default + @user = User.create!(:login => 'testapi', :password => 'testapi', :password_confirmation => 'testapi', :email => 'test@test.org', :environment => @environment) + @user.activate + @person = @user.person + + post "/api/v1/login?login=testapi&password=testapi" + json = JSON.parse(last_response.body) + @private_token = json["private_token"] + unless @private_token + @user.generate_private_token! + @private_token = @user.private_token + end + + @params = {:private_token => @private_token} + end + attr_accessor :private_token, :user, :person, :params, :environment + + private + + def json_response_ids(kind) + json = JSON.parse(last_response.body) + json[kind.to_s].map {|c| c['id']} + end + +end diff --git a/test/unit/serpro_captcha_verification_test.rb b/test/unit/serpro_captcha_verification_test.rb new file mode 100644 index 0000000..efe4d4c --- /dev/null +++ b/test/unit/serpro_captcha_verification_test.rb @@ -0,0 +1,109 @@ +require 'webmock' +include WebMock::API +require File.dirname(__FILE__) + '/../../../../test/test_helper' +require_relative '../test_helper' + +class SerproCaptchaVerificationTest < ActiveSupport::TestCase + + def setup + @environment = Environment.default + @environment.enabled_plugins = ['SerproCaptchaPlugin'] + @environment.serpro_captcha_verify_uri="http://www.somecompany.com:443/validate" + @environment.serpro_captcha_client_id='323232' + @environment.save! + @captcha_token = "642646" + @captcha_text = "44641441" + @captcha_verification_body = "#{@environment.serpro_captcha_client_id}{@captcha_token}{@captcha_text}" + end + + def login_with_captcha + store = Noosfero::API::SessionStore.create("captcha") + ## Initialize the data for the session store + store.data = [] + ## Put it back in cache + store.store + { "private_token" => "#{store.private_token}" } + end + + def create_article(name) + person = fast_create(Person, :environment_id => @environment.id) + fast_create(Article, :profile_id => person.id, :name => name) + end + + should 'register a user when there are no enabled captcha pluging' do + @environment.enabled_plugins = [] + @environment.save! + Environment.default.enable('skip_new_user_email_confirmation') + params = {:login => "newuserapi", :password => "newuserapi", :password_confirmation => "newuserapi", :email => "newuserapi@email.com" } + post "/api/v1/register?#{params.to_query}" + assert_equal 201, last_response.status + json = JSON.parse(last_response.body) + assert User['newuserapi'].activated? + assert json['user']['private_token'].present? + end + + should 'not register a user if captcha fails' do + fail_captcha_text @environment.serpro_captcha_verify_uri, @captcha_verification_body + Environment.default.enable('skip_new_user_email_confirmation') + params = {:login => "newuserapi", :password => "newuserapi", :password_confirmation => "newuserapi", :email => "newuserapi@email.com", :txtToken_captcha_serpro_gov_br => @captcha_token, :captcha_text => @captcha_text} + post "/api/v1/register?#{params.to_query}" + assert_equal 403, last_response.status + json = JSON.parse(last_response.body) + assert_equal json["message"], _("Wrong captcha text, please try again") + end + + should 'verify_serpro_captcha' do + pass_captcha @environment.serpro_captcha_verify_uri, @captcha_verification_body + scv = SerproCaptchaVerification.new + assert scv.verify_serpro_captcha(@environment.serpro_captcha_client_id, @captcha_token, @captcha_text, @environment.serpro_captcha_verify_uri) + end + + should 'fail captcha if user has not filled Serpro\' captcha text' do + pass_captcha @environment.serpro_captcha_verify_uri, @captcha_verification_body + scv = SerproCaptchaVerification.new + hash = scv.verify_serpro_captcha(@environment.serpro_captcha_client_id, @captcha_token, nil, @environment.serpro_captcha_verify_uri) + assert hash[:user_message], _('Captcha text has not been filled') + end + + should 'fail captcha if Serpro\' captcha token has not been sent' do + pass_captcha @environment.serpro_captcha_verify_uri, @captcha_verification_body + scv = SerproCaptchaVerification.new + hash = scv.verify_serpro_captcha(@environment.serpro_captcha_client_id, nil, @captcha_text, @environment.serpro_captcha_verify_uri) + assert hash[:javascript_console_message], _("Missing Serpro's Captcha token") + end + + should 'fail captcha text' do + fail_captcha_text @environment.serpro_captcha_verify_uri, @captcha_verification_body + scv = SerproCaptchaVerification.new + hash = scv.verify_serpro_captcha(@environment.serpro_captcha_client_id, nil, @captcha_text, @environment.serpro_captcha_verify_uri) + assert hash[:javascript_console_message], _("Wrong captcha text, please try again") + end + + should 'not perform a vote without authentication' do + article = create_article('Article 1') + params = {} + params[:value] = 1 + + post "/api/v1/articles/#{article.id}/vote?#{params.to_query}" + json = JSON.parse(last_response.body) + assert_equal 401, last_response.status + end + + should 'perform a vote on an article identified by id' do + pass_captcha @environment.serpro_captcha_verify_uri, @captcha_verification_body + params = {} + params[:txtToken_captcha_serpro_gov_br]= @captcha_token + params[:captcha_text]= @captcha_text + post "/api/v1/login-captcha?#{params.to_query}" + json = JSON.parse(last_response.body) + article = create_article('Article 1') + params = {} + params[:private_token] = json['private_token'] + params[:value] = 1 + post "/api/v1/articles/#{article.id}/vote?#{params.to_query}" + json = JSON.parse(last_response.body) + assert_not_equal 401, last_response.status + assert_equal true, json['vote'] + end + +end diff --git a/views/serpro_captcha_plugin_admin/index.html.erb b/views/serpro_captcha_plugin_admin/index.html.erb new file mode 100644 index 0000000..07f01fd --- /dev/null +++ b/views/serpro_captcha_plugin_admin/index.html.erb @@ -0,0 +1,28 @@ +
| <%= c_('Configuration') %> | +<%= _('Value') %> | +
|---|---|
| <%= _('Verify URI') %> | +<%= text_field :environment, :serpro_captcha_verify_uri %> | +
| <%= _('Client Id') %> | +<%= text_field :environment, :serpro_captcha_client_id %> | +