merb-auth-more 0.9.9
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.
- data/LICENSE +42 -0
- data/README.textile +50 -0
- data/Rakefile +65 -0
- data/TODO +0 -0
- data/lib/merb-auth-more.rb +24 -0
- data/lib/merb-auth-more/merbtasks.rb +6 -0
- data/lib/merb-auth-more/mixins/redirect_back.rb +59 -0
- data/lib/merb-auth-more/mixins/salted_user.rb +73 -0
- data/lib/merb-auth-more/mixins/salted_user/ar_salted_user.rb +25 -0
- data/lib/merb-auth-more/mixins/salted_user/dm_salted_user.rb +26 -0
- data/lib/merb-auth-more/mixins/salted_user/sq_salted_user.rb +35 -0
- data/lib/merb-auth-more/strategies/abstract_password.rb +31 -0
- data/lib/merb-auth-more/strategies/basic/basic_auth.rb +76 -0
- data/lib/merb-auth-more/strategies/basic/openid.rb +129 -0
- data/lib/merb-auth-more/strategies/basic/password_form.rb +32 -0
- data/spec/merb-auth-more_spec.rb +4 -0
- data/spec/mixins/redirect_back_spec.rb +84 -0
- data/spec/mixins/salted_user_spec.rb +105 -0
- data/spec/spec_helper.rb +34 -0
- metadata +88 -0
data/LICENSE
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
Copyright (c) 2008 Daniel Neighman
|
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.
|
21
|
+
|
22
|
+
Some of the code in this plugin is based on Restful Merb::Authentication by Rick Olsen. License File here:
|
23
|
+
|
24
|
+
Copyright (c) 2005 Rick Olson
|
25
|
+
|
26
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
27
|
+
this software and associated documentation files (the "Software"), to deal in
|
28
|
+
the Software without restriction, including without limitation the rights to
|
29
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
30
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
31
|
+
subject to the following conditions:
|
32
|
+
|
33
|
+
The above copyright notice and this permission notice shall be included in all
|
34
|
+
copies or substantial portions of the Software.
|
35
|
+
|
36
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
37
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
38
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
39
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
40
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
41
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
42
|
+
=======
|
data/README.textile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
merb-auth-more
|
2
|
+
==============
|
3
|
+
|
4
|
+
merb-auth-more provides stuff that's useful, but not core to the functionality of
|
5
|
+
merb-auth. Strategies and "User" object mixins are here.
|
6
|
+
|
7
|
+
h3. What Strategies are there?
|
8
|
+
|
9
|
+
Strategies are really simple to implement, but we've made some basic ones available.
|
10
|
+
|
11
|
+
The built in ones are basic but should be enough to get you going for most things.
|
12
|
+
|
13
|
+
To specify them, simply require them. For example,in lib/auth-strategies.rb
|
14
|
+
|
15
|
+
<pre><code>
|
16
|
+
Merb::Authentication.activate!(:default_password_form)
|
17
|
+
Merb::Authentication.activate!(:default_openid)
|
18
|
+
|
19
|
+
class MyApiLogin < ::Merb::Authentication::Strategy
|
20
|
+
def run!
|
21
|
+
# check for api login
|
22
|
+
end
|
23
|
+
end
|
24
|
+
</code></pre>
|
25
|
+
|
26
|
+
So far there are:
|
27
|
+
|
28
|
+
* :default_password_form
|
29
|
+
* :default_basic_auth
|
30
|
+
* :default_openid
|
31
|
+
|
32
|
+
h3. "User" mixins
|
33
|
+
|
34
|
+
To assist with your authenticating needs, there is user mixins available to enhance your user model
|
35
|
+
for basic cases. These really are just for the basic case, so if you need something extra you should
|
36
|
+
overwrite the methods, or implement (and share ;) ;) ) your requirements.
|
37
|
+
|
38
|
+
To use these, require the specific mixin, and then include it into your "User" class.
|
39
|
+
|
40
|
+
<pre><code>
|
41
|
+
require 'merb-auth-more/mixins/salted_user'
|
42
|
+
|
43
|
+
class User
|
44
|
+
include Merb::Authentication::Mixins::SaltedUser
|
45
|
+
|
46
|
+
end
|
47
|
+
</code></pre>
|
48
|
+
|
49
|
+
The salted user mixin provides basic salted SHA1 password authentication. It implements the required User.authenticate method
|
50
|
+
for use with the default password strategies.
|
data/Rakefile
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
require 'rubygems/specification'
|
4
|
+
require 'date'
|
5
|
+
require "spec/rake/spectask"
|
6
|
+
require 'merb-core/version'
|
7
|
+
require 'merb-core/tasks/merb_rake_helper'
|
8
|
+
require 'rake/testtask'
|
9
|
+
|
10
|
+
require File.join(File.dirname(__FILE__), "../../merb-core/lib/merb-core/version.rb")
|
11
|
+
|
12
|
+
GEM_NAME = "merb-auth-more"
|
13
|
+
GEM_VERSION = Merb::VERSION
|
14
|
+
AUTHOR = "Daniel Neighman"
|
15
|
+
EMAIL = "has.sox@gmail.com"
|
16
|
+
HOMEPAGE = "http://merbivore.com/"
|
17
|
+
SUMMARY = "Additional resources for use with the merb-auth-core authentication framework."
|
18
|
+
|
19
|
+
spec = Gem::Specification.new do |s|
|
20
|
+
s.rubyforge_project = 'merb'
|
21
|
+
s.name = GEM_NAME
|
22
|
+
s.version = GEM_VERSION
|
23
|
+
s.platform = Gem::Platform::RUBY
|
24
|
+
s.has_rdoc = true
|
25
|
+
s.extra_rdoc_files = ["README.textile", "LICENSE", 'TODO']
|
26
|
+
s.summary = SUMMARY
|
27
|
+
s.description = s.summary
|
28
|
+
s.author = AUTHOR
|
29
|
+
s.email = EMAIL
|
30
|
+
s.homepage = HOMEPAGE
|
31
|
+
s.add_dependency("merb-auth-core", "= #{Merb::VERSION}")
|
32
|
+
s.require_path = 'lib'
|
33
|
+
s.files = %w(LICENSE README.textile Rakefile TODO) + Dir.glob("{lib,spec}/**/*")
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
38
|
+
pkg.gem_spec = spec
|
39
|
+
end
|
40
|
+
|
41
|
+
desc "install the plugin as a gem"
|
42
|
+
task :install do
|
43
|
+
Merb::RakeHelper.install(GEM_NAME, :version => GEM_VERSION)
|
44
|
+
end
|
45
|
+
|
46
|
+
desc "Uninstall the gem"
|
47
|
+
task :uninstall do
|
48
|
+
Merb::RakeHelper.uninstall(GEM_NAME, :version => GEM_VERSION)
|
49
|
+
end
|
50
|
+
|
51
|
+
desc "Create a gemspec file"
|
52
|
+
task :gemspec do
|
53
|
+
File.open("#{GEM_NAME}.gemspec", "w") do |file|
|
54
|
+
file.puts spec.to_ruby
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
desc "Run all specs"
|
59
|
+
Spec::Rake::SpecTask.new("specs") do |t|
|
60
|
+
t.spec_opts = ["--format", "specdoc", "--colour"]
|
61
|
+
t.spec_files = Dir["spec/**/*_spec.rb"].sort
|
62
|
+
t.rcov = false
|
63
|
+
t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
|
64
|
+
t.rcov_opts << '--only-uncovered'
|
65
|
+
end
|
data/TODO
ADDED
File without changes
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# make sure we're running inside Merb
|
2
|
+
if defined?(Merb::Plugins)
|
3
|
+
# Merb gives you a Merb::Plugins.config hash...feel free to put your stuff in your piece of it
|
4
|
+
Merb::Plugins.config[:"merb-auth_more"] = {
|
5
|
+
:chickens => false
|
6
|
+
}
|
7
|
+
|
8
|
+
# Register the strategies so that plugins and apps may utilize them
|
9
|
+
basic_path = File.expand_path(File.dirname(__FILE__)) / "merb-auth-more" / "strategies" / "basic"
|
10
|
+
|
11
|
+
Merb::Authentication.register(:default_basic_auth, basic_path / "basic_auth.rb")
|
12
|
+
Merb::Authentication.register(:default_openid, basic_path / "openid.rb")
|
13
|
+
Merb::Authentication.register(:default_password_form, basic_path / "password_form.rb")
|
14
|
+
|
15
|
+
Merb::BootLoader.before_app_loads do
|
16
|
+
# require code that must be loaded before the application
|
17
|
+
end
|
18
|
+
|
19
|
+
Merb::BootLoader.after_app_loads do
|
20
|
+
# code that can be required after the application loads
|
21
|
+
end
|
22
|
+
|
23
|
+
Merb::Plugins.add_rakefiles "merb-auth-more/merbtasks"
|
24
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# Impelments redirect_back_or. i.e. remembers where you've come from on a failed login
|
2
|
+
# and stores this inforamtion in the session. When you're finally logged in you can use
|
3
|
+
# the redirect_back_or helper to redirect them either back where they came from, or a pre-defined url.
|
4
|
+
#
|
5
|
+
# Here's some examples:
|
6
|
+
#
|
7
|
+
# 1. User visits login form and is logged in
|
8
|
+
# - redirect to the provided (default) url
|
9
|
+
#
|
10
|
+
# 2. User vists a page (/page) and gets kicked to login (raised Unauthenticated)
|
11
|
+
# - On successful login, the user may be redirect_back_or("/home") and they will
|
12
|
+
# return to the /page url. The /home url is ignored
|
13
|
+
#
|
14
|
+
#
|
15
|
+
|
16
|
+
#
|
17
|
+
module Merb::AuthenticatedHelper
|
18
|
+
|
19
|
+
# Add a helper to do the redirect_back_or for you. Also tidies up the session afterwards
|
20
|
+
# If there has been a failed login attempt on some page using this method
|
21
|
+
# you'll be redirected back to that page. Otherwise redirect to the default_url
|
22
|
+
#
|
23
|
+
# To make sure you're not redirected back to the login page after a failed then successful login,
|
24
|
+
# you can include an ignore url. Basically, if the return url == the ignore url go to the default_url
|
25
|
+
#
|
26
|
+
# set the ignore url via an :ignore option in the opts hash.
|
27
|
+
def redirect_back_or(default_url, opts = {})
|
28
|
+
if session.authentication.return_to_url && ![opts[:ignore]].flatten.include?(session.authentication.return_to_url)
|
29
|
+
redirect session.authentication.return_to_url, opts
|
30
|
+
else
|
31
|
+
redirect default_url, opts
|
32
|
+
end
|
33
|
+
session.authentication.return_to_url = nil
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
class Application < Merb::Controller; end
|
40
|
+
|
41
|
+
class Exceptions < Application
|
42
|
+
after :_set_return_to, :only => :unauthenticated
|
43
|
+
|
44
|
+
private
|
45
|
+
def _set_return_to
|
46
|
+
session.authentication.return_to_url ||= request.uri unless request.exceptions.blank?
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class Merb::Authentication
|
51
|
+
|
52
|
+
def return_to_url
|
53
|
+
@return_to_url ||= session[:return_to]
|
54
|
+
end
|
55
|
+
|
56
|
+
def return_to_url=(return_url)
|
57
|
+
@return_to_url = session[:return_to] = return_url
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require "digest/sha1"
|
2
|
+
class Merb::Authentication
|
3
|
+
module Mixins
|
4
|
+
# This mixin provides basic salted user password encryption.
|
5
|
+
#
|
6
|
+
# Added properties:
|
7
|
+
# :crypted_password, String
|
8
|
+
# :salt, String
|
9
|
+
#
|
10
|
+
# To use it simply require it and include it into your user class.
|
11
|
+
#
|
12
|
+
# class User
|
13
|
+
# include Merb::Authentication::Mixins::SaltedUser
|
14
|
+
#
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
module SaltedUser
|
18
|
+
|
19
|
+
def self.included(base)
|
20
|
+
base.class_eval do
|
21
|
+
attr_accessor :password, :password_confirmation
|
22
|
+
|
23
|
+
include Merb::Authentication::Mixins::SaltedUser::InstanceMethods
|
24
|
+
extend Merb::Authentication::Mixins::SaltedUser::ClassMethods
|
25
|
+
|
26
|
+
path = File.expand_path(File.dirname(__FILE__)) / "salted_user"
|
27
|
+
if defined?(DataMapper) && DataMapper::Resource > self
|
28
|
+
require path / "dm_salted_user"
|
29
|
+
extend(Merb::Authentication::Mixins::SaltedUser::DMClassMethods)
|
30
|
+
elsif defined?(ActiveRecord) && ancestors.include?(ActiveRecord::Base)
|
31
|
+
require path / "ar_salted_user"
|
32
|
+
extend(Merb::Authentication::Mixins::SaltedUser::ARClassMethods)
|
33
|
+
elsif defined?(Sequel) && ancestors.include?(Sequel::Model)
|
34
|
+
require path / "sq_salted_user"
|
35
|
+
extend(Merb::Authentication::Mixins::SaltedUser::SQClassMethods)
|
36
|
+
end
|
37
|
+
|
38
|
+
end # base.class_eval
|
39
|
+
end # self.included
|
40
|
+
|
41
|
+
|
42
|
+
module ClassMethods
|
43
|
+
# Encrypts some data with the salt.
|
44
|
+
def encrypt(password, salt)
|
45
|
+
Digest::SHA1.hexdigest("--#{salt}--#{password}--")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
module InstanceMethods
|
50
|
+
def authenticated?(password)
|
51
|
+
crypted_password == encrypt(password)
|
52
|
+
end
|
53
|
+
|
54
|
+
def encrypt(password)
|
55
|
+
self.class.encrypt(password, salt)
|
56
|
+
end
|
57
|
+
|
58
|
+
def password_required?
|
59
|
+
crypted_password.blank? || !password.blank?
|
60
|
+
end
|
61
|
+
|
62
|
+
def encrypt_password
|
63
|
+
return if password.blank?
|
64
|
+
self.salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{Merb::Authentication::Strategies::Basic::Base.login_param}--") if new_record?
|
65
|
+
self.crypted_password = encrypt(password)
|
66
|
+
end
|
67
|
+
|
68
|
+
end # InstanceMethods
|
69
|
+
|
70
|
+
end # SaltedUser
|
71
|
+
end # Mixins
|
72
|
+
end # Merb::Authentication
|
73
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Merb::Authentication
|
2
|
+
module Mixins
|
3
|
+
module SaltedUser
|
4
|
+
module ARClassMethods
|
5
|
+
|
6
|
+
def self.extended(base)
|
7
|
+
base.class_eval do
|
8
|
+
|
9
|
+
validates_presence_of :password, :if => :password_required?
|
10
|
+
validates_presence_of :password_confirmation, :if => :password_required?
|
11
|
+
validates_confirmation_of :password, :if => :password_required?
|
12
|
+
|
13
|
+
before_save :encrypt_password
|
14
|
+
end # base.class_eval
|
15
|
+
|
16
|
+
end # self.extended
|
17
|
+
|
18
|
+
def authenticate(login, password)
|
19
|
+
@u = find(:first, :conditions => ["#{Merb::Authentication::Strategies::Basic::Base.login_param} = ?", login])
|
20
|
+
@u && @u.authenticated?(password) ? @u : nil
|
21
|
+
end
|
22
|
+
end # ARClassMethods
|
23
|
+
end # SaltedUser
|
24
|
+
end # Mixins
|
25
|
+
end # Merb::Authentication
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class Merb::Authentication
|
2
|
+
module Mixins
|
3
|
+
module SaltedUser
|
4
|
+
module DMClassMethods
|
5
|
+
def self.extended(base)
|
6
|
+
base.class_eval do
|
7
|
+
|
8
|
+
property :crypted_password, String
|
9
|
+
property :salt, String
|
10
|
+
|
11
|
+
validates_present :password, :if => proc{|m| m.password_required?}
|
12
|
+
validates_is_confirmed :password, :if => proc{|m| m.password_required?}
|
13
|
+
|
14
|
+
before :save, :encrypt_password
|
15
|
+
end # base.class_eval
|
16
|
+
|
17
|
+
end # self.extended
|
18
|
+
|
19
|
+
def authenticate(login, password)
|
20
|
+
@u = first(Merb::Authentication::Strategies::Basic::Base.login_param => login)
|
21
|
+
@u && @u.authenticated?(password) ? @u : nil
|
22
|
+
end
|
23
|
+
end # DMClassMethods
|
24
|
+
end # SaltedUser
|
25
|
+
end # Mixins
|
26
|
+
end # Merb::Authentication
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class Merb::Authentication
|
2
|
+
module Mixins
|
3
|
+
module SaltedUser
|
4
|
+
module SQClassMethods
|
5
|
+
|
6
|
+
def self.extended(base)
|
7
|
+
base.class_eval do
|
8
|
+
|
9
|
+
validates_presence_of :password, :if => :password_required?
|
10
|
+
validates_presence_of :password_confirmation, :if => :password_required?
|
11
|
+
validates_confirmation_of :password, :if => :password_required?
|
12
|
+
|
13
|
+
before_save :encrypt_password
|
14
|
+
|
15
|
+
include Merb::Authentication::Mixins::SaltedUser::SQInstanceMethods
|
16
|
+
|
17
|
+
end # base.class_eval
|
18
|
+
|
19
|
+
end # self.extended
|
20
|
+
|
21
|
+
def authenticate(login, password)
|
22
|
+
@u = find(Merb::Authentication::Strategies::Basic::Base.login_param => login)
|
23
|
+
@u && @u.authenticated?(password) ? @u : nil
|
24
|
+
end
|
25
|
+
end # SQClassMethods
|
26
|
+
|
27
|
+
module SQInstanceMethods
|
28
|
+
def new_record?
|
29
|
+
new?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end # SaltedUser
|
34
|
+
end # Mixins
|
35
|
+
end # Merb::Authentication
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class Merb::Authentication
|
2
|
+
module Strategies
|
3
|
+
# To use the password strategies, it is expected that you will provide
|
4
|
+
# an @authenticate@ method on your user class. This should take two parameters
|
5
|
+
# login, and password. It should return nil or the user object.
|
6
|
+
module Basic
|
7
|
+
|
8
|
+
class Base < Merb::Authentication::Strategy
|
9
|
+
abstract!
|
10
|
+
|
11
|
+
# Overwrite this method to customize the field
|
12
|
+
def self.password_param
|
13
|
+
(Merb::Plugins.config[:"merb-auth"][:password_param] || :password).to_s.to_sym
|
14
|
+
end
|
15
|
+
|
16
|
+
# Overwrite this method to customize the field
|
17
|
+
def self.login_param
|
18
|
+
(Merb::Plugins.config[:"merb-auth"][:login_param] || :login).to_s.to_sym
|
19
|
+
end
|
20
|
+
|
21
|
+
def password_param
|
22
|
+
@password_param ||= Base.password_param
|
23
|
+
end
|
24
|
+
|
25
|
+
def login_param
|
26
|
+
@login_param ||= Base.login_param
|
27
|
+
end
|
28
|
+
end # Base
|
29
|
+
end # Password
|
30
|
+
end # Strategies
|
31
|
+
end # Merb::Authentication
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'merb-auth-more/strategies/abstract_password'
|
2
|
+
# This strategy is used with basic authentication in Merb.
|
3
|
+
#
|
4
|
+
# == Requirements
|
5
|
+
#
|
6
|
+
# == Methods
|
7
|
+
# <User>.authenticate(login_field, password_field)
|
8
|
+
#
|
9
|
+
class Merb::Authentication
|
10
|
+
module Strategies
|
11
|
+
module Basic
|
12
|
+
class BasicAuth < Base
|
13
|
+
|
14
|
+
def run!
|
15
|
+
if basic_authentication?
|
16
|
+
basic_authentication do |login, password|
|
17
|
+
user = user_class.authenticate(login, password)
|
18
|
+
unless user
|
19
|
+
request_basic_auth!
|
20
|
+
end
|
21
|
+
user
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.realm
|
27
|
+
@realm ||= "Application"
|
28
|
+
end
|
29
|
+
|
30
|
+
cattr_writer :realm
|
31
|
+
def realm
|
32
|
+
@realm ||= self.class.realm
|
33
|
+
end
|
34
|
+
|
35
|
+
cattr_accessor :failure_message
|
36
|
+
@@failure_message = "Login Required"
|
37
|
+
|
38
|
+
private
|
39
|
+
def initialize(request, params)
|
40
|
+
super
|
41
|
+
@auth = Rack::Auth::Basic::Request.new(request.env)
|
42
|
+
end
|
43
|
+
|
44
|
+
def basic_authentication?
|
45
|
+
@auth.provided? and @auth.basic?
|
46
|
+
end
|
47
|
+
|
48
|
+
def username
|
49
|
+
basic_authentication? ? @auth.credentials.first : nil
|
50
|
+
end
|
51
|
+
|
52
|
+
def password
|
53
|
+
basic_authentication? ? @auth.credentials.last : nil
|
54
|
+
end
|
55
|
+
|
56
|
+
def request_basic_auth!
|
57
|
+
self.status = Merb::Controller::Unauthorized.status
|
58
|
+
self.headers['WWW-Authenticate'] = 'Basic realm="%s"' % realm
|
59
|
+
self.body = self.class.failure_message
|
60
|
+
halt!
|
61
|
+
end
|
62
|
+
|
63
|
+
def basic_authentication(realm = nil, &authenticator)
|
64
|
+
self.realm = realm if realm
|
65
|
+
if basic_authentication?
|
66
|
+
authenticator.call(*@auth.credentials)
|
67
|
+
else
|
68
|
+
false
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
end # BasicAuth
|
74
|
+
end # Password
|
75
|
+
end # Strategies
|
76
|
+
end # Merb::Authentication
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# The openid strategy attempts to login users based on the OpenID protocol
|
2
|
+
# http://openid.net/
|
3
|
+
#
|
4
|
+
# Overwrite the on_sucess!, on_failure!, on_setup_needed!, and on_cancel! to customize events.
|
5
|
+
#
|
6
|
+
# Overwite the required_reg_fields method to require different sreg fields. Default is email and nickname
|
7
|
+
#
|
8
|
+
# Overwrite the openid_store method to customize your session store
|
9
|
+
#
|
10
|
+
# == Requirments
|
11
|
+
#
|
12
|
+
# === Routes:
|
13
|
+
# :openid - an action that is accessilbe via http GET and protected via ensure_authenticated
|
14
|
+
# :signup - a url accessed via GET that takes a user to a signup form (overwritable)
|
15
|
+
#
|
16
|
+
# === Attributes
|
17
|
+
# :identity_url - A string for holding the identity_url associated with this user (overwritable)
|
18
|
+
#
|
19
|
+
# install the ruby-openid gem
|
20
|
+
require 'openid'
|
21
|
+
require 'openid/store/filesystem'
|
22
|
+
require 'openid/extensions/sreg'
|
23
|
+
|
24
|
+
class Merb::Authentication
|
25
|
+
module Strategies
|
26
|
+
module Basic
|
27
|
+
class OpenID < Base
|
28
|
+
def run!
|
29
|
+
if request.params[:'openid.mode']
|
30
|
+
response = consumer.complete(request.send(:query_params), "#{request.protocol}://#{request.host}" + request.path)
|
31
|
+
case response.status.to_s
|
32
|
+
when 'success'
|
33
|
+
sreg_response = ::OpenID::SReg::Response.from_success_response(response)
|
34
|
+
result = on_success!(response, sreg_response)
|
35
|
+
Merb.logger.info "\n\n#{result.inspect}\n\n"
|
36
|
+
result
|
37
|
+
when 'failure'
|
38
|
+
on_failure!(response)
|
39
|
+
when 'setup_needed'
|
40
|
+
on_setup_needed!(response)
|
41
|
+
when 'cancel'
|
42
|
+
on_cancel!(response)
|
43
|
+
end
|
44
|
+
elsif identity_url = params[:openid_url]
|
45
|
+
begin
|
46
|
+
openid_request = consumer.begin(identity_url)
|
47
|
+
openid_reg = ::OpenID::SReg::Request.new
|
48
|
+
openid_reg.request_fields(required_reg_fields)
|
49
|
+
openid_request.add_extension(openid_reg)
|
50
|
+
redirect_to = "#{request.protocol}://#{request.host}#{Merb::Router.url(:openid)}"
|
51
|
+
redirect!(openid_request.redirect_url("#{request.protocol}://#{request.host}", redirect_to))
|
52
|
+
rescue ::OpenID::OpenIDError => e
|
53
|
+
request.session.authentication.errors.clear!
|
54
|
+
request.session.authentication.errors.add(:openid, 'The OpenID verification failed')
|
55
|
+
nil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end # run!
|
59
|
+
|
60
|
+
|
61
|
+
# Overwrite the on_success! method with the required behavior for successful logins
|
62
|
+
#
|
63
|
+
# @api overwritable
|
64
|
+
def on_success!(response, sreg_response)
|
65
|
+
if user = find_user_by_identity_url(response.identity_url)
|
66
|
+
user
|
67
|
+
else
|
68
|
+
request.session[:'openid.url'] = response.identity_url
|
69
|
+
required_reg_fields.each do |f|
|
70
|
+
session[:"openid.#{f}"] = sreg_response.data[f] if sreg_response.data[f]
|
71
|
+
end if sreg_response
|
72
|
+
redirect!(Merb::Router.url(:signup))
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Overwrite the on_failure! method with the required behavior for failed logins
|
77
|
+
#
|
78
|
+
# @api overwritable
|
79
|
+
def on_failure!(response)
|
80
|
+
session.authentication.errors.clear!
|
81
|
+
session.authentication.errors.add(:openid, 'OpenID verification failed, maybe the provider is down? Or the session timed out')
|
82
|
+
nil
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# @api overwritable
|
87
|
+
def on_setup_needed!(response)
|
88
|
+
request.session.authentication.errors.clear!
|
89
|
+
request.session.authentication.errors.add(:openid, 'OpenID does not seem to be configured correctly')
|
90
|
+
nil
|
91
|
+
end
|
92
|
+
|
93
|
+
#
|
94
|
+
# @api overwritable
|
95
|
+
def on_cancel!(response)
|
96
|
+
request.session.authentication.errors.clear!
|
97
|
+
request.session.authentication.errors.add(:openid, 'OpenID rejected our request')
|
98
|
+
nil
|
99
|
+
end
|
100
|
+
|
101
|
+
#
|
102
|
+
# @api overwritable
|
103
|
+
def required_reg_fields
|
104
|
+
['nickname', 'email']
|
105
|
+
end
|
106
|
+
|
107
|
+
# Overwrite this to support an ORM other than DataMapper
|
108
|
+
#
|
109
|
+
# @api overwritable
|
110
|
+
def find_user_by_identity_url(url)
|
111
|
+
user_class.first(:identity_url => url)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Overwrite this method to set your store
|
115
|
+
#
|
116
|
+
# @api overwritable
|
117
|
+
def openid_store
|
118
|
+
::OpenID::Store::Filesystem.new("#{Merb.root}/tmp/openid")
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
def consumer
|
123
|
+
@consumer ||= ::OpenID::Consumer.new(request.session, openid_store)
|
124
|
+
end
|
125
|
+
|
126
|
+
end # OpenID
|
127
|
+
end # Basic
|
128
|
+
end # Strategies
|
129
|
+
end # Merb::Authentication
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'merb-auth-more/strategies/abstract_password'
|
2
|
+
# This strategy uses a login and password parameter.
|
3
|
+
#
|
4
|
+
# Overwrite the :password_param, and :login_param
|
5
|
+
# to return the name of the field (on the form) that you're using the
|
6
|
+
# login with. These can be strings or symbols
|
7
|
+
#
|
8
|
+
# == Required
|
9
|
+
#
|
10
|
+
# === Methods
|
11
|
+
# <User>.authenticate(login_param, password_param)
|
12
|
+
#
|
13
|
+
class Merb::Authentication
|
14
|
+
module Strategies
|
15
|
+
module Basic
|
16
|
+
class Form < Base
|
17
|
+
|
18
|
+
def run!
|
19
|
+
if request.params[login_param] && request.params[password_param]
|
20
|
+
user = user_class.authenticate(request.params[login_param], request.params[password_param])
|
21
|
+
if !user
|
22
|
+
request.session.authentication.errors.clear!
|
23
|
+
request.session.authentication.errors.add(login_param, "#{login_param.to_s.capitalize} or #{password_param.to_s.capitalize} were incorrect")
|
24
|
+
end
|
25
|
+
user
|
26
|
+
end
|
27
|
+
end # run!
|
28
|
+
|
29
|
+
end # Form
|
30
|
+
end # Password
|
31
|
+
end # Strategies
|
32
|
+
end # Authentication
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "..", 'spec_helper.rb')
|
2
|
+
require File.join(File.expand_path(File.dirname(__FILE__)), "..", ".." ,"lib", "merb-auth-more", "mixins", "redirect_back")
|
3
|
+
|
4
|
+
describe "redirect_back" do
|
5
|
+
|
6
|
+
before(:all) do
|
7
|
+
clear_strategies!
|
8
|
+
|
9
|
+
class Merb::Authentication
|
10
|
+
def store_user(user); user; end
|
11
|
+
def fetch_user(session_info); session_info; end
|
12
|
+
end
|
13
|
+
|
14
|
+
class MyStrategy < Merb::Authentication::Strategy; def run!; request.env["USER"]; end; end
|
15
|
+
|
16
|
+
class Application < Merb::Controller; end
|
17
|
+
|
18
|
+
class Exceptions < Application
|
19
|
+
def unauthenticated; end
|
20
|
+
end
|
21
|
+
|
22
|
+
class MyController < Application
|
23
|
+
before :ensure_authenticated
|
24
|
+
|
25
|
+
def index; "HERE!" end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should set the return_to in the session when sent to the exceptions controller from a failed login" do
|
31
|
+
controller = dispatch_to(Exceptions, :unauthenticated, {}, {:user => "winna", :request_uri => "go_back"}) do |c|
|
32
|
+
c.request.exceptions = [Merb::Controller::Unauthenticated.new]
|
33
|
+
end
|
34
|
+
controller.session.authentication.return_to_url.should == "go_back"
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should not set the return_to in the session when deliberately going to unauthenticated" do
|
38
|
+
controller = dispatch_to(Exceptions, :unauthenticated, {}, {:user => "winna", :request_uri => "don't_go_back"}) do |c|
|
39
|
+
c.request.exceptions = []
|
40
|
+
end
|
41
|
+
controller.session.authentication.return_to_url.should be_nil
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should not set the return_to when loggin into a controller directly" do
|
45
|
+
controller = dispatch_to(MyController, :index, {}, :user => "winna", :request_uri => "NOOO")
|
46
|
+
controller.session.authentication.return_to_url.should be_nil
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "redirect_back helper" do
|
50
|
+
|
51
|
+
before(:each) do
|
52
|
+
@with_redirect = dispatch_to(Exceptions, :unauthenticated, {}, :user => "WINNA", :request_uri => "request_uri") do |c|
|
53
|
+
c.request.exceptions = [Merb::Controller::Unauthenticated.new]
|
54
|
+
end
|
55
|
+
@no_redirect = dispatch_to(MyController, :index, {}, :user => "winna", :request_uri => "NOOO")
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should provide the url stored in the session" do
|
59
|
+
@with_redirect.session.authentication.return_to_url.should == "request_uri"
|
60
|
+
@with_redirect.redirect_back_or("/some/path")
|
61
|
+
@with_redirect.headers["Location"].should == "request_uri"
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should provide the url passed in by default when there is no return_to" do
|
65
|
+
@no_redirect.session.authentication.return_to_url.should be_nil
|
66
|
+
@no_redirect.redirect_back_or("/some/path")
|
67
|
+
@no_redirect.headers["Location"].should == "/some/path"
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should wipe out the return_to in the session after the redirect" do
|
71
|
+
@with_redirect.session.authentication.return_to_url.should == "request_uri"
|
72
|
+
@with_redirect.redirect_back_or("somewhere")
|
73
|
+
@with_redirect.headers["Location"].should == "request_uri"
|
74
|
+
@with_redirect.session.authentication.return_to_url.should be_nil
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should ignore a return_to if it's the same as the ignore url" do
|
78
|
+
@with_redirect.redirect_back_or("somewhere", :ignore => "request_uri")
|
79
|
+
@with_redirect.headers["Location"].should == "somewhere"
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "..", 'spec_helper.rb')
|
2
|
+
require 'dm-core'
|
3
|
+
require 'dm-validations'
|
4
|
+
require File.join(File.expand_path(File.dirname(__FILE__)), "..", ".." ,"lib", "merb-auth-more", "strategies", "abstract_password")
|
5
|
+
require File.join(File.expand_path(File.dirname(__FILE__)), "..", ".." ,"lib", "merb-auth-more", "mixins", "salted_user")
|
6
|
+
|
7
|
+
describe "A Salted User" do
|
8
|
+
|
9
|
+
before(:all) do
|
10
|
+
DataMapper.setup(:default, "sqlite3::memory:")
|
11
|
+
|
12
|
+
class Utilisateur
|
13
|
+
include DataMapper::Resource
|
14
|
+
include Merb::Authentication::Mixins::SaltedUser
|
15
|
+
|
16
|
+
property :id, Serial
|
17
|
+
property :email, String
|
18
|
+
property :login, String
|
19
|
+
end
|
20
|
+
Utilisateur.auto_migrate!
|
21
|
+
end
|
22
|
+
|
23
|
+
after(:each) do
|
24
|
+
Utilisateur.all.destroy!
|
25
|
+
end
|
26
|
+
|
27
|
+
def default_user_params
|
28
|
+
{:login => "fred", :email => "fred@example.com", :password => "sekrit", :password_confirmation => "sekrit"}
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should authenticate a user using a class method" do
|
32
|
+
user = Utilisateur.new(default_user_params)
|
33
|
+
user.save
|
34
|
+
user.should_not be_new_record
|
35
|
+
Utilisateur.authenticate("fred", "sekrit").should_not be_nil
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should not authenticate a user using the wrong password" do
|
39
|
+
user = Utilisateur.new(default_user_params)
|
40
|
+
user.save
|
41
|
+
Utilisateur.authenticate("fred", "not_the_password").should be_nil
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should not authenticate a user using the wrong login" do
|
45
|
+
user = Utilisateur.create(default_user_params)
|
46
|
+
Utilisateur.authenticate("not_the_login@blah.com", "sekrit").should be_nil
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should not authenticate a user that does not exist" do
|
50
|
+
Utilisateur.authenticate("i_dont_exist", "password").should be_nil
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "passwords" do
|
54
|
+
before(:each) do
|
55
|
+
@user = Utilisateur.new(default_user_params)
|
56
|
+
end
|
57
|
+
|
58
|
+
it{@user.should respond_to(:password)}
|
59
|
+
it{@user.should respond_to(:password_confirmation)}
|
60
|
+
it{@user.should respond_to(:crypted_password)}
|
61
|
+
|
62
|
+
it "should require password if password is required" do
|
63
|
+
user = Utilisateur.new(:login => "fred", :email => "fred@example.com")
|
64
|
+
user.stub!(:password_required?).and_return(true)
|
65
|
+
user.valid?
|
66
|
+
user.errors.on(:password).should_not be_nil
|
67
|
+
user.errors.on(:password).should_not be_empty
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should set the salt" do
|
71
|
+
@user.salt.should be_nil
|
72
|
+
@user.send(:encrypt_password)
|
73
|
+
@user.salt.should_not be_nil
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should require the password on create" do
|
77
|
+
user = Utilisateur.new(:login => "fred", :email => "fred@example.com")
|
78
|
+
user.save
|
79
|
+
user.errors.on(:password).should_not be_nil
|
80
|
+
user.errors.on(:password).should_not be_empty
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should require password_confirmation if the password_required?" do
|
84
|
+
user = Utilisateur.new(:login => "fred", :email => "fred@example.com", :password => "sekrit")
|
85
|
+
user.save
|
86
|
+
(user.errors.on(:password) || user.errors.on(:password_confirmation)).should_not be_nil
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should autenticate against a password" do
|
90
|
+
@user.save
|
91
|
+
@user.should be_authenticated("sekrit")
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should not require a password when saving an existing user" do
|
95
|
+
@user.save
|
96
|
+
@user.should_not be_a_new_record
|
97
|
+
@user = Utilisateur.first(:login => "fred")
|
98
|
+
@user.password.should be_nil
|
99
|
+
@user.password_confirmation.should be_nil
|
100
|
+
@user.login = "some_different_login_to_allow_saving"
|
101
|
+
(@user.save).should be_true
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
$TESTING=true
|
2
|
+
$:.push File.join(File.dirname(__FILE__), '..', 'lib')
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'merb-core'
|
6
|
+
require 'merb-core/test'
|
7
|
+
require 'merb-core/dispatch/session'
|
8
|
+
require 'spec' # Satisfies Autotest and anyone else not using the Rake tasks
|
9
|
+
require 'merb-auth-core'
|
10
|
+
|
11
|
+
Merb.start :environment => "test",
|
12
|
+
:adapter => "runner",
|
13
|
+
:session_store => "cookie",
|
14
|
+
:session_secret_key => "d3a6e6f99a25004da82b71af8b9ed0ab71d3ea21"
|
15
|
+
|
16
|
+
module StrategyHelper
|
17
|
+
def clear_strategies!
|
18
|
+
Merb::Authentication.strategies.each do |s|
|
19
|
+
begin
|
20
|
+
Object.class_eval{ remove_const(s.name) if defined?(s)}
|
21
|
+
rescue
|
22
|
+
end
|
23
|
+
end
|
24
|
+
Merb::Authentication.strategies.clear
|
25
|
+
Merb::Authentication.default_strategy_order.clear
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
Spec::Runner.configure do |config|
|
30
|
+
config.include(Merb::Test::ViewHelper)
|
31
|
+
config.include(Merb::Test::RouteHelper)
|
32
|
+
config.include(Merb::Test::ControllerHelper)
|
33
|
+
config.include(StrategyHelper)
|
34
|
+
end
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: merb-auth-more
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.9
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Daniel Neighman
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-10-13 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: merb-auth-core
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - "="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.9.9
|
24
|
+
version:
|
25
|
+
description: Additional resources for use with the merb-auth-core authentication framework.
|
26
|
+
email: has.sox@gmail.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README.textile
|
33
|
+
- LICENSE
|
34
|
+
- TODO
|
35
|
+
files:
|
36
|
+
- LICENSE
|
37
|
+
- README.textile
|
38
|
+
- Rakefile
|
39
|
+
- TODO
|
40
|
+
- lib/merb-auth-more
|
41
|
+
- lib/merb-auth-more/merbtasks.rb
|
42
|
+
- lib/merb-auth-more/mixins
|
43
|
+
- lib/merb-auth-more/mixins/redirect_back.rb
|
44
|
+
- lib/merb-auth-more/mixins/salted_user
|
45
|
+
- lib/merb-auth-more/mixins/salted_user/ar_salted_user.rb
|
46
|
+
- lib/merb-auth-more/mixins/salted_user/dm_salted_user.rb
|
47
|
+
- lib/merb-auth-more/mixins/salted_user/sq_salted_user.rb
|
48
|
+
- lib/merb-auth-more/mixins/salted_user.rb
|
49
|
+
- lib/merb-auth-more/strategies
|
50
|
+
- lib/merb-auth-more/strategies/abstract_password.rb
|
51
|
+
- lib/merb-auth-more/strategies/basic
|
52
|
+
- lib/merb-auth-more/strategies/basic/basic_auth.rb
|
53
|
+
- lib/merb-auth-more/strategies/basic/openid.rb
|
54
|
+
- lib/merb-auth-more/strategies/basic/password_form.rb
|
55
|
+
- lib/merb-auth-more.rb
|
56
|
+
- spec/merb-auth-more_spec.rb
|
57
|
+
- spec/mixins
|
58
|
+
- spec/mixins/redirect_back_spec.rb
|
59
|
+
- spec/mixins/salted_user_spec.rb
|
60
|
+
- spec/spec_helper.rb
|
61
|
+
has_rdoc: true
|
62
|
+
homepage: http://merbivore.com/
|
63
|
+
post_install_message:
|
64
|
+
rdoc_options: []
|
65
|
+
|
66
|
+
require_paths:
|
67
|
+
- lib
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: "0"
|
73
|
+
version:
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: "0"
|
79
|
+
version:
|
80
|
+
requirements: []
|
81
|
+
|
82
|
+
rubyforge_project: merb
|
83
|
+
rubygems_version: 1.2.0
|
84
|
+
signing_key:
|
85
|
+
specification_version: 2
|
86
|
+
summary: Additional resources for use with the merb-auth-core authentication framework.
|
87
|
+
test_files: []
|
88
|
+
|