yl_simple_captcha 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,86 @@
1
+ require 'tempfile'
2
+ module SimpleCaptcha #:nodoc
3
+ module ImageHelpers #:nodoc
4
+
5
+ mattr_accessor :image_styles
6
+ @@image_styles = {
7
+ 'embosed_silver' => ['-fill darkblue', '-shade 20x60', '-background white'],
8
+ 'simply_red' => ['-fill darkred', '-background white'],
9
+ 'simply_green' => ['-fill darkgreen', '-background white'],
10
+ 'simply_blue' => ['-fill darkblue', '-background white'],
11
+ 'distorted_black' => ['-fill darkblue', '-edge 10', '-background white'],
12
+ 'all_black' => ['-fill darkblue', '-edge 2', '-background white'],
13
+ 'charcoal_grey' => ['-fill darkblue', '-charcoal 5', '-background white'],
14
+ 'almost_invisible' => ['-fill red', '-solarize 50', '-background white']
15
+ }
16
+
17
+ DISTORTIONS = ['low', 'medium', 'high']
18
+
19
+ IMPLODES = { 'none' => 0, 'low' => 0.1, 'medium' => 0.2, 'high' => 0.3 }
20
+ DEFAULT_IMPLODE = 'medium'
21
+
22
+ class << self
23
+
24
+ def image_params(key = 'simply_blue')
25
+ image_keys = @@image_styles.keys
26
+
27
+ style = begin
28
+ if key == 'random'
29
+ image_keys[rand(image_keys.length)]
30
+ else
31
+ image_keys.include?(key) ? key : 'simply_blue'
32
+ end
33
+ end
34
+
35
+ @@image_styles[style]
36
+ end
37
+
38
+ def distortion(key='low')
39
+ key =
40
+ key == 'random' ?
41
+ DISTORTIONS[rand(DISTORTIONS.length)] :
42
+ DISTORTIONS.include?(key) ? key : 'low'
43
+ case key.to_s
44
+ when 'low' then return [0 + rand(2), 80 + rand(20)]
45
+ when 'medium' then return [2 + rand(2), 50 + rand(20)]
46
+ when 'high' then return [4 + rand(2), 30 + rand(20)]
47
+ end
48
+ end
49
+
50
+ def implode
51
+ IMPLODES[SimpleCaptcha.implode] || IMPLODES[DEFAULT_IMPLODE]
52
+ end
53
+ end
54
+
55
+ if RUBY_VERSION < '1.9'
56
+ class Tempfile < ::Tempfile
57
+ # Replaces Tempfile's +make_tmpname+ with one that honors file extensions.
58
+ def make_tmpname(basename, n = 0)
59
+ extension = File.extname(basename)
60
+ sprintf("%s,%d,%d%s", File.basename(basename, extension), $$, n, extension)
61
+ end
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def generate_simple_captcha_image(simple_captcha_key) #:nodoc
68
+ amplitude, frequency = ImageHelpers.distortion(SimpleCaptcha.distortion)
69
+ text = Utils::simple_captcha_value(simple_captcha_key)
70
+
71
+ params = ImageHelpers.image_params(SimpleCaptcha.image_style).dup
72
+ params << "-size #{SimpleCaptcha.image_size}"
73
+ params << "-wave #{amplitude}x#{frequency}"
74
+ params << "-gravity Center"
75
+ params << "-pointsize 22"
76
+ params << "-implode #{ImageHelpers.implode}"
77
+ params << "label:#{text}"
78
+ if SimpleCaptcha.noise and SimpleCaptcha.noise > 0
79
+ params << "-evaluate Uniform-noise #{SimpleCaptcha.noise}"
80
+ end
81
+ params << "jpeg:-"
82
+
83
+ SimpleCaptcha::Utils::run("convert", params.join(' '))
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,92 @@
1
+ # encoding: utf-8
2
+ module SimpleCaptcha
3
+ class Middleware
4
+ include SimpleCaptcha::ImageHelpers
5
+ include SimpleCaptcha::ViewHelper
6
+
7
+ DEFAULT_SEND_FILE_OPTIONS = {
8
+ :type => 'application/octet-stream'.freeze,
9
+ :disposition => 'attachment'.freeze,
10
+ }.freeze
11
+
12
+ def initialize(app, options={})
13
+ @app = app
14
+ self
15
+ end
16
+
17
+ def call(env) # :nodoc:
18
+ if env["REQUEST_METHOD"] == "GET" && captcha_path?(env['PATH_INFO'])
19
+ request = Rack::Request.new(env)
20
+ if request.params.present? && request.params['code'].present?
21
+ make_image(env)
22
+ else
23
+ refresh_code(env)
24
+ end
25
+ else
26
+ @app.call(env)
27
+ end
28
+ end
29
+
30
+ protected
31
+ def make_image(env, headers = {}, status = 404)
32
+ request = Rack::Request.new(env)
33
+ code = request.params["code"]
34
+ body = []
35
+
36
+ if Utils::simple_captcha_value(code)
37
+ #status, headers, body = @app.call(env)
38
+ #status = 200
39
+ #body = generate_simple_captcha_image(code)
40
+ #headers['Content-Type'] = 'image/jpeg'
41
+
42
+ send_data(generate_simple_captcha_image(code), :type => 'image/jpeg', :disposition => 'inline', :filename => 'simple_captcha.jpg')
43
+ else
44
+ [status, headers, body]
45
+ end
46
+ end
47
+
48
+ def captcha_path?(request_path)
49
+ request_path.include?('/simple_captcha')
50
+ end
51
+
52
+ def send_file(path, options = {})
53
+ raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path)
54
+
55
+ options[:filename] ||= File.basename(path) unless options[:url_based_filename]
56
+
57
+ status = options[:status] || 200
58
+ headers = {"Content-Disposition" => "#{options[:disposition]}; filename='#{options[:filename]}'", "Content-Type" => options[:type], 'Content-Transfer-Encoding' => 'binary', 'Cache-Control' => 'private'}
59
+ response_body = File.open(path, "rb")
60
+
61
+ [status, headers, response_body]
62
+ end
63
+
64
+ def send_data(response_body, options = {})
65
+ status = options[:status] || 200
66
+ headers = {"Content-Disposition" => "#{options[:disposition]}; filename='#{options[:filename]}'", "Content-Type" => options[:type], 'Content-Transfer-Encoding' => 'binary', 'Cache-Control' => 'private'}
67
+
68
+ [status, headers, [response_body]]
69
+ end
70
+
71
+ def refresh_code(env)
72
+ request = Rack::Request.new(env)
73
+
74
+ request.session.delete :captcha
75
+ key = simple_captcha_key(nil, request)
76
+ options = {}
77
+ options[:field_value] = set_simple_captcha_data(key, options)
78
+ url = simple_captcha_image_url(key, options)
79
+
80
+ status = 200
81
+ id = request.params['id']
82
+ captcha_hidden_field_id = simple_captch_hidden_field_id(id)
83
+
84
+ body = %Q{
85
+ $("##{id}").attr('src', '#{url}');
86
+ $("##{ captcha_hidden_field_id }").attr('value', '#{key}');
87
+ }
88
+ headers = {'Content-Type' => 'text/javascript; charset=utf-8', "Content-Disposition" => "inline; filename='captcha.js'", "Content-Length" => body.length.to_s}.merge(SimpleCaptcha.extra_response_headers)
89
+ [status, headers, [body]]
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,74 @@
1
+ module SimpleCaptcha #:nodoc
2
+ module ModelHelpers #:nodoc
3
+ def self.included(base)
4
+ base.extend(SingletonMethods)
5
+ end
6
+
7
+ # To implement model based simple captcha use this method in the model as...
8
+ #
9
+ # class User < ActiveRecord::Base
10
+ #
11
+ # apply_simple_captcha :message => "my customized message"
12
+ #
13
+ # end
14
+ #
15
+ # Customize the error message by using :message, the default message is "Captcha did not match".
16
+ # As in the applications captcha is needed with a very few cases like signing up the new user, but
17
+ # not every time you need to authenticate the captcha with @user.save. So as to maintain simplicity
18
+ # here we have the explicit method to save the instace with captcha validation as...
19
+ #
20
+ # * to validate the instance
21
+ #
22
+ # @user.valid_with_captcha? # whene captcha validation is required.
23
+ #
24
+ # @user.valid? # when captcha validation is not required.
25
+ #
26
+ # * to save the instance
27
+ #
28
+ # @user.save_with_captcha # whene captcha validation is required.
29
+ #
30
+ # @user.save # when captcha validation is not required.
31
+ module SingletonMethods
32
+ def apply_simple_captcha(options = {})
33
+ options = { :add_to_base => false }.merge(options)
34
+
35
+ class_attribute :simple_captcha_options
36
+ self.simple_captcha_options = options
37
+
38
+ unless self.is_a?(ClassMethods)
39
+ include InstanceMethods
40
+ extend ClassMethods
41
+
42
+ attr_accessor :captcha, :captcha_key
43
+ end
44
+ end
45
+ end
46
+
47
+ module ClassMethods
48
+ end
49
+
50
+ module InstanceMethods
51
+
52
+ def valid_with_captcha?
53
+ [valid?, is_captcha_valid?].all?
54
+ end
55
+
56
+ def is_captcha_valid?
57
+ return true if SimpleCaptcha.always_pass
58
+
59
+ if captcha && captcha.upcase.delete(" ") == SimpleCaptcha::Utils::simple_captcha_value(captcha_key)
60
+ SimpleCaptcha::Utils::simple_captcha_passed!(captcha_key)
61
+ return true
62
+ else
63
+ message = simple_captcha_options[:message] || I18n.t(self.class.model_name.to_s.downcase, :scope => [:simple_captcha, :message], :default => :default)
64
+ simple_captcha_options[:add_to_base] ? errors.add(:base, message) : errors.add(:captcha, message)
65
+ return false
66
+ end
67
+ end
68
+
69
+ def save_with_captcha
70
+ valid_with_captcha? && save(:validate => false)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,32 @@
1
+ module SimpleCaptcha
2
+ class SimpleCaptchaData < ::ActiveRecord::Base
3
+
4
+ if ::ActiveRecord::VERSION::MAJOR >= 3
5
+ # Fixes deprecation warning in Rails 3.2:
6
+ # DEPRECATION WARNING: Calling set_table_name is deprecated. Please use `self.table_name = 'the_name'` instead.
7
+ self.table_name = SimpleCaptcha.table_name #"simple_captcha_data"
8
+ else
9
+ set_table_name SimpleCaptcha.table_name #"simple_captcha_data"
10
+ end
11
+ if ::ActiveRecord::VERSION::MAJOR == 3 and defined? attr_protected
12
+ attr_protected
13
+ end
14
+
15
+
16
+ class << self
17
+ def get_data(key)
18
+ where(key: key).first_or_initialize
19
+ end
20
+
21
+ def remove_data(key)
22
+ delete_all(["#{connection.quote_column_name(:key)} = ?", key])
23
+ clear_old_data(1.hour.ago)
24
+ end
25
+
26
+ def clear_old_data(time = 1.hour.ago)
27
+ return unless Time === time
28
+ delete_all(["#{connection.quote_column_name(:updated_at)} < ?", time])
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,25 @@
1
+ module SimpleCaptcha
2
+ class SimpleCaptchaData
3
+ include Mongoid::Document
4
+ include Mongoid::Timestamps
5
+
6
+ field :key, type: String
7
+ field :value, type: String
8
+
9
+ class << self
10
+ def get_data(key)
11
+ data = where(:key => key).first || new(:key => key)
12
+ end
13
+
14
+ def remove_data(key)
15
+ where(:key => key).delete_all
16
+ clear_old_data(1.hour.ago)
17
+ end
18
+
19
+ def clear_old_data(time = 1.hour.ago)
20
+ return unless Time === time
21
+ where(:updated_at.lte => time).delete_all
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,36 @@
1
+ require 'digest/sha1'
2
+
3
+ module SimpleCaptcha #:nodoc
4
+ module Utils #:nodoc
5
+ # Execute command with params and return output if exit status equal expected_outcodes
6
+ def self.run(cmd, params = "", expected_outcodes = 0)
7
+ command = %Q[#{cmd} #{params}].gsub(/\s+/, " ")
8
+ command = "#{command} 2>&1"
9
+
10
+ unless (image_magick_path = SimpleCaptcha.image_magick_path).blank?
11
+ command = File.join(image_magick_path, command)
12
+ end
13
+
14
+ output = `#{command}`
15
+
16
+ unless [expected_outcodes].flatten.include?($?.exitstatus)
17
+ raise ::StandardError, "Error while running #{cmd}: #{output}"
18
+ end
19
+
20
+ output
21
+ end
22
+
23
+ def self.simple_captcha_value(key) #:nodoc
24
+ SimpleCaptchaData.get_data(key).value rescue nil
25
+ end
26
+
27
+ def self.simple_captcha_passed!(key) #:nodoc
28
+ SimpleCaptchaData.remove_data(key)
29
+ end
30
+
31
+ def self.generate_key(*args)
32
+ args << (Time.now.to_f * 1_000_000_000).to_s
33
+ Digest::SHA1.hexdigest(args.join)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,3 @@
1
+ module SimpleCaptcha
2
+ VERSION = "1.1.0".freeze
3
+ end
@@ -0,0 +1,158 @@
1
+ module SimpleCaptcha #:nodoc
2
+ module ViewHelper #:nodoc
3
+
4
+ # Simple Captcha is a very simplified captcha.
5
+ #
6
+ # It can be used as a *Model* or a *Controller* based Captcha depending on what options
7
+ # we are passing to the method show_simple_captcha.
8
+ #
9
+ # *show_simple_captcha* method will return the image, the label and the text box.
10
+ # This method should be called from the view within your form as...
11
+ #
12
+ # <%= show_simple_captcha %>
13
+ #
14
+ # The available options to pass to this method are
15
+ # * label
16
+ # * object
17
+ #
18
+ # <b>Label:</b>
19
+ #
20
+ # default label is "type the text from the image", it can be modified by passing :label as
21
+ #
22
+ # <%= show_simple_captcha(:label => "new captcha label") %>.
23
+ #
24
+ # *Object*
25
+ #
26
+ # This option is needed to create a model based captcha.
27
+ # If this option is not provided, the captcha will be controller based and
28
+ # should be checked in controller's action just by calling the method simple_captcha_valid?
29
+ #
30
+ # To make a model based captcha give this option as...
31
+ #
32
+ # <%= show_simple_captcha(:object => "user") %>
33
+ # and also call the method apply_simple_captcha in the model
34
+ # this will consider "user" as the object of the model class.
35
+ #
36
+ # *Examples*
37
+ # * controller based
38
+ # <%= show_simple_captcha(:label => "Human Authentication: type the text from image above") %>
39
+ # * model based
40
+ # <%= show_simple_captcha(:object => "person", :label => "Human Authentication: type the text from image above") %>
41
+ #
42
+ # Find more detailed examples with sample images here on my blog http://EXPRESSICA.com
43
+ #
44
+ # All Feedbacks/CommentS/Issues/Queries are welcome.
45
+ def show_simple_captcha(options = {})
46
+ render :partial => SimpleCaptcha.partial_path, :locals => { :simple_captcha_options => simple_captcha_options(options) }
47
+ end
48
+
49
+
50
+ def simple_captcha_options(options = {})
51
+ key = simple_captcha_key(options[:object])
52
+ if options[:multiple] === false
53
+ # It's not the first captcha, we only need to return the key
54
+ options[:field_value] = key
55
+ else
56
+ # It's the first captcha in the page, we generate a new key
57
+ options[:field_value] = set_simple_captcha_data(key, options)
58
+ end
59
+
60
+ defaults = {
61
+ :image => simple_captcha_image(key, options),
62
+ :label => I18n.t('simple_captcha.label'),
63
+ :field => simple_captcha_field(options),
64
+ :refresh_button => simple_captcha_refresh_button(options),
65
+ }.merge(options)
66
+ end
67
+
68
+ private
69
+
70
+ def simple_captcha_image(simple_captcha_key, options = {})
71
+ url = simple_captcha_image_url simple_captcha_key, options: options
72
+ id = simple_captcha_image_id(options)
73
+ tag('img', :src => url, :alt => 'captcha', :id => id)
74
+ end
75
+
76
+ def simple_captcha_image_url(simple_captcha_key, options = {})
77
+ defaults = {}
78
+ defaults[:time] = options[:time] || Time.now.to_i
79
+
80
+ query = defaults.to_query
81
+ path = "/simple_captcha?code=#{simple_captcha_key}&#{query}"
82
+ build_url(options, path)
83
+ end
84
+
85
+ def build_url(options, path)
86
+ if defined?(request) && request
87
+ "#{request.protocol}#{request.host_with_port}#{ENV['RAILS_RELATIVE_URL_ROOT']}#{path}"
88
+ else
89
+ "#{ENV['RAILS_RELATIVE_URL_ROOT']}#{path}"
90
+ end
91
+ end
92
+
93
+ def simple_captcha_field(options={})
94
+ html = {:autocomplete => 'off', :autocorrect => 'off', :autocapitalize => 'off', :required => 'required'}
95
+ html.merge!(options[:input_html] || {})
96
+ html[:placeholder] = options[:placeholder] || I18n.t('simple_captcha.placeholder')
97
+
98
+ if options[:object]
99
+ text_field(options[:object], :captcha, html.merge(:value => '')) +
100
+ hidden_field(options[:object], :captcha_key, {:value => options[:field_value], :id => simple_captch_hidden_field_id(options)})
101
+ else
102
+ text_field_tag(:captcha, nil, html) +
103
+ hidden_field_tag(:captcha_key, options[:field_value], :id => simple_captch_hidden_field_id(options))
104
+ end
105
+ end
106
+
107
+ def simple_captcha_refresh_button(options={})
108
+ html = {remote: true}
109
+ html.merge!(options[:refresh_button_html] || {})
110
+
111
+ text = options[:refresh_button_text] || I18n.t('simple_captcha.refresh_button_text', default: 'Refresh')
112
+
113
+ url = build_url(options, "/simple_captcha?id=#{simple_captcha_image_id(options)}")
114
+ link_to(text, url, html)
115
+ end
116
+
117
+ def simple_captcha_image_id(options={})
118
+ "simple_captcha-#{options[:field_value][0..10]}"
119
+ end
120
+
121
+ def simple_captch_hidden_field_id(image_id)
122
+ image_id = simple_captcha_image_id(image_id) if image_id.is_a?(Hash)
123
+ "simple-captcha-hidden-field-#{ image_id }"
124
+ end
125
+
126
+ def set_simple_captcha_data(key, options={})
127
+ code_type = options[:code_type]
128
+
129
+ value = generate_simple_captcha_data(code_type)
130
+ data = SimpleCaptcha::SimpleCaptchaData.get_data(key)
131
+ data.value = value
132
+ data.save
133
+ key
134
+ end
135
+
136
+ def generate_simple_captcha_data(code)
137
+ value = ''
138
+
139
+ case code
140
+ when 'numeric' then
141
+ SimpleCaptcha.length.times{value << (48 + rand(10)).chr}
142
+ else
143
+ SimpleCaptcha.length.times{value << (65 + rand(26)).chr}
144
+ end
145
+
146
+ return value
147
+ end
148
+
149
+ def simple_captcha_key(key_name = nil, prequest = request)
150
+ local_session = prequest.try(:session) || session
151
+ if key_name.nil?
152
+ local_session[:captcha] ||= SimpleCaptcha::Utils.generate_key(local_session[:id].to_s, 'captcha')
153
+ else
154
+ SimpleCaptcha::Utils.generate_key(local_session[:id].to_s, key_name)
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,83 @@
1
+ # encoding: utf-8
2
+
3
+ module SimpleCaptcha
4
+ class << self
5
+ attr_accessor :always_pass
6
+ end
7
+ self.always_pass = Rails.env.test?
8
+ autoload :Utils, 'simple_captcha/utils'
9
+
10
+ autoload :ImageHelpers, 'simple_captcha/image'
11
+ autoload :ViewHelper, 'simple_captcha/view'
12
+ autoload :ControllerHelpers, 'simple_captcha/controller'
13
+
14
+ autoload :FormBuilder, 'simple_captcha/form_builder'
15
+ autoload :CustomFormBuilder, 'simple_captcha/formtastic'
16
+
17
+ autoload :ModelHelpers, 'simple_captcha/model_helpers'
18
+
19
+ if defined?(ActiveRecord)
20
+ autoload :SimpleCaptchaData, 'simple_captcha/simple_captcha_data'
21
+ else
22
+ autoload :SimpleCaptchaData, 'simple_captcha/simple_captcha_data_mongoid.rb'
23
+ end
24
+
25
+ autoload :Middleware, 'simple_captcha/middleware'
26
+
27
+ mattr_accessor :image_size
28
+ @@image_size = "100x28"
29
+
30
+ mattr_accessor :length
31
+ @@length = 5
32
+
33
+ mattr_accessor :table_name
34
+ @@table_name = "simple_captcha_data"
35
+
36
+ # 'embosed_silver',
37
+ # 'simply_red',
38
+ # 'simply_green',
39
+ # 'simply_blue',
40
+ # 'distorted_black',
41
+ # 'all_black',
42
+ # 'charcoal_grey',
43
+ # 'almost_invisible'
44
+ # 'random'
45
+ mattr_accessor :image_style
46
+ @@image_style = 'simply_blue'
47
+
48
+ # 'low', 'medium', 'high', 'random'
49
+ mattr_accessor :distortion
50
+ @@distortion = 'low'
51
+
52
+ # 'none', 'low', 'medium', 'high'
53
+ mattr_accessor :implode
54
+ @@implode = SimpleCaptcha::ImageHelpers::DEFAULT_IMPLODE
55
+
56
+ # command path
57
+ mattr_accessor :image_magick_path
58
+ @@image_magick_path = ''
59
+
60
+ # tmp directory
61
+ mattr_accessor :tmp_path
62
+ @@tmp_path = nil
63
+
64
+ # additive noise
65
+ mattr_accessor :noise
66
+ @@noise = 0
67
+
68
+ mattr_accessor :extra_response_headers
69
+ @@extra_response_headers = {}
70
+
71
+ mattr_accessor :partial_path
72
+ @@partial_path = 'simple_captcha/simple_captcha'
73
+
74
+ def self.add_image_style(name, params = [])
75
+ SimpleCaptcha::ImageHelpers.image_styles.update(name.to_s => params)
76
+ end
77
+
78
+ def self.setup
79
+ yield self
80
+ end
81
+ end
82
+
83
+ require 'simple_captcha/engine' if defined?(Rails)
@@ -0,0 +1 @@
1
+ require 'simple_captcha'