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.
- 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
|