sparkly-auth 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 (71) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +17 -0
  3. data/Rakefile +50 -0
  4. data/VERSION +1 -0
  5. data/app/controllers/sparkly_accounts_controller.rb +59 -0
  6. data/app/controllers/sparkly_controller.rb +47 -0
  7. data/app/controllers/sparkly_sessions_controller.rb +52 -0
  8. data/app/models/password.rb +3 -0
  9. data/app/models/remembrance_token.rb +50 -0
  10. data/app/views/sparkly_accounts/edit.html.erb +24 -0
  11. data/app/views/sparkly_accounts/new.html.erb +24 -0
  12. data/app/views/sparkly_accounts/show.html.erb +0 -0
  13. data/app/views/sparkly_sessions/new.html.erb +22 -0
  14. data/dependencies.rb +1 -0
  15. data/generators/sparkly/USAGE +27 -0
  16. data/generators/sparkly/sparkly_generator.rb +76 -0
  17. data/generators/sparkly/templates/accounts_controller.rb +65 -0
  18. data/generators/sparkly/templates/accounts_helper.rb +2 -0
  19. data/generators/sparkly/templates/help_file.txt +56 -0
  20. data/generators/sparkly/templates/initializer.rb +30 -0
  21. data/generators/sparkly/templates/migrations/add_confirmed_to_sparkly_passwords.rb +9 -0
  22. data/generators/sparkly/templates/migrations/create_sparkly_passwords.rb +19 -0
  23. data/generators/sparkly/templates/migrations/create_sparkly_remembered_tokens.rb +15 -0
  24. data/generators/sparkly/templates/sessions_controller.rb +45 -0
  25. data/generators/sparkly/templates/sessions_helper.rb +2 -0
  26. data/generators/sparkly/templates/tasks/migrations.rb +1 -0
  27. data/generators/sparkly/templates/views/sparkly_accounts/edit.html.erb +24 -0
  28. data/generators/sparkly/templates/views/sparkly_accounts/new.html.erb +24 -0
  29. data/generators/sparkly/templates/views/sparkly_accounts/show.html.erb +0 -0
  30. data/generators/sparkly/templates/views/sparkly_sessions/new.html.erb +22 -0
  31. data/init.rb +44 -0
  32. data/lib/auth.rb +52 -0
  33. data/lib/auth/behavior/base.rb +64 -0
  34. data/lib/auth/behavior/core.rb +87 -0
  35. data/lib/auth/behavior/core/authenticated_model_methods.rb +52 -0
  36. data/lib/auth/behavior/core/controller_extensions.rb +52 -0
  37. data/lib/auth/behavior/core/controller_extensions/class_methods.rb +24 -0
  38. data/lib/auth/behavior/core/controller_extensions/current_user.rb +54 -0
  39. data/lib/auth/behavior/core/password_methods.rb +65 -0
  40. data/lib/auth/behavior/remember_me.rb +17 -0
  41. data/lib/auth/behavior/remember_me/configuration.rb +21 -0
  42. data/lib/auth/behavior/remember_me/controller_extensions.rb +66 -0
  43. data/lib/auth/behavior_lookup.rb +10 -0
  44. data/lib/auth/configuration.rb +328 -0
  45. data/lib/auth/encryptors/sha512.rb +20 -0
  46. data/lib/auth/generators/configuration_generator.rb +20 -0
  47. data/lib/auth/generators/controllers_generator.rb +34 -0
  48. data/lib/auth/generators/migration_generator.rb +32 -0
  49. data/lib/auth/generators/route_generator.rb +19 -0
  50. data/lib/auth/generators/views_generator.rb +26 -0
  51. data/lib/auth/model.rb +94 -0
  52. data/lib/auth/observer.rb +21 -0
  53. data/lib/auth/target_list.rb +5 -0
  54. data/lib/auth/tasks/migrations.rb +71 -0
  55. data/lib/auth/token.rb +10 -0
  56. data/lib/sparkly-auth.rb +1 -0
  57. data/rails/init.rb +17 -0
  58. data/rails/routes.rb +19 -0
  59. data/sparkly-auth.gemspec +143 -0
  60. data/spec/controllers/application_controller_spec.rb +13 -0
  61. data/spec/generators/sparkly_spec.rb +64 -0
  62. data/spec/lib/auth/behavior/core_spec.rb +184 -0
  63. data/spec/lib/auth/behavior/remember_me_spec.rb +127 -0
  64. data/spec/lib/auth/extensions/controller_spec.rb +32 -0
  65. data/spec/lib/auth/model_spec.rb +57 -0
  66. data/spec/lib/auth_spec.rb +32 -0
  67. data/spec/mocks/models/user.rb +3 -0
  68. data/spec/routes_spec.rb +24 -0
  69. data/spec/spec_helper.rb +61 -0
  70. data/spec/views_spec.rb +18 -0
  71. metadata +210 -0
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Colin MacKenzie IV
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,17 @@
1
+ = sparkly-auth
2
+
3
+ Description goes here.
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with rakefile, version, or history.
12
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
13
+ * Send me a pull request. Bonus points for topic branches.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2010 Colin MacKenzie IV. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,50 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "sparkly-auth"
8
+ gem.summary = %Q{User authentication with Sparkles!}
9
+ gem.description = %Q{As fate would have it, I found other authentication solutions unable to suit my needs. So I rolled my own.}
10
+ gem.email = "sinisterchipmunk@gmail.com"
11
+ gem.homepage = "http://www.thoughtsincomputation.com"
12
+ gem.authors = ["Colin MacKenzie IV"]
13
+ gem.add_dependency "sc-core-ext", ">= 1.2.0"
14
+ gem.add_development_dependency 'rspec-rails', '>= 1.3.2'
15
+ gem.add_development_dependency 'webrat', '>= 0.7.1'
16
+ gem.add_development_dependency 'genspec', '>= 0.1.1'
17
+ gem.add_development_dependency 'email_spec', '>= 0.6.2'
18
+ # WHY does jeweler insist on using test/* files? THEY DON'T EXIST!
19
+ gem.test_files = FileList['spec/**/*']
20
+ end
21
+ Jeweler::GemcutterTasks.new
22
+ rescue LoadError
23
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
24
+ end
25
+
26
+ require 'spec/rake/spectask'
27
+ Spec::Rake::SpecTask.new(:spec) do |spec|
28
+ spec.libs << 'lib' << 'spec'
29
+ spec.spec_files = FileList['spec/**/*_spec.rb']
30
+ end
31
+
32
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
33
+ spec.libs << 'lib' << 'spec'
34
+ spec.pattern = 'spec/**/*_spec.rb'
35
+ spec.rcov = true
36
+ end
37
+
38
+ task :spec => :check_dependencies
39
+
40
+ task :default => :spec
41
+
42
+ require 'rake/rdoctask'
43
+ Rake::RDocTask.new do |rdoc|
44
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
45
+
46
+ rdoc.rdoc_dir = 'rdoc'
47
+ rdoc.title = "sparkly-auth #{version}"
48
+ rdoc.rdoc_files.include('README*')
49
+ rdoc.rdoc_files.include('lib/**/*.rb')
50
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,59 @@
1
+ class SparklyAccountsController < SparklyController
2
+ unloadable
3
+ require_login_for :show, :edit, :update, :destroy
4
+
5
+ # GET new_model_url
6
+ def new
7
+ end
8
+
9
+ # POST model_url
10
+ def create
11
+ if model.save
12
+ login!(model)
13
+ redirect_back_or_default Auth.default_destination, Auth.account_created_message
14
+ else
15
+ render :action => 'new'
16
+ end
17
+ end
18
+
19
+ # GET model_url
20
+ def show
21
+ end
22
+
23
+ # GET edit_model_url
24
+ def edit
25
+ end
26
+
27
+ # PUT model_url
28
+ def update
29
+ if !model_params[:password].blank? || !model_params[:password_confirmation].blank?
30
+ model.password = model_params[:password]
31
+ model.password_confirmation = model_params[:password_confirmation]
32
+ end
33
+
34
+ if model.save
35
+ redirect_back_or_default user_path, Auth.account_updated_message
36
+ else
37
+ render :action => 'edit'
38
+ end
39
+ end
40
+
41
+ # DELETE model_url
42
+ def destroy
43
+ current_user && current_user.destroy
44
+ logout!
45
+ @current_user = nil
46
+ flash[:notice] = Auth.account_deleted_message
47
+ redirect_back_or_default Auth.default_destination
48
+ end
49
+
50
+ protected
51
+ def find_user_model
52
+ # password fields are protected attrs, so we need to exclude them then add them explicitly.
53
+ self.model_instance = current_user ||
54
+ returning(model_class.new(model_params.without(:password, :password_confirmation))) { |model|
55
+ model.password = model_params[:password]
56
+ model.password_confirmation = model_params[:password_confirmation]
57
+ }
58
+ end
59
+ end
@@ -0,0 +1,47 @@
1
+ class SparklyController < (Auth.base_controller)
2
+ unloadable
3
+ helper_method :model_class, :model_instance, :model_name, :model, :model_path, :new_model_path, :edit_model_path,
4
+ :model_config, :model_session_path, :model_params
5
+ before_filter :find_user_model
6
+
7
+ protected
8
+ attr_accessor :model_instance
9
+
10
+ def find_user_model
11
+ self.model_instance = current_user || model_class.new()
12
+ end
13
+
14
+ def model_class
15
+ model_name.constantize
16
+ end
17
+
18
+ def model_name
19
+ params[:model]
20
+ end
21
+
22
+ def model_path
23
+ send("#{model_name.underscore}_path")
24
+ end
25
+
26
+ def new_model_path
27
+ send("new_#{model_name.underscore}_path")
28
+ end
29
+
30
+ def edit_model_path
31
+ send("edit_#{model_name.underscore}_path")
32
+ end
33
+
34
+ def model_session_path
35
+ send("#{model_name.underscore}_session_path")
36
+ end
37
+
38
+ def model_config
39
+ Auth.configuration.for_model(model_name)
40
+ end
41
+
42
+ def model_params
43
+ params[model_name.underscore] || {}
44
+ end
45
+
46
+ alias_method :model, :model_instance
47
+ end
@@ -0,0 +1,52 @@
1
+ class SparklySessionsController < SparklyController
2
+ unloadable
3
+
4
+ # GET new_model_session_url
5
+ def new
6
+ end
7
+
8
+ # POST model_session_url
9
+ def create
10
+ if session[:locked_out_at] && session[:locked_out_at] > Auth.account_lock_duration.ago
11
+ flash[:error] = Auth.account_locked_message
12
+ render :action => 'new'
13
+ return
14
+ end
15
+
16
+ model = model_class.find(:first, :conditions => { model_config.key => model_params[model_config.key] },
17
+ :include => :passwords)
18
+
19
+ if model && model.password_matches?(model_params[:password])
20
+ login! model, :remember => remember_me?
21
+ redirect_back_or_default Auth.default_destination, Auth.login_successful_message
22
+ else
23
+ session[:login_failures] = session[:login_failures].to_i + 1
24
+ if Auth.max_login_failures && session[:login_failures] >= Auth.max_login_failures
25
+ session[:locked_out_at] = Time.now
26
+ flash[:error] = Auth.account_locked_message
27
+ else
28
+ flash[:error] = Auth.invalid_credentials_message
29
+ end
30
+ render :action => "new"
31
+ end
32
+ end
33
+
34
+ # DELETE model_session_url
35
+ def destroy
36
+ logout!(:forget => true)
37
+ redirect_back_or_default Auth.default_destination, Auth.logout_message
38
+ end
39
+
40
+ private
41
+ def remember_me?
42
+ remembrance = model_params[:remember_me]
43
+ if remembrance.kind_of?(String)
44
+ return false if remembrance.blank?
45
+ return remembrance.to_i != 0
46
+ elsif remembrance.kind_of?(Numeric)
47
+ return remembrance != 0
48
+ else
49
+ return remembrance
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,3 @@
1
+ class Password < ActiveRecord::Base
2
+ unloadable
3
+ end
@@ -0,0 +1,50 @@
1
+ class RemembranceToken < ActiveRecord::Base
2
+ unloadable
3
+ belongs_to :authenticatable, :polymorphic => true
4
+ validates_presence_of :series_token
5
+ validates_presence_of :remembrance_token
6
+ validates_presence_of :authenticatable
7
+
8
+ def value
9
+ "#{authenticatable_type}|#{authenticatable_id}|#{series_token}|#{remembrance_token}"
10
+ end
11
+
12
+ def before_validation
13
+ regenerate if new_record?
14
+ end
15
+
16
+ def should_equal(remembrance_token)
17
+ @theft = self.remembrance_token != remembrance_token
18
+ end
19
+
20
+ def theft?
21
+ @theft
22
+ end
23
+
24
+ def regenerate
25
+ if attributes.keys.include?('series_token')
26
+ # We need to only ever generate the series token once per record.
27
+ self.series_token ||= Auth::Token.new.to_s
28
+ end
29
+
30
+ if attributes.keys.include?('remembrance_token')
31
+ # We need to regenerate the auth token every time the record is saved.
32
+ self.remembrance_token = Auth::Token.new.to_s
33
+ end
34
+ end
35
+
36
+ class << self
37
+ def find_by_value(value)
38
+ authenticatable_type, authenticatable_id, series_token, remembrance_token = *value.split(/\|/)
39
+ return nil if authenticatable_type.blank? || authenticatable_id.blank? || series_token.blank?
40
+
41
+ token = RemembranceToken.find(:first, :conditions => {
42
+ :authenticatable_type => authenticatable_type,
43
+ :authenticatable_id => authenticatable_id,
44
+ :series_token => series_token
45
+ })
46
+ token.should_equal(remembrance_token)
47
+ token
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,24 @@
1
+ <%form_for model, :url => model_path do |f|%>
2
+ <p>
3
+ <%=f.error_messages%>
4
+ </p>
5
+
6
+ <p>
7
+ <%=f.label model_config.key%><br/>
8
+ <%=f.text_field model_config.key%>
9
+ </p>
10
+
11
+ <p>
12
+ <%=f.label :password%><br/>
13
+ <%=f.password_field :password, :value => ''%>
14
+ </p>
15
+
16
+ <p>
17
+ <%=f.label :password_confirmation%><br/>
18
+ <%=f.password_field :password_confirmation, :value => ''%>
19
+ </p>
20
+
21
+ <p>
22
+ <%=f.submit "Update Profile"%>
23
+ </p>
24
+ <%end%>
@@ -0,0 +1,24 @@
1
+ <%form_for model, :url => model_path do |f|%>
2
+ <p>
3
+ <%=f.error_messages%>
4
+ </p>
5
+
6
+ <p>
7
+ <%=f.label model_config.key%><br/>
8
+ <%=f.text_field model_config.key%>
9
+ </p>
10
+
11
+ <p>
12
+ <%=f.label :password%><br/>
13
+ <%=f.password_field :password, :value => ''%>
14
+ </p>
15
+
16
+ <p>
17
+ <%=f.label :password_confirmation%><br/>
18
+ <%=f.password_field :password_confirmation, :value => ''%>
19
+ </p>
20
+
21
+ <p>
22
+ <%=f.submit "Sign up"%>
23
+ </p>
24
+ <%end%>
File without changes
@@ -0,0 +1,22 @@
1
+ <%form_for model, :url => model_session_path do |f|%>
2
+ <p>
3
+ <%=f.label model_config.key%><br/>
4
+ <%=f.text_field model_config.key%>
5
+ </p>
6
+
7
+ <p>
8
+ <%=f.label :password%><br/>
9
+ <%=f.password_field :password, :value => ''%>
10
+ </p>
11
+
12
+ <%if Auth.remember_me.enabled?%>
13
+ <p>
14
+ <%=f.check_box :remember_me, :checked => false%>
15
+ <%=f.label :remember_me%>
16
+ </p>
17
+ <%end%>
18
+
19
+ <p>
20
+ <%=f.submit "Sign in"%>
21
+ </p>
22
+ <%end%>
data/dependencies.rb ADDED
@@ -0,0 +1 @@
1
+ Rails.configuration.gem "sc-core-ext", :version => ">= 1.2.0"
@@ -0,0 +1,27 @@
1
+ Description:
2
+ Generates the various aspects of Sparkly Authentication:
3
+
4
+ 1. After you've created the model or models on which you wish to
5
+ authenticate users, you should generate your configuration:
6
+
7
+ script/generate sparkly config
8
+
9
+ 2. After editing the generated configuration file to match your
10
+ goals, you should generate the necessary database migrations:
11
+
12
+ script/generate sparkly migrations
13
+
14
+ 3. Other interesting generators:
15
+
16
+ script/generate sparkly controllers
17
+ Generates resource controllers for each authenticated
18
+ model, which you can modify independently. Also generates
19
+ the corresponding views, giving you maximum customization
20
+ of your app. Not required.
21
+
22
+ script/generate sparkly views
23
+ Generates views for the authenticated models which you
24
+ can then modify independently. Not required.
25
+
26
+ script/generate sparkly help
27
+ For much more detailed instructions.
@@ -0,0 +1,76 @@
1
+ class SparklyGenerator < Rails::Generator::NamedBase
2
+ # I'm still treating this as NamedBase because I'm not sure whether I'll need that in the future.
3
+ def initialize(args, options = {}) #:nodoc:
4
+ @options = options
5
+ unless args.empty?
6
+ which = args.first
7
+ @type = which.downcase
8
+ args << "generic" if nameless_type?
9
+ end
10
+ super(args, options)
11
+ end
12
+
13
+ def manifest
14
+ record do |m|
15
+ case @type
16
+ when *nameless_types then send(@type, m)
17
+ else raise ArgumentError, "Expected type to be one of #{nameless_types.to_sentence(:connector => 'or ')}"
18
+ end
19
+ end
20
+ end
21
+
22
+ private
23
+ def help(m)
24
+ m.directory 'doc'
25
+ m.file 'help_file.txt', 'doc/sparkly_authentication.txt'
26
+ logger.log '', File.read(File.join(File.dirname(__FILE__), 'templates/help_file.txt'))
27
+ logger.quiet = true # we plan to tell the user the file has been saved, so why tell them twice?
28
+ end
29
+
30
+ def controllers(m)
31
+ spawn_model_generator(m, Auth::Generators::ControllersGenerator)
32
+ end
33
+
34
+ def routes(m)
35
+ spawn_model_generator(m, Auth::Generators::RouteGenerator)
36
+ end
37
+
38
+ def views(m)
39
+ spawn_model_generator(m, Auth::Generators::ViewsGenerator)
40
+ end
41
+
42
+ def migrations(m)
43
+ spawn_model_generator(m, Auth::Generators::MigrationGenerator)
44
+ end
45
+
46
+ def config(m)
47
+ spawn_generator(m, Auth::Generators::ConfigurationGenerator, [])
48
+ end
49
+
50
+ def spawn_model_generator(manifest, type)
51
+ each_model do |model|
52
+ spawn_generator(manifest, type, model)
53
+ end
54
+ end
55
+
56
+ def spawn_generator(manifest, type, args)
57
+ generator = type.new(args, spawned_generator_options)
58
+ generator.manifest.replay(manifest)
59
+ end
60
+
61
+ def each_model(&block)
62
+ Auth.configuration.authenticated_models.each &block
63
+ end
64
+
65
+ def nameless_type?
66
+ nameless_types.include? @type
67
+ end
68
+
69
+ def nameless_types
70
+ %w(migrations config help views controllers)
71
+ end
72
+
73
+ def spawned_generator_options
74
+ options.merge(:source => File.join(source_root), :destination => destination_root)
75
+ end
76
+ end