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