rails_captcha 0.0.1

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