billfold 1.0.0

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.
Files changed (82) hide show
  1. data/.gitignore +1 -0
  2. data/.rvmrc +1 -0
  3. data/Gemfile +3 -0
  4. data/README.md +39 -0
  5. data/Rakefile +26 -0
  6. data/VERSION +1 -0
  7. data/app/controllers/billfold/identities_controller.rb +53 -0
  8. data/app/views/billfold/identities/_list.html.erb +8 -0
  9. data/app/views/billfold/identities/index.html.erb +2 -0
  10. data/billfold.gemspec +24 -0
  11. data/config/locales/en.yml +6 -0
  12. data/config/routes.rb +11 -0
  13. data/lib/billfold.rb +56 -0
  14. data/lib/billfold/active_record_identity.rb +47 -0
  15. data/lib/billfold/active_record_user.rb +46 -0
  16. data/lib/billfold/controller_support.rb +51 -0
  17. data/lib/billfold/engine.rb +25 -0
  18. data/lib/billfold/identity.rb +99 -0
  19. data/lib/billfold/user.rb +25 -0
  20. data/lib/rails/generators/billfold/migration_generator.rb +27 -0
  21. data/lib/rails/generators/billfold/models_generator.rb +14 -0
  22. data/lib/rails/generators/billfold/templates/identity.rb +5 -0
  23. data/lib/rails/generators/billfold/templates/migration.rb +23 -0
  24. data/lib/rails/generators/billfold/templates/user.rb +5 -0
  25. data/spec/billfold_spec.rb +23 -0
  26. data/spec/controllers/identities_controller_spec.rb +31 -0
  27. data/spec/dummy/.gitignore +5 -0
  28. data/spec/dummy/README +261 -0
  29. data/spec/dummy/Rakefile +7 -0
  30. data/spec/dummy/app/assets/images/rails.png +0 -0
  31. data/spec/dummy/app/assets/javascripts/application.js +9 -0
  32. data/spec/dummy/app/assets/stylesheets/application.css +7 -0
  33. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  34. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  35. data/spec/dummy/app/mailers/.gitkeep +0 -0
  36. data/spec/dummy/app/models/.gitkeep +0 -0
  37. data/spec/dummy/app/models/identity.rb +5 -0
  38. data/spec/dummy/app/models/user.rb +5 -0
  39. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  40. data/spec/dummy/config.ru +4 -0
  41. data/spec/dummy/config/application.rb +48 -0
  42. data/spec/dummy/config/boot.rb +6 -0
  43. data/spec/dummy/config/database.yml +25 -0
  44. data/spec/dummy/config/environment.rb +5 -0
  45. data/spec/dummy/config/environments/development.rb +30 -0
  46. data/spec/dummy/config/environments/production.rb +60 -0
  47. data/spec/dummy/config/environments/test.rb +42 -0
  48. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  49. data/spec/dummy/config/initializers/inflections.rb +10 -0
  50. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  51. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  52. data/spec/dummy/config/initializers/session_store.rb +8 -0
  53. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  54. data/spec/dummy/config/locales/en.yml +5 -0
  55. data/spec/dummy/config/routes.rb +58 -0
  56. data/spec/dummy/db/migrate/20110904204023_create_users_and_identities.rb +23 -0
  57. data/spec/dummy/db/schema.rb +31 -0
  58. data/spec/dummy/db/seeds.rb +7 -0
  59. data/spec/dummy/doc/README_FOR_APP +2 -0
  60. data/spec/dummy/lib/assets/.gitkeep +0 -0
  61. data/spec/dummy/lib/tasks/.gitkeep +0 -0
  62. data/spec/dummy/log/.gitkeep +0 -0
  63. data/spec/dummy/public/404.html +26 -0
  64. data/spec/dummy/public/422.html +26 -0
  65. data/spec/dummy/public/500.html +26 -0
  66. data/spec/dummy/public/favicon.ico +0 -0
  67. data/spec/dummy/public/index.html +241 -0
  68. data/spec/dummy/public/robots.txt +5 -0
  69. data/spec/dummy/script/rails +6 -0
  70. data/spec/dummy/test/fixtures/.gitkeep +0 -0
  71. data/spec/dummy/test/functional/.gitkeep +0 -0
  72. data/spec/dummy/test/integration/.gitkeep +0 -0
  73. data/spec/dummy/test/performance/browsing_test.rb +12 -0
  74. data/spec/dummy/test/test_helper.rb +13 -0
  75. data/spec/dummy/test/unit/.gitkeep +0 -0
  76. data/spec/dummy/vendor/assets/stylesheets/.gitkeep +0 -0
  77. data/spec/dummy/vendor/plugins/.gitkeep +0 -0
  78. data/spec/factories/identity_factories.rb +18 -0
  79. data/spec/factories/user_factories.rb +7 -0
  80. data/spec/models/identity_spec.rb +123 -0
  81. data/spec/spec_helper.rb +30 -0
  82. metadata +260 -0
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ Gemfile.lock
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm 1.9.2
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,39 @@
1
+ ## Billfold
2
+
3
+ Billfold provides backend Rails support for OmniAuth. Specifically, it
4
+ routes `/auth/:provider/callback` to
5
+ `Billfold::IdentitiesController#update_or_create!`, which handles
6
+ identity management.
7
+
8
+ ### Requirements
9
+
10
+ * Rails 3.x
11
+
12
+ ### Installation
13
+
14
+ 1. Add `gem 'billfold'` to your `Gemfile`
15
+ 1. Run `bundle` (or `bundle install`)
16
+ 1. Run `rails g billfold:migration`
17
+
18
+ ### Configuration
19
+
20
+ #### With ActiveRecord
21
+
22
+ If you don't have User and Identity model classes, run
23
+ `rails g billfold:models` to create them. Otherwise, include
24
+ `Billfold::ActiveRecordUser` and `Billfold::ActiveRecordIdentity` in
25
+ them respectively. You *may* wish to define
26
+ `User#perform_additional_merge_operations!` if you need to do additional
27
+ logic during a user merge.
28
+
29
+ #### Without ActiveRecord
30
+
31
+ Include `Billfold::User` and `Billfold::Identity` in the model classes.
32
+ You'll also have do define the following methods:
33
+
34
+ * `User.find_by_id(id)`
35
+ * `User#merge_into!(other_user)`
36
+ * `Identity.with_provider_and_value(provider, value)`
37
+ * `Identity#user`
38
+ * `Identity#update_attributes!`
39
+ * `Identity#save!`
data/Rakefile ADDED
@@ -0,0 +1,26 @@
1
+ # encoding: UTF-8
2
+ require 'rubygems'
3
+ begin
4
+ require 'bundler/setup'
5
+ rescue LoadError
6
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
8
+
9
+ require 'rake'
10
+ require 'rdoc/task'
11
+
12
+ require 'rspec/core'
13
+ require 'rspec/core/rake_task'
14
+ RSpec::Core::RakeTask.new(:spec)
15
+
16
+ desc "Default: run the unit tests."
17
+ task :default => [:spec]
18
+
19
+ Rake::RDocTask.new(:rdoc) do |rdoc|
20
+ rdoc.rdoc_dir = 'rdoc'
21
+ rdoc.title = "Billfold #{File.read './VERSION'}"
22
+ rdoc.options << '--line-numbers' << '--inline-source'
23
+ rdoc.rdoc_files.include('README.rdoc')
24
+ rdoc.rdoc_files.include('lib/**/*.rb')
25
+ rdoc.rdoc_files.include('app/**/*.rb')
26
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,53 @@
1
+ module Billfold
2
+ class IdentitiesController < ApplicationController
3
+
4
+ before_filter :require_sign_in, :only => [ :index, :destroy ]
5
+
6
+ respond_to :html, :json, :xml
7
+ helper_method :identities, :identity
8
+
9
+ def index
10
+ respond_to do |format|
11
+ format.html {}
12
+ format.json { render :json => identities }
13
+ format.xml { render :xml => identities }
14
+ end
15
+ end
16
+
17
+ def update_or_create
18
+ omniauth_hash = request.env['omniauth.auth'] || {}
19
+ identity = Billfold.identity_class.update_or_create!({
20
+ :provider => params[:provider],
21
+ :value => omniauth_hash['uid'],
22
+ :data => omniauth_hash['user_info'],
23
+ :user => current_user
24
+ })
25
+ self.current_user ||= identity.user
26
+ respond_to do |format|
27
+ format.html { redirect_to '/' }
28
+ format.json { head 201 }
29
+ format.xml { head 201 }
30
+ end
31
+ end
32
+
33
+ def destroy
34
+ identity.destroy
35
+ respond_to do |format|
36
+ format.html { redirect_to :index }
37
+ format.json { head 200 }
38
+ format.xml { head 200 }
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def identities
45
+ @identities ||= current_user.identities
46
+ end
47
+
48
+ def identity
49
+ @identity ||= current_user.identities.find_by_id(params[:identity_id] || params[:id])
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,8 @@
1
+ <ul class='identities'>
2
+ <% identities.each do |id| %>
3
+ <li class="identity <%= id.provider %>">
4
+ <%= id.value %>
5
+ <%= button_to t('billfold.delete'), identity_path(id), :method => :delete, :remote => true, :confirm => true %>
6
+ </li>
7
+ <% end %>
8
+ </ul>
@@ -0,0 +1,2 @@
1
+ <h2><%= t '.title' %></h2>
2
+ <%= render :partial => 'list', :identities => identities %>
data/billfold.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.name = 'billfold'
5
+ gem.version = File.read('VERSION')
6
+ gem.description = %q{Identity Management with OmniAuth}
7
+ gem.summary = gem.description
8
+ gem.email = ['james.a.rosen@gmail.com']
9
+ gem.homepage = 'http://github.com/jamesarosen/billfold'
10
+ gem.authors = ['James A. Rosen']
11
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{|f| File.basename(f)}
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
+ gem.require_paths = ['lib']
15
+ gem.required_rubygems_version = Gem::Requirement.new('>= 1.3.6') if gem.respond_to? :required_rubygems_version=
16
+
17
+ gem.add_development_dependency 'rails', '~> 3.1'
18
+ gem.add_development_dependency 'bundler'
19
+ gem.add_development_dependency 'mocha'
20
+ gem.add_development_dependency 'rake'
21
+ gem.add_development_dependency 'rspec-rails'
22
+ gem.add_development_dependency 'sqlite3'
23
+ gem.add_development_dependency 'factory_girl'
24
+ end
@@ -0,0 +1,6 @@
1
+ en:
2
+ billfold:
3
+ delete: delete
4
+ identities:
5
+ index:
6
+ title: Your Identities
data/config/routes.rb ADDED
@@ -0,0 +1,11 @@
1
+ Rails.application.routes.draw do
2
+
3
+ mount_at = Billfold::Engine.config.mount_at
4
+
5
+ post "#{mount_at}auth/:provider/callback" => 'billfold/identities#update_or_create'
6
+
7
+ resources :identities, :only => [ :index, :destroy ],
8
+ :module => 'billfold',
9
+ :path => "#{mount_at}/identities"
10
+
11
+ end
data/lib/billfold.rb ADDED
@@ -0,0 +1,56 @@
1
+ module Billfold
2
+
3
+ autoload :ControllerSupport, 'billfold/controller_support'
4
+ autoload :Identity, 'billfold/identity'
5
+ autoload :User, 'billfold/user'
6
+ autoload :ActiveRecordIdentity, 'billfold/active_record_identity'
7
+ autoload :ActiveRecordUser, 'billfold/active_record_user'
8
+
9
+ class <<self
10
+ # ## Billfold.user_class
11
+ #
12
+ # Used by `Billfold::Identity.update_or_create!` when building new users
13
+ # and `Billfold::ControllerSupport` when looking up the current user
14
+ # from the session. Calculated from `Billfold.user_class_name`.
15
+ def user_class
16
+ constantize user_class_name
17
+ end
18
+
19
+ # Used by `Billfold::ActiveRecordIdentity` for the `belongs_to :user`
20
+ # association and by `Billfold.user_class` for getting the actual
21
+ # class. By default, "User"
22
+ def user_class_name
23
+ @user_class ||= 'User'
24
+ end
25
+
26
+ attr_writer :user_class_name
27
+
28
+ # ## Billfold.identity_class
29
+ #
30
+ # Used by `Billfold::IdentitiesController.update_or_create`. Calculated
31
+ # from `Billfold.identity_class_name`.
32
+ def identity_class
33
+ constantize identity_class_name
34
+ end
35
+
36
+ def identity_class_name
37
+ @identity_class ||= 'Identity'
38
+ end
39
+
40
+ attr_writer :identity_class_name
41
+
42
+ private
43
+
44
+ def constantize(string)
45
+ return nil if string.blank?
46
+ return string.constantize if string.respond_to?(:constantize)
47
+ string.to_s.split('::').inject(Object) do |memo, name|
48
+ raise "#{memo}::#{name} does not exist" unless memo.const_defined?(name)
49
+ memo = memo.const_get(name)
50
+ end
51
+ end
52
+ end
53
+
54
+ require 'billfold/identity'
55
+ require 'billfold/engine' if defined?(Rails) && Rails::VERSION::MAJOR == 3
56
+ end
@@ -0,0 +1,47 @@
1
+ require 'active_support/concern'
2
+ require 'billfold/identity'
3
+
4
+ module Billfold
5
+
6
+ # ## Billfold::ActiveRecordIdentity
7
+ #
8
+ # Support for an ActiveRecord Identity class. (Builds on top of
9
+ # `Billfold::Identity`)
10
+ module ActiveRecordIdentity
11
+
12
+ extend ActiveSupport::Concern
13
+
14
+ included do
15
+ raise "Cannot define identity class until Billfold.user_class_name is set" unless Billfold.user_class_name.present?
16
+ belongs_to :user, :class_name => ::Billfold.user_class_name
17
+ serialize :data
18
+ validates_presence_of :user, :provider, :value
19
+
20
+ # There can only be one identity with a given provider and value
21
+ validates_uniqueness_of :value, :scope => [ :provider ]
22
+ end
23
+
24
+ module ClassMethods
25
+
26
+ include Billfold::Identity::ClassMethods
27
+
28
+ # ### Billfold::Identity.with_provider_and_value
29
+ #
30
+ # Return the identity with the given `provider` and `value`, or `nil`,
31
+ # if no such identity exists. Including classes *must* redefine this
32
+ # method.
33
+ def with_provider_and_value(provider, value)
34
+ where(:provider => provider, :value => value).first
35
+ end
36
+
37
+ end
38
+
39
+ module InstanceMethods
40
+
41
+ include Billfold::Identity::InstanceMethods
42
+
43
+ end
44
+
45
+ end
46
+
47
+ end
@@ -0,0 +1,46 @@
1
+ require 'active_support/concern'
2
+ require 'billfold/user'
3
+
4
+ module Billfold
5
+
6
+ # ## Billfold::ActiveRecordUser
7
+ #
8
+ # Support for an ActiveRecord User class. (Builds on top of
9
+ # `Billfold::User`)
10
+ module ActiveRecordUser
11
+
12
+ extend ActiveSupport::Concern
13
+
14
+ included do
15
+ raise "Cannot define user class until Billfold.identity_class_name is set" unless Billfold.identity_class_name.present?
16
+ validates_presence_of :name
17
+ has_many :identities, :class_name => ::Billfold.identity_class_name
18
+ end
19
+
20
+ module InstanceMethods
21
+
22
+ # Merge this user into another user, deleting this user and moving its
23
+ # identities to the other.
24
+ def merge_into!(other)
25
+ raise ArgumentError.new("#{other} is not a #{Billfold.user_class}") unless other.kind_of?(Billfold.user_class)
26
+ raise ArgumentError.new("#{other} is not saved") if other.new_record?
27
+ transaction do
28
+ identities.update_all({ :user_id => other.id })
29
+ perform_additional_merge_operations!(other)
30
+ destroy
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ # This method is called after all identities have been moved from
37
+ # `other` to `self`, but before `other` has been destroyed and before the
38
+ # end of the transaction. By default, it does nothing.
39
+ def perform_additional_merge_operations!(other)
40
+ end
41
+
42
+ end
43
+
44
+ end
45
+
46
+ end
@@ -0,0 +1,51 @@
1
+ require 'active_support/concern'
2
+ require 'billfold'
3
+
4
+ module Billfold
5
+
6
+ # ## Billfold::ControllerSupport
7
+ #
8
+ # Gets mixed in to `ApplicationController` automatically.
9
+ module ControllerSupport
10
+
11
+ extend ActiveSupport::Concern
12
+
13
+ included do
14
+ helper_method :current_user
15
+ end
16
+
17
+ module InstanceMethods
18
+
19
+ protected
20
+
21
+ # ### Billfold::ControllerSupport#current_user
22
+ #
23
+ # Return the current user, if signed in.
24
+ def current_user
25
+ @current_user ||= ::Billfold::user_class.find_by_id(session[:user_id])
26
+ end
27
+
28
+ # ### Billfold::ControllerSupport#current_user=
29
+ #
30
+ # Set the signed-in user.
31
+ def current_user=(user)
32
+ session[:user_id] = user ? user.id : nil
33
+ @current_user = user
34
+ end
35
+
36
+ # ### Billfold::ControllerSupport#require_sign_in
37
+ #
38
+ # A before filter that requires a user to be signed in. The default
39
+ # implementation adds a flash message and redirects to /
40
+ def require_sign_in
41
+ unless current_user
42
+ flash['info'] = 'Please sign in'
43
+ redirect_to root_path
44
+ end
45
+ end
46
+
47
+ end
48
+
49
+ end
50
+
51
+ end
@@ -0,0 +1,25 @@
1
+ require 'billfold'
2
+ require 'rails'
3
+
4
+ module Billfold
5
+ class Engine < Rails::Engine
6
+ engine_name :my_rails_engine
7
+
8
+ # Config defaults
9
+ config.mount_at = '/'
10
+
11
+ # Check the gem config
12
+ initializer "check config" do |app|
13
+ # make sure mount_at ends with trailing slash
14
+ config.mount_at += '/' unless config.mount_at.last == '/'
15
+ end
16
+
17
+ initializer "static assets" do |app|
18
+ app.middleware.use ::ActionDispatch::Static, "#{root}/public"
19
+ end
20
+
21
+ ActiveSupport.on_load(:action_controller) do
22
+ include Billfold::ControllerSupport
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,99 @@
1
+ require 'billfold'
2
+
3
+ module Billfold
4
+
5
+ # ## Billfold::Identity
6
+ #
7
+ # Support for an identity class. If you are using ActiveRecord, you
8
+ # probably want `Billfold::ActiveRecordIdentity`, which includes everything
9
+ # in this module.
10
+ #
11
+ # Requires the following attributes
12
+ # * `user` -- the owning `Billfold::User` instance; required
13
+ # * `provider` -- the OmniAuth provider name (e.g. "twitter"); required
14
+ # * `value` -- the unique identifier (within the scope of `provider`) of
15
+ # the identity (e.g. "my_twitter_handle"); required
16
+ # * `data` -- additional information passed in from OmniAuth under the
17
+ # `"user_info"` key
18
+ module Identity
19
+
20
+ def self.included(base)
21
+ base.extend ClassMethods
22
+ base.send(:include, InstanceMethods)
23
+ end
24
+
25
+ module ClassMethods
26
+
27
+ # ### Billfold::Identity.with_provider_and_value
28
+ #
29
+ # Return the identity with the given `provider` and `value`, or `nil`,
30
+ # if no such identity exists. Including classes *must* redefine this
31
+ # method.
32
+ def with_provider_and_value(provider, value)
33
+ raise NotImplementedError.new('classes including Billfold::Identity MUST redefine class method with_provider_and_value')
34
+ end
35
+
36
+ # ### Billfold::Identity.update_or_create!
37
+ #
38
+ # Updates or creates a `Billfold::Identity`.
39
+ #
40
+ # ### Parameters: a single `Hash` with the following keys:
41
+ # * `:provider` -- the name of an OmniAuth provider (e.g. "twitter")
42
+ # * `:user` -- if nil, creates a new `User` for the `Identity`
43
+ # * `:value` -- the unique identifier
44
+ # * `:data` -- extra data for the Identity
45
+ #
46
+ # ### Behavior
47
+ #
48
+ # If `:provider` or `:value` is `nil`, this method raises an
49
+ # `ArgumentError`.
50
+ #
51
+ # If `:user` is `nil`, this method creates a new `User` for the `Identity`.
52
+ #
53
+ # If `:user` exists and there is no other `Identity` with the
54
+ # same `:value`, this method adds a new `Identity` to the `User`.
55
+ #
56
+ # If there exists another `Identity` with the same `:value` and
57
+ # that `Identity` is owned by the given `User`, this method updates that
58
+ # `Identity`.
59
+ #
60
+ # If `:user` exists and there exists another `Identity` with
61
+ # the same `:value` and that `Identity` is owned by a *different* `User`,
62
+ # this method merges that User into `:user` and updates the `Identity`.
63
+ def update_or_create!(attributes = {})
64
+ raise ArgumentError.new("provider must not be blank") if attributes[:provider].blank?
65
+ raise ArgumentError.new("value must not be blank") if attributes[:value].blank?
66
+ identity = with_provider_and_value(attributes[:provider], attributes[:value])
67
+ if identity
68
+ old_owner, new_owner = identity.user, attributes[:user]
69
+ transaction do
70
+ identity.update_attributes!(attributes)
71
+ old_owner.merge_into!(new_owner) if old_owner != new_owner
72
+ end
73
+ else
74
+ identity = new(attributes)
75
+ identity.user = attributes[:user] || ::Billfold.user_class.new(:name => identity.name_for_user)
76
+ identity.save!
77
+ end
78
+ identity
79
+ end
80
+
81
+ end
82
+
83
+ module InstanceMethods
84
+
85
+ # ### Billfold::Identity#name_for_user
86
+ #
87
+ # When creating a new user from an identity, this method is used to
88
+ # generate a display name. By default, it tries to get the user's name
89
+ # from the OmniAuth data and falls back on using the identity's provider
90
+ # and value, but subclasses may have something better to do.
91
+ def name_for_user
92
+ (data && data['name']) || "#{provider} #{value}"
93
+ end
94
+
95
+ end
96
+
97
+ end
98
+
99
+ end