Commit e9a853378d9311740f61ef2037486d0135cfaf3c

Authored by Leandro Santos
2 parents f709f891 7684f638

Merge branch 'login-captcha' into staging

lib/noosfero/api/captcha_session_store.rb 0 → 100644
... ... @@ -0,0 +1,30 @@
  1 +class Noosfero::API::CaptchaSessionStore
  2 +
  3 + attr_accessor :data
  4 + attr_reader :private_token
  5 +
  6 + def self.create
  7 + key = SecureRandom.hex
  8 + store = Noosfero::API::CaptchaSessionStore.new(key)
  9 + Rails.cache.write(store.private_token, store, expires_in: 300)
  10 + return store
  11 + end
  12 +
  13 + def initialize(key)
  14 + @private_token = key
  15 + end
  16 +
  17 + def self.get(key)
  18 + Rails.cache.fetch(key)
  19 + end
  20 +
  21 + def store
  22 + Rails.cache.write(@private_token, self)
  23 + end
  24 +
  25 + def destroy
  26 + Rails.cache.delete(@private_token)
  27 + end
  28 +
  29 +
  30 +end
... ...
lib/noosfero/api/helpers.rb
... ... @@ -21,6 +21,16 @@ require 'grape'
21 21 plugins
22 22 end
23 23  
  24 + def current_tmp_user
  25 + private_token = (params[PRIVATE_TOKEN_PARAM] || headers['Private-Token']).to_s
  26 + @current_tmp_user = Noosfero::API::CaptchaSessionStore.get(private_token)
  27 + @current_tmp_user
  28 + end
  29 +
  30 + def logout_tmp_user
  31 + @current_tmp_user = nil
  32 + end
  33 +
24 34 def current_user
25 35 private_token = (params[PRIVATE_TOKEN_PARAM] || headers['Private-Token']).to_s
26 36 @current_user ||= User.find_by_private_token(private_token)
... ... @@ -257,6 +267,13 @@ require 'grape'
257 267 unauthorized! unless current_user
258 268 end
259 269  
  270 + # Allows the anonymous captcha user authentication
  271 + # to pass the check. Used by the articles/vote to allow
  272 + # the vote without login
  273 + def authenticate_allow_captcha!
  274 + unauthorized! unless current_tmp_user || current_user
  275 + end
  276 +
260 277 # Checks the occurrences of uniqueness of attributes, each attribute must be present in the params hash
261 278 # or a Bad Request error is invoked.
262 279 #
... ... @@ -331,6 +348,8 @@ require 'grape'
331 348  
332 349 def set_session_cookie
333 350 cookies['_noosfero_api_session'] = { value: @current_user.private_token, httponly: true } if @current_user.present?
  351 + # Set also the private_token for the current_tmp_user
  352 + cookies['_noosfero_api_session'] = { value: @current_tmp_user.private_token, httponly: true } if @current_tmp_user.present?
334 353 end
335 354  
336 355 def setup_multitenancy
... ...
lib/noosfero/api/session.rb
... ... @@ -4,6 +4,22 @@ module Noosfero
4 4 module API
5 5 class Session < Grape::API
6 6  
  7 + ################################
  8 + # => Login with captcha only
  9 + # This method will attempt to login the user using only the captcha.
  10 + # To do this, we generate a temporary in-memory user and generate a private
  11 + # token to it.
  12 + ################################
  13 + post "/login-captcha" do
  14 + remote_ip = (request.respond_to?(:remote_ip) && request.remote_ip) || (env && env['REMOTE_ADDR'])
  15 + # test_captcha will render_api_error! and exit in case of any problem
  16 + # this return is just to improve the clarity of the execution path
  17 + return unless test_captcha(remote_ip, params, environment)
  18 + ## Creates and caches a captcha session store
  19 + store = Noosfero::API::CaptchaSessionStore.create
  20 + { "private_token" => "#{store.private_token}" }
  21 + end
  22 +
7 23 # Login to get token
8 24 #
9 25 # Parameters:
... ...
lib/noosfero/api/v1/articles.rb
... ... @@ -133,7 +133,8 @@ module Noosfero
133 133 named 'ArticleVote'
134 134 end
135 135 post ':id/vote' do
136   - authenticate!
  136 + ## The vote api should allow regular login or with captcha
  137 + authenticate_allow_captcha!
137 138 value = (params[:value] || 1).to_i
138 139 # FIXME verify allowed values
139 140 render_api_error!('Vote value not allowed', 400) unless [-1, 1].include?(value)
... ...
plugins/serpro_captcha/lib/serpro_captcha_plugin.rb 0 → 100644
... ... @@ -0,0 +1,15 @@
  1 +class SerproCaptchaPlugin < Noosfero::Plugin
  2 +
  3 + def self.plugin_name
  4 + _('Serpro captcha plugin')
  5 + end
  6 +
  7 + def self.plugin_description
  8 + _("Provide a plugin to Serpro's captcha infrastructure.")
  9 + end
  10 +
  11 + def self.api_mount_points
  12 + [SerproCaptchaPlugin::API ]
  13 + end
  14 +
  15 +end
... ...
plugins/serpro_captcha/lib/serpro_captcha_plugin/api.rb 0 → 100644
... ... @@ -0,0 +1,16 @@
  1 +class SerproCaptchaPlugin::API < Grape::API
  2 +
  3 + # resource :dialoga_plugin do
  4 + # get 'random_topics/:discussion_id' do
  5 + # discussion = ProposalsDiscussionPlugin::Discussion.find(params[:discussion_id])
  6 + #
  7 + # # render articles using Entity Article
  8 + # present discussion.random_topics_one_by_category, :with => Noosfero::API::Entities::Article, :fields => params[:fields]
  9 + # end
  10 +
  11 + get 'test_captcha' do
  12 + present 'chegou no test_captcha do SerproCaptchaPlugin'
  13 + end
  14 + # end
  15 +
  16 +end
... ...
plugins/serpro_captcha/test/test_helper.rb 0 → 100644
... ... @@ -0,0 +1 @@
  1 +require File.dirname(__FILE__) + '/../../../test/test_helper'
... ...
plugins/serpro_captcha/test/unit/api_test.rb 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +require_relative '../test_helper'
  2 +require_relative '../../../../test/unit/api/test_helper'
  3 +
  4 +class APITest < ActiveSupport::TestCase
  5 +
  6 + def setup
  7 +# login_api
  8 + end
  9 +
  10 + should 'return something' do
  11 + end
  12 +
  13 +end
... ...
test/unit/api/login_captcha_test.rb 0 → 100644
... ... @@ -0,0 +1,73 @@
  1 +require File.dirname(__FILE__) + '/test_helper'
  2 +
  3 +class LoginCaptchaTest < ActiveSupport::TestCase
  4 +
  5 + def setup()
  6 + @environment = Environment.default
  7 + @environment.api_captcha_settings = {
  8 + enabled: true,
  9 + provider: 'serpro',
  10 + serpro_client_id: '0000000000000000',
  11 + verify_uri: 'http://captcha.serpro.gov.br/validate',
  12 + }
  13 + @environment.save!
  14 + @url = "/api/v1/login-captcha?"
  15 + end
  16 +
  17 + def create_article(name)
  18 + person = fast_create(Person, :environment_id => @environment.id)
  19 + fast_create(Article, :profile_id => person.id, :name => name)
  20 + end
  21 +
  22 + should 'not perform a vote without authentication' do
  23 + article = create_article('Article 1')
  24 + params = {}
  25 + params[:value] = 1
  26 +
  27 + post "/api/v1/articles/#{article.id}/vote?#{params.to_query}"
  28 + json = JSON.parse(last_response.body)
  29 + assert_equal 401, last_response.status
  30 + end
  31 +
  32 + should 'perform login from helpers' do
  33 + login_with_captcha
  34 + assert_not_nil @private_token
  35 + end
  36 +
  37 +
  38 + should 'perform a vote in an article identified by id' do
  39 + login_with_captcha
  40 + article = create_article('Article 1')
  41 + params[:value] = 1
  42 +
  43 + post "/api/v1/articles/#{article.id}/vote?#{params.to_query}"
  44 + json = JSON.parse(last_response.body)
  45 +
  46 + assert_not_equal 401, last_response.status
  47 + assert_equal true, json['vote']
  48 + end
  49 +
  50 + should 'not follow any article' do
  51 + login_with_captcha
  52 + article = create_article('Article 1')
  53 + post "/api/v1/articles/#{article.id}/follow?#{params.to_query}"
  54 + json = JSON.parse(last_response.body)
  55 +
  56 + assert_equal 401, last_response.status
  57 + end
  58 +
  59 + should 'not generate private token when login without captcha' do
  60 + params = {}
  61 + post "#{@url}#{params.to_query}"
  62 + json = JSON.parse(last_response.body)
  63 + assert json["private_token"].blank?
  64 + end
  65 +
  66 + should 'generate private token when login with captcha' do
  67 + json = login_with_captcha
  68 + ret = json["private_token"]
  69 + assert !ret.blank?
  70 + assert ret == @private_token
  71 + end
  72 +
  73 +end
0 74 \ No newline at end of file
... ...
test/unit/api/test_helper.rb
... ... @@ -8,6 +8,38 @@ class ActiveSupport::TestCase
8 8 Noosfero::API::API
9 9 end
10 10  
  11 + def login_with_captcha
  12 + json = do_login_captcha_from_api
  13 + @private_token = json["private_token"]
  14 + @params = { "private_token" => @private_token}
  15 + json
  16 + end
  17 +
  18 + ## Performs a login using the session.rb but mocking the
  19 + ## real HTTP request to validate the captcha.
  20 + def do_login_captcha_from_api
  21 + # Request mocking
  22 + #Net::HTTP::Post Mock
  23 + request = mock
  24 + #Net::HTTP Mock
  25 + http = mock
  26 + uri = URI(environment.api_captcha_settings[:verify_uri])
  27 + Net::HTTP.expects(:new).with(uri.host, uri.port).returns(http)
  28 + Net::HTTP::Post.expects(:new).with(uri.path).returns(request)
  29 +
  30 + # Captcha required codes
  31 + request.stubs(:body=).with("0000000000000000&4324343&4030320")
  32 + http.stubs(:request).with(request).returns(http)
  33 +
  34 + # Captcha validation success !!
  35 + http.stubs(:body).returns("1")
  36 +
  37 + params = {:txtToken_captcha_serpro_gov_br => '4324343', :captcha_text => '4030320'}
  38 + post "#{@url}#{params.to_query}"
  39 + json = JSON.parse(last_response.body)
  40 + json
  41 + end
  42 +
11 43 def login_api
12 44 @environment = Environment.default
13 45 @user = User.create!(:login => 'testapi', :password => 'testapi', :password_confirmation => 'testapi', :email => 'test@test.org', :environment => @environment)
... ...