token_action 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/LICENSE +20 -0
  2. data/README.md +143 -0
  3. data/Rakefile +16 -0
  4. data/app/controllers/token_action/application_controller.rb +4 -0
  5. data/app/controllers/token_action/tokens_controller.rb +73 -0
  6. data/config/locales/en.yml +10 -0
  7. data/config/routes.rb +3 -0
  8. data/lib/generators/active_record/templates/migration.rb +15 -0
  9. data/lib/generators/active_record/token_action_generator.rb +25 -0
  10. data/lib/generators/mongoid/token_action_generator.rb +7 -0
  11. data/lib/generators/templates/README +10 -0
  12. data/lib/generators/templates/token_action.rb +25 -0
  13. data/lib/generators/token_action_generator.rb +35 -0
  14. data/lib/token_action/engine.rb +5 -0
  15. data/lib/token_action/mixins/model.rb +24 -0
  16. data/lib/token_action/mixins/token_generator.rb +19 -0
  17. data/lib/token_action/orm/active_record.rb +10 -0
  18. data/lib/token_action/orm/mongoid.rb +34 -0
  19. data/lib/token_action/version.rb +3 -0
  20. data/lib/token_action.rb +71 -0
  21. data/spec/controllers/token_action/tokens_controller_spec.rb +172 -0
  22. data/spec/dummy/Rakefile +7 -0
  23. data/spec/dummy/app/active_record/cat.rb +18 -0
  24. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  25. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  26. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  27. data/spec/dummy/app/controllers/hello_controller.rb +25 -0
  28. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  29. data/spec/dummy/app/mongoid/cat.rb +22 -0
  30. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  31. data/spec/dummy/config/application.rb +71 -0
  32. data/spec/dummy/config/boot.rb +15 -0
  33. data/spec/dummy/config/database.yml +18 -0
  34. data/spec/dummy/config/environment.rb +5 -0
  35. data/spec/dummy/config/environments/development.rb +37 -0
  36. data/spec/dummy/config/environments/production.rb +67 -0
  37. data/spec/dummy/config/environments/test.rb +41 -0
  38. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  39. data/spec/dummy/config/initializers/session_store.rb +8 -0
  40. data/spec/dummy/config/initializers/token_action.rb +5 -0
  41. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  42. data/spec/dummy/config/locales/en.yml +5 -0
  43. data/spec/dummy/config/locales/token_action.en.yml +26 -0
  44. data/spec/dummy/config/mongoid.yml +68 -0
  45. data/spec/dummy/config/routes.rb +9 -0
  46. data/spec/dummy/config.ru +4 -0
  47. data/spec/dummy/db/development.sqlite3 +0 -0
  48. data/spec/dummy/db/migrate/20130104151630_create_token_action_tokens.rb +15 -0
  49. data/spec/dummy/db/migrate/20130104201018_create_cats.rb +7 -0
  50. data/spec/dummy/log/development.log +16 -0
  51. data/spec/dummy/log/test.log +4739 -0
  52. data/spec/dummy/public/404.html +26 -0
  53. data/spec/dummy/public/422.html +26 -0
  54. data/spec/dummy/public/500.html +25 -0
  55. data/spec/dummy/public/favicon.ico +0 -0
  56. data/spec/dummy/script/rails +6 -0
  57. data/spec/factories.rb +6 -0
  58. data/spec/routing/token_action/tokens_routing_spec.rb +33 -0
  59. data/spec/spec_helper.rb +94 -0
  60. data/spec/token_action/mixins/model_spec.rb +13 -0
  61. data/spec/token_action/mixins/token_generator_spec.rb +11 -0
  62. data/spec/token_action/orm/active_record_spec.rb +6 -0
  63. data/spec/token_action/orm/mongoid_spec.rb +6 -0
  64. data/spec/token_action_spec.rb +67 -0
  65. metadata +394 -0
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2013 Open North Inc.
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,143 @@
1
+ # TokenAction
2
+
3
+ [![Build Status](https://secure.travis-ci.org/opennorth/token_action.png)](http://travis-ci.org/opennorth/token_action)
4
+ [![Dependency Status](https://gemnasium.com/opennorth/token_action.png)](https://gemnasium.com/opennorth/token_action)
5
+ [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/opennorth/token_action)
6
+
7
+ TokenAction lets you create tokens to be used to authenticate actions. For example:
8
+
9
+ * Create a token for a user to confirm an account
10
+ * Create a token for a subscriber to unsubscribe from notifications
11
+ * Create a token for a user to confirm a change of email address
12
+
13
+ ## Getting Started
14
+
15
+ TokenAction works with Rails 3.1 onwards. Add it to your Gemfile:
16
+
17
+ gem 'token_action'
18
+
19
+ Install the gem and configure your application to use TokenAction with:
20
+
21
+ bundle
22
+ bundle exec rails generate token_action
23
+
24
+ If you are using ActiveRecord, run the migration:
25
+
26
+ bundle exec rake db:migrate
27
+
28
+ ## Basic Usage
29
+
30
+ Create a shared secret to confirm a user account, for example:
31
+
32
+ token = TokenAction::Token.create! :kind => 'User', :args => [1, :confirm]
33
+ token.token # a 20-character alphanumeric string like "j7NtCaYfUpZXyDCseKG2"
34
+
35
+ You can then send the token with instructions to the user via email. When the user visits `/tokens/j7NtCaYfUpZXyDCseKG2/confirm`, TokenAction will look up the token. If not found, it will log an informational message to the Rails logger, set `flash[:alert]` and redirect to the `root_path`. If found, it will call the `redeem_token` method on the `User` class, passing in the arguments `1` and `:confirm`. You must implement the public `redeem_token` method. For example:
36
+
37
+ class User < ActiveRecord::Base
38
+ def self.redeem_token(id, meth)
39
+ User.find(id).send(meth)
40
+ end
41
+
42
+ def confirm
43
+ update_attribute :confirmed, true
44
+ end
45
+ end
46
+
47
+ If an exception is raised, TokenAction will log a warning message to the Rails logger, set `flash[:alert]` and redirect to the `root_path`. If no exception is raised, TokenAction will set `flash[:notice]` and redirect to the `root_path`. That's it!
48
+
49
+ ## Customization
50
+
51
+ You can customize the redirect URLs, the routes, the flash messages and the tokens controller.
52
+
53
+ ### Redirect URLs
54
+
55
+ To change the default success and failure URLs from `root_path`, edit the `config/initializers/token_action.rb` file created by `rails generate token_action`. You may also set success and failure URLs for each token, by creating tokens with `:success_url` and `:failure_url` arguments:
56
+
57
+ token = TokenAction::Token.create! :kind => 'Cat', :success_url => cat_path(1), :failure_url => '/oops'
58
+
59
+ **Note:** If you change your URL structure after creating tokens, TokenAction may attempt to redirect to an unroutable path. If a path is unroutable, TokenAction will redirect to another URL in this order of precedence:
60
+
61
+ * `Token#success_url`
62
+ * `TokenAction.success_url`
63
+ * `root_path`
64
+
65
+ If an exception was raised and a path is unroutable, it will redirect in this order of precedence:
66
+
67
+ * `Token#failure_url`
68
+ * `TokenAction.failure_url`
69
+ * `root_path`
70
+
71
+ ### Routes
72
+
73
+ The TokenAction generator will add `mount TokenAction::Engine => '/token_action'` to your routes, which defines:
74
+
75
+ get 'tokens/:token/*path', :to => 'tokens#redeem'
76
+
77
+ As such, you can write URLs like `tokens/xxx/confirm` or `tokens/xxx/unsubscribe` or `tokens/xxx/a/b/c`.
78
+
79
+ To customize the first part of the route, replace `mount TokenAction::Engine => '/token_action'` with something like:
80
+
81
+ get 'jetons/:token/*path', :to => 'token_action/tokens#redeem'
82
+
83
+ ### Flash messages
84
+
85
+ The TokenAction generator will create a `config/locales/token_action.en.yml` file with the default messages for when a token is not found, an exception is raised, or the action is performed successfully. You will probably want to change these messages depending on the action taken and the exception raised.
86
+
87
+ * If a token was redeemed by accessing a URL like `tokens/xxx/confirm`, TokenAction will look for a message in the scope `token_action.tokens.confirm`. If not found, it will default to the scope `token_action.tokens.default`.
88
+
89
+ * If a token was redeemed by accessing a long URL like `tokens/xxx/a/b/c`, TokenAction will look for a message in the scope `token_action.tokens.a.b.c`.
90
+
91
+ You may want to raise an exception if an action has already been performed or if it is no longer valid, in which case you may want the failure message to change according to the exception raised.
92
+
93
+ * If an `AlreadyConfirmed` exception is raised, TokenAction will look for a message in the scope `token_action.tokens.default.already_confirmed`. If not found, it will default to `token_action.tokens.default.failure`.
94
+
95
+ * If a namespaced `MyModule::AlreadyConfirmed` exception is raised, TokenAction will look for a message in the scope `token_action.tokens.default.my_module.already_confirmed`.
96
+
97
+ Putting it all together, if an `AlreadyConfirmed` exception is raised accessing a URL like `tokens/xxx/confirm`, TokenAction will look for messages in this order of preference:
98
+
99
+ * `token_action.tokens.confirm.already_confirmed`
100
+ * `token_action.tokens.default.already_confirmed`
101
+ * `token_action.tokens.confirm.failure`
102
+ * `token_action.tokens.default.failure`
103
+
104
+ ### Tokens controller
105
+
106
+ To customize the controller without monkey patching, create a new controller like:
107
+
108
+ class TokensController < TokenAction::TokensController
109
+ end
110
+
111
+ And replace the route with something like:
112
+
113
+ get 'tokens/:token/*path', :to => 'tokens#redeem'
114
+
115
+ You can then override the `redeem` method in your new controller.
116
+
117
+ ## Supported ORMs
118
+
119
+ * [ActiveRecord](https://rubygems.org/gems/activerecord)
120
+ * [Mongoid](https://rubygems.org/gems/mongoid)
121
+
122
+ It should be easy to add adapters for [MongoMapper](https://rubygems.org/gems/mongo_mapper), DataMapper, Sequel and CouchPotato. If you are using DataMapper or Sequel, you must use an ActiveModel compliance plugin:
123
+
124
+ * [DataMapper plugin](https://github.com/datamapper/dm-active_model)
125
+ * [Sequel plugin](http://sequel.rubyforge.org/rdoc-plugins/classes/Sequel/Plugins/ActiveModel.html)
126
+
127
+ If using Sequel, you will want to use the [orm_adapter-sequel](https://github.com/elskwid/orm_adapter-sequel) gem. If using CouchPotato, you will need to write your own adapter for ORM Adapter.
128
+
129
+ ## Caveats
130
+
131
+ * If you change the name of a class, update the `kind` attribute on its tokens to avoid making them unprocessable.
132
+ * Be careful when changing the behavior of a `redeem_token` method, to avoid making tokens unprocessable.
133
+
134
+ ## Roadmap
135
+
136
+ * The tokens controller only responds to HTML requests. Pull requests to add JSON and XML responders are welcome!
137
+ * The generators do not have specs.
138
+
139
+ ## Bugs? Questions?
140
+
141
+ This engine's main repository is on GitHub: [http://github.com/opennorth/token_action](http://github.com/opennorth/token_action), where your contributions, forks, bug reports, feature requests, and feedback are greatly welcomed.
142
+
143
+ Copyright (c) 2012 Open North Inc., released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task :default => :spec
8
+
9
+ begin
10
+ require 'yard'
11
+ YARD::Rake::YardocTask.new
12
+ rescue LoadError
13
+ task :yard do
14
+ abort 'YARD is not available. In order to run yard, you must: gem install yard'
15
+ end
16
+ end
@@ -0,0 +1,4 @@
1
+ module TokenAction
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,73 @@
1
+ module TokenAction
2
+ class TokensController < ApplicationController
3
+ def redeem
4
+ @token = TokenAction::Token.to_adapter.find_first(:token => params[:token])
5
+ if @token
6
+ begin
7
+ # Do this in the controller's scope to allow access to request methods.
8
+ ActiveSupport::Inflector.constantize(@token.kind).redeem_token(*@token.args)
9
+ redirect_to success_url, :notice => translate(:success)
10
+ rescue => e
11
+ key = ActiveSupport::Inflector.underscore(e.class).gsub('/', I18n.default_separator)
12
+ logger.warn "TokenAction failed to perform the action for the token '#{params[:token]}': #{e.message}"
13
+ redirect_to failure_url, :alert => translate(key, :failure)
14
+ end
15
+ else
16
+ logger.info "TokenAction failed to find the token '#{params[:token]}'"
17
+ redirect_to failure_url, :alert => translate(:not_found)
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ # Returns a translation.
24
+ #
25
+ # @param [Array] args translation keys
26
+ # @return [String] a translation
27
+ def translate(*args)
28
+ path = params[:path].gsub('/', I18n.default_separator)
29
+ keys = []
30
+
31
+ # Respect the order of precedence described in the readme.
32
+ args.each do |key|
33
+ [path, :default].each do |scope|
34
+ keys << [:token_action, :tokens, scope, key]
35
+ end
36
+ end
37
+ keys.map! do |key|
38
+ key.join(I18n.default_separator).to_sym
39
+ end
40
+
41
+ key, *default = *keys
42
+ I18n.translate(key, :default => default)
43
+ end
44
+
45
+ # Returns the success URL.
46
+ #
47
+ # @return [String] a URL
48
+ def success_url
49
+ [@token && @token.success_url, TokenAction.success_url].find do |url|
50
+ url.present? && routable?(url)
51
+ end || main_app.root_path
52
+ end
53
+
54
+ # Returns the failure URL.
55
+ #
56
+ # @return [String] a URL
57
+ def failure_url
58
+ [@token && @token.failure_url, TokenAction.failure_url].find do |url|
59
+ url.present? && routable?(url)
60
+ end || main_app.root_path
61
+ end
62
+
63
+ # Returns whether a path is routable.
64
+ #
65
+ # @param [String] path a path
66
+ # @return [Boolean] whether the path is routable
67
+ def routable?(path)
68
+ !!Rails.application.routes.recognize_path(path, :method => :get)
69
+ rescue ActionController::RoutingError
70
+ false
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,10 @@
1
+ en:
2
+ token_action:
3
+ tokens:
4
+ default:
5
+ # If the token is found and no exception is raised.
6
+ success: Your request was successfully processed.
7
+ # If the token is found and an exception is raised.
8
+ failure: An error occurred while processing your request.
9
+ # If the token cannot be found.
10
+ not_found: Sorry, your request could not be processed.
data/config/routes.rb ADDED
@@ -0,0 +1,3 @@
1
+ TokenAction::Engine.routes.draw do
2
+ get 'tokens/:token/*path', :to => 'tokens#redeem'
3
+ end
@@ -0,0 +1,15 @@
1
+ class CreateTokenActionTokens < ActiveRecord::Migration
2
+ def change
3
+ create_table(:token_action_tokens) do |t|
4
+ t.string :token
5
+ t.string :kind
6
+ t.text :args
7
+ t.string :success_url
8
+ t.string :failure_url
9
+
10
+ t.timestamps
11
+ end
12
+
13
+ add_index :token_action_tokens, :token, :unique => true
14
+ end
15
+ end
@@ -0,0 +1,25 @@
1
+ require 'rails/generators/active_record'
2
+
3
+ module ActiveRecord
4
+ module Generators
5
+ # This generator will run during `rails generate token_action`.
6
+ #
7
+ # @see ActiveRecord::Generators::DeviseGenerator
8
+ # @see Devise::Generators::OrmHelpers
9
+ class TokenActionGenerator < Rails::Generators::Base
10
+ include Rails::Generators::Migration
11
+
12
+ source_root File.expand_path('../templates', __FILE__)
13
+
14
+ def copy_migration_file
15
+ migration_template 'migration.rb', 'db/migrate/create_token_action_tokens'
16
+ end
17
+
18
+ private
19
+
20
+ def self.next_migration_number(number)
21
+ ActiveRecord::Generators::Base.next_migration_number(number)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,7 @@
1
+ module Mongoid
2
+ module Generators
3
+ class TokenActionGenerator < Rails::Generators::Base
4
+ # Do nothing. Avoids error during generation.
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ To complete the installation of TokenAction:
2
+
3
+ 1. Define a root route in config/routes.rb. For example:
4
+
5
+ root :to => 'home#index'
6
+
7
+ 2. Add flash messages to app/views/layouts/application.html.erb. For example:
8
+
9
+ <p class="alert alert-success"><%= notice %></p>
10
+ <p class="alert alert-error"><%= alert %></p>
@@ -0,0 +1,25 @@
1
+ TokenAction.setup do |config|
2
+ # Load and configure the ORM. Supports "active_record" and "mongoid". You
3
+ # should not require more than one ORM for TokenAction.
4
+ require 'token_action/orm/<%= options[:orm] %>'
5
+
6
+ # If not overridden on a per-token basis, TokenAction will redirect to this
7
+ # URL after performing an action successfully. If not set, it will redirect to
8
+ # your application's root_path. Set it with:
9
+ #
10
+ # config.success_url = 'custom/redirect/url'
11
+ #
12
+ # If using a URL helper, wrap the helper in a lambda or proc:
13
+ #
14
+ # config.success_url = lambda { Rails.application.routes.url_helpers.root_url }
15
+
16
+ # If not overridden on a per-token basis, TokenAction will redirect to this
17
+ # URL when an exception is raised while performing an action, or when a token
18
+ # cannot be found. If not set, it will redirect to your app's root_path.
19
+ #
20
+ # config.failure_url = 'custom/redirect/url'
21
+ #
22
+ # If using a URL helper, wrap the helper in a lambda or proc:
23
+ #
24
+ # config.failure_url = lambda { Rails.application.routes.url_helpers.root_url }
25
+ end
@@ -0,0 +1,35 @@
1
+ module TokenAction
2
+ module Generators
3
+ # Run this generator once before using TokenAction in your application.
4
+ #
5
+ # @example
6
+ # rails generate token_action
7
+ #
8
+ # @see Devise::Generators::InstallGenerator
9
+ # @see Devise::Generators::DeviseGenerator
10
+ class TokenActionGenerator < Rails::Generators::Base
11
+ desc "Copies TokenAction's initializer and locale files, adds routes, " <<
12
+ "and creates an ActiveRecord migration file if necessary."
13
+
14
+ hook_for :orm
15
+ namespace 'token_action'
16
+ source_root File.expand_path('../templates', __FILE__)
17
+
18
+ def copy_initializer_file
19
+ template 'token_action.rb', 'config/initializers/token_action.rb'
20
+ end
21
+
22
+ def copy_locale_file
23
+ copy_file '../../../config/locales/en.yml', 'config/locales/token_action.en.yml'
24
+ end
25
+
26
+ def add_routes
27
+ route "mount TokenAction::Engine => '/token_action'"
28
+ end
29
+
30
+ def show_readme
31
+ readme 'README' if behavior == :invoke
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,5 @@
1
+ module TokenAction
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace TokenAction
4
+ end
5
+ end
@@ -0,0 +1,24 @@
1
+ module TokenAction
2
+ module Model
3
+ extend ActiveSupport::Concern
4
+ include TokenAction::TokenGenerator
5
+
6
+ included do
7
+ validates_presence_of :token, :kind
8
+
9
+ # ActiveRecord, Mongoid, MongoMapper and DataMapper implement
10
+ # `validates_uniqueness_of`. Sequel implements it in a plugin.
11
+ #
12
+ # @see http://sequel.rubyforge.org/rdoc-plugins/classes/Sequel/Plugins/ValidationClassMethods.html
13
+ validates_uniqueness_of :token
14
+
15
+ before_validation :set_token
16
+ end
17
+
18
+ private
19
+
20
+ def set_token
21
+ self.token ||= self.class.generate_token(:token)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,19 @@
1
+ module TokenAction
2
+ module TokenGenerator
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ # Returns a new alphanumeric token.
7
+ #
8
+ # @param [String,Symbol] column an attribute
9
+ # @return [String] a random alphanumeric string
10
+ # @see Devise::Models::Authenticatable::ClassMethods
11
+ def generate_token(column)
12
+ loop do
13
+ token = TokenAction.friendly_token
14
+ break token unless to_adapter.find_first({ column => token })
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end