devise_password_sharing_extension 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +163 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +33 -0
- data/Rakefile +42 -0
- data/VERSION +1 -0
- data/app/models/login_event.rb +15 -0
- data/devise_password_sharing_extension.gemspec +166 -0
- data/lib/devise_password_sharing_extension.rb +20 -0
- data/lib/devise_password_sharing_extension/hooks/password_sharing.rb +12 -0
- data/lib/devise_password_sharing_extension/models/password_sharing.rb +51 -0
- data/lib/devise_password_sharing_extension/rails.rb +4 -0
- data/lib/devise_password_sharing_extension/schema.rb +9 -0
- data/lib/devise_password_sharing_extension/version.rb +3 -0
- data/lib/generators/active_record/devise_password_sharing_extension_generator.rb +14 -0
- data/lib/generators/active_record/templates/migration.rb +26 -0
- data/lib/generators/devise_password_sharing_extension/devise_password_sharing_extension_generator.rb +16 -0
- data/lib/generators/devise_password_sharing_extension/install_generator.rb +23 -0
- data/lib/generators/devise_password_sharing_extension/templates/white_listed_ips.yml +1 -0
- data/spec/models/login_event_spec.rb +10 -0
- data/spec/models/password_sharing_spec.rb +54 -0
- data/spec/orm/active_record.rb +3 -0
- data/spec/rails_app/.gitignore +5 -0
- data/spec/rails_app/app/assets/images/rails.png +0 -0
- data/spec/rails_app/app/assets/javascripts/application.js +9 -0
- data/spec/rails_app/app/assets/stylesheets/application.css +7 -0
- data/spec/rails_app/app/controllers/application_controller.rb +3 -0
- data/spec/rails_app/app/controllers/home_controller.rb +9 -0
- data/spec/rails_app/app/helpers/application_helper.rb +2 -0
- data/spec/rails_app/app/mailers/.gitkeep +0 -0
- data/spec/rails_app/app/models/user.rb +7 -0
- data/spec/rails_app/app/views/home/index.html.erb +1 -0
- data/spec/rails_app/app/views/home/secure.html.erb +1 -0
- data/spec/rails_app/app/views/layouts/application.html.erb +14 -0
- data/spec/rails_app/config.ru +4 -0
- data/spec/rails_app/config/application.rb +19 -0
- data/spec/rails_app/config/boot.rb +6 -0
- data/spec/rails_app/config/database.yml +22 -0
- data/spec/rails_app/config/environment.rb +5 -0
- data/spec/rails_app/config/environments/development.rb +30 -0
- data/spec/rails_app/config/environments/production.rb +60 -0
- data/spec/rails_app/config/environments/test.rb +39 -0
- data/spec/rails_app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/rails_app/config/initializers/devise.rb +184 -0
- data/spec/rails_app/config/initializers/inflections.rb +10 -0
- data/spec/rails_app/config/initializers/mime_types.rb +5 -0
- data/spec/rails_app/config/initializers/secret_token.rb +7 -0
- data/spec/rails_app/config/initializers/session_store.rb +8 -0
- data/spec/rails_app/config/initializers/wrap_parameters.rb +14 -0
- data/spec/rails_app/config/locales/en.yml +5 -0
- data/spec/rails_app/config/mongoid.yml +12 -0
- data/spec/rails_app/config/routes.rb +5 -0
- data/spec/rails_app/config/white_listed_ips.yml +1 -0
- data/spec/rails_app/db/migrate/20111019173200_create_tables.rb +29 -0
- data/spec/rails_app/db/seeds.rb +7 -0
- data/spec/rails_app/script/rails +6 -0
- data/spec/request_spec.rb +49 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/support/fixtures.rb +32 -0
- 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,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
|
data/lib/generators/devise_password_sharing_extension/devise_password_sharing_extension_generator.rb
ADDED
@@ -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 @@
|
|
1
|
+
- '127.0.0.1'
|
@@ -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
|
Binary file
|
@@ -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
|
+
*/
|
File without changes
|
@@ -0,0 +1 @@
|
|
1
|
+
<h1>HomeController#index</h1>
|
@@ -0,0 +1 @@
|
|
1
|
+
secure
|
@@ -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,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,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
|