sparkly-auth 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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