honeypot 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 ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Seamus Abshere
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,108 @@
1
+ = honeypot
2
+
3
+ Catch bad guys when they stick their hands in the honey.
4
+
5
+ == models
6
+
7
+ copy gemdir/models/*.rb to your models dir
8
+
9
+ == requestables interface
10
+
11
+ requestables (like User, Vote, etc.) should define #actor
12
+
13
+ class User < ActiveRecord::Base
14
+ include RemoteRequestLogger
15
+ def actor; self; end
16
+ end
17
+
18
+ class Vote < ActiveRecord::Base
19
+ belongs_to :user
20
+ include RemoteRequestLogger
21
+ def actor; user; end
22
+ end
23
+
24
+ == usage in controllers
25
+
26
+ put this in your application controller so it has a maximum chance of grabbing the ip
27
+
28
+ class ApplicationController < ActionController::Base
29
+ # this is safe enough for engineyard cloud, where internal ip addresses always start with 10.
30
+ def ensure_remote_ip
31
+ session[:remote_ip] = request.remote_ip unless request.remote_ip.starts_with?('10.')
32
+ end
33
+ prepend_before_filter :ensure_remote_ip
34
+ end
35
+
36
+ then you invoke log_remote_request with both session and request
37
+
38
+ class SessionsController < ApplicationController
39
+ def create
40
+ # @user = [...]
41
+ @user.log_remote_request session, request
42
+ end
43
+ end
44
+
45
+ == migration
46
+
47
+ create_table "remote_hosts" do |t|
48
+ t.string "ip_address"
49
+ t.string "hostname"
50
+ t.datetime "created_at"
51
+ t.datetime "updated_at"
52
+ t.float "latitude"
53
+ t.float "longitude"
54
+ t.string "city"
55
+ t.string "country_code"
56
+ t.string "state_name"
57
+ end
58
+ add_index "remote_hosts", ["ip_address"], :name => "index_remote_hosts_on_ip_address"
59
+ create_table "remote_requests" do |t|
60
+ t.integer "requestable_id"
61
+ t.string "requestable_type"
62
+ t.integer "remote_host_id"
63
+ t.integer "hits"
64
+ t.string "last_http_referer"
65
+ t.string "last_request_uri"
66
+ t.datetime "created_at"
67
+ t.datetime "updated_at"
68
+ end
69
+ add_index "remote_requests", ["remote_host_id"], :name => "index_remote_requests_on_remote_host_id"
70
+ add_index "remote_requests", ["requestable_type", "requestable_id"], :name => "index_remote_requests_on_requestable"
71
+
72
+ == helper for active-scaffold
73
+
74
+ def remote_requests_column(record)
75
+ str = '<ul>'
76
+ str << record.remote_requests.map { |x| content_tag :li, x.to_label }.join
77
+ str << '</ul>'
78
+ str
79
+ end
80
+
81
+ == remote hosts controller with active-scaffold
82
+
83
+ class RemoteHostsController < ApplicationController
84
+ allow_access :admin
85
+ layout 'admin'
86
+ helper :remote_hosts
87
+ active_scaffold :remote_host do |config|
88
+ config.list.per_page = 100
89
+ config.actions.exclude :search, :create, :update, :delete
90
+ config.columns.add :remote_requests_count
91
+ config.columns[:remote_requests_count].sort_by :sql => "(SELECT COUNT(remote_requests.id) FROM remote_requests WHERE remote_requests.remote_host_id = remote_hosts.id)"
92
+ config.columns.add :last_remote_request_at
93
+ config.columns[:last_remote_request_at].sort_by :sql => "(SELECT MAX(remote_requests.updated_at) FROM remote_requests WHERE remote_requests.remote_host_id = remote_hosts.id)"
94
+ config.list.columns = %w{
95
+ hostname
96
+ ip_address
97
+ country_code
98
+ state_name
99
+ city
100
+ remote_requests_count
101
+ last_remote_request_at
102
+ }
103
+ end
104
+ end
105
+
106
+ == Copyright
107
+
108
+ Copyright (c) 2010 Seamus Abshere. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,54 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "honeypot"
8
+ gem.summary = %Q{Track remote requests to catch fraud.}
9
+ gem.description = %Q{Catch bad guys when they stick their hands in the honey.}
10
+ gem.email = "seamus@abshere.net"
11
+ gem.homepage = "http://github.com/seamusabshere/honeypot"
12
+ gem.authors = ["Seamus Abshere"]
13
+ gem.add_dependency 'fast_timestamp', '0.0.4'
14
+ gem.add_dependency 'geokit', '1.5.0'
15
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
+ end
21
+
22
+ require 'rake/testtask'
23
+ Rake::TestTask.new(:test) do |test|
24
+ test.libs << 'lib' << 'test'
25
+ test.pattern = 'test/**/test_*.rb'
26
+ test.verbose = true
27
+ end
28
+
29
+ begin
30
+ require 'rcov/rcovtask'
31
+ Rcov::RcovTask.new do |test|
32
+ test.libs << 'test'
33
+ test.pattern = 'test/**/test_*.rb'
34
+ test.verbose = true
35
+ end
36
+ rescue LoadError
37
+ task :rcov do
38
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
39
+ end
40
+ end
41
+
42
+ task :test => :check_dependencies
43
+
44
+ task :default => :test
45
+
46
+ require 'rake/rdoctask'
47
+ Rake::RDocTask.new do |rdoc|
48
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
49
+
50
+ rdoc.rdoc_dir = 'rdoc'
51
+ rdoc.title = "honeypot #{version}"
52
+ rdoc.rdoc_files.include('README*')
53
+ rdoc.rdoc_files.include('lib/**/*.rb')
54
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/honeypot.gemspec ADDED
@@ -0,0 +1,60 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{honeypot}
8
+ s.version = "0.0.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Seamus Abshere"]
12
+ s.date = %q{2010-02-02}
13
+ s.description = %q{Catch bad guys when they stick their hands in the honey.}
14
+ s.email = %q{seamus@abshere.net}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "honeypot.gemspec",
27
+ "lib/honeypot.rb",
28
+ "lib/remote_host.rb",
29
+ "lib/remote_request.rb",
30
+ "lib/remote_request_logger.rb",
31
+ "test/helper.rb",
32
+ "test/test_honeypot.rb"
33
+ ]
34
+ s.homepage = %q{http://github.com/seamusabshere/honeypot}
35
+ s.rdoc_options = ["--charset=UTF-8"]
36
+ s.require_paths = ["lib"]
37
+ s.rubygems_version = %q{1.3.5}
38
+ s.summary = %q{Track remote requests to catch fraud.}
39
+ s.test_files = [
40
+ "test/helper.rb",
41
+ "test/test_honeypot.rb"
42
+ ]
43
+
44
+ if s.respond_to? :specification_version then
45
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
46
+ s.specification_version = 3
47
+
48
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
49
+ s.add_runtime_dependency(%q<fast_timestamp>, ["= 0.0.4"])
50
+ s.add_runtime_dependency(%q<geokit>, ["= 1.5.0"])
51
+ else
52
+ s.add_dependency(%q<fast_timestamp>, ["= 0.0.4"])
53
+ s.add_dependency(%q<geokit>, ["= 1.5.0"])
54
+ end
55
+ else
56
+ s.add_dependency(%q<fast_timestamp>, ["= 0.0.4"])
57
+ s.add_dependency(%q<geokit>, ["= 1.5.0"])
58
+ end
59
+ end
60
+
data/lib/honeypot.rb ADDED
@@ -0,0 +1,4 @@
1
+ # auto-loaded by rails
2
+ # require 'remote_host'
3
+ # require 'remote_request'
4
+ # require 'remote_request_logger'
@@ -0,0 +1,74 @@
1
+ class RemoteHost < ActiveRecord::Base
2
+ has_many :remote_requests, :dependent => :destroy
3
+
4
+ # start active-scaffold interface
5
+ def remote_requests_count
6
+ remote_requests.count
7
+ end
8
+ def last_remote_request_at
9
+ remote_requests.calculate :max, :updated_at
10
+ end
11
+ # end active-scaffold interface
12
+
13
+ include FastTimestamp
14
+
15
+ def lookup_hostname
16
+ result = Resolv.getname ip_address
17
+ if result.present?
18
+ update_attribute :hostname, result
19
+ untimestamp! :failed_to_lookup_hostname
20
+ else
21
+ timestamp! :failed_to_lookup_hostname
22
+ end
23
+ rescue # Resolv::ResolvError
24
+ timestamp! :failed_to_lookup_hostname
25
+ end
26
+
27
+ def geolocate
28
+ location = Geokit::Geocoders::MultiGeocoder.geocode ip_address
29
+
30
+ # take what we can get
31
+ if location.success
32
+ self.latitude = location.lat if location.lat.present?
33
+ self.longitude = location.lng if location.lng.present?
34
+ self.country_code = location.country_code if location.country_code.present?
35
+ # state -> state_name
36
+ self.state_name = location.state if location.state.present?
37
+ self.city = location.city if location.city.present?
38
+ save!
39
+ end
40
+
41
+ # but only call it a success if we get latitude
42
+ # this way if we don't, it will check again in 5 days
43
+ if location.success and location.latitude.present?
44
+ untimestamp! :failed_to_geolocate
45
+ else
46
+ timestamp! :failed_to_geolocate
47
+ end
48
+ rescue
49
+ timestamp! :failed_to_geolocate
50
+ end
51
+
52
+ def delayed_lookup_hostname
53
+ if (rand(20) == 1 or hostname.blank?) and (
54
+ !timestamped?(:failed_to_lookup_hostname) or
55
+ timestamp_for(:failed_to_lookup_hostname) < 5.days.ago
56
+ )
57
+ defined?(Delayed::Job) ? send_later(:lookup_hostname) : lookup_hostname
58
+ end
59
+ true
60
+ end
61
+
62
+ def delayed_geolocate
63
+ if (rand(20) == 1 or latitude.blank?) and (
64
+ !timestamped?(:failed_to_geolocate) or
65
+ timestamp_for(:failed_to_geolocate) < 5.days.ago
66
+ )
67
+ defined?(Delayed::Job) ? send_later(:geolocate) : geolocate
68
+ end
69
+ true
70
+ end
71
+
72
+ after_save :delayed_lookup_hostname
73
+ after_save :delayed_geolocate
74
+ end
@@ -0,0 +1,6 @@
1
+ class RemoteRequest < ActiveRecord::Base
2
+ belongs_to :requestable, :polymorphic => :true
3
+ belongs_to :remote_host
4
+
5
+ delegate :to_label, :to => :requestable
6
+ end
@@ -0,0 +1,36 @@
1
+ module RemoteRequestLogger
2
+ def self.included(base)
3
+ base.class_eval do
4
+ has_many :remote_requests, :as => :requestable, :dependent => :destroy
5
+ has_many :remote_hosts, :through => :remote_requests, :uniq => true
6
+ end
7
+ end
8
+
9
+ def log_remote_request(session, request)
10
+ effective_ip_address = session[:remote_ip].present? ? session[:remote_ip] : request.remote_ip
11
+ x = remote_requests.find_or_create_by_remote_host_id RemoteHost.find_or_create_by_ip_address(effective_ip_address).id
12
+ x.last_http_referer = request.referer if request.referer.present?
13
+ x.last_request_uri = request.request_uri if request.request_uri.present?
14
+ x.increment :hits
15
+ x.save!
16
+ true
17
+ end
18
+
19
+ def related_requestables(seen_remote_host_ids = [])
20
+ set = Set.new
21
+ conditions = seen_remote_host_ids.present? ? [ "remote_hosts.id NOT IN (?)", seen_remote_host_ids ] : nil
22
+ remote_hosts.scoped(:conditions => conditions).find_in_batches do |batch|
23
+ batch.each do |remote_host|
24
+ seen_remote_host_ids << remote_host.id
25
+ remote_host.remote_requests.all(:include => :requestable).each do |remote_request|
26
+ set << remote_request.requestable
27
+ end
28
+ end
29
+ end
30
+ if respond_to?(:actor) and actor != self
31
+ set += actor.related_requestables(seen_remote_host_ids)
32
+ end
33
+ set.delete self
34
+ set
35
+ end
36
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+
4
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ require 'honeypot'
7
+
8
+ class Test::Unit::TestCase
9
+ end
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ class TestHoneypot < Test::Unit::TestCase
4
+ def test_something_for_real
5
+ flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: honeypot
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Seamus Abshere
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-02-02 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: fast_timestamp
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - "="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.4
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: geokit
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.5.0
34
+ version:
35
+ description: Catch bad guys when they stick their hands in the honey.
36
+ email: seamus@abshere.net
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - LICENSE
43
+ - README.rdoc
44
+ files:
45
+ - .document
46
+ - .gitignore
47
+ - LICENSE
48
+ - README.rdoc
49
+ - Rakefile
50
+ - VERSION
51
+ - honeypot.gemspec
52
+ - lib/honeypot.rb
53
+ - lib/remote_host.rb
54
+ - lib/remote_request.rb
55
+ - lib/remote_request_logger.rb
56
+ - test/helper.rb
57
+ - test/test_honeypot.rb
58
+ has_rdoc: true
59
+ homepage: http://github.com/seamusabshere/honeypot
60
+ licenses: []
61
+
62
+ post_install_message:
63
+ rdoc_options:
64
+ - --charset=UTF-8
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: "0"
72
+ version:
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: "0"
78
+ version:
79
+ requirements: []
80
+
81
+ rubyforge_project:
82
+ rubygems_version: 1.3.5
83
+ signing_key:
84
+ specification_version: 3
85
+ summary: Track remote requests to catch fraud.
86
+ test_files:
87
+ - test/helper.rb
88
+ - test/test_honeypot.rb