kandr-easy_captcha 0.9.1 → 0.9.2
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.
- checksums.yaml +4 -4
- data/lib/easy_captcha.rb +106 -0
- data/lib/easy_captcha/captcha.rb +27 -0
- data/lib/easy_captcha/captcha_controller.rb +26 -0
- data/lib/easy_captcha/controller_helpers.rb +159 -0
- data/lib/easy_captcha/espeak.rb +91 -0
- data/lib/easy_captcha/generator.rb +9 -0
- data/lib/easy_captcha/generator/base.rb +17 -0
- data/lib/easy_captcha/generator/default.rb +199 -0
- data/lib/easy_captcha/model_helpers.rb +32 -0
- data/lib/easy_captcha/routes.rb +12 -0
- data/lib/easy_captcha/version.rb +5 -0
- data/lib/easy_captcha/view_helpers.rb +13 -0
- data/lib/generators/easy_captcha/install_generator.rb +25 -0
- data/lib/generators/templates/easy_captcha.rb +77 -0
- data/resources/captcha.ttf +0 -0
- metadata +17 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6a9098d3c82557be66d9f3769ef7a815d1bea207b9b0de7931cde92c5289766e
|
4
|
+
data.tar.gz: 4df8b4194f3e77b338e1193f8f0973da28cdcf52c332beee4dfbca1338a21bbe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1f103d10c86ebd21a5e7c2e364d63c3a58d84276e225b03d499498f929d742ca304ea6b53af277de01aa8ffb56b1af7d2ceb8defbec730b32cd3b11d88add2ae
|
7
|
+
data.tar.gz: 9050a9ba91f71fc3a4990b84ba013604539add8a3ad291e5a9ef73a8e21d08d4e68f6b9c289e75bb531ddc56037fa60338c743019f1e57f3cb77eec46a150c62
|
data/lib/easy_captcha.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails'
|
4
|
+
require 'action_controller'
|
5
|
+
require 'active_record'
|
6
|
+
require 'active_support'
|
7
|
+
|
8
|
+
# Captcha-Plugin for Rails
|
9
|
+
module EasyCaptcha
|
10
|
+
autoload :Espeak, 'easy_captcha/espeak'
|
11
|
+
autoload :Captcha, 'easy_captcha/captcha'
|
12
|
+
autoload :CaptchaController, 'easy_captcha/captcha_controller'
|
13
|
+
autoload :ModelHelpers, 'easy_captcha/model_helpers'
|
14
|
+
autoload :ViewHelpers, 'easy_captcha/view_helpers'
|
15
|
+
autoload :ControllerHelpers, 'easy_captcha/controller_helpers'
|
16
|
+
autoload :Generator, 'easy_captcha/generator'
|
17
|
+
|
18
|
+
DEFAULT_CONFIG = {
|
19
|
+
cache: false,
|
20
|
+
cache_expire: nil,
|
21
|
+
cache_temp_dir: nil,
|
22
|
+
cache_size: 500,
|
23
|
+
captcha_character_pool: %w[2 3 4 5 6 7 9 A C D E F G H J K L M N P Q R S T U X Y Z],
|
24
|
+
captcha_character_count: 6,
|
25
|
+
captcha_image_height: 40,
|
26
|
+
captcha_image_width: 140
|
27
|
+
}.freeze
|
28
|
+
|
29
|
+
# Cache
|
30
|
+
mattr_accessor :cache
|
31
|
+
|
32
|
+
# Cache temp
|
33
|
+
mattr_accessor :cache_temp_dir
|
34
|
+
|
35
|
+
# Cache size
|
36
|
+
mattr_accessor :cache_size
|
37
|
+
|
38
|
+
# Cache expire
|
39
|
+
mattr_accessor :cache_expire
|
40
|
+
|
41
|
+
# Chars
|
42
|
+
mattr_accessor :captcha_character_pool
|
43
|
+
|
44
|
+
# Length
|
45
|
+
mattr_accessor :captcha_character_count
|
46
|
+
|
47
|
+
# Image dimensions
|
48
|
+
mattr_accessor :captcha_image_height, :captcha_image_width
|
49
|
+
|
50
|
+
class << self
|
51
|
+
# to configure easy_captcha
|
52
|
+
# for a sample look the readme.rdoc file
|
53
|
+
def setup
|
54
|
+
DEFAULT_CONFIG.map do |k, v|
|
55
|
+
send("#{k}=", v) if respond_to? "#{k}=".to_sym
|
56
|
+
end
|
57
|
+
yield self if block_given?
|
58
|
+
end
|
59
|
+
|
60
|
+
def cache? #:nodoc:
|
61
|
+
cache
|
62
|
+
end
|
63
|
+
|
64
|
+
# select generator and configure this
|
65
|
+
def generator(generator = nil, &block)
|
66
|
+
resolve_generator(generator, &block) unless generator.nil?
|
67
|
+
@generator
|
68
|
+
end
|
69
|
+
|
70
|
+
def espeak=(state)
|
71
|
+
@espeak = state.is_a?(TrueClass) ? Espeak.new : false
|
72
|
+
end
|
73
|
+
|
74
|
+
def espeak(&block)
|
75
|
+
@espeak = Espeak.new(&block) if block_given?
|
76
|
+
@espeak ||= false
|
77
|
+
end
|
78
|
+
|
79
|
+
def espeak?
|
80
|
+
!espeak.is_a?(FalseClass)
|
81
|
+
end
|
82
|
+
|
83
|
+
def init
|
84
|
+
require 'easy_captcha/routes'
|
85
|
+
ActiveRecord::Base.include ModelHelpers
|
86
|
+
ActionController::Base.include ControllerHelpers
|
87
|
+
ActionView::Base.include ViewHelpers
|
88
|
+
|
89
|
+
# set default generator
|
90
|
+
generator :default
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def resolve_generator(generator, &block)
|
96
|
+
generator = generator.to_s if generator.is_a? Symbol
|
97
|
+
if generator.is_a? String
|
98
|
+
generator.gsub!(/^[a-z]|\s+[a-z]/, &:upcase)
|
99
|
+
generator = "EasyCaptcha::Generator::#{generator}".constantize
|
100
|
+
end
|
101
|
+
@generator = generator.new(&block)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
EasyCaptcha.init
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EasyCaptcha
|
4
|
+
# captcha generation class
|
5
|
+
class Captcha
|
6
|
+
# code for captcha generation
|
7
|
+
attr_reader :code
|
8
|
+
# blob of generated captcha image
|
9
|
+
attr_reader :image
|
10
|
+
|
11
|
+
# generate captcha by code
|
12
|
+
def initialize(code)
|
13
|
+
@code = code
|
14
|
+
generate_captcha
|
15
|
+
end
|
16
|
+
|
17
|
+
def inspect #:nodoc:
|
18
|
+
"<EasyCaptcha::Captcha @code=#{code}>"
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def generate_captcha #:nodoc:
|
24
|
+
@image = EasyCaptcha.generator.generate(@code)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EasyCaptcha
|
4
|
+
# captcha controller
|
5
|
+
class CaptchaController < ActionController::Base
|
6
|
+
before_action :overwrite_cache_control
|
7
|
+
|
8
|
+
# send the generated image to browser
|
9
|
+
def captcha
|
10
|
+
if (params[:format] == 'wav') && EasyCaptcha.espeak?
|
11
|
+
send_data(generate_speech_captcha, disposition: 'inline', type: 'audio/wav')
|
12
|
+
else
|
13
|
+
send_data(generate_captcha, disposition: 'inline', type: 'image/png')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
# Overwrite cache control for Samsung Galaxy S3 (remove no-store)
|
20
|
+
def overwrite_cache_control
|
21
|
+
response.headers['Cache-Control'] = 'no-cache, max-age=0, must-revalidate'
|
22
|
+
response.headers['Pragma'] = 'no-cache'
|
23
|
+
response.headers['Expires'] = 'Fri, 01 Jan 1990 00:00:00 GMT'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EasyCaptcha
|
4
|
+
# rubocop:disable Metrics/ModuleLength
|
5
|
+
# helper class for ActionController
|
6
|
+
module ControllerHelpers
|
7
|
+
def self.included(base) #:nodoc:
|
8
|
+
base.class_eval do
|
9
|
+
helper_method :valid_captcha?, :captcha_valid?
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# generate captcha image and return it as blob
|
14
|
+
def generate_captcha
|
15
|
+
Rails.logger.info("#{Time.now}: generate_captcha in EasyCaptcha. params: #{params}.")
|
16
|
+
# create image
|
17
|
+
image = generate_captcha_image
|
18
|
+
# cache image
|
19
|
+
cache_image(image) if EasyCaptcha.cache
|
20
|
+
# return image
|
21
|
+
image
|
22
|
+
end
|
23
|
+
|
24
|
+
# generate speech by captcha from session
|
25
|
+
def generate_speech_captcha
|
26
|
+
fail 'espeak disabled' unless EasyCaptcha.espeak?
|
27
|
+
if EasyCaptcha.cache && cache_audio_file_exists?
|
28
|
+
load_cache_audio_file
|
29
|
+
else
|
30
|
+
new_audio_captcha_file
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# return cache path of captcha image
|
35
|
+
def captcha_cache_path
|
36
|
+
"#{EasyCaptcha.cache_temp_dir}/#{current_captcha_code}.png"
|
37
|
+
end
|
38
|
+
|
39
|
+
# return cache path of speech captcha
|
40
|
+
def speech_captcha_cache_path
|
41
|
+
"#{EasyCaptcha.cache_temp_dir}/#{current_captcha_code}.wav"
|
42
|
+
end
|
43
|
+
|
44
|
+
# current active captcha from session
|
45
|
+
def current_captcha_code
|
46
|
+
session[:captcha] ||= generate_captcha_code
|
47
|
+
end
|
48
|
+
|
49
|
+
# generate captcha code, save in session and return
|
50
|
+
# rubocop:disable Metrics/AbcSize, Naming/MemoizedInstanceVariableName
|
51
|
+
def generate_captcha_code
|
52
|
+
@captcha_code ||= begin
|
53
|
+
length = EasyCaptcha.captcha_character_count
|
54
|
+
# overwrite `current_captcha_code`
|
55
|
+
session[:captcha] = Array.new(length) { EasyCaptcha.captcha_character_pool.sample }.join
|
56
|
+
Rails.logger.info(
|
57
|
+
"#{Time.now}: generate_captcha_code in EasyCaptcha. " \
|
58
|
+
"session[:captcha]: #{session[:captcha]} " \
|
59
|
+
"length: #{length}, " \
|
60
|
+
"original length: #{EasyCaptcha.captcha_character_count} " \
|
61
|
+
"chars count: #{EasyCaptcha.captcha_character_pool.size}."
|
62
|
+
)
|
63
|
+
session[:captcha]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
# rubocop:enable Metrics/AbcSize, Naming/MemoizedInstanceVariableName
|
67
|
+
|
68
|
+
# validate given captcha code
|
69
|
+
def captcha_valid?(code)
|
70
|
+
return false if session[:captcha].to_s.blank? || code.to_s.blank?
|
71
|
+
session[:captcha].to_s == code.to_s
|
72
|
+
end
|
73
|
+
alias_method :valid_captcha?, :captcha_valid?
|
74
|
+
|
75
|
+
def captcha_invalid?(code)
|
76
|
+
!captcha_valid?(code)
|
77
|
+
end
|
78
|
+
alias_method :invalid_captcha?, :captcha_invalid?
|
79
|
+
|
80
|
+
# reset the captcha code in session for security after each request
|
81
|
+
def reset_last_captcha_code!
|
82
|
+
session.delete(:captcha)
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def ensure_cache_dir
|
88
|
+
# create cache dir
|
89
|
+
FileUtils.mkdir_p(EasyCaptcha.cache_temp_dir)
|
90
|
+
end
|
91
|
+
|
92
|
+
def cached_captcha_files
|
93
|
+
ensure_cache_dir
|
94
|
+
# select all generated captcha image files from cache
|
95
|
+
Dir.glob("#{EasyCaptcha.cache_temp_dir}*.png")
|
96
|
+
end
|
97
|
+
|
98
|
+
# Grab random file from the cached files, return its file data
|
99
|
+
# rubocop:disable Metrics/AbcSize
|
100
|
+
def chose_image_from_cache
|
101
|
+
cache_files = cached_captcha_files
|
102
|
+
|
103
|
+
return nil if cache_files.size < EasyCaptcha.cache_size
|
104
|
+
|
105
|
+
file = File.open(cache_files.samlpe)
|
106
|
+
session[:captcha] = File.basename(file.path, '.*')
|
107
|
+
|
108
|
+
if file.mtime < EasyCaptcha.cache_expire.ago
|
109
|
+
# remove expired cached image
|
110
|
+
remove_cached_image_file(file)
|
111
|
+
# return nil
|
112
|
+
session[:captcha] = nil
|
113
|
+
else
|
114
|
+
# return file data from cache
|
115
|
+
file.readlines.join
|
116
|
+
end
|
117
|
+
end
|
118
|
+
# rubocop:enable Metrics/AbcSize
|
119
|
+
|
120
|
+
def remove_cached_image_file(file)
|
121
|
+
File.unlink(file.path)
|
122
|
+
# remove speech version
|
123
|
+
audio_file = file.path.gsub(/png\z/, 'wav')
|
124
|
+
File.unlink(audio_file) if File.exist?(audio_file)
|
125
|
+
end
|
126
|
+
|
127
|
+
def cache_image(image)
|
128
|
+
code = current_captcha_code
|
129
|
+
# write captcha for caching
|
130
|
+
File.open(captcha_cache_path(code), 'w') { |f| f.write image }
|
131
|
+
# write speech file if u create a new captcha image
|
132
|
+
EasyCaptcha.espeak.generate(code, speech_captcha_cache_path(code)) if EasyCaptcha.espeak?
|
133
|
+
end
|
134
|
+
|
135
|
+
def generate_captcha_image
|
136
|
+
if EasyCaptcha.cache
|
137
|
+
image = chose_image_from_cache
|
138
|
+
return image if image
|
139
|
+
end
|
140
|
+
# Default, if cache not enabled or an expired cache image was chosen, make a new one
|
141
|
+
Captcha.new(current_captcha_code).image
|
142
|
+
end
|
143
|
+
|
144
|
+
def load_cache_audio_file
|
145
|
+
File.read(speech_captcha_cache_path)
|
146
|
+
end
|
147
|
+
|
148
|
+
def new_audio_captcha_file
|
149
|
+
wav_file = Tempfile.new("#{current_captcha_code}.wav")
|
150
|
+
EasyCaptcha.espeak.generate(current_captcha_code, wav_file.path)
|
151
|
+
File.read(wav_file.path)
|
152
|
+
end
|
153
|
+
|
154
|
+
def cache_audio_file_exists?
|
155
|
+
File.exist?(speech_captcha_cache_path)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
# rubocop:enable Metrics/ModuleLength
|
159
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EasyCaptcha
|
4
|
+
# espeak wrapper
|
5
|
+
class Espeak
|
6
|
+
DEFAULT_CONFIG = {
|
7
|
+
amplitude: 80..120,
|
8
|
+
pitch: 30..70,
|
9
|
+
gap: 80,
|
10
|
+
voice: nil
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
attr_writer :amplitude, :pitch, :gap, :voice
|
14
|
+
|
15
|
+
# generator for captcha images
|
16
|
+
def initialize
|
17
|
+
set_defaults
|
18
|
+
yield self if block_given?
|
19
|
+
end
|
20
|
+
|
21
|
+
# set default values
|
22
|
+
def set_defaults
|
23
|
+
DEFAULT_CONFIG.map do |k, v|
|
24
|
+
send("#{k}=", v) if respond_to? "#{k}=".to_sym
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# return amplitude
|
29
|
+
def amplitude
|
30
|
+
@amplitude.is_a?(Range) ? @amplitude.to_a.sample : @amplitude.to_i
|
31
|
+
end
|
32
|
+
|
33
|
+
# return amplitude
|
34
|
+
def pitch
|
35
|
+
@pitch.is_a?(Range) ? @pitch.to_a.sample : @pitch.to_i
|
36
|
+
end
|
37
|
+
|
38
|
+
def gap
|
39
|
+
@gap.to_i
|
40
|
+
end
|
41
|
+
|
42
|
+
def voice
|
43
|
+
(@voice.is_a?(Array) ? @voice.sample : @voice).try(:gsub, /[^A-Za-z0-9\-+]/, '')
|
44
|
+
end
|
45
|
+
|
46
|
+
# generate wav file by captcha
|
47
|
+
def generate(captcha, wav_file)
|
48
|
+
cmd = [
|
49
|
+
'espeak -g 10',
|
50
|
+
espeak_amplitude_param,
|
51
|
+
espeak_pitch_param,
|
52
|
+
espeak_gap_param,
|
53
|
+
espeak_voice_param,
|
54
|
+
"-w #{wav_file}",
|
55
|
+
"'#{get_code(captcha)}'"
|
56
|
+
].compact.join(' ')
|
57
|
+
|
58
|
+
`#{cmd}`
|
59
|
+
true
|
60
|
+
end
|
61
|
+
|
62
|
+
def espeak_amplitude_param
|
63
|
+
"-a #{amplitude}" unless @amplitude.nil?
|
64
|
+
end
|
65
|
+
|
66
|
+
def espeak_pitch_param
|
67
|
+
"-p #{pitch}" unless @pitch.nil?
|
68
|
+
end
|
69
|
+
|
70
|
+
def espeak_gap_param
|
71
|
+
"-g #{gap}" unless @gap.nil?
|
72
|
+
end
|
73
|
+
|
74
|
+
def espeak_voice_param
|
75
|
+
"-v '#{voice}'" unless @voice.nil?
|
76
|
+
end
|
77
|
+
|
78
|
+
def get_code(captcha)
|
79
|
+
case captcha
|
80
|
+
when Captcha
|
81
|
+
code = captcha.code
|
82
|
+
when String
|
83
|
+
code = captcha
|
84
|
+
else
|
85
|
+
fail ArgumentError, 'invalid captcha'
|
86
|
+
end
|
87
|
+
# add spaces
|
88
|
+
code.each_char.to_a.join(' ')
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EasyCaptcha
|
4
|
+
module Generator
|
5
|
+
# base class for generators
|
6
|
+
class Base
|
7
|
+
# generator for captcha images
|
8
|
+
def initialize
|
9
|
+
defaults
|
10
|
+
yield self if block_given?
|
11
|
+
end
|
12
|
+
|
13
|
+
# default values for generator
|
14
|
+
def defaults; end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,199 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rmagick'
|
4
|
+
|
5
|
+
# EasyCaptcha module
|
6
|
+
module EasyCaptcha
|
7
|
+
# Generator submodule
|
8
|
+
module Generator
|
9
|
+
# rubocop:disable Metrics/ClassLength
|
10
|
+
# default generator
|
11
|
+
class Default < Base
|
12
|
+
DEFAULT_CONFIG = {
|
13
|
+
background_image: nil,
|
14
|
+
blur: true,
|
15
|
+
blur_radius: 1,
|
16
|
+
blur_sigma: 2,
|
17
|
+
font: File.expand_path('../../../resources/captcha.ttf', __dir__),
|
18
|
+
font_fill_color: '#333333',
|
19
|
+
font_size: 24,
|
20
|
+
font_stroke: 0,
|
21
|
+
font_stroke_color: '#000000',
|
22
|
+
image_background_color: '#FFFFFF',
|
23
|
+
implode: 0.05,
|
24
|
+
sketch: true,
|
25
|
+
sketch_radius: 3,
|
26
|
+
sketch_sigma: 1,
|
27
|
+
wave: true,
|
28
|
+
wave_amplitude: (3..5),
|
29
|
+
wave_length: (60..100)
|
30
|
+
}.freeze
|
31
|
+
|
32
|
+
# set default values
|
33
|
+
def defaults
|
34
|
+
DEFAULT_CONFIG.map do |k, v|
|
35
|
+
send("#{k}=", v)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Gaussian Blur
|
40
|
+
attr_accessor :blur, :blur_radius, :blur_sigma
|
41
|
+
|
42
|
+
# Font
|
43
|
+
attr_accessor :font_size, :font_fill_color, :font, :font_family, :font_stroke, :font_stroke_color
|
44
|
+
|
45
|
+
# Background
|
46
|
+
attr_accessor :image_background_color, :background_image
|
47
|
+
|
48
|
+
# Implode
|
49
|
+
attr_accessor :implode
|
50
|
+
|
51
|
+
# Sketch
|
52
|
+
attr_accessor :sketch, :sketch_radius, :sketch_sigma
|
53
|
+
|
54
|
+
# Wave
|
55
|
+
attr_accessor :wave, :wave_length, :wave_amplitude
|
56
|
+
|
57
|
+
# The CAPTCHA code
|
58
|
+
attr_reader :code
|
59
|
+
|
60
|
+
# The CAPTCHA image
|
61
|
+
attr_reader :image
|
62
|
+
|
63
|
+
def blur? #:nodoc:
|
64
|
+
@blur
|
65
|
+
end
|
66
|
+
|
67
|
+
def sketch? #:nodoc:
|
68
|
+
@sketch
|
69
|
+
end
|
70
|
+
|
71
|
+
def wave? #:nodoc:
|
72
|
+
@wave
|
73
|
+
end
|
74
|
+
|
75
|
+
# generate image
|
76
|
+
def generate(code)
|
77
|
+
@code = code
|
78
|
+
render_code_in_image
|
79
|
+
apply_blur
|
80
|
+
apply_wave
|
81
|
+
apply_sketch
|
82
|
+
apply_implode
|
83
|
+
apply_crop
|
84
|
+
create_blob
|
85
|
+
set_image_encoding
|
86
|
+
free_canvas
|
87
|
+
@image
|
88
|
+
end
|
89
|
+
|
90
|
+
def create_blob
|
91
|
+
@image = begin
|
92
|
+
if generator_config.background_image.present?
|
93
|
+
create_composite_blob
|
94
|
+
else
|
95
|
+
canvas.to_blob { self.format = 'PNG' }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def create_composite_blob
|
101
|
+
# Background Random Position
|
102
|
+
gravity = [
|
103
|
+
Magick::CenterGravity,
|
104
|
+
Magick::EastGravity,
|
105
|
+
Magick::NorthEastGravity,
|
106
|
+
Magick::NorthGravity,
|
107
|
+
Magick::NorthWestGravity,
|
108
|
+
Magick::SouthGravity,
|
109
|
+
Magick::SouthEastGravity,
|
110
|
+
Magick::SouthWestGravity,
|
111
|
+
Magick::WestGravity
|
112
|
+
].sample
|
113
|
+
|
114
|
+
background = Magick::Image.read(generator_config.background_image).first
|
115
|
+
background.composite!(canvas, gravity, Magick::OverCompositeOp)
|
116
|
+
background = background.crop(gravity, EasyCaptcha.captcha_image_width, EasyCaptcha.captcha_image_height)
|
117
|
+
background.to_blob { self.format = 'PNG' }
|
118
|
+
end
|
119
|
+
|
120
|
+
def set_image_encoding
|
121
|
+
@image = image.force_encoding 'UTF-8' if image.respond_to? :force_encoding
|
122
|
+
end
|
123
|
+
|
124
|
+
def free_canvas
|
125
|
+
@canvas.destroy! if canvas.respond_to?('destroy!')
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
def apply_blur
|
131
|
+
return unless blur?
|
132
|
+
@canvas = canvas.blur_image(generator_config.blur_radius, generator_config.blur_sigma)
|
133
|
+
end
|
134
|
+
|
135
|
+
def apply_crop
|
136
|
+
# Crop image because to big after waveing
|
137
|
+
@canvas = canvas.crop(Magick::CenterGravity, EasyCaptcha.captcha_image_width, EasyCaptcha.captcha_image_height)
|
138
|
+
end
|
139
|
+
|
140
|
+
def apply_implode
|
141
|
+
@canvas = canvas.implode(generator_config.implode.to_f) if generator_config.implode.is_a? Float
|
142
|
+
end
|
143
|
+
|
144
|
+
def apply_sketch
|
145
|
+
return unless sketch? && canvas.respond_to?(:sketch)
|
146
|
+
@canvas = canvas.sketch(generator_config.sketch_radius, generator_config.sketch_sigma, rand(180))
|
147
|
+
end
|
148
|
+
|
149
|
+
def apply_wave
|
150
|
+
return unless wave?
|
151
|
+
@canvas = canvas.wave(random_wave_amplitude, random_wave_length)
|
152
|
+
end
|
153
|
+
|
154
|
+
def canvas
|
155
|
+
config = generator_config
|
156
|
+
@canvas = nil if @canvas.respond_to?('destroyed?') && @canvas.destroyed?
|
157
|
+
@canvas ||= Magick::Image.new(EasyCaptcha.captcha_image_width, EasyCaptcha.captcha_image_height) do |_variable|
|
158
|
+
self.background_color = config.image_background_color unless config.image_background_color.nil?
|
159
|
+
self.background_color = 'none' if config.background_image.present?
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def generator_config
|
164
|
+
@generator_config ||= self
|
165
|
+
end
|
166
|
+
|
167
|
+
def random_wave_amplitude
|
168
|
+
rand(
|
169
|
+
generator_config.wave_amplitude.last - generator_config.wave_amplitude.first
|
170
|
+
) + generator_config.wave_amplitude.first
|
171
|
+
end
|
172
|
+
|
173
|
+
def random_wave_length
|
174
|
+
rand(
|
175
|
+
generator_config.wave_length.last - generator_config.wave_length.first
|
176
|
+
) + generator_config.wave_length.first
|
177
|
+
end
|
178
|
+
|
179
|
+
# rubocop:disable Metrics/AbcSize
|
180
|
+
def render_code_in_image
|
181
|
+
config = generator_config
|
182
|
+
# Render the text in the image
|
183
|
+
Magick::Draw.new.annotate(canvas, 0, 0, 0, 0, code.gsub(/%/, '\%')) do
|
184
|
+
self.gravity = Magick::CenterGravity
|
185
|
+
self.font = config.font
|
186
|
+
self.font_weight = Magick::LighterWeight
|
187
|
+
self.fill = config.font_fill_color
|
188
|
+
if config.font_stroke.to_i.positive?
|
189
|
+
self.stroke = config.font_stroke_color
|
190
|
+
self.stroke_width = config.font_stroke
|
191
|
+
end
|
192
|
+
self.pointsize = config.font_size
|
193
|
+
end
|
194
|
+
end
|
195
|
+
# rubocop:enable Metrics/AbcSize
|
196
|
+
end
|
197
|
+
# rubocop:enable Metrics/ClassLength
|
198
|
+
end
|
199
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EasyCaptcha
|
4
|
+
module ModelHelpers #:nodoc:
|
5
|
+
# helper class for ActiveRecord
|
6
|
+
def self.included(base) #:nodoc:
|
7
|
+
base.extend ClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods #:nodoc:
|
11
|
+
# to activate model captcha validation
|
12
|
+
def acts_as_easy_captcha
|
13
|
+
include InstanceMethods
|
14
|
+
attr_accessor :captcha, :captcha_verification
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module InstanceMethods #:nodoc:
|
19
|
+
# validate captcha
|
20
|
+
def captcha_valid?
|
21
|
+
return true if captcha.present? && captcha_verification.present? && captcha_verification_match?
|
22
|
+
errors.add(:captcha, :invalid)
|
23
|
+
false
|
24
|
+
end
|
25
|
+
alias_method :valid_captcha?, :captcha_valid?
|
26
|
+
|
27
|
+
def captcha_verification_match?
|
28
|
+
captcha.to_s.casecmp(captcha_verification.to_s).zero?
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionDispatch #:nodoc:
|
4
|
+
module Routing #:nodoc:
|
5
|
+
class Mapper #:nodoc:
|
6
|
+
# call to add default captcha route
|
7
|
+
def captcha_route
|
8
|
+
get '/captcha' => 'easy_captcha/captcha#captcha', action: :captcha, as: :captcha
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EasyCaptcha
|
4
|
+
# helper class for ActionView
|
5
|
+
module ViewHelpers
|
6
|
+
# generate an image_tag for captcha image
|
7
|
+
def captcha_tag(*args)
|
8
|
+
options = { alt: 'captcha', width: EasyCaptcha.captcha_image_width, height: EasyCaptcha.captcha_image_height }
|
9
|
+
options.merge! args.extract_options!
|
10
|
+
image_tag(captcha_path(i: Time.now.to_i), options)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EasyCaptcha
|
4
|
+
module Generators #:nodoc:
|
5
|
+
class InstallGenerator < Rails::Generators::Base #:nodoc:
|
6
|
+
source_root File.expand_path('../templates', __dir__)
|
7
|
+
|
8
|
+
desc 'Install easy_captcha'
|
9
|
+
|
10
|
+
def copy_initializer #:nodoc:
|
11
|
+
template 'easy_captcha.rb', 'config/initializers/easy_captcha.rb'
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_devise_routes #:nodoc:
|
15
|
+
route 'captcha_route'
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_after_filter #:nodoc:
|
19
|
+
inject_into_class 'app/controllers/application_controller.rb', ApplicationController do
|
20
|
+
" # reset captcha code after each request for security\n after_filter :reset_last_captcha_code!\n\n"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
EasyCaptcha.setup do |config|
|
4
|
+
# #####
|
5
|
+
# # Cache
|
6
|
+
# config.cache = true
|
7
|
+
# # Cache temp dir from Rails.root
|
8
|
+
# config.cache_temp_dir = Rails.root + 'tmp' + 'captchas'
|
9
|
+
# # Cache max number of images to cache
|
10
|
+
# config.cache_size = 500
|
11
|
+
# # Cache max length of time to cache an image
|
12
|
+
# config.cache_expire = 1.days
|
13
|
+
#
|
14
|
+
# #####
|
15
|
+
# # CAPTCHA
|
16
|
+
# # Chars available for CAPTCHA
|
17
|
+
# config.captcha_character_pool = %w(2 3 4 5 6 7 9 A C D E F G H J K L M N P Q R S T U X Y Z)
|
18
|
+
#
|
19
|
+
# # Length of CAPTCHA string
|
20
|
+
# config.captcha_character_count = 6
|
21
|
+
#
|
22
|
+
# # CAPTCHA Image dimensions
|
23
|
+
# config.captcha_image_height = 40
|
24
|
+
# config.captcha_image_width = 140
|
25
|
+
|
26
|
+
# #####
|
27
|
+
# # eSpeak
|
28
|
+
# # Enable eSpeak using all defaults:
|
29
|
+
# config.espeak = true
|
30
|
+
#
|
31
|
+
# # Enable eSpeak using custom config:
|
32
|
+
# config.espeak do |espeak|
|
33
|
+
# Amplitude, 0 to 200
|
34
|
+
# espeak.amplitude = 80..120
|
35
|
+
#
|
36
|
+
# Word gap. Pause between words
|
37
|
+
# espeak.gap = 80
|
38
|
+
#
|
39
|
+
# Pitch adjustment, 0 to 99
|
40
|
+
# espeak.pitch = 30..70
|
41
|
+
#
|
42
|
+
# Use voice file of this name from espeak-data/voices
|
43
|
+
# espeak.voice = nil
|
44
|
+
# end
|
45
|
+
|
46
|
+
# #####
|
47
|
+
# # Generator
|
48
|
+
# config.generator :default do |generator|
|
49
|
+
# # Blur
|
50
|
+
# generator.blur = true
|
51
|
+
# generator.blur_radius = 1
|
52
|
+
# generator.blur_sigma = 2
|
53
|
+
#
|
54
|
+
# # Font
|
55
|
+
# generator.font_family = File.expand_path('../../resources/afont.ttf', __FILE__)
|
56
|
+
# generator.font_fill_color = '#333333'
|
57
|
+
# generator.font_size = 24
|
58
|
+
# generator.font_stroke = 0
|
59
|
+
# generator.font_stroke_color = '#000000'
|
60
|
+
#
|
61
|
+
# # Image
|
62
|
+
# generator.image_background_color = "#FFFFFF"
|
63
|
+
#
|
64
|
+
# # Implode
|
65
|
+
# generator.implode = 0.1
|
66
|
+
#
|
67
|
+
# # Sketch
|
68
|
+
# generator.sketch = true
|
69
|
+
# generator.sketch_radius = 3
|
70
|
+
# generator.sketch_sigma = 1
|
71
|
+
#
|
72
|
+
# # Wave
|
73
|
+
# generator.wave = true
|
74
|
+
# generator.wave_amplitude = (3..5)
|
75
|
+
# generator.wave_length = (60..100)
|
76
|
+
# end
|
77
|
+
end
|
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kandr-easy_captcha
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Marco Scholl
|
@@ -50,6 +50,21 @@ extra_rdoc_files:
|
|
50
50
|
files:
|
51
51
|
- LICENSE.txt
|
52
52
|
- README.md
|
53
|
+
- lib/easy_captcha.rb
|
54
|
+
- lib/easy_captcha/captcha.rb
|
55
|
+
- lib/easy_captcha/captcha_controller.rb
|
56
|
+
- lib/easy_captcha/controller_helpers.rb
|
57
|
+
- lib/easy_captcha/espeak.rb
|
58
|
+
- lib/easy_captcha/generator.rb
|
59
|
+
- lib/easy_captcha/generator/base.rb
|
60
|
+
- lib/easy_captcha/generator/default.rb
|
61
|
+
- lib/easy_captcha/model_helpers.rb
|
62
|
+
- lib/easy_captcha/routes.rb
|
63
|
+
- lib/easy_captcha/version.rb
|
64
|
+
- lib/easy_captcha/view_helpers.rb
|
65
|
+
- lib/generators/easy_captcha/install_generator.rb
|
66
|
+
- lib/generators/templates/easy_captcha.rb
|
67
|
+
- resources/captcha.ttf
|
53
68
|
- spec/easy_captcha/captcha_controller_spec.rb
|
54
69
|
- spec/easy_captcha/captcha_spec.rb
|
55
70
|
- spec/easy_captcha/controller_helpers_spec.rb
|
@@ -83,8 +98,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
83
98
|
- !ruby/object:Gem::Version
|
84
99
|
version: 2.7.0
|
85
100
|
requirements: []
|
86
|
-
|
87
|
-
rubygems_version: 2.7.3
|
101
|
+
rubygems_version: 3.1.4
|
88
102
|
signing_key:
|
89
103
|
specification_version: 4
|
90
104
|
summary: CAPTCHA Plugin for Rails
|