Permalink
Fetching contributors…
Cannot retrieve contributors at this time
146 lines (119 sloc) 4.67 KB
require 'yaml'
require 'net/http'
require 'digest/md5'
require 'acts_as_textcaptcha/textcaptcha_cache'
require 'acts_as_textcaptcha/textcaptcha_api'
module ActsAsTextcaptcha
module Textcaptcha
def acts_as_textcaptcha(options = nil)
cattr_accessor :textcaptcha_config
attr_accessor :textcaptcha_question, :textcaptcha_answer, :textcaptcha_key
# ensure these attrs are accessible (Rails 3)
if respond_to?(:accessible_attributes) && respond_to?(:attr_accessible)
attr_accessible :textcaptcha_answer, :textcaptcha_key
end
self.textcaptcha_config = build_textcaptcha_config(options).symbolize_keys!
validate :validate_textcaptcha, if: :perform_textcaptcha?
include InstanceMethods
end
module InstanceMethods
# override this method to toggle textcaptcha checking, by default this
# will only allow new records to be protected with textcaptchas
def perform_textcaptcha?
(!respond_to?('new_record?') || new_record?)
end
def textcaptcha
if perform_textcaptcha? && textcaptcha_config
assign_textcaptcha(fetch_q_and_a || config_q_and_a)
end
end
private
def fetch_q_and_a
return unless should_fetch?
TextcaptchaApi.new(
api_key: textcaptcha_config[:api_key],
api_endpoint: textcaptcha_config[:api_endpoint],
raise_errors: textcaptcha_config[:raise_errors]
).fetch
end
def should_fetch?
textcaptcha_config[:api_key] || textcaptcha_config[:api_endpoint]
end
def config_q_and_a
if textcaptcha_config[:questions]
random_question = textcaptcha_config[:questions][rand(textcaptcha_config[:questions].size)].symbolize_keys!
answers = (random_question[:answers] || '').split(',').map!{ |answer| safe_md5(answer) }
if random_question && answers.present?
{ 'q' => random_question[:question], 'a' => answers }
end
end
end
# check textcaptcha, if incorrect, generate a new textcaptcha
def validate_textcaptcha
valid_answers = textcaptcha_cache.read(textcaptcha_key) || []
reset_textcaptcha
if valid_answers.include?(safe_md5(textcaptcha_answer))
# answer was valid, mutate the key again
self.textcaptcha_key = textcaptcha_random_key
textcaptcha_cache.write(textcaptcha_key, valid_answers, textcaptcha_cache_options)
true
else
add_textcaptcha_error(too_slow: valid_answers.empty?)
textcaptcha
false
end
end
def add_textcaptcha_error(too_slow: false)
if too_slow
errors.add(:textcaptcha_answer, :expired, :message => 'was not submitted quickly enough, try another question instead')
else
errors.add(:textcaptcha_answer, :incorrect, :message => 'is incorrect, try another question instead')
end
end
def reset_textcaptcha
if textcaptcha_key
textcaptcha_cache.delete(textcaptcha_key)
self.textcaptcha_key = nil
end
end
def assign_textcaptcha(q_and_a)
return unless q_and_a
self.textcaptcha_question = q_and_a['q']
self.textcaptcha_key = textcaptcha_random_key
textcaptcha_cache.write(textcaptcha_key, q_and_a['a'], textcaptcha_cache_options)
end
# strip whitespace pass through mb_chars (a multibyte safe proxy for
# strings) then downcase
def safe_md5(answer)
Digest::MD5.hexdigest(answer.to_s.strip.mb_chars.downcase)
end
# a random cache key, time based, random
def textcaptcha_random_key
safe_md5(Time.now.to_i + rand(1_000_000))
end
def textcaptcha_cache_options
if textcaptcha_config[:cache_expiry_minutes]
{ :expires_in => textcaptcha_config[:cache_expiry_minutes].to_f.minutes }
else
{}
end
end
def textcaptcha_cache
@@textcaptcha_cache ||= TextcaptchaCache.new
end
end
private
def build_textcaptcha_config(options)
if options.is_a?(Hash)
options
else
YAML.load(ERB.new(read_textcaptcha_config).result)[Rails.env]
end
rescue
raise ArgumentError.new('could not find any textcaptcha options, in config/textcaptcha.yml or model - run rake textcaptcha:config to generate a template config file')
end
def read_textcaptcha_config
File.read("#{Rails.root ? Rails.root : '.'}/config/textcaptcha.yml")
end
end
end