devise_password_sharing_extension 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 (62) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +17 -0
  4. data/Gemfile.lock +163 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.rdoc +33 -0
  7. data/Rakefile +42 -0
  8. data/VERSION +1 -0
  9. data/app/models/login_event.rb +15 -0
  10. data/devise_password_sharing_extension.gemspec +166 -0
  11. data/lib/devise_password_sharing_extension.rb +20 -0
  12. data/lib/devise_password_sharing_extension/hooks/password_sharing.rb +12 -0
  13. data/lib/devise_password_sharing_extension/models/password_sharing.rb +51 -0
  14. data/lib/devise_password_sharing_extension/rails.rb +4 -0
  15. data/lib/devise_password_sharing_extension/schema.rb +9 -0
  16. data/lib/devise_password_sharing_extension/version.rb +3 -0
  17. data/lib/generators/active_record/devise_password_sharing_extension_generator.rb +14 -0
  18. data/lib/generators/active_record/templates/migration.rb +26 -0
  19. data/lib/generators/devise_password_sharing_extension/devise_password_sharing_extension_generator.rb +16 -0
  20. data/lib/generators/devise_password_sharing_extension/install_generator.rb +23 -0
  21. data/lib/generators/devise_password_sharing_extension/templates/white_listed_ips.yml +1 -0
  22. data/spec/models/login_event_spec.rb +10 -0
  23. data/spec/models/password_sharing_spec.rb +54 -0
  24. data/spec/orm/active_record.rb +3 -0
  25. data/spec/rails_app/.gitignore +5 -0
  26. data/spec/rails_app/app/assets/images/rails.png +0 -0
  27. data/spec/rails_app/app/assets/javascripts/application.js +9 -0
  28. data/spec/rails_app/app/assets/stylesheets/application.css +7 -0
  29. data/spec/rails_app/app/controllers/application_controller.rb +3 -0
  30. data/spec/rails_app/app/controllers/home_controller.rb +9 -0
  31. data/spec/rails_app/app/helpers/application_helper.rb +2 -0
  32. data/spec/rails_app/app/mailers/.gitkeep +0 -0
  33. data/spec/rails_app/app/models/user.rb +7 -0
  34. data/spec/rails_app/app/views/home/index.html.erb +1 -0
  35. data/spec/rails_app/app/views/home/secure.html.erb +1 -0
  36. data/spec/rails_app/app/views/layouts/application.html.erb +14 -0
  37. data/spec/rails_app/config.ru +4 -0
  38. data/spec/rails_app/config/application.rb +19 -0
  39. data/spec/rails_app/config/boot.rb +6 -0
  40. data/spec/rails_app/config/database.yml +22 -0
  41. data/spec/rails_app/config/environment.rb +5 -0
  42. data/spec/rails_app/config/environments/development.rb +30 -0
  43. data/spec/rails_app/config/environments/production.rb +60 -0
  44. data/spec/rails_app/config/environments/test.rb +39 -0
  45. data/spec/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  46. data/spec/rails_app/config/initializers/devise.rb +184 -0
  47. data/spec/rails_app/config/initializers/inflections.rb +10 -0
  48. data/spec/rails_app/config/initializers/mime_types.rb +5 -0
  49. data/spec/rails_app/config/initializers/secret_token.rb +7 -0
  50. data/spec/rails_app/config/initializers/session_store.rb +8 -0
  51. data/spec/rails_app/config/initializers/wrap_parameters.rb +14 -0
  52. data/spec/rails_app/config/locales/en.yml +5 -0
  53. data/spec/rails_app/config/mongoid.yml +12 -0
  54. data/spec/rails_app/config/routes.rb +5 -0
  55. data/spec/rails_app/config/white_listed_ips.yml +1 -0
  56. data/spec/rails_app/db/migrate/20111019173200_create_tables.rb +29 -0
  57. data/spec/rails_app/db/seeds.rb +7 -0
  58. data/spec/rails_app/script/rails +6 -0
  59. data/spec/request_spec.rb +49 -0
  60. data/spec/spec_helper.rb +25 -0
  61. data/spec/support/fixtures.rb +32 -0
  62. metadata +361 -0
@@ -0,0 +1,20 @@
1
+ require 'geoip'
2
+ require 'devise'
3
+ require 'devise_password_sharing_extension/schema'
4
+ require 'devise_password_sharing_extension/rails'
5
+
6
+ module Devise
7
+ mattr_accessor :enable_banning
8
+ @@enable_banning = true
9
+
10
+ mattr_accessor :geoip_database
11
+ @@geoip_database = '/var/tmp/geoip.dat'
12
+
13
+ mattr_accessor :time_frame
14
+ @@time_frame = 2.hour
15
+
16
+ mattr_accessor :number_of_cities
17
+ @@number_of_cities = 10
18
+ end
19
+
20
+ Devise.add_module(:password_sharing, :model => 'devise_password_sharing_extension/models/password_sharing')
@@ -0,0 +1,12 @@
1
+ Warden::Manager.after_set_user(:except => :fetch) do |record, warden, options|
2
+ if record.respond_to?(:password_sharing?) && warden.authenticated?(options[:scope])
3
+ record.create_login_event!(warden.request)
4
+
5
+ if record.password_sharing?
6
+ record.ban_for_password_sharing
7
+ scope = options[:scope]
8
+ warden.logout(scope)
9
+ throw :warden, :scope => scope, :message => 'Account banned for password sharing.'
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,51 @@
1
+ require 'devise_password_sharing_extension/hooks/password_sharing'
2
+
3
+ module Devise
4
+ module Models
5
+ module PasswordSharing
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+ Devise::Models.config(self, :enable_banning)
10
+ Devise::Models.config(self, :geoip_database)
11
+ Devise::Models.config(self, :time_frame)
12
+ Devise::Models.config(self, :number_of_cities)
13
+ Devise::Models.config(self, :white_listed_ips)
14
+ end
15
+
16
+ included do
17
+ has_many :login_events
18
+
19
+ @@white_listed_ips = YAML::load(File.read(Rails.root.join('config', 'white_listed_ips.yml')))
20
+ end
21
+
22
+ def create_login_event!(request)
23
+ unless @@white_listed_ips.include?(request.remote_ip)
24
+ database = GeoIP.new(self.class.geoip_database)
25
+ if geo = database.city(request.remote_ip)
26
+ login_events.create!(
27
+ :ip_address => request.remote_ip,
28
+ :latitude => geo.latitude,
29
+ :longitude => geo.longitude,
30
+ :city => geo.city_name,
31
+ :country_code => geo.country_code2,
32
+ :region_name => geo.region_name)
33
+ end
34
+ end
35
+ end
36
+
37
+ def ban_for_password_sharing!
38
+ return unless self.class.enable_banning
39
+ self.banned_for_password_sharing_at = Time.now
40
+ save(:validate => false)
41
+ end
42
+
43
+ def password_sharing?
44
+ return true unless banned_for_password_sharing_at.nil?
45
+ login_events.grouped_by_city(self.class.time_frame).any? do |g|
46
+ g.count >= self.class.number_of_cities
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,4 @@
1
+ module DevisePasswordSharingExtension
2
+ class Engine < ::Rails::Engine
3
+ end
4
+ end
@@ -0,0 +1,9 @@
1
+ module DevisePasswordSharingExtension
2
+ module Schema
3
+ def password_sharing
4
+ apply_devise_schema :banned_for_password_sharing_at, DateTime, :default => false
5
+ end
6
+ end
7
+ end
8
+
9
+ Devise::Schema.send(:include, DevisePasswordSharingExtension::Schema)
@@ -0,0 +1,3 @@
1
+ module DevisePasswordSharingExtension
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,14 @@
1
+ require 'rails/generators/active_record'
2
+
3
+ module ActiveRecord
4
+ module Generators
5
+ class DevisePasswordSharingExtensionGenerator < ActiveRecord::Generators::Base
6
+ source_root File.expand_path('../templates', __FILE__)
7
+
8
+ def copy_devise_migration
9
+ migration_template('migration.rb', "db/migrate/devise_password_sharing_add_to_#{table_name}")
10
+ end
11
+ end
12
+ end
13
+ end
14
+
@@ -0,0 +1,26 @@
1
+ class DevisePasswordSharingAddTo<%= table_name.camelize %> < ActiveRecord::Migration
2
+ def self.up
3
+ change_table :<%= table_name %> do |t|
4
+ t.datetime :banned_for_password_sharing_at, :default => nil
5
+ end
6
+
7
+ create_table :login_events do |t|
8
+ t.integer :<%= table_name.singularize.underscore %>_id
9
+ t.string :ip_address
10
+ t.float :latitude
11
+ t.float :longitude
12
+ t.string :city
13
+ t.string :country_code
14
+ t.string :region_name
15
+ t.datetime :created_at
16
+ end
17
+ end
18
+
19
+ def self.down
20
+ change_table :<%= table_name %> do |t|
21
+ t.remove :banned_for_password_sharing_at
22
+ end
23
+
24
+ drop_table :login_events
25
+ end
26
+ end
@@ -0,0 +1,16 @@
1
+ module DevisePasswordSharingExtension
2
+ module Generators
3
+ class DevisePasswordSharingExtensionGenerator < Rails::Generators::NamedBase
4
+ namespace 'devise_password_sharing_extension'
5
+
6
+ desc 'Add :password_sharing directive in the given model. Also generate migration for ActiveRecord'
7
+
8
+ def inject_devise_password_sharing_content
9
+ path = File.join('app', 'models', "#{file_path}.rb")
10
+ inject_into_file(path, 'password_sharing, :', :after => 'devise :') if File.exists?(path)
11
+ end
12
+
13
+ hook_for :orm
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,23 @@
1
+ module DevisePasswordSharingExtension
2
+ module Generators # :nodoc:
3
+ # Install Generator
4
+ class InstallGenerator < Rails::Generators::Base
5
+ source_root File.expand_path("../templates", __FILE__)
6
+
7
+ desc "Install the devise security extension"
8
+
9
+ def add_configs
10
+ inject_into_file "config/initializers/devise.rb", "\n # ==> Password Sharing Extension\n" +
11
+ " # config.enable_banning = true\n" +
12
+ " # config.geoip_database = '/var/tmp/geoip.dat'\n" +
13
+ " # config.time_frame = 2.hour\n" +
14
+ " # config.number_of_cities = 10\n" +
15
+ "\n", :before => /end[ |\n|]+\Z/
16
+ end
17
+
18
+ def copy_white_listed_ips
19
+ copy_file("white_listed_ips.yml", "config/white_listed_ips.yml")
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+
3
+ describe LoginEvent do
4
+ it { should validate_presence_of(:ip_address) }
5
+ it { should validate_presence_of(:latitude) }
6
+ it { should validate_presence_of(:longitude) }
7
+ it { should validate_presence_of(:city) }
8
+ it { should validate_presence_of(:country_code) }
9
+ it { should validate_presence_of(:region_name) }
10
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+
3
+ describe Devise::Models::PasswordSharing do
4
+ subject { user_fixture }
5
+
6
+ it { should have_many(:login_events) }
7
+
8
+ describe "#create_login_event!" do
9
+ it "creates login event with ip information" do
10
+ lambda {
11
+ mock_geo_database
12
+ subject.create_login_event!(request_fixture)
13
+ }.should change(LoginEvent, :count).by(1)
14
+ end
15
+ end
16
+
17
+ describe "#ban_for_password_sharing!" do
18
+ context "when banning is enabled" do
19
+ it "sets :banned_for_password_sharing_at to the current time" do
20
+ subject.ban_for_password_sharing!
21
+ subject.banned_for_password_sharing_at.should_not be_nil
22
+ end
23
+ end
24
+
25
+ context "when banning is not enabled" do
26
+ it "does not ban user" do
27
+ User.enable_banning = false
28
+ subject.ban_for_password_sharing!
29
+ subject.banned_for_password_sharing_at.should be_nil
30
+ end
31
+ end
32
+ end
33
+
34
+ describe "#password_sharing?" do
35
+ context "when there is password sharing" do
36
+ before do
37
+ 10.times do
38
+ mock_geo_database(:city => 'Las Vegas')
39
+ subject.create_login_event!(request_fixture)
40
+ end
41
+ end
42
+
43
+ it "returns true" do
44
+ subject.password_sharing?.should be_true
45
+ end
46
+ end
47
+
48
+ context "when there is no password sharing" do
49
+ it "returns false" do
50
+ subject.password_sharing?.should be_false
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,3 @@
1
+ ActiveRecord::Migration.verbose = false
2
+ ActiveRecord::Base.logger = Logger.new(nil)
3
+ ActiveRecord::Migrator.migrate(File.expand_path('../../rails_app/db/migrate/', __FILE__))
@@ -0,0 +1,5 @@
1
+ .bundle
2
+ db/*.sqlite3
3
+ log/*.log
4
+ tmp/
5
+ .sass-cache/
@@ -0,0 +1,9 @@
1
+ // This is a manifest file that'll be compiled into including all the files listed below.
2
+ // Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
3
+ // be included in the compiled file accessible from http://example.com/assets/application.js
4
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
5
+ // the compiled file.
6
+ //
7
+ //= require jquery
8
+ //= require jquery_ujs
9
+ //= require_tree .
@@ -0,0 +1,7 @@
1
+ /*
2
+ * This is a manifest file that'll automatically include all the stylesheets available in this directory
3
+ * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
4
+ * the top of the compiled file, but it's generally better to create a new file per style scope.
5
+ *= require_self
6
+ *= require_tree .
7
+ */
@@ -0,0 +1,3 @@
1
+ class ApplicationController < ActionController::Base
2
+ protect_from_forgery
3
+ end
@@ -0,0 +1,9 @@
1
+ class HomeController < ApplicationController
2
+ before_filter :authenticate_user!, :only => [:secure]
3
+
4
+ def index
5
+ end
6
+
7
+ def secure
8
+ end
9
+ end
@@ -0,0 +1,2 @@
1
+ module ApplicationHelper
2
+ end
File without changes
@@ -0,0 +1,7 @@
1
+ class User < ActiveRecord::Base
2
+ devise :database_authenticatable, :password_sharing
3
+
4
+ attr_accessible :email, :username, :password, :password_confirmation
5
+
6
+ validates :username, :length => { :maximum => 20 }
7
+ end
@@ -0,0 +1 @@
1
+ <h1>HomeController#index</h1>
@@ -0,0 +1 @@
1
+ secure
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>RailsApp</title>
5
+ <%= stylesheet_link_tag "application" %>
6
+ <%= javascript_include_tag "application" %>
7
+ <%= csrf_meta_tags %>
8
+ </head>
9
+ <body>
10
+
11
+ <%= yield %>
12
+
13
+ </body>
14
+ </html>
@@ -0,0 +1,4 @@
1
+ # This file is used by Rack-based servers to start the application.
2
+
3
+ require ::File.expand_path('../config/environment', __FILE__)
4
+ run RailsApp::Application
@@ -0,0 +1,19 @@
1
+ require File.expand_path('../boot', __FILE__)
2
+
3
+ require "action_controller/railtie"
4
+ require "action_mailer/railtie"
5
+ require "active_resource/railtie"
6
+ require "rails/test_unit/railtie"
7
+ require "active_record/railtie"
8
+
9
+ Bundler.require(:default) if defined?(Bundler)
10
+
11
+ require "devise"
12
+ require "devise_password_sharing_extension"
13
+
14
+ module RailsApp
15
+ class Application < Rails::Application
16
+ config.filter_parameters << :password
17
+ config.action_mailer.default_url_options = { :host => "localhost:3000" }
18
+ end
19
+ end
@@ -0,0 +1,6 @@
1
+ require 'rubygems'
2
+
3
+ # Set up gems listed in the Gemfile.
4
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
5
+
6
+ require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
@@ -0,0 +1,22 @@
1
+ # SQLite version 3.x
2
+ # gem install sqlite3-ruby (not necessary on OS X Leopard)
3
+ development:
4
+ adapter: sqlite3
5
+ database: ":memory:"
6
+ pool: 5
7
+ timeout: 5000
8
+
9
+ # Warning: The database defined as "test" will be erased and
10
+ # re-generated from your development database when you run "rake".
11
+ # Do not set this db to the same as development or production.
12
+ test:
13
+ adapter: sqlite3
14
+ database: ":memory:"
15
+ pool: 5
16
+ timeout: 5000
17
+
18
+ production:
19
+ adapter: sqlite3
20
+ database: db/production.sqlite3
21
+ pool: 5
22
+ timeout: 5000
@@ -0,0 +1,5 @@
1
+ # Load the rails application
2
+ require File.expand_path('../application', __FILE__)
3
+
4
+ # Initialize the rails application
5
+ RailsApp::Application.initialize!
@@ -0,0 +1,30 @@
1
+ RailsApp::Application.configure do
2
+ # Settings specified here will take precedence over those in config/application.rb
3
+
4
+ # In the development environment your application's code is reloaded on
5
+ # every request. This slows down response time but is perfect for development
6
+ # since you don't have to restart the web server when you make code changes.
7
+ config.cache_classes = false
8
+
9
+ # Log error messages when you accidentally call methods on nil.
10
+ config.whiny_nils = true
11
+
12
+ # Show full error reports and disable caching
13
+ config.consider_all_requests_local = true
14
+ config.action_controller.perform_caching = false
15
+
16
+ # Don't care if the mailer can't send
17
+ config.action_mailer.raise_delivery_errors = false
18
+
19
+ # Print deprecation notices to the Rails logger
20
+ config.active_support.deprecation = :log
21
+
22
+ # Only use best-standards-support built into browsers
23
+ config.action_dispatch.best_standards_support = :builtin
24
+
25
+ # Do not compress assets
26
+ config.assets.compress = false
27
+
28
+ # Expands the lines which load the assets
29
+ config.assets.debug = true
30
+ end