kandr-easy_captcha 0.9.1 → 0.9.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|