rails_captcha 0.0.1

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.
Files changed (54) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +3 -0
  3. data/Rakefile +40 -0
  4. data/app/controllers/rails_captcha/application_controller.rb +4 -0
  5. data/app/controllers/rails_captcha/captcha_controller.rb +17 -0
  6. data/app/helpers/rails_captcha/application_helper.rb +7 -0
  7. data/app/views/layouts/rails_captcha/application.html.erb +14 -0
  8. data/app/views/rails_captcha/captcha/_form.html.erb +9 -0
  9. data/app/views/rails_captcha/captcha/load_captcha.js.erb +16 -0
  10. data/config/locales/ru.yml +6 -0
  11. data/config/routes.rb +4 -0
  12. data/lib/rails_captcha/captcha/action.rb +35 -0
  13. data/lib/rails_captcha/captcha/cipher.rb +47 -0
  14. data/lib/rails_captcha/captcha/config.rb +76 -0
  15. data/lib/rails_captcha/captcha/generator.rb +33 -0
  16. data/lib/rails_captcha/captcha/image.rb +49 -0
  17. data/lib/rails_captcha/captcha/model.rb +58 -0
  18. data/lib/rails_captcha/captcha/railtie.rb +19 -0
  19. data/lib/rails_captcha/engine.rb +11 -0
  20. data/lib/rails_captcha/version.rb +3 -0
  21. data/lib/rails_captcha.rb +7 -0
  22. data/lib/tasks/rails_captcha_tasks.rake +6 -0
  23. data/test/dummy/README.rdoc +261 -0
  24. data/test/dummy/Rakefile +7 -0
  25. data/test/dummy/app/assets/javascripts/application.js +15 -0
  26. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  27. data/test/dummy/app/controllers/application_controller.rb +3 -0
  28. data/test/dummy/app/helpers/application_helper.rb +2 -0
  29. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  30. data/test/dummy/config/application.rb +59 -0
  31. data/test/dummy/config/boot.rb +10 -0
  32. data/test/dummy/config/database.yml +25 -0
  33. data/test/dummy/config/environment.rb +5 -0
  34. data/test/dummy/config/environments/development.rb +37 -0
  35. data/test/dummy/config/environments/production.rb +67 -0
  36. data/test/dummy/config/environments/test.rb +37 -0
  37. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  38. data/test/dummy/config/initializers/inflections.rb +15 -0
  39. data/test/dummy/config/initializers/mime_types.rb +5 -0
  40. data/test/dummy/config/initializers/secret_token.rb +7 -0
  41. data/test/dummy/config/initializers/session_store.rb +8 -0
  42. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  43. data/test/dummy/config/locales/en.yml +5 -0
  44. data/test/dummy/config/routes.rb +4 -0
  45. data/test/dummy/config.ru +4 -0
  46. data/test/dummy/public/404.html +26 -0
  47. data/test/dummy/public/422.html +26 -0
  48. data/test/dummy/public/500.html +25 -0
  49. data/test/dummy/public/favicon.ico +0 -0
  50. data/test/dummy/script/rails +6 -0
  51. data/test/integration/navigation_test.rb +10 -0
  52. data/test/rails_captcha_test.rb +7 -0
  53. data/test/test_helper.rb +15 -0
  54. metadata +161 -0
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2012 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,3 @@
1
+ = RailsCaptcha
2
+
3
+ This project rocks and uses MIT-LICENSE.
data/Rakefile ADDED
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'RailsCaptcha'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
24
+ load 'rails/tasks/engine.rake'
25
+
26
+
27
+
28
+ Bundler::GemHelper.install_tasks
29
+
30
+ require 'rake/testtask'
31
+
32
+ Rake::TestTask.new(:test) do |t|
33
+ t.libs << 'lib'
34
+ t.libs << 'test'
35
+ t.pattern = 'test/**/*_test.rb'
36
+ t.verbose = false
37
+ end
38
+
39
+
40
+ task :default => :test
@@ -0,0 +1,4 @@
1
+ module RailsCaptcha
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,17 @@
1
+ # coding: utf-8
2
+ class RailsCaptcha::CaptchaController < RailsCaptcha::ApplicationController
3
+ acts_as_captcha
4
+
5
+ def captcha
6
+ reset_captcha
7
+ text = session[:captcha].blank? ? "" : session[:captcha]
8
+ render :text => text
9
+ end
10
+
11
+ def load_captcha
12
+ reset_captcha
13
+ respond_to do |format|
14
+ format.js
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,7 @@
1
+ module RailsCaptcha
2
+ module ApplicationHelper
3
+ def form_rails_captcha(object)
4
+ render :partial => "rails_captcha/captcha/form", :locals => {:object => object}
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>RailsCaptcha</title>
5
+ <%= stylesheet_link_tag "rails_captcha/application", :media => "all" %>
6
+ <%= javascript_include_tag "rails_captcha/application" %>
7
+ <%= csrf_meta_tags %>
8
+ </head>
9
+ <body>
10
+
11
+ <%= yield %>
12
+
13
+ </body>
14
+ </html>
@@ -0,0 +1,9 @@
1
+ <label for="captcha">
2
+ <%= t('rails_captcha.security_code') %>
3
+ (<a id="update_captcha" href="javascript://"><%= t('rails_captcha.update') %></a>)
4
+ </label>
5
+ <% unless object.errors[:captcha].blank? %>
6
+ <span class="error"><%= object.errors[:captcha].join %></span>
7
+ <% end %>
8
+ <span class="<%= Rails.env.to_s %>" id="captcha_image_container">&nbsp;</span>
9
+ <%= text_field_tag :captcha, nil, :maxlength => "6" %>
@@ -0,0 +1,16 @@
1
+ $(document).ready(function() {
2
+ $('#captcha_image_container').html("<%= escape_javascript image_tag("/images/captchas/#{Rails.env.to_s}/#{session[:captcha]}.jpg", :id => "captcha_image", :rel => Rails.env.to_s, :alt => t('rails_captcha.security_code')) %>");
3
+ $("#update_captcha").click(function () {
4
+ var env = $('#captcha_image_container').attr('class');
5
+ $.ajax({
6
+ type: "GET",
7
+ url: '/captcha/captcha.js',
8
+ dataType: "html",
9
+ success: function(data){
10
+ $('#captcha_image_container').html('<img id="captcha_image" src="'+'/images/captchas/'+env+'/'+data+'.jpg'+'" alt="<%= t('rails_captcha.security_code') %>" />');
11
+ },
12
+ error: function(result) { alert('Captcha ERROR'); }
13
+ });
14
+ return false;
15
+ });
16
+ });
@@ -0,0 +1,6 @@
1
+ ru:
2
+ rails_captcha:
3
+ security_code: Защитный код
4
+ update: Обновить
5
+ enter_the_correct_captcha: Введите корректный текст с защитной картинки
6
+ wrong_captcha: Неверный защитный код
data/config/routes.rb ADDED
@@ -0,0 +1,4 @@
1
+ RailsCaptcha::Engine.routes.draw do
2
+ match "/captcha.js" => "captcha#captcha", :as => :captcha
3
+ match "/load_captcha.js" => "captcha#load_captcha", :as => :load_captcha, :format => "js"
4
+ end
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ module RailsCaptcha
3
+ module Action
4
+
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+ end
8
+
9
+ module ClassMethods
10
+ def acts_as_captcha
11
+ unless included_modules.include? InstanceMethods
12
+ include InstanceMethods
13
+ end
14
+ before_filter :assign_captcha
15
+ end
16
+ end
17
+
18
+ module InstanceMethods
19
+ private
20
+
21
+ def assign_captcha
22
+ unless session[:captcha] && RailsCaptcha::Config.exists?(session[:captcha])
23
+ files = RailsCaptcha::Config.captchas
24
+ session[:captcha] = File.basename(files[rand(files.length)], '.jpg')
25
+ end
26
+ end
27
+
28
+ def reset_captcha
29
+ session[:captcha] = nil
30
+ assign_captcha
31
+ end
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,47 @@
1
+ # coding: utf-8
2
+
3
+ require 'openssl'
4
+ require 'digest/sha1'
5
+
6
+ module RailsCaptcha
7
+ class Cipher
8
+ @@key = Digest::SHA1.hexdigest(Config.options[:password])
9
+ @@iv = 'captchas'*2
10
+
11
+ def self.encrypt(text)
12
+ # Encrypt
13
+ cipher = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
14
+ cipher.encrypt
15
+ cipher.key = @@key
16
+ cipher.iv = @@iv
17
+ encrypted = cipher.update(text)
18
+ encrypted << cipher.final
19
+ # Turn into chr codes separated by underscores
20
+ # 135_14_163_53_43_135_172_31_1_23_169_81_49_110_49_230
21
+ if encrypted.respond_to?(:codepoints)
22
+ encrypted = encrypted.codepoints.to_a
23
+ else
24
+ encrypted = (0..encrypted.length-1).collect do |x|
25
+ encrypted[x]
26
+ end
27
+ end
28
+ encrypted.join('_')
29
+ end
30
+
31
+ def self.decrypt(text)
32
+ # Decode chr coded string
33
+ encrypted = text.split('_').collect do |x|
34
+ x.to_i.chr
35
+ end
36
+ encrypted = encrypted.join('')
37
+ # Decrypt
38
+ cipher = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
39
+ cipher.decrypt
40
+ cipher.key = @@key
41
+ cipher.iv = @@iv
42
+ decrypted = cipher.update(encrypted)
43
+ decrypted << cipher.final
44
+ decrypted.gsub(/[^a-z]/, '')
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,76 @@
1
+ # coding: utf-8
2
+ module RailsCaptcha
3
+ class Config
4
+ ONE_DAY = 24 * 60 * 60
5
+
6
+ @@options = {
7
+ :password => 'captcha',
8
+ :colors => {
9
+ :background => '#FFFFFF',
10
+ :font => '#080288'
11
+ },
12
+ # number of captcha images to generate
13
+ :count => Rails.env.production? ? 100 : 3,
14
+ :destination => "public/images/captchas/#{Rails.env.to_s}",
15
+ :dimensions => {
16
+ # canvas height (px)
17
+ :height => 32,
18
+ # canvas width (px)
19
+ :width => 110
20
+ },
21
+ :generate_every => Rails.env.production? ? ONE_DAY * 1 : ONE_DAY * 10000,
22
+ # http://www.imagemagick.org/RMagick/doc/image2.html#implode
23
+ :implode => 0.2,
24
+ :letters => {
25
+ # text baseline (px)
26
+ :baseline => 25,
27
+ # number of letters in captcha
28
+ :count => 6,
29
+ :ignore => ['a','e','i','o','u','l','j','q','v','f','t'],
30
+ # font size (pts)
31
+ :points => 36,
32
+ # width of a character (used to decrease or increase space between characters) (px)
33
+ :width => 17
34
+ },
35
+ :ttf => File.expand_path("#{File.dirname(__FILE__)}/../resources/captcha.ttf"),
36
+ # http://www.imagemagick.org/RMagick/doc/image3.html#wave
37
+ :wave => {
38
+ # range is used for randomness (px)
39
+ :wavelength => (40..60),
40
+ # distance between peak and valley of sin wave (px)
41
+ :amplitude => 2
42
+ }
43
+ }
44
+
45
+ def initialize(options={})
46
+ @@options.merge!(options)
47
+ end
48
+
49
+ def self.captchas
50
+ Dir["#{@@options[:destination]}/*.jpg"]
51
+ end
52
+
53
+ def self.codes
54
+ self.captchas.collect do |f|
55
+ File.basename f, '.jpg'
56
+ end
57
+ end
58
+
59
+ def self.exists?(code)
60
+ File.exists?("#{@@options[:destination]}/#{code}.jpg")
61
+ end
62
+
63
+ def self.options
64
+ @@options
65
+ end
66
+
67
+ def self.last_modified
68
+ file = self.captchas.first
69
+ if file && File.exists?(file)
70
+ File.mtime(file)
71
+ else
72
+ nil
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ module RailsCaptcha
3
+ class Generator
4
+ def initialize
5
+ generate
6
+ end
7
+
8
+ def generate
9
+ require 'etc'
10
+ uid = File.stat("#{Rails.root}/Gemfile").uid
11
+ owner = Etc.getpwuid(uid).name
12
+ return unless Config.options
13
+ return if Config.last_modified && Config.last_modified > Time.now - Config.options[:generate_every]
14
+ path = Rails.root.join(Config.options[:destination])
15
+ Config.captchas.each do |captcha|
16
+ FileUtils.rm_f captcha
17
+ end
18
+ FileUtils.rm_rf path
19
+ FileUtils.mkdir path
20
+ FileUtils.chown_R owner, owner, path if Rails.env.production?
21
+ (1..Config.options[:count]).each do |x|
22
+ image = Image.new Config.options
23
+ path = "#{Config.options[:destination]}/#{Cipher.encrypt(image.code)}.jpg"
24
+ next if File.exists?(path)
25
+ File.open(path, 'w') do |f|
26
+ FileUtils.chown owner, owner, path if Rails.env.production?
27
+ f << image.data.force_encoding("UTF-8")
28
+ end
29
+ end
30
+ GC.start
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,49 @@
1
+ # coding: utf-8
2
+
3
+ require 'RMagick'
4
+
5
+ module RailsCaptcha
6
+ class Image
7
+
8
+ include Magick
9
+ attr_reader :code, :data
10
+
11
+ def initialize(o)
12
+ generate_code o
13
+
14
+ canvas = Magick::Image.new(o[:dimensions][:width], o[:dimensions][:height]) {
15
+ self.background_color = o[:colors][:background]
16
+ }
17
+
18
+ text = Magick::Draw.new
19
+ text.font = File.expand_path o[:ttf]
20
+ text.pointsize = o[:letters][:points]
21
+
22
+ cur = 0
23
+ @code.each { |c|
24
+ text.annotate(canvas, 0, 0, cur, o[:letters][:baseline], c) {
25
+ self.fill = o[:colors][:font]
26
+ }
27
+ cur += o[:letters][:width]
28
+ }
29
+
30
+ w = o[:wave][:wavelength]
31
+ canvas = canvas.wave(o[:wave][:amplitude], rand(w.last - w.first) + w.first)
32
+ canvas = canvas.implode(o[:implode])
33
+
34
+ @code = @code.to_s
35
+ @data = canvas.to_blob { self.format = "JPG" }
36
+ canvas.destroy!
37
+ end
38
+
39
+ private
40
+
41
+ def generate_code(o)
42
+ chars = ('a'..'z').to_a - o[:letters][:ignore]
43
+ @code = []
44
+ 1.upto(o[:letters][:count]) do
45
+ @code << chars[rand(chars.length)]
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,58 @@
1
+ # coding: utf-8
2
+ module RailsCaptcha
3
+ module Model
4
+ def self.included(base)
5
+ base.extend ActMethods
6
+ end
7
+
8
+ module ActMethods
9
+ def acts_as_captcha(options={})
10
+ extend ClassMethods
11
+ include InstanceMethods
12
+ attr_reader :captcha, :known_captcha
13
+ cattr_accessor :captcha_options
14
+ self.captcha_options = options
15
+ validate :captcha_must_match_known_captcha
16
+ end
17
+ end
18
+
19
+ module ClassMethods
20
+ end
21
+
22
+ module InstanceMethods
23
+ def captcha=(c)
24
+ @captcha = c || ''
25
+ end
26
+
27
+ def known_captcha=(c)
28
+ @known_captcha = c || ''
29
+ end
30
+
31
+ def captcha_must_match_known_captcha
32
+ return true if self.captcha.nil? || self.known_captcha.nil?
33
+ decrypted = RailsCaptcha::Cipher.decrypt(self.known_captcha)
34
+ if self.captcha.strip.downcase != decrypted
35
+ if self.captcha_options[:base]
36
+ self.errors.add_to_base(
37
+ case self.captcha_options[:base]
38
+ when true
39
+ I18n.t("rails_captcha.enter_the_correct_captcha")
40
+ else
41
+ self.captcha_options[:base]
42
+ end
43
+ )
44
+ else
45
+ self.errors.add(:captcha,
46
+ case self.captcha_options[:field]
47
+ when true, nil
48
+ I18n.t("rails_captcha.wrong_captcha")
49
+ else
50
+ self.captcha_options[:field]
51
+ end
52
+ )
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end