first_click_free 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +97 -0
  4. data/Rakefile +30 -0
  5. data/config/domains.yml +195 -0
  6. data/lib/first_click_free.rb +42 -0
  7. data/lib/first_click_free/concerns/controller.rb +108 -0
  8. data/lib/first_click_free/exceptions/subsequent_access_exception.rb +6 -0
  9. data/lib/first_click_free/helpers/google.rb +41 -0
  10. data/lib/first_click_free/helpers/path.rb +22 -0
  11. data/lib/first_click_free/helpers/referrer.rb +26 -0
  12. data/lib/first_click_free/version.rb +3 -0
  13. data/lib/tasks/first_click_free_tasks.rake +24 -0
  14. data/spec/concerns/controller_spec.rb +128 -0
  15. data/spec/dummy/README.rdoc +28 -0
  16. data/spec/dummy/Rakefile +6 -0
  17. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  18. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  19. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  20. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  21. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  22. data/spec/dummy/bin/bundle +3 -0
  23. data/spec/dummy/bin/rails +4 -0
  24. data/spec/dummy/bin/rake +4 -0
  25. data/spec/dummy/config.ru +4 -0
  26. data/spec/dummy/config/application.rb +23 -0
  27. data/spec/dummy/config/boot.rb +5 -0
  28. data/spec/dummy/config/database.yml +25 -0
  29. data/spec/dummy/config/environment.rb +5 -0
  30. data/spec/dummy/config/environments/development.rb +29 -0
  31. data/spec/dummy/config/environments/production.rb +80 -0
  32. data/spec/dummy/config/environments/test.rb +36 -0
  33. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  34. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  35. data/spec/dummy/config/initializers/inflections.rb +16 -0
  36. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  37. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  38. data/spec/dummy/config/initializers/session_store.rb +3 -0
  39. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  40. data/spec/dummy/config/locales/en.yml +23 -0
  41. data/spec/dummy/config/routes.rb +56 -0
  42. data/spec/dummy/db/development.sqlite3 +0 -0
  43. data/spec/dummy/db/schema.rb +16 -0
  44. data/spec/dummy/db/test.sqlite3 +0 -0
  45. data/spec/dummy/log/development.log +18 -0
  46. data/spec/dummy/log/test.log +548 -0
  47. data/spec/dummy/public/404.html +58 -0
  48. data/spec/dummy/public/422.html +58 -0
  49. data/spec/dummy/public/500.html +57 -0
  50. data/spec/dummy/public/favicon.ico +0 -0
  51. data/spec/exceptions/subsequent_access_exception_spec.rb +5 -0
  52. data/spec/first_click_free_spec.rb +46 -0
  53. data/spec/helpers/google_spec.rb +104 -0
  54. data/spec/helpers/path_spec.rb +35 -0
  55. data/spec/helpers/referrer_spec.rb +39 -0
  56. data/spec/spec_helper.rb +18 -0
  57. metadata +185 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 77ff2ae4a9b5357d67a37ed73451b4c62d9dec19
4
+ data.tar.gz: e5e39506a08daf5c744fa21cb6e0ccd5a9aa7f3e
5
+ SHA512:
6
+ metadata.gz: d2788937dec98a853231e51023d0e5df183041f19efc4b35d45fe1fb1de5358ffd570943cd34579d62884b6df95cb6e0811607f698968cd0e16387455e3d199c
7
+ data.tar.gz: 1cb34f11618f073a0ebae026a2df2f06403f09b42d22b6076255bcc3c15b9c55a017d40716c4514ec94ee10e9886177f7359b1c8a2c1f0f7525b0ce1883a664c
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2013 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.md ADDED
@@ -0,0 +1,97 @@
1
+ First Click Free
2
+ ===
3
+
4
+ [![Build Status](https://travis-ci.org/3months/first_click_free.svg)](https://travis-ci.org/3months/first_click_free)
5
+
6
+ [First Click Free](https://support.google.com/webmasters/answer/74536?hl=en) is one of three methods recommended officially by Google for improving search rankings for subscription, paywall and other restricted access sites.
7
+
8
+ The technique involves making available the first n 'clicks', or pageviews for each user, and giving Google's crawling bots full access to index site content. This way, users coming from social media sites and search results are able to 'preview' the content they have been looking for, and search engines are able to fully index a site, even though it would normally require registration and/or payment.
9
+
10
+ This gem aims to simplify and centralize the process of adding first click free support to a Rails application. It's fully tested, and used in production with many of our clients at [3months.com](https://3months.com).
11
+
12
+ Use
13
+ ---
14
+
15
+ **Rails is required to use this gem**
16
+
17
+ 1. Install: Add `gem 'first_click_free'` to your `Gemfile` and run `bundle install`
18
+ 2. For any controller that should have first click free activated, simply call `allow_first_click_free` in your controller definition, for example:
19
+ ``` ruby
20
+ class PagesController
21
+ allow_first_click_free
22
+ end
23
+ ```
24
+ You may pass through `only`, or `except` to restrict which actions will have first click free turned on, or you can put this in your `ApplicationController` to turn on first click free for all controllers.
25
+ 3. Handle the exception that is raised when a user tries to visit more than one page without being signed in:
26
+ ``` ruby
27
+ rescue_from FirstClickFree::Exceptions::SubsequentAccessException do
28
+ redirect_to root_path, alert: 'Please sign in to continue.'
29
+ end
30
+ ```
31
+ 4. Good to go!
32
+
33
+ Optional use
34
+ ---
35
+
36
+ 1. You may also permit certain individual paths to bypass first click free by setting them in an initializer like so
37
+ `FirstClickFree.permitted_paths = [ '/about', '/contact' ]`. These paths do not set or reset the users' first click free status.
38
+ 2. By default users will get just 1 free click, however by setting `FirstClickFree.free_clicks` in an initializer you can allow n free clicks to content.
39
+ 3. A count of users' free clicks are available in request.env["first_click_free_count"].
40
+
41
+ #### Registered Users
42
+
43
+ If you have registered users that should always be allowed through (they shouldn't be affected by any first click free rules), then you can override the `user_for_first_click_free` method in `ApplicationController`, or any of your controllers using `allow_first_click_free`. This method should return either a falsy value if no-one is signed in, or the current user.
44
+
45
+ Example:
46
+
47
+ ``` ruby
48
+ class ApplicationController
49
+ # …snip
50
+
51
+ protected
52
+
53
+ def user_for_first_click_free
54
+ current_member
55
+ end
56
+ end
57
+ ```
58
+
59
+
60
+
61
+ How it works
62
+ ---
63
+
64
+ ##### For visitors
65
+
66
+ * When a user first lands in a controller marked as being first click free, a session variable is set.
67
+ * If that same user attempts to access any other URL marked as first click free, an exception is raised so that the application can redirect or display a message to that user.
68
+
69
+ ##### For visitors coming from a Google, Bing, or Yahoo search
70
+
71
+ * When a user's HTTP referrer matches a list of known search engine domains, the request is allowed to override any previously set first click free.
72
+ * It does not disable first click free, it just modifies which page that user may access.
73
+ * For example, if a user searches for a page on your site using Google, and clicks on the first result, that page will be marked as first click free for them - any subsequent clicks from that page will trigger the first click free error. If they go back to the search results though, and then click on the second result, that page will take the place of the first and they will be able to access that page as normal.
74
+
75
+
76
+ ##### For Google's indexing services
77
+
78
+ * If the requesting agent is recognized as a 'Googlebot', the request is allowed though as if they were a registered user, so that the content may be indexed.
79
+ * Googlebot recognition is based on two factors:
80
+ * User agent: Google's indexers request a page with a user agent string of 'Googlebot' - this is used as the first-level of checking to make sure the page should be displayed. A user-agent string can be spoofed though, so the second check is:
81
+ * DNS: A reverse DNS request is issued against the remote IP, to ensure that the hostname returned matches a 'googlebot.com' domain. A forward DNS request is then issued against the hostname, to ensure that it matches back up with the original IP address.
82
+
83
+ Contributing
84
+ ---
85
+
86
+ * Contributions are welcome!
87
+ * Please fork this repository, and run `bundle install` to install the development dependencies (RSpec and SQLite).
88
+ * Create a new git branch to contain your changes. Try and limit commits to this branch to the specific changes you want to be merged in.
89
+ * Push up your branch to Github, and create a pull request. Please don't change the gem version or anything, I can do that bit.
90
+ * All pull requests will be reviewed ASAP. If it's not ready for merge, I'll help you to get it to a stage where it is!
91
+
92
+
93
+
94
+ License
95
+ ---
96
+
97
+ This project rocks and uses MIT-LICENSE.
data/Rakefile ADDED
@@ -0,0 +1,30 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'FirstClickFree'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+ load 'tasks/first_click_free_tasks.rake'
21
+
22
+ Bundler::GemHelper.install_tasks
23
+
24
+ require 'rspec/core'
25
+ require 'rspec/core/rake_task'
26
+
27
+ desc "Run all specs in spec directory (excluding plugin specs)"
28
+ RSpec::Core::RakeTask.new(:spec => 'app:db:test:prepare')
29
+
30
+ task :default => :spec
@@ -0,0 +1,195 @@
1
+ ---
2
+ - .google.com
3
+ - .google.ad
4
+ - .google.ae
5
+ - .google.com.af
6
+ - .google.com.ag
7
+ - .google.com.ai
8
+ - .google.al
9
+ - .google.am
10
+ - .google.co.ao
11
+ - .google.com.ar
12
+ - .google.as
13
+ - .google.at
14
+ - .google.com.au
15
+ - .google.az
16
+ - .google.ba
17
+ - .google.com.bd
18
+ - .google.be
19
+ - .google.bf
20
+ - .google.bg
21
+ - .google.com.bh
22
+ - .google.bi
23
+ - .google.bj
24
+ - .google.com.bn
25
+ - .google.com.bo
26
+ - .google.com.br
27
+ - .google.bs
28
+ - .google.bt
29
+ - .google.co.bw
30
+ - .google.by
31
+ - .google.com.bz
32
+ - .google.ca
33
+ - .google.cd
34
+ - .google.cf
35
+ - .google.cg
36
+ - .google.ch
37
+ - .google.ci
38
+ - .google.co.ck
39
+ - .google.cl
40
+ - .google.cm
41
+ - .google.cn
42
+ - .google.com.co
43
+ - .google.co.cr
44
+ - .google.com.cu
45
+ - .google.cv
46
+ - .google.com.cy
47
+ - .google.cz
48
+ - .google.de
49
+ - .google.dj
50
+ - .google.dk
51
+ - .google.dm
52
+ - .google.com.do
53
+ - .google.dz
54
+ - .google.com.ec
55
+ - .google.ee
56
+ - .google.com.eg
57
+ - .google.es
58
+ - .google.com.et
59
+ - .google.fi
60
+ - .google.com.fj
61
+ - .google.fm
62
+ - .google.fr
63
+ - .google.ga
64
+ - .google.ge
65
+ - .google.gg
66
+ - .google.com.gh
67
+ - .google.com.gi
68
+ - .google.gl
69
+ - .google.gm
70
+ - .google.gp
71
+ - .google.gr
72
+ - .google.com.gt
73
+ - .google.gy
74
+ - .google.com.hk
75
+ - .google.hn
76
+ - .google.hr
77
+ - .google.ht
78
+ - .google.hu
79
+ - .google.co.id
80
+ - .google.ie
81
+ - .google.co.il
82
+ - .google.im
83
+ - .google.co.in
84
+ - .google.iq
85
+ - .google.is
86
+ - .google.it
87
+ - .google.je
88
+ - .google.com.jm
89
+ - .google.jo
90
+ - .google.co.jp
91
+ - .google.co.ke
92
+ - .google.com.kh
93
+ - .google.ki
94
+ - .google.kg
95
+ - .google.co.kr
96
+ - .google.com.kw
97
+ - .google.kz
98
+ - .google.la
99
+ - .google.com.lb
100
+ - .google.li
101
+ - .google.lk
102
+ - .google.co.ls
103
+ - .google.lt
104
+ - .google.lu
105
+ - .google.lv
106
+ - .google.com.ly
107
+ - .google.co.ma
108
+ - .google.md
109
+ - .google.me
110
+ - .google.mg
111
+ - .google.mk
112
+ - .google.ml
113
+ - .google.com.mm
114
+ - .google.mn
115
+ - .google.ms
116
+ - .google.com.mt
117
+ - .google.mu
118
+ - .google.mv
119
+ - .google.mw
120
+ - .google.com.mx
121
+ - .google.com.my
122
+ - .google.co.mz
123
+ - .google.com.na
124
+ - .google.com.nf
125
+ - .google.com.ng
126
+ - .google.com.ni
127
+ - .google.ne
128
+ - .google.nl
129
+ - .google.no
130
+ - .google.com.np
131
+ - .google.nr
132
+ - .google.nu
133
+ - .google.co.nz
134
+ - .google.com.om
135
+ - .google.com.pa
136
+ - .google.com.pe
137
+ - .google.com.pg
138
+ - .google.com.ph
139
+ - .google.com.pk
140
+ - .google.pl
141
+ - .google.pn
142
+ - .google.com.pr
143
+ - .google.ps
144
+ - .google.pt
145
+ - .google.com.py
146
+ - .google.com.qa
147
+ - .google.ro
148
+ - .google.ru
149
+ - .google.rw
150
+ - .google.com.sa
151
+ - .google.com.sb
152
+ - .google.sc
153
+ - .google.se
154
+ - .google.com.sg
155
+ - .google.sh
156
+ - .google.si
157
+ - .google.sk
158
+ - .google.com.sl
159
+ - .google.sn
160
+ - .google.so
161
+ - .google.sm
162
+ - .google.st
163
+ - .google.com.sv
164
+ - .google.td
165
+ - .google.tg
166
+ - .google.co.th
167
+ - .google.com.tj
168
+ - .google.tk
169
+ - .google.tl
170
+ - .google.tm
171
+ - .google.tn
172
+ - .google.to
173
+ - .google.com.tr
174
+ - .google.tt
175
+ - .google.com.tw
176
+ - .google.co.tz
177
+ - .google.com.ua
178
+ - .google.co.ug
179
+ - .google.co.uk
180
+ - .google.com.uy
181
+ - .google.co.uz
182
+ - .google.com.vc
183
+ - .google.co.ve
184
+ - .google.vg
185
+ - .google.co.vi
186
+ - .google.com.vn
187
+ - .google.vu
188
+ - .google.ws
189
+ - .google.rs
190
+ - .google.co.za
191
+ - .google.co.zm
192
+ - .google.co.zw
193
+ - .google.cat
194
+ - .bing.com
195
+ - .yahoo.com
@@ -0,0 +1,42 @@
1
+ module FirstClickFree
2
+ require 'first_click_free/exceptions/subsequent_access_exception'
3
+ require 'first_click_free/helpers/google'
4
+ require 'first_click_free/helpers/path'
5
+ require 'first_click_free/helpers/referrer'
6
+ require 'first_click_free/concerns/controller'
7
+
8
+ class << self
9
+
10
+ require 'yaml'
11
+
12
+ attr_accessor :test_mode, :permitted_paths, :free_clicks
13
+
14
+ def root
15
+ File.expand_path(File.join(File.dirname(__FILE__), '..'))
16
+ end
17
+
18
+ def permitted_domains
19
+ @permitted_domains ||= YAML.load_file(File.join(FirstClickFree.root, 'config', 'domains.yml'))
20
+ end
21
+
22
+ def permitted_paths
23
+ @permitted_paths || []
24
+ end
25
+
26
+ def free_clicks
27
+ @free_clicks || 1 # default is 1 click free
28
+ end
29
+
30
+ def test_mode
31
+ @test_mode || false
32
+ end
33
+ end
34
+
35
+ class Railtie < Rails::Railtie
36
+ initializer "first_click_free.action_controller" do
37
+ ActiveSupport.on_load(:action_controller) do
38
+ include FirstClickFree::Concerns::Controller
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,108 @@
1
+ require 'active_support/concern'
2
+
3
+ module FirstClickFree
4
+ module Concerns
5
+ module Controller
6
+ extend ActiveSupport::Concern
7
+ include FirstClickFree::Helpers::Google
8
+ include FirstClickFree::Helpers::Path
9
+ include FirstClickFree::Helpers::Referrer
10
+
11
+ module ClassMethods
12
+
13
+ # Public: Turn 'on' first click free for this controller.
14
+ #
15
+ # options - The options to pass through to `before_filter`.
16
+ # Common options are `only` and `except`, to limit
17
+ # which actions are affected.
18
+ #
19
+ # DEPRECATION: `before_filter` is deprecated in favour of `before_action`
20
+ # in Rails 4.
21
+ #
22
+ # Returns the result of the call to `before_filter`.
23
+ def allow_first_click_free(options = {})
24
+ before_filter :record_or_reject_first_click_free!, options
25
+ end
26
+
27
+ # Public: Skip first click free for a controller or action.
28
+ #
29
+ # options - The options to pass through to `skip_before_filter`,
30
+ # for example to limit which actions should be skipped.
31
+ #
32
+ # DEPRECATION: `skip_before_filter` is deprecated in favour of
33
+ # with `skip_before_action` in Rails 4.
34
+ #
35
+ # Returns the result of the call to `skip_before_filter`.
36
+ def skip_first_click_free(options = {})
37
+ skip_before_filter :record_or_reject_first_click_free!, options
38
+ end
39
+
40
+ end
41
+
42
+ # Public: Return the user to use when bypassing first click free
43
+ # (i.e. there is a user signed in at the time).
44
+ #
45
+ # This method is intended to be overridden in the controller
46
+ # with the appropriate method call (e.g. current_user).
47
+ #
48
+ # Returns nil, which will cause first click free to always be active.
49
+ def user_for_first_click_free
50
+ nil
51
+ end
52
+
53
+
54
+ # Public: Either record a first click free request, or reject
55
+ # the request for a subsequent content access.
56
+ #
57
+ # Raises FirstClickFree::Exceptions::SubsequentAccessException if
58
+ # a first click has already been recorded for this user.
59
+ #
60
+ # Returns true if the referrer User agent is GoogleBot, or
61
+ # if this is the first click recorded for this session.
62
+ def record_or_reject_first_click_free!
63
+ # Always allow requests from Googlebot
64
+ return true if googlebot?
65
+
66
+ # Always allow requests from authenticated users
67
+ return true if user_for_first_click_free
68
+
69
+ # Always allow requests to particular paths
70
+ return true if permitted_path?
71
+
72
+ # Reset first click free if the domain is permitted
73
+ # (new first click free will be set)
74
+ reset_first_click_free! if permitted_domain?
75
+
76
+ if session[:first_click] && session[:first_click].include?(checksum(url_for))
77
+ # already visited, can visit again
78
+ elsif session[:first_click] && session[:first_click].length < FirstClickFree.free_clicks
79
+ # new page but within free click limit
80
+ session[:first_click] << checksum(url_for)
81
+ elsif session[:first_click] && session[:first_click].length == FirstClickFree.free_clicks
82
+ raise FirstClickFree::Exceptions::SubsequentAccessException
83
+ else
84
+ # first click!
85
+ session[:first_click] = [ checksum(url_for) ]
86
+ end
87
+ request.env["first_click_free_count"] = session[:first_click].length
88
+ return true
89
+ end
90
+
91
+ private
92
+
93
+ # Private: Reset first click free session.
94
+ #
95
+ # Returns the value set in the first click session (nil)
96
+ def reset_first_click_free!
97
+ session.delete(:first_click)
98
+ end
99
+
100
+ # Private: Create a checksum string.
101
+ #
102
+ # Returns a checksum of the url as a string
103
+ def checksum(url)
104
+ Zlib.adler32(url).to_s
105
+ end
106
+ end
107
+ end
108
+ end