mirror of
https://github.com/omniauth/omniauth.git
synced 2022-11-09 12:31:49 -05:00
Clearing out everything to move towards the new 1.0 independent gems strategy.
This commit is contained in:
parent
9f8119544a
commit
cc73281406
253 changed files with 60 additions and 10805 deletions
13
Gemfile
13
Gemfile
|
@ -1,14 +1,3 @@
|
|||
source 'http://rubygems.org'
|
||||
|
||||
platforms :jruby do
|
||||
gem 'jruby-openssl', '~> 0.7'
|
||||
end
|
||||
|
||||
gemspec :path => 'oa-core'
|
||||
gemspec :path => 'oa-enterprise'
|
||||
gemspec :path => 'oa-more'
|
||||
gemspec :path => 'oa-oauth'
|
||||
gemspec :path => 'oa-openid'
|
||||
|
||||
gem 'activerecord', '3.1.0.rc1'
|
||||
|
||||
gemspec
|
||||
|
|
27
Guardfile
27
Guardfile
|
@ -1,27 +0,0 @@
|
|||
# A sample Guardfile
|
||||
# More info at https://github.com/guard/guard#readme
|
||||
|
||||
guard 'rspec', :version => 2 do
|
||||
watch(%r{^spec/.+_spec\.rb$})
|
||||
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
||||
watch('spec/spec_helper.rb') { "spec/" }
|
||||
|
||||
# Rails example
|
||||
watch(%r{^spec/.+_spec\.rb$})
|
||||
watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
||||
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
||||
watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
|
||||
watch(%r{^spec/support/(.+)\.rb$}) { "spec/" }
|
||||
watch('spec/spec_helper.rb') { "spec/" }
|
||||
watch('config/routes.rb') { "spec/routing" }
|
||||
watch('app/controllers/application_controller.rb') { "spec/controllers" }
|
||||
# Capybara request specs
|
||||
watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
|
||||
end
|
||||
|
||||
|
||||
guard 'bundler' do
|
||||
watch('Gemfile')
|
||||
# Uncomment next line if Gemfile contain `gemspec' command
|
||||
# watch(/^.+\.gemspec/)
|
||||
end
|
19
LICENSE.md
19
LICENSE.md
|
@ -1,19 +0,0 @@
|
|||
Copyright (c) 2010-2011 Michael Bleigh, Erik Michaels-Ober, and Intridea, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
55
Rakefile
Executable file → Normal file
55
Rakefile
Executable file → Normal file
|
@ -1,51 +1,6 @@
|
|||
#!/usr/bin/env rake
|
||||
|
||||
$:.unshift File.expand_path('..', __FILE__)
|
||||
require 'tasks/all'
|
||||
|
||||
desc 'Clean up temporary files'
|
||||
task :clean => 'all:clean'
|
||||
|
||||
desc 'Build gem files for all projects into the package directory'
|
||||
task :build => 'all:build'
|
||||
|
||||
desc 'Build and install gems for all projects'
|
||||
task :install => 'all:install'
|
||||
|
||||
desc 'Write version with MAJOR, MINOR, PATCH, and PRE environment variables'
|
||||
task 'version:write' => 'all:version:write'
|
||||
|
||||
desc 'Display the current version for all projects'
|
||||
task :version => 'all:version'
|
||||
desc 'Increment the major version for all projects'
|
||||
task 'version:bump:major' => 'all:version:bump:major'
|
||||
desc 'Increment the minor version for all projects'
|
||||
task 'version:bump:minor' => 'all:version:bump:minor'
|
||||
desc 'Increment the patch version for all projects'
|
||||
task 'version:bump:patch' => 'all:version:bump:patch'
|
||||
|
||||
desc 'Run specs for all projects'
|
||||
task :spec => 'all:spec'
|
||||
require 'bundler'
|
||||
Bundler::GemHelper.install_tasks
|
||||
require 'rspec/core/rake_task'
|
||||
RSpec::Core::RakeTask.new(:spec)
|
||||
task :default => :spec
|
||||
task :test => :spec
|
||||
task :default => :test
|
||||
|
||||
desc 'Generate docs for all projects'
|
||||
task 'doc:yard' => 'all:doc:yard'
|
||||
|
||||
task :tag do
|
||||
sh "git tag -a -m \"Version #{version}\" v#{version}"
|
||||
sh "git push"
|
||||
sh "git push --tags"
|
||||
end
|
||||
|
||||
task :push => 'all:push'
|
||||
|
||||
desc 'Build, tag, and push gems for all projects to Rubygems'
|
||||
task :release => [:build, :tag, :push]
|
||||
|
||||
namespace :doc do
|
||||
require 'yard'
|
||||
YARD::Rake::YardocTask.new do |task|
|
||||
task.files = PROJECTS.map{|project| "#{root}/#{project}/lib/**/*.rb"} + ['README.markdown', 'LICENSE']
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
require 'omniauth/core'
|
||||
require 'omniauth/oauth'
|
||||
require 'omniauth/openid'
|
||||
require 'omniauth/enterprise'
|
||||
require 'omniauth/more'
|
||||
require 'omniauth/identity'
|
|
@ -8,6 +8,7 @@ module OmniAuth
|
|||
autoload :Strategy, 'omniauth/strategy'
|
||||
autoload :Test, 'omniauth/test'
|
||||
autoload :Form, 'omniauth/form'
|
||||
autoload :AuthHash, 'omniauth/auth_hash'
|
||||
|
||||
def self.strategies
|
||||
@@strategies ||= []
|
||||
|
|
|
@ -99,7 +99,12 @@ module OmniAuth
|
|||
|
||||
def self.build(title=nil,&block)
|
||||
form = OmniAuth::Form.new(title)
|
||||
form.instance_eval(&block)
|
||||
if block.arity > 0
|
||||
yield form
|
||||
else
|
||||
form.instance_eval(&block)
|
||||
end
|
||||
form
|
||||
end
|
||||
|
||||
def label_field(text, target)
|
||||
|
|
|
@ -38,12 +38,19 @@ module OmniAuth
|
|||
|
||||
return mock_call!(env) if OmniAuth.config.test_mode
|
||||
|
||||
return options_call if on_auth_path? && options_request?
|
||||
return request_call if on_request_path? && OmniAuth.config.allowed_request_methods.include?(request.request_method.downcase.to_sym)
|
||||
return callback_call if on_callback_path?
|
||||
return other_phase if respond_to?(:other_phase)
|
||||
@app.call(env)
|
||||
end
|
||||
|
||||
# Responds to an OPTIONS request.
|
||||
def options_call
|
||||
verbs = OmniAuth.config.allowed_request_methods.map(&:to_s).map(&:upcase).join(', ')
|
||||
return [ 200, { 'Allow' => verbs }, [] ]
|
||||
end
|
||||
|
||||
# Performs the steps necessary to run the request phase of a strategy.
|
||||
def request_call
|
||||
setup_phase
|
||||
|
@ -73,11 +80,19 @@ module OmniAuth
|
|||
end
|
||||
|
||||
def on_request_path?
|
||||
current_path.casecmp(request_path) == 0
|
||||
on_path?(request_path)
|
||||
end
|
||||
|
||||
def on_callback_path?
|
||||
current_path.casecmp(callback_path) == 0
|
||||
on_path?(callback_path)
|
||||
end
|
||||
|
||||
def on_path?(path)
|
||||
current_path.casecmp(path) == 0
|
||||
end
|
||||
|
||||
def options_request?
|
||||
request.request_method == 'OPTIONS'
|
||||
end
|
||||
|
||||
def mock_call!(env)
|
||||
|
@ -168,10 +183,7 @@ module OmniAuth
|
|||
end
|
||||
|
||||
def auth_hash
|
||||
{
|
||||
'provider' => name.to_s,
|
||||
'uid' => nil
|
||||
}
|
||||
AuthHash.new(:provider => name.to_s)
|
||||
end
|
||||
|
||||
def full_host
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
--color
|
||||
--format=nested
|
||||
--backtrace
|
|
@ -1,4 +0,0 @@
|
|||
--markup markdown
|
||||
--markup-provider maruku
|
||||
-
|
||||
LICENSE
|
|
@ -1,3 +0,0 @@
|
|||
source 'http://rubygems.org'
|
||||
|
||||
gemspec
|
|
@ -1,19 +0,0 @@
|
|||
Copyright (c) 2010-2011 Michael Bleigh and Intridea, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -1,6 +0,0 @@
|
|||
require 'bundler'
|
||||
Bundler::GemHelper.install_tasks
|
||||
require 'rspec/core/rake_task'
|
||||
RSpec::Core::RakeTask.new(:spec)
|
||||
task :default => :spec
|
||||
task :test => :spec
|
|
@ -1 +0,0 @@
|
|||
Autotest.add_discovery { "rspec2" }
|
|
@ -1 +0,0 @@
|
|||
require 'omniauth/core'
|
|
@ -1,33 +0,0 @@
|
|||
require 'omniauth/core'
|
||||
|
||||
module OmniAuth
|
||||
class Builder < ::Rack::Builder
|
||||
def initialize(app, &block)
|
||||
@app = app
|
||||
super(&block)
|
||||
end
|
||||
|
||||
def on_failure(&block)
|
||||
OmniAuth.config.on_failure = block
|
||||
end
|
||||
|
||||
def configure(&block)
|
||||
OmniAuth.configure(&block)
|
||||
end
|
||||
|
||||
def provider(klass, *args, &block)
|
||||
if klass.is_a?(Class)
|
||||
middleware = klass
|
||||
else
|
||||
middleware = OmniAuth::Strategies.const_get("#{OmniAuth::Utils.camelize(klass.to_s)}")
|
||||
end
|
||||
|
||||
use middleware, *args, &block
|
||||
end
|
||||
|
||||
def call(env)
|
||||
@ins << @app unless @ins.include?(@app)
|
||||
to_app.call(env)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,141 +0,0 @@
|
|||
require 'rack'
|
||||
require 'singleton'
|
||||
|
||||
module OmniAuth
|
||||
module Strategies; end
|
||||
|
||||
autoload :Builder, 'omniauth/builder'
|
||||
autoload :Strategy, 'omniauth/strategy'
|
||||
autoload :Test, 'omniauth/test'
|
||||
autoload :Form, 'omniauth/form'
|
||||
autoload :AuthHash, 'omniauth/auth_hash'
|
||||
|
||||
def self.strategies
|
||||
@@strategies ||= []
|
||||
end
|
||||
|
||||
class Configuration
|
||||
include Singleton
|
||||
|
||||
@@defaults = {
|
||||
:path_prefix => '/auth',
|
||||
:on_failure => Proc.new do |env|
|
||||
message_key = env['omniauth.error.type']
|
||||
new_path = "#{OmniAuth.config.path_prefix}/failure?message=#{message_key}"
|
||||
[302, {'Location' => new_path, 'Content-Type'=> 'text/html'}, []]
|
||||
end,
|
||||
:form_css => Form::DEFAULT_CSS,
|
||||
:test_mode => false,
|
||||
:allowed_request_methods => [:get, :post],
|
||||
:mock_auth => {
|
||||
:default => {
|
||||
'provider' => 'default',
|
||||
'uid' => '1234',
|
||||
'user_info' => {
|
||||
'name' => 'Bob Example'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def self.defaults
|
||||
@@defaults
|
||||
end
|
||||
|
||||
def initialize
|
||||
@@defaults.each_pair{|k,v| self.send("#{k}=",v)}
|
||||
end
|
||||
|
||||
def on_failure(&block)
|
||||
if block_given?
|
||||
@on_failure = block
|
||||
else
|
||||
@on_failure
|
||||
end
|
||||
end
|
||||
|
||||
def add_mock(provider, mock={})
|
||||
# Stringify keys recursively one level.
|
||||
mock.keys.each do |key|
|
||||
mock[key.to_s] = mock.delete(key)
|
||||
end
|
||||
mock.each_pair do |key, val|
|
||||
if val.is_a? Hash
|
||||
val.keys.each do |subkey|
|
||||
val[subkey.to_s] = val.delete(subkey)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Merge with the default mock and ensure provider is correct.
|
||||
mock = self.mock_auth[:default].dup.merge(mock)
|
||||
mock["provider"] = provider.to_s
|
||||
|
||||
# Add it to the mocks.
|
||||
self.mock_auth[provider.to_sym] = mock
|
||||
end
|
||||
|
||||
attr_writer :on_failure
|
||||
attr_accessor :path_prefix, :allowed_request_methods, :form_css, :test_mode, :mock_auth, :full_host
|
||||
end
|
||||
|
||||
def self.config
|
||||
Configuration.instance
|
||||
end
|
||||
|
||||
def self.configure
|
||||
yield config
|
||||
end
|
||||
|
||||
def self.mock_auth_for(provider)
|
||||
config.mock_auth[provider.to_sym] || config.mock_auth[:default]
|
||||
end
|
||||
|
||||
module Utils
|
||||
CAMELIZE_SPECIAL = {
|
||||
'oauth' => 'OAuth',
|
||||
'oauth2' => 'OAuth2',
|
||||
'openid' => 'OpenID',
|
||||
'open_id' => 'OpenID',
|
||||
'github' => 'GitHub',
|
||||
'tripit' => 'TripIt',
|
||||
'soundcloud' => 'SoundCloud',
|
||||
'smugmug' => 'SmugMug',
|
||||
'cas' => 'CAS',
|
||||
'trademe' => 'TradeMe',
|
||||
'ldap' => 'LDAP',
|
||||
'google_oauth2' => 'GoogleOAuth2'
|
||||
}
|
||||
|
||||
module_function
|
||||
|
||||
def form_css
|
||||
"<style type='text/css'>#{OmniAuth.config.form_css}</style>"
|
||||
end
|
||||
|
||||
def deep_merge(hash, other_hash)
|
||||
target = hash.dup
|
||||
|
||||
other_hash.keys.each do |key|
|
||||
if other_hash[key].is_a? ::Hash and hash[key].is_a? ::Hash
|
||||
target[key] = deep_merge(target[key],other_hash[key])
|
||||
next
|
||||
end
|
||||
|
||||
target[key] = other_hash[key]
|
||||
end
|
||||
|
||||
target
|
||||
end
|
||||
|
||||
def camelize(word, first_letter_in_uppercase = true)
|
||||
return CAMELIZE_SPECIAL[word.to_s] if CAMELIZE_SPECIAL[word.to_s]
|
||||
|
||||
if first_letter_in_uppercase
|
||||
word.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
|
||||
else
|
||||
word.first + camelize(word)[1..-1]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,191 +0,0 @@
|
|||
require 'omniauth/core'
|
||||
|
||||
module OmniAuth
|
||||
class Form
|
||||
DEFAULT_CSS = <<-CSS
|
||||
body {
|
||||
background: #ccc;
|
||||
font-family: "Lucida Grande", "Lucida Sans", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
margin: 30px auto 0px;
|
||||
font-size: 18px;
|
||||
padding: 10px 10px 15px;
|
||||
background: #555;
|
||||
color: white;
|
||||
width: 320px;
|
||||
border: 10px solid #444;
|
||||
border-bottom: 0;
|
||||
-moz-border-radius-topleft: 10px;
|
||||
-moz-border-radius-topright: 10px;
|
||||
-webkit-border-top-left-radius: 10px;
|
||||
-webkit-border-top-right-radius: 10px;
|
||||
border-top-left-radius: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
}
|
||||
|
||||
h1, form {
|
||||
-moz-box-shadow: 2px 2px 7px rgba(0,0,0,0.3);
|
||||
-webkit-box-shadow: 2px 2px 7px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
form {
|
||||
background: white;
|
||||
border: 10px solid #eee;
|
||||
border-top: 0;
|
||||
padding: 20px;
|
||||
margin: 0px auto 40px;
|
||||
width: 300px;
|
||||
-moz-border-radius-bottomleft: 10px;
|
||||
-moz-border-radius-bottomright: 10px;
|
||||
-webkit-border-bottom-left-radius: 10px;
|
||||
-webkit-border-bottom-right-radius: 10px;
|
||||
border-bottom-left-radius: 10px;
|
||||
border-bottom-right-radius: 10px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
input {
|
||||
font-size: 18px;
|
||||
padding: 4px 8px;
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
width: 280px;
|
||||
}
|
||||
|
||||
input#identifier, input#openid_url {
|
||||
background: url(http://openid.net/login-bg.gif) no-repeat;
|
||||
background-position: 0 50%;
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
||||
button {
|
||||
font-size: 22px;
|
||||
padding: 4px 8px;
|
||||
display: block;
|
||||
margin: 20px auto 0;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border: 1px solid #ccc;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
fieldset input {
|
||||
width: 260px;
|
||||
font-size: 16px;
|
||||
}
|
||||
CSS
|
||||
|
||||
attr_accessor :options
|
||||
|
||||
def initialize(options = {})
|
||||
options[:title] ||= "Authentication Info Required"
|
||||
options[:header_info] ||= ""
|
||||
self.options = options
|
||||
|
||||
@html = ""
|
||||
header(options[:title],options[:header_info])
|
||||
end
|
||||
|
||||
def self.build(title=nil,&block)
|
||||
form = OmniAuth::Form.new(title)
|
||||
if block.arity > 0
|
||||
yield form
|
||||
else
|
||||
form.instance_eval(&block)
|
||||
end
|
||||
form
|
||||
end
|
||||
|
||||
def label_field(text, target)
|
||||
@html << "\n<label for='#{target}'>#{text}:</label>"
|
||||
self
|
||||
end
|
||||
|
||||
def input_field(type, name)
|
||||
@html << "\n<input type='#{type}' id='#{name}' name='#{name}'/>"
|
||||
self
|
||||
end
|
||||
|
||||
def text_field(label, name)
|
||||
label_field(label, name)
|
||||
input_field('text', name)
|
||||
self
|
||||
end
|
||||
|
||||
def password_field(label, name)
|
||||
label_field(label, name)
|
||||
input_field('password', name)
|
||||
self
|
||||
end
|
||||
|
||||
def button(text)
|
||||
@html << "\n<button type='submit'>#{text}</button>"
|
||||
end
|
||||
|
||||
def html(html)
|
||||
@html << html
|
||||
end
|
||||
|
||||
def fieldset(legend, options = {}, &block)
|
||||
@html << "\n<fieldset#{" style='#{options[:style]}'" if options[:style]}#{" id='#{options[:id]}'" if options[:id]}>\n <legend>#{legend}</legend>\n"
|
||||
self.instance_eval &block
|
||||
@html << "\n</fieldset>"
|
||||
self
|
||||
end
|
||||
|
||||
def header(title,header_info)
|
||||
@html << <<-HTML
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>#{title}</title>
|
||||
#{css}
|
||||
#{header_info}
|
||||
</head>
|
||||
<body>
|
||||
<h1>#{title}</h1>
|
||||
<form method='post' #{"action='#{options[:url]}' " if options[:url]}noValidate='noValidate'>
|
||||
HTML
|
||||
self
|
||||
end
|
||||
|
||||
def footer
|
||||
return self if @footer
|
||||
@html << <<-HTML
|
||||
<button type='submit'>Connect</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
HTML
|
||||
@footer = true
|
||||
self
|
||||
end
|
||||
|
||||
def to_html
|
||||
footer
|
||||
@html
|
||||
end
|
||||
|
||||
def to_response
|
||||
footer
|
||||
Rack::Response.new(@html).finish
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def css
|
||||
"\n<style type='text/css'>#{OmniAuth.config.form_css}</style>"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,242 +0,0 @@
|
|||
require 'omniauth/core'
|
||||
|
||||
module OmniAuth
|
||||
class NoSessionError < StandardError; end
|
||||
# The Strategy is the base unit of OmniAuth's ability to
|
||||
# wrangle multiple providers. Each strategy provided by
|
||||
# OmniAuth includes this mixin to gain the default functionality
|
||||
# necessary to be compatible with the OmniAuth library.
|
||||
module Strategy
|
||||
def self.included(base)
|
||||
OmniAuth.strategies << base
|
||||
base.class_eval do
|
||||
attr_reader :app, :name, :env, :options, :response
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(app, name, *args, &block)
|
||||
@app = app
|
||||
@name = name.to_sym
|
||||
@options = args.last.is_a?(Hash) ? args.pop : {}
|
||||
|
||||
yield self if block_given?
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<#{self.class.to_s}>"
|
||||
end
|
||||
|
||||
def call(env)
|
||||
dup.call!(env)
|
||||
end
|
||||
|
||||
def call!(env)
|
||||
raise OmniAuth::NoSessionError.new("You must provide a session to use OmniAuth.") unless env['rack.session']
|
||||
|
||||
@env = env
|
||||
@env['omniauth.strategy'] = self if on_auth_path?
|
||||
|
||||
return mock_call!(env) if OmniAuth.config.test_mode
|
||||
|
||||
return options_call if on_auth_path? && options_request?
|
||||
return request_call if on_request_path? && OmniAuth.config.allowed_request_methods.include?(request.request_method.downcase.to_sym)
|
||||
return callback_call if on_callback_path?
|
||||
return other_phase if respond_to?(:other_phase)
|
||||
@app.call(env)
|
||||
end
|
||||
|
||||
# Responds to an OPTIONS request.
|
||||
def options_call
|
||||
verbs = OmniAuth.config.allowed_request_methods.map(&:to_s).map(&:upcase).join(', ')
|
||||
return [ 200, { 'Allow' => verbs }, [] ]
|
||||
end
|
||||
|
||||
# Performs the steps necessary to run the request phase of a strategy.
|
||||
def request_call
|
||||
setup_phase
|
||||
if response = call_through_to_app
|
||||
response
|
||||
else
|
||||
if request.params['origin']
|
||||
@env['rack.session']['omniauth.origin'] = request.params['origin']
|
||||
elsif env['HTTP_REFERER'] && !env['HTTP_REFERER'].match(/#{request_path}$/)
|
||||
@env['rack.session']['omniauth.origin'] = env['HTTP_REFERER']
|
||||
end
|
||||
request_phase
|
||||
end
|
||||
end
|
||||
|
||||
# Performs the steps necessary to run the callback phase of a strategy.
|
||||
def callback_call
|
||||
setup_phase
|
||||
@env['omniauth.origin'] = session.delete('omniauth.origin')
|
||||
@env['omniauth.origin'] = nil if env['omniauth.origin'] == ''
|
||||
|
||||
callback_phase
|
||||
end
|
||||
|
||||
def on_auth_path?
|
||||
on_request_path? || on_callback_path?
|
||||
end
|
||||
|
||||
def on_request_path?
|
||||
on_path?(request_path)
|
||||
end
|
||||
|
||||
def on_callback_path?
|
||||
on_path?(callback_path)
|
||||
end
|
||||
|
||||
def on_path?(path)
|
||||
current_path.casecmp(path) == 0
|
||||
end
|
||||
|
||||
def options_request?
|
||||
request.request_method == 'OPTIONS'
|
||||
end
|
||||
|
||||
def mock_call!(env)
|
||||
return mock_request_call if on_request_path?
|
||||
return mock_callback_call if on_callback_path?
|
||||
call_app!
|
||||
end
|
||||
|
||||
def mock_request_call
|
||||
setup_phase
|
||||
return response if response = call_through_to_app
|
||||
|
||||
if request.params['origin']
|
||||
@env['rack.session']['omniauth.origin'] = request.params['origin']
|
||||
elsif env['HTTP_REFERER'] && !env['HTTP_REFERER'].match(/#{request_path}$/)
|
||||
@env['rack.session']['omniauth.origin'] = env['HTTP_REFERER']
|
||||
end
|
||||
redirect(script_name + callback_path + query_string)
|
||||
end
|
||||
|
||||
def mock_callback_call
|
||||
setup_phase
|
||||
mocked_auth = OmniAuth.mock_auth_for(name.to_sym)
|
||||
if mocked_auth.is_a?(Symbol)
|
||||
fail!(mocked_auth)
|
||||
else
|
||||
@env['omniauth.auth'] = mocked_auth
|
||||
@env['omniauth.origin'] = session.delete('omniauth.origin')
|
||||
@env['omniauth.origin'] = nil if env['omniauth.origin'] == ''
|
||||
call_app!
|
||||
end
|
||||
end
|
||||
|
||||
def setup_phase
|
||||
if options[:setup].respond_to?(:call)
|
||||
options[:setup].call(env)
|
||||
elsif options[:setup]
|
||||
setup_env = env.merge('PATH_INFO' => setup_path, 'REQUEST_METHOD' => 'GET')
|
||||
call_app!(setup_env)
|
||||
end
|
||||
end
|
||||
|
||||
def request_phase
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def callback_phase
|
||||
@env['omniauth.auth'] = auth_hash
|
||||
@env['omniauth.params'] = session['query_params'] || {}
|
||||
session['query_params'] = nil if session['query_params']
|
||||
call_app!
|
||||
end
|
||||
|
||||
def path_prefix
|
||||
options[:path_prefix] || OmniAuth.config.path_prefix
|
||||
end
|
||||
|
||||
def request_path
|
||||
options[:request_path] || "#{path_prefix}/#{name}"
|
||||
end
|
||||
|
||||
def callback_path
|
||||
options[:callback_path] || "#{path_prefix}/#{name}/callback"
|
||||
end
|
||||
|
||||
def setup_path
|
||||
options[:setup_path] || "#{path_prefix}/#{name}/setup"
|
||||
end
|
||||
|
||||
def current_path
|
||||
request.path_info.downcase.sub(/\/$/,'')
|
||||
end
|
||||
|
||||
def query_string
|
||||
request.query_string.empty? ? "" : "?#{request.query_string}"
|
||||
end
|
||||
|
||||
def call_through_to_app
|
||||
status, headers, body = *call_app!
|
||||
session['query_params'] = Rack::Request.new(env).params
|
||||
@response = Rack::Response.new(body, status, headers)
|
||||
|
||||
status == 404 ? nil : @response.finish
|
||||
end
|
||||
|
||||
def call_app!(env = @env)
|
||||
@app.call(env)
|
||||
end
|
||||
|
||||
def auth_hash
|
||||
AuthHash.new(:provider => name.to_s)
|
||||
end
|
||||
|
||||
def full_host
|
||||
case OmniAuth.config.full_host
|
||||
when String
|
||||
OmniAuth.config.full_host
|
||||
when Proc
|
||||
OmniAuth.config.full_host.call(env)
|
||||
else
|
||||
uri = URI.parse(request.url.gsub(/\?.*$/,''))
|
||||
uri.path = ''
|
||||
uri.query = nil
|
||||
uri.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def callback_url
|
||||
full_host + script_name + callback_path + query_string
|
||||
end
|
||||
|
||||
def script_name
|
||||
@env['SCRIPT_NAME'] || ''
|
||||
end
|
||||
|
||||
def session
|
||||
@env['rack.session']
|
||||
end
|
||||
|
||||
def request
|
||||
@request ||= Rack::Request.new(@env)
|
||||
end
|
||||
|
||||
def redirect(uri)
|
||||
r = Rack::Response.new
|
||||
|
||||
if options[:iframe]
|
||||
r.write("<script type='text/javascript' charset='utf-8'>top.location.href = '#{uri}';</script>")
|
||||
else
|
||||
r.write("Redirecting to #{uri}...")
|
||||
r.redirect(uri)
|
||||
end
|
||||
|
||||
r.finish
|
||||
end
|
||||
|
||||
def user_info; {} end
|
||||
|
||||
def fail!(message_key, exception = nil)
|
||||
self.env['omniauth.error'] = exception
|
||||
self.env['omniauth.error.type'] = message_key.to_sym
|
||||
self.env['omniauth.error.strategy'] = self
|
||||
|
||||
OmniAuth.config.on_failure.call(self.env)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,12 +0,0 @@
|
|||
module OmniAuth
|
||||
|
||||
# Support for testing OmniAuth strategies.
|
||||
module Test
|
||||
|
||||
autoload :PhonySession, 'omniauth/test/phony_session'
|
||||
autoload :StrategyMacros, 'omniauth/test/strategy_macros'
|
||||
autoload :StrategyTestCase, 'omniauth/test/strategy_test_case'
|
||||
|
||||
end
|
||||
|
||||
end
|
|
@ -1,8 +0,0 @@
|
|||
class OmniAuth::Test::PhonySession
|
||||
def initialize(app); @app = app end
|
||||
def call(env)
|
||||
@session ||= (env['rack.session'] || {})
|
||||
env['rack.session'] = @session
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
|
@ -1,34 +0,0 @@
|
|||
module OmniAuth
|
||||
|
||||
module Test
|
||||
|
||||
module StrategyMacros
|
||||
|
||||
def sets_an_auth_hash
|
||||
it 'should set an auth hash' do
|
||||
last_request.env['omniauth.auth'].should be_kind_of(Hash)
|
||||
end
|
||||
end
|
||||
|
||||
def sets_provider_to(provider)
|
||||
it "should set the provider to #{provider}" do
|
||||
(last_request.env['omniauth.auth'] || {})['provider'].should == provider
|
||||
end
|
||||
end
|
||||
|
||||
def sets_uid_to(uid)
|
||||
it "should set the UID to #{uid}" do
|
||||
(last_request.env['omniauth.auth'] || {})['uid'].should == uid
|
||||
end
|
||||
end
|
||||
|
||||
def sets_user_info_to(user_info)
|
||||
it "should set the user_info to #{user_info}" do
|
||||
(last_request.env['omniauth.auth'] || {})['user_info'].should == user_info
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
|
@ -1,49 +0,0 @@
|
|||
require 'rack'
|
||||
require 'omniauth/test'
|
||||
|
||||
module OmniAuth
|
||||
|
||||
module Test
|
||||
|
||||
# Support for testing OmniAuth strategies.
|
||||
#
|
||||
# @example Usage
|
||||
# class MyStrategyTest < Test::Unit::TestCase
|
||||
# include OmniAuth::Test::StrategyTestCase
|
||||
# def strategy
|
||||
# # return the parameters to a Rack::Builder map call:
|
||||
# [MyStrategy.new, :some, :configuration, :options => 'here']
|
||||
# end
|
||||
# setup do
|
||||
# post '/auth/my_strategy/callback', :user => { 'name' => 'Dylan', 'id' => '445' }
|
||||
# end
|
||||
# end
|
||||
module StrategyTestCase
|
||||
|
||||
def app
|
||||
strat = self.strategy
|
||||
resp = self.app_response
|
||||
Rack::Builder.new {
|
||||
use OmniAuth::Test::PhonySession
|
||||
use *strat
|
||||
run lambda {|env| [404, {'Content-Type' => 'text/plain'}, [resp || env.key?('omniauth.auth').to_s]] }
|
||||
}.to_app
|
||||
end
|
||||
|
||||
def app_response
|
||||
nil
|
||||
end
|
||||
|
||||
def session
|
||||
last_request.env['rack.session']
|
||||
end
|
||||
|
||||
def strategy
|
||||
raise NotImplementedError.new('Including specs must define #strategy')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
|
@ -1,19 +0,0 @@
|
|||
module OmniAuth
|
||||
module Version
|
||||
unless defined?(::OmniAuth::Version::MAJOR)
|
||||
MAJOR = 0
|
||||
end
|
||||
unless defined?(::OmniAuth::Version::MINOR)
|
||||
MINOR = 3
|
||||
end
|
||||
unless defined?(::OmniAuth::Version::PATCH)
|
||||
PATCH = 0
|
||||
end
|
||||
unless defined?(::OmniAuth::Version::PRE)
|
||||
PRE = "rc3"
|
||||
end
|
||||
unless defined?(::OmniAuth::Version::STRING)
|
||||
STRING = [MAJOR, MINOR, PATCH, PRE].compact.join('.')
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,20 +0,0 @@
|
|||
require File.expand_path('../../spec_helper', __FILE__)
|
||||
|
||||
describe OmniAuth::Builder do
|
||||
describe '#provider' do
|
||||
it 'should translate a symbol to a constant' do
|
||||
OmniAuth::Strategies.should_receive(:const_get).with('MyStrategy').and_return(Class.new)
|
||||
OmniAuth::Builder.new(nil) do
|
||||
provider :my_strategy
|
||||
end
|
||||
end
|
||||
|
||||
it 'should also just accept a class' do
|
||||
class ::ExampleClass; end
|
||||
|
||||
lambda{ OmniAuth::Builder.new(nil) do
|
||||
provider ::ExampleClass
|
||||
end }.should_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,79 +0,0 @@
|
|||
require File.expand_path('../../spec_helper', __FILE__)
|
||||
|
||||
describe OmniAuth do
|
||||
describe '.strategies' do
|
||||
it 'should increase when a new strategy is made' do
|
||||
lambda{ class ExampleStrategy
|
||||
include OmniAuth::Strategy
|
||||
end }.should change(OmniAuth.strategies, :size).by(1)
|
||||
OmniAuth.strategies.last.should == ExampleStrategy
|
||||
end
|
||||
end
|
||||
|
||||
context 'configuration' do
|
||||
it 'should be callable from .configure' do
|
||||
OmniAuth.configure do |c|
|
||||
c.should be_kind_of(OmniAuth::Configuration)
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
@old_path_prefix = OmniAuth.config.path_prefix
|
||||
@old_on_failure = OmniAuth.config.on_failure
|
||||
end
|
||||
|
||||
after do
|
||||
OmniAuth.configure do |config|
|
||||
config.path_prefix = @old_path_prefix
|
||||
config.on_failure = @old_on_failure
|
||||
end
|
||||
end
|
||||
|
||||
it 'should be able to set the path' do
|
||||
OmniAuth.configure do |config|
|
||||
config.path_prefix = '/awesome'
|
||||
end
|
||||
|
||||
OmniAuth.config.path_prefix.should == '/awesome'
|
||||
end
|
||||
|
||||
it 'should be able to set the on_failure rack app' do
|
||||
OmniAuth.configure do |config|
|
||||
config.on_failure do
|
||||
'yoyo'
|
||||
end
|
||||
end
|
||||
|
||||
OmniAuth.config.on_failure.call.should == 'yoyo'
|
||||
end
|
||||
end
|
||||
|
||||
describe '::Utils' do
|
||||
describe '.deep_merge' do
|
||||
it 'should combine hashes' do
|
||||
OmniAuth::Utils.deep_merge({'abc' => {'def' => 123}}, {'abc' => {'foo' => 'bar'}}).should == {
|
||||
'abc' => {'def' => 123, 'foo' => 'bar'}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
describe '.camelize' do
|
||||
it 'should work on normal cases' do
|
||||
{
|
||||
'some_word' => 'SomeWord',
|
||||
'AnotherWord' => 'AnotherWord',
|
||||
'one' => 'One',
|
||||
'three_words_now' => 'ThreeWordsNow'
|
||||
}.each_pair{ |k,v| OmniAuth::Utils.camelize(k).should == v }
|
||||
end
|
||||
|
||||
it 'should work in special cases' do
|
||||
{
|
||||
'oauth' => "OAuth",
|
||||
'openid' => 'OpenID',
|
||||
'open_id' => 'OpenID'
|
||||
}.each_pair{ |k,v| OmniAuth::Utils.camelize(k).should == v}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,397 +0,0 @@
|
|||
require File.expand_path('../../spec_helper', __FILE__)
|
||||
|
||||
class ExampleStrategy
|
||||
include OmniAuth::Strategy
|
||||
def call(env); self.call!(env) end
|
||||
attr_reader :last_env
|
||||
def request_phase
|
||||
@fail = fail!(options[:failure]) if options[:failure]
|
||||
@last_env = env
|
||||
return @fail if @fail
|
||||
raise "Request Phase"
|
||||
end
|
||||
def callback_phase
|
||||
@fail = fail!(options[:failure]) if options[:failure]
|
||||
@last_env = env
|
||||
return @fail if @fail
|
||||
raise "Callback Phase"
|
||||
end
|
||||
end
|
||||
|
||||
def make_env(path = '/auth/test', props = {})
|
||||
{
|
||||
'REQUEST_METHOD' => 'GET',
|
||||
'PATH_INFO' => path,
|
||||
'rack.session' => {},
|
||||
'rack.input' => StringIO.new('test=true')
|
||||
}.merge(props)
|
||||
end
|
||||
|
||||
describe OmniAuth::Strategy do
|
||||
let(:app){ lambda{|env| [404, {}, ['Awesome']]}}
|
||||
describe '#initialize' do
|
||||
context 'options extraction' do
|
||||
it 'should be the last argument if the last argument is a Hash' do
|
||||
ExampleStrategy.new(app, 'test', :abc => 123).options[:abc].should == 123
|
||||
end
|
||||
|
||||
it 'should be a blank hash if none are provided' do
|
||||
ExampleStrategy.new(app, 'test').options.should == {}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#full_host' do
|
||||
let(:strategy){ ExampleStrategy.new(app, 'test', {}) }
|
||||
it 'should not freak out if there is a pipe in the URL' do
|
||||
strategy.call!(make_env('/whatever', 'rack.url_scheme' => 'http', 'SERVER_NAME' => 'facebook.lame', 'QUERY_STRING' => 'code=asofibasf|asoidnasd', 'SCRIPT_NAME' => '', 'SERVER_PORT' => 80))
|
||||
lambda{ strategy.full_host }.should_not raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe '#call' do
|
||||
let(:strategy){ ExampleStrategy.new(app, 'test', @options) }
|
||||
|
||||
context 'omniauth.origin' do
|
||||
it 'should be set on the request phase' do
|
||||
lambda{ strategy.call(make_env('/auth/test', 'HTTP_REFERER' => 'http://example.com/origin')) }.should raise_error("Request Phase")
|
||||
strategy.last_env['rack.session']['omniauth.origin'].should == 'http://example.com/origin'
|
||||
end
|
||||
|
||||
it 'should be turned into an env variable on the callback phase' do
|
||||
lambda{ strategy.call(make_env('/auth/test/callback', 'rack.session' => {'omniauth.origin' => 'http://example.com/origin'})) }.should raise_error("Callback Phase")
|
||||
strategy.last_env['omniauth.origin'].should == 'http://example.com/origin'
|
||||
end
|
||||
|
||||
it 'should set from the params if provided' do
|
||||
lambda{ strategy.call(make_env('/auth/test', 'QUERY_STRING' => 'origin=/foo')) }.should raise_error('Request Phase')
|
||||
strategy.last_env['rack.session']['omniauth.origin'].should == '/foo'
|
||||
end
|
||||
|
||||
it 'should be set on the failure env' do
|
||||
OmniAuth.config.should_receive(:on_failure).and_return(lambda{|env| env})
|
||||
@options = {:failure => :forced_fail}
|
||||
strategy.call(make_env('/auth/test/callback', 'rack.session' => {'omniauth.origin' => '/awesome'}))
|
||||
end
|
||||
|
||||
context "with script_name" do
|
||||
it 'should be set on the request phase, containing full path' do
|
||||
env = {'HTTP_REFERER' => 'http://example.com/sub_uri/origin', 'SCRIPT_NAME' => '/sub_uri' }
|
||||
lambda{ strategy.call(make_env('/auth/test', env)) }.should raise_error("Request Phase")
|
||||
strategy.last_env['rack.session']['omniauth.origin'].should == 'http://example.com/sub_uri/origin'
|
||||
end
|
||||
|
||||
it 'should be turned into an env variable on the callback phase, containing full path' do
|
||||
env = {
|
||||
'rack.session' => {'omniauth.origin' => 'http://example.com/sub_uri/origin'},
|
||||
'SCRIPT_NAME' => '/sub_uri'
|
||||
}
|
||||
|
||||
lambda{ strategy.call(make_env('/auth/test/callback', env)) }.should raise_error("Callback Phase")
|
||||
strategy.last_env['omniauth.origin'].should == 'http://example.com/sub_uri/origin'
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
context 'default paths' do
|
||||
it 'should use the default request path' do
|
||||
lambda{ strategy.call(make_env) }.should raise_error("Request Phase")
|
||||
end
|
||||
|
||||
it 'should be case insensitive on request path' do
|
||||
lambda{ strategy.call(make_env('/AUTH/Test'))}.should raise_error("Request Phase")
|
||||
end
|
||||
|
||||
it 'should be case insensitive on callback path' do
|
||||
lambda{ strategy.call(make_env('/AUTH/TeSt/CaLlBAck'))}.should raise_error("Callback Phase")
|
||||
end
|
||||
|
||||
it 'should use the default callback path' do
|
||||
lambda{ strategy.call(make_env('/auth/test/callback')) }.should raise_error("Callback Phase")
|
||||
end
|
||||
|
||||
it 'should strip trailing spaces on request' do
|
||||
lambda{ strategy.call(make_env('/auth/test/')) }.should raise_error("Request Phase")
|
||||
end
|
||||
|
||||
it 'should strip trailing spaces on callback' do
|
||||
lambda{ strategy.call(make_env('/auth/test/callback/')) }.should raise_error("Callback Phase")
|
||||
end
|
||||
|
||||
context 'callback_url' do
|
||||
it 'uses the default callback_path' do
|
||||
strategy.should_receive(:full_host).and_return('http://example.com')
|
||||
|
||||
lambda{ strategy.call(make_env) }.should raise_error("Request Phase")
|
||||
|
||||
strategy.callback_url.should == 'http://example.com/auth/test/callback'
|
||||
end
|
||||
|
||||
it 'preserves the query parameters' do
|
||||
strategy.stub(:full_host).and_return('http://example.com')
|
||||
begin
|
||||
strategy.call(make_env('/auth/test', 'QUERY_STRING' => 'id=5'))
|
||||
rescue RuntimeError; end
|
||||
strategy.callback_url.should == 'http://example.com/auth/test/callback?id=5'
|
||||
end
|
||||
|
||||
it 'consider script name' do
|
||||
strategy.stub(:full_host).and_return('http://example.com')
|
||||
begin
|
||||
strategy.call(make_env('/auth/test', 'SCRIPT_NAME' => '/sub_uri'))
|
||||
rescue RuntimeError; end
|
||||
strategy.callback_url.should == 'http://example.com/sub_uri/auth/test/callback'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'pre-request call through' do
|
||||
subject { ExampleStrategy.new(app, 'test') }
|
||||
let(:app){ lambda{|env| env['omniauth.boom'] = true; [env['test.status'] || 404, {}, ['Whatev']] } }
|
||||
it 'should be able to modify the env on the fly before the request_phase' do
|
||||
lambda{ subject.call(make_env) }.should raise_error("Request Phase")
|
||||
subject.response.status.should == 404
|
||||
subject.last_env.should be_key('omniauth.boom')
|
||||
end
|
||||
|
||||
it 'should call through to the app instead if a non-404 response is received' do
|
||||
lambda{ subject.call(make_env('/auth/test', 'test.status' => 200)) }.should_not raise_error
|
||||
subject.response.body.should == ['Whatev']
|
||||
end
|
||||
end
|
||||
|
||||
context 'custom paths' do
|
||||
it 'should use a custom request_path if one is provided' do
|
||||
@options = {:request_path => '/awesome'}
|
||||
lambda{ strategy.call(make_env('/awesome')) }.should raise_error("Request Phase")
|
||||
end
|
||||
|
||||
it 'should use a custom callback_path if one is provided' do
|
||||
@options = {:callback_path => '/radical'}
|
||||
lambda{ strategy.call(make_env('/radical')) }.should raise_error("Callback Phase")
|
||||
end
|
||||
|
||||
context 'callback_url' do
|
||||
it 'uses a custom callback_path if one is provided' do
|
||||
@options = {:callback_path => '/radical'}
|
||||
strategy.should_receive(:full_host).and_return('http://example.com')
|
||||
|
||||
lambda{ strategy.call(make_env('/radical')) }.should raise_error("Callback Phase")
|
||||
|
||||
strategy.callback_url.should == 'http://example.com/radical'
|
||||
end
|
||||
|
||||
it 'preserves the query parameters' do
|
||||
@options = {:callback_path => '/radical'}
|
||||
strategy.stub(:full_host).and_return('http://example.com')
|
||||
begin
|
||||
strategy.call(make_env('/auth/test', 'QUERY_STRING' => 'id=5'))
|
||||
rescue RuntimeError; end
|
||||
strategy.callback_url.should == 'http://example.com/radical?id=5'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'custom prefix' do
|
||||
before do
|
||||
@options = {:path_prefix => '/wowzers'}
|
||||
end
|
||||
|
||||
it 'should use a custom prefix for request' do
|
||||
lambda{ strategy.call(make_env('/wowzers/test')) }.should raise_error("Request Phase")
|
||||
end
|
||||
|
||||
it 'should use a custom prefix for callback' do
|
||||
lambda{ strategy.call(make_env('/wowzers/test/callback')) }.should raise_error("Callback Phase")
|
||||
end
|
||||
|
||||
context 'callback_url' do
|
||||
it 'uses a custom prefix' do
|
||||
strategy.should_receive(:full_host).and_return('http://example.com')
|
||||
|
||||
lambda{ strategy.call(make_env('/wowzers/test')) }.should raise_error("Request Phase")
|
||||
|
||||
strategy.callback_url.should == 'http://example.com/wowzers/test/callback'
|
||||
end
|
||||
|
||||
it 'preserves the query parameters' do
|
||||
strategy.stub(:full_host).and_return('http://example.com')
|
||||
begin
|
||||
strategy.call(make_env('/auth/test', 'QUERY_STRING' => 'id=5'))
|
||||
rescue RuntimeError; end
|
||||
strategy.callback_url.should == 'http://example.com/wowzers/test/callback?id=5'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'request method restriction' do
|
||||
before do
|
||||
OmniAuth.config.allowed_request_methods = [:post]
|
||||
end
|
||||
|
||||
it 'should not allow a request method of the wrong type' do
|
||||
lambda{ strategy.call(make_env)}.should_not raise_error
|
||||
end
|
||||
|
||||
it 'should allow a request method of the correct type' do
|
||||
lambda{ strategy.call(make_env('/auth/test', 'REQUEST_METHOD' => 'POST'))}.should raise_error("Request Phase")
|
||||
end
|
||||
|
||||
after do
|
||||
OmniAuth.config.allowed_request_methods = [:get, :post]
|
||||
end
|
||||
end
|
||||
|
||||
context 'receiving an OPTIONS request' do
|
||||
shared_examples_for "an OPTIONS request" do
|
||||
it 'should respond with 200' do
|
||||
response[0].should == 200
|
||||
end
|
||||
|
||||
it 'should set the Allow header properly' do
|
||||
response[1]['Allow'].should == "GET, POST"
|
||||
end
|
||||
end
|
||||
|
||||
context 'to the request path' do
|
||||
let(:response) { strategy.call(make_env('/auth/test', 'REQUEST_METHOD' => 'OPTIONS')) }
|
||||
it_should_behave_like 'an OPTIONS request'
|
||||
end
|
||||
|
||||
context 'to the request path' do
|
||||
let(:response) { strategy.call(make_env('/auth/test/callback', 'REQUEST_METHOD' => 'OPTIONS')) }
|
||||
it_should_behave_like 'an OPTIONS request'
|
||||
end
|
||||
|
||||
context 'to some other path' do
|
||||
it 'should not short-circuit the request' do
|
||||
env = make_env('/other', 'REQUEST_METHOD' => 'OPTIONS')
|
||||
strategy.call(env).should == app.call(env)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'test mode' do
|
||||
before do
|
||||
OmniAuth.config.test_mode = true
|
||||
end
|
||||
|
||||
it 'should short circuit the request phase entirely' do
|
||||
response = strategy.call(make_env)
|
||||
response[0].should == 302
|
||||
response[1]['Location'].should == '/auth/test/callback'
|
||||
end
|
||||
|
||||
it 'should be case insensitive on request path' do
|
||||
strategy.call(make_env('/AUTH/Test'))[0].should == 302
|
||||
end
|
||||
|
||||
it 'should respect SCRIPT_NAME (a.k.a. BaseURI)' do
|
||||
response = strategy.call(make_env('/auth/test', 'SCRIPT_NAME' => '/sub_uri'))
|
||||
response[1]['Location'].should == '/sub_uri/auth/test/callback'
|
||||
end
|
||||
|
||||
it 'should be case insensitive on callback path' do
|
||||
strategy.call(make_env('/AUTH/TeSt/CaLlBAck')).should == strategy.call(make_env('/auth/test/callback'))
|
||||
end
|
||||
|
||||
it 'should maintain query string parameters' do
|
||||
response = strategy.call(make_env('/auth/test', 'QUERY_STRING' => 'cheese=stilton'))
|
||||
response[1]['Location'].should == '/auth/test/callback?cheese=stilton'
|
||||
end
|
||||
|
||||
it 'should not short circuit requests outside of authentication' do
|
||||
strategy.call(make_env('/')).should == app.call(make_env('/'))
|
||||
end
|
||||
|
||||
it 'should respond with the default hash if none is set' do
|
||||
strategy.call make_env('/auth/test/callback')
|
||||
strategy.env['omniauth.auth']['uid'].should == '1234'
|
||||
end
|
||||
|
||||
it 'should respond with a provider-specific hash if one is set' do
|
||||
OmniAuth.config.mock_auth[:test] = {
|
||||
'uid' => 'abc'
|
||||
}
|
||||
|
||||
strategy.call make_env('/auth/test/callback')
|
||||
strategy.env['omniauth.auth']['uid'].should == 'abc'
|
||||
end
|
||||
|
||||
it 'should simulate login failure if mocked data is set as a symbol' do
|
||||
OmniAuth.config.mock_auth[:test] = :invalid_credentials
|
||||
|
||||
strategy.call make_env('/auth/test/callback')
|
||||
strategy.env['omniauth.error.type'].should == :invalid_credentials
|
||||
end
|
||||
|
||||
it 'should set omniauth.origin on the request phase' do
|
||||
strategy.call(make_env('/auth/test', 'HTTP_REFERER' => 'http://example.com/origin'))
|
||||
strategy.env['rack.session']['omniauth.origin'].should == 'http://example.com/origin'
|
||||
end
|
||||
|
||||
it 'should set omniauth.origin from the params if provided' do
|
||||
strategy.call(make_env('/auth/test', 'QUERY_STRING' => 'origin=/foo'))
|
||||
strategy.env['rack.session']['omniauth.origin'].should == '/foo'
|
||||
end
|
||||
|
||||
it 'should turn omniauth.origin into an env variable on the callback phase' do
|
||||
OmniAuth.config.mock_auth[:test] = {}
|
||||
|
||||
strategy.call(make_env('/auth/test/callback', 'rack.session' => {'omniauth.origin' => 'http://example.com/origin'}))
|
||||
strategy.env['omniauth.origin'].should == 'http://example.com/origin'
|
||||
end
|
||||
end
|
||||
|
||||
context 'custom full_host' do
|
||||
it 'should be the string when a string is there' do
|
||||
OmniAuth.config.full_host = 'my.host.com'
|
||||
strategy.full_host.should == 'my.host.com'
|
||||
end
|
||||
|
||||
it 'should run the proc with the env when it is a proc' do
|
||||
OmniAuth.config.full_host = Proc.new{|env| env['HOST']}
|
||||
strategy.call(make_env('/auth/test', 'HOST' => 'my.host.net'))
|
||||
strategy.full_host.should == 'my.host.net'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'setup phase' do
|
||||
context 'when options[:setup] = true' do
|
||||
let(:strategy){ ExampleStrategy.new(app, 'test', :setup => true) }
|
||||
let(:app){lambda{|env| env['omniauth.strategy'].options[:awesome] = 'sauce' if env['PATH_INFO'] == '/auth/test/setup'; [404, {}, 'Awesome'] }}
|
||||
|
||||
it 'should call through to /auth/:provider/setup' do
|
||||
strategy.call(make_env('/auth/test'))
|
||||
strategy.options[:awesome].should == 'sauce'
|
||||
end
|
||||
|
||||
it 'should not call through on a non-omniauth endpoint' do
|
||||
strategy.call(make_env('/somewhere/else'))
|
||||
strategy.options[:awesome].should_not == 'sauce'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when options[:setup] is an app' do
|
||||
let(:setup_proc) do
|
||||
Proc.new do |env|
|
||||
env['omniauth.strategy'].options[:awesome] = 'sauce'
|
||||
end
|
||||
end
|
||||
|
||||
let(:strategy){ ExampleStrategy.new(app, 'test', :setup => setup_proc) }
|
||||
|
||||
it 'should not call the app on a non-omniauth endpoint' do
|
||||
strategy.call(make_env('/somehwere/else'))
|
||||
strategy.options[:awesome].should_not == 'sauce'
|
||||
end
|
||||
|
||||
it 'should call the rack app' do
|
||||
strategy.call(make_env('/auth/test'))
|
||||
strategy.options[:awesome].should == 'sauce'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,12 +0,0 @@
|
|||
require 'simplecov'
|
||||
SimpleCov.start
|
||||
require 'rspec'
|
||||
require 'rack/test'
|
||||
require 'omniauth/core'
|
||||
require 'omniauth/test'
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.include Rack::Test::Methods
|
||||
config.extend OmniAuth::Test::StrategyMacros, :type => :strategy
|
||||
end
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
--color
|
||||
--format=nested
|
||||
--backtrace
|
|
@ -1,4 +0,0 @@
|
|||
--markup markdown
|
||||
--markup-provider maruku
|
||||
-
|
||||
LICENSE
|
|
@ -1,11 +0,0 @@
|
|||
require File.expand_path('../lib/omniauth/version', __FILE__)
|
||||
|
||||
source 'http://rubygems.org'
|
||||
|
||||
gem 'oa-core', OmniAuth::Version::STRING, :path => '../oa-core'
|
||||
|
||||
platforms :jruby do
|
||||
gem 'jruby-openssl', '~> 0.7'
|
||||
end
|
||||
|
||||
gemspec
|
|
@ -1,19 +0,0 @@
|
|||
Copyright (c) 2010-2011 Michael Bleigh and Intridea, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -1,115 +0,0 @@
|
|||
= OmniAuth::Enterprise
|
||||
|
||||
OmniAuth strategies for use in your intranet.
|
||||
|
||||
== Installation
|
||||
|
||||
To get just enterprise functionality:
|
||||
|
||||
gem install oa-enterprise
|
||||
|
||||
For the full auth suite:
|
||||
|
||||
gem install omniauth
|
||||
|
||||
== CAS
|
||||
|
||||
Use the CAS strategy as a middleware in your application:
|
||||
|
||||
require 'omniauth/enterprise'
|
||||
|
||||
use OmniAuth::Strategies::CAS, :server => 'http://cas.mycompany.com/cas'
|
||||
|
||||
Then simply direct users to '/auth/cas' to have them sign in via your company's CAS server.
|
||||
See OmniAuth::Strategies::CAS::Configuration for more configuration options.
|
||||
|
||||
== LDAP
|
||||
|
||||
Use the LDAP strategy as a middleware in your application:
|
||||
|
||||
require 'omniauth/enterprise'
|
||||
use OmniAuth::Strategies::LDAP,
|
||||
:title => "My LDAP",
|
||||
:host => '10.101.10.1',
|
||||
:port => 389,
|
||||
:method => :plain,
|
||||
:base => 'dc=intridea, dc=com',
|
||||
:uid => 'sAMAccountName',
|
||||
:name_proc => Proc.new {|name| name.gsub(/@.*$/,'')}
|
||||
:bind_dn => 'default_bind_dn'
|
||||
:password => 'password'
|
||||
|
||||
All of the listed options are required, with the exception of :name_proc, :bind_dn, and :password
|
||||
Allowed values of :method are: :plain, :ssl, :tls.
|
||||
|
||||
:bind_dn and :password are used to perform the initial binding if user lookup is
|
||||
needed. If the user lookup returns result, the DN attribute from the result set is used
|
||||
to perform the final binding. This is needed only when the LDAP server requires
|
||||
DN to be used for binding and you may only want user to using email or username
|
||||
in the login form.
|
||||
|
||||
:uid is the LDAP attribute name for the user name in the login form. typically
|
||||
AD would be 'sAMAccountName' or 'UserPrincipalName', while OpenLDAP is 'uid'.
|
||||
You can also use 'dn', if your user choose the put in the dn in the login form
|
||||
(but usually is too long for user to remember or know).
|
||||
|
||||
:name_proc allows you to match the user name entered with the format of the
|
||||
:uid attributes. For example, value of 'sAMAccountName' in AD contains only the
|
||||
windows user name. If your user prefers use email to login, a name_proc as
|
||||
above will trim the email string down to just the windows name. In summary,
|
||||
:name_proc helps you to fill the gap between the authentication and user lookup
|
||||
process.
|
||||
|
||||
:try_sasl and :sasl_mechanisms are optional. Use them to initialize a SASL
|
||||
connection to server. Allowed values are 'DIGEST-MD5' and 'GSS-SPNEGO'. If you
|
||||
are not familiar with these authentication methods, please just avoid them.
|
||||
|
||||
Direct users to '/auth/ldap' to have them authenticated via your
|
||||
company's LDAP server.
|
||||
|
||||
== SAML
|
||||
|
||||
Use the SAML strategy as a middleware in your application:
|
||||
|
||||
require 'omniauth/enterprise'
|
||||
use OmniAuth::Strategies::SAML,
|
||||
:assertion_consumer_service_url => "consumer_service_url",
|
||||
:issuer => "issuer",
|
||||
:idp_sso_target_url => "idp_sso_target_url",
|
||||
:idp_cert_fingerprint => "E7:91:B2:E1:...",
|
||||
:name_identifier_format => "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
||||
|
||||
:assertion_consumer_service_url
|
||||
The URL at which the SAML assertion should be received.
|
||||
|
||||
:issuer
|
||||
The name of your application. Some identity providers might need this to establish the
|
||||
identity of the service provider requesting the login.
|
||||
|
||||
:idp_sso_target_url
|
||||
The URL to which the authentication request should be sent. This would be on the identity provider.
|
||||
|
||||
:idp_cert_fingerprint
|
||||
The certificate fingerprint, e.g. "90:CC:16:F0:8D:A6:D1:C6:BB:27:2D:BA:93:80:1A:1F:16:8E:4E:08".
|
||||
This is provided from the identity provider when setting up the relationship.
|
||||
|
||||
:name_identifier_format
|
||||
Describes the format of the username required by this application.
|
||||
If you need the email address, use "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress".
|
||||
See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf section 8.3 for
|
||||
other options. Note that the identity provider might not support all options.
|
||||
|
||||
|
||||
== Multiple Strategies
|
||||
|
||||
If you're using multiple strategies together, use OmniAuth's Builder. That's
|
||||
what it's there for:
|
||||
|
||||
require 'omniauth/enterprise'
|
||||
require 'omniauth/oauth' # for Campfire
|
||||
require 'openid/store/filesystem'
|
||||
|
||||
use OmniAuth::Builder do
|
||||
provider :cas, :server => 'http://cas.mycompany.com/cas'
|
||||
provider :campfire
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
require 'bundler'
|
||||
Bundler::GemHelper.install_tasks
|
||||
require 'rspec/core/rake_task'
|
||||
RSpec::Core::RakeTask.new(:spec)
|
||||
task :default => :spec
|
||||
task :test => :spec
|
|
@ -1 +0,0 @@
|
|||
require 'omniauth/enterprise'
|
|
@ -1,9 +0,0 @@
|
|||
require 'omniauth/core'
|
||||
|
||||
module OmniAuth
|
||||
module Strategies
|
||||
autoload :CAS, 'omniauth/strategies/cas'
|
||||
autoload :LDAP, 'omniauth/strategies/ldap'
|
||||
autoload :SAML, 'omniauth/strategies/saml'
|
||||
end
|
||||
end
|
|
@ -1,47 +0,0 @@
|
|||
require 'omniauth/enterprise'
|
||||
|
||||
module OmniAuth
|
||||
module Strategies
|
||||
class CAS
|
||||
include OmniAuth::Strategy
|
||||
|
||||
autoload :Configuration, 'omniauth/strategies/cas/configuration'
|
||||
autoload :ServiceTicketValidator, 'omniauth/strategies/cas/service_ticket_validator'
|
||||
|
||||
def initialize(app, options = {}, &block)
|
||||
super(app, options[:name] || :cas, options.dup, &block)
|
||||
@configuration = OmniAuth::Strategies::CAS::Configuration.new(options)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def request_phase
|
||||
[
|
||||
302,
|
||||
{
|
||||
'Location' => @configuration.login_url(callback_url),
|
||||
'Content-Type' => 'text/plain'
|
||||
},
|
||||
["You are being redirected to CAS for sign-in."]
|
||||
]
|
||||
end
|
||||
|
||||
def callback_phase
|
||||
ticket = request.params['ticket']
|
||||
return fail!(:no_ticket, 'No CAS Ticket') unless ticket
|
||||
validator = ServiceTicketValidator.new(@configuration, callback_url, ticket)
|
||||
@user_info = validator.user_info
|
||||
return fail!(:invalid_ticket, 'Invalid CAS Ticket') if @user_info.nil? || @user_info.empty?
|
||||
super
|
||||
end
|
||||
|
||||
def auth_hash
|
||||
OmniAuth::Utils.deep_merge(super, {
|
||||
'uid' => @user_info.delete('user'),
|
||||
'extra' => @user_info
|
||||
})
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,98 +0,0 @@
|
|||
require 'rack'
|
||||
|
||||
module OmniAuth
|
||||
module Strategies
|
||||
class CAS
|
||||
class Configuration
|
||||
|
||||
DEFAULT_LOGIN_URL = "%s/login"
|
||||
|
||||
DEFAULT_SERVICE_VALIDATE_URL = "%s/serviceValidate"
|
||||
|
||||
# @param [Hash] params configuration options
|
||||
# @option params [String, nil] :cas_server the CAS server root URL; probably something like
|
||||
# `http://cas.mycompany.com` or `http://cas.mycompany.com/cas`; optional.
|
||||
# @option params [String, nil] :cas_login_url (:cas_server + '/login') the URL to which to
|
||||
# redirect for logins; options if `:cas_server` is specified,
|
||||
# required otherwise.
|
||||
# @option params [String, nil] :cas_service_validate_url (:cas_server + '/serviceValidate') the
|
||||
# URL to use for validating service tickets; optional if `:cas_server` is
|
||||
# specified, requred otherwise.
|
||||
# @option params [Boolean, nil] :disable_ssl_verification disable verification for SSL cert,
|
||||
# helpful when you developing with a fake cert.
|
||||
def initialize(params)
|
||||
parse_params params
|
||||
end
|
||||
|
||||
# Build a CAS login URL from +service+.
|
||||
#
|
||||
# @param [String] service the service (a.k.a. return-to) URL
|
||||
#
|
||||
# @return [String] a URL like `http://cas.mycompany.com/login?service=...`
|
||||
def login_url(service)
|
||||
append_service @login_url, service
|
||||
end
|
||||
|
||||
# Build a service-validation URL from +service+ and +ticket+.
|
||||
# If +service+ has a ticket param, first remove it. URL-encode
|
||||
# +service+ and add it and the +ticket+ as paraemters to the
|
||||
# CAS serviceValidate URL.
|
||||
#
|
||||
# @param [String] service the service (a.k.a. return-to) URL
|
||||
# @param [String] ticket the ticket to validate
|
||||
#
|
||||
# @return [String] a URL like `http://cas.mycompany.com/serviceValidate?service=...&ticket=...`
|
||||
def service_validate_url(service, ticket)
|
||||
service = service.sub(/[?&]ticket=[^?&]+/, '')
|
||||
url = append_service(@service_validate_url, service)
|
||||
url << '&ticket=' << Rack::Utils.escape(ticket)
|
||||
end
|
||||
|
||||
def disable_ssl_verification?
|
||||
@disable_ssl_verification
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_params(params)
|
||||
if params[:cas_server].nil? && params[:cas_login_url].nil?
|
||||
raise ArgumentError.new(":cas_server or :cas_login_url MUST be provided")
|
||||
end
|
||||
@login_url = params[:cas_login_url]
|
||||
@login_url ||= DEFAULT_LOGIN_URL % params[:cas_server]
|
||||
validate_is_url 'login URL', @login_url
|
||||
|
||||
if params[:cas_server].nil? && params[:cas_service_validate_url].nil?
|
||||
raise ArgumentError.new(":cas_server or :cas_service_validate_url MUST be provided")
|
||||
end
|
||||
@service_validate_url = params[:cas_service_validate_url]
|
||||
@service_validate_url ||= DEFAULT_SERVICE_VALIDATE_URL % params[:cas_server]
|
||||
validate_is_url 'service-validate URL', @service_validate_url
|
||||
|
||||
@disable_ssl_verification = params[:disable_ssl_verification]
|
||||
end
|
||||
|
||||
IS_NOT_URL_ERROR_MESSAGE = "%s is not a valid URL"
|
||||
|
||||
def validate_is_url(name, possibly_a_url)
|
||||
url = URI.parse(possibly_a_url) rescue nil
|
||||
raise ArgumentError.new(IS_NOT_URL_ERROR_MESSAGE % name) unless url.kind_of?(URI::HTTP)
|
||||
end
|
||||
|
||||
# Adds +service+ as an URL-escaped parameter to +base+.
|
||||
#
|
||||
# @param [String] base the base URL
|
||||
# @param [String] service the service (a.k.a. return-to) URL.
|
||||
#
|
||||
# @return [String] the new joined URL.
|
||||
def append_service(base, service)
|
||||
result = base.dup
|
||||
result << (result.include?('?') ? '&' : '?')
|
||||
result << 'service='
|
||||
result << Rack::Utils.escape(service)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,91 +0,0 @@
|
|||
require 'net/http'
|
||||
require 'net/https'
|
||||
require 'nokogiri'
|
||||
|
||||
module OmniAuth
|
||||
module Strategies
|
||||
class CAS
|
||||
class ServiceTicketValidator
|
||||
|
||||
VALIDATION_REQUEST_HEADERS = { 'Accept' => '*/*' }
|
||||
|
||||
# Build a validator from a +configuration+, a
|
||||
# +return_to+ URL, and a +ticket+.
|
||||
#
|
||||
# @param [OmniAuth::Strategies::CAS::Configuration] configuration the CAS configuration
|
||||
# @param [String] return_to_url the URL of this CAS client service
|
||||
# @param [String] ticket the service ticket to validate
|
||||
def initialize(configuration, return_to_url, ticket)
|
||||
@configuration = configuration
|
||||
@uri = URI.parse(@configuration.service_validate_url(return_to_url, ticket))
|
||||
end
|
||||
|
||||
# Request validation of the ticket from the CAS server's
|
||||
# serviceValidate (CAS 2.0) function.
|
||||
#
|
||||
# Swallows all XML parsing errors (and returns +nil+ in those cases).
|
||||
#
|
||||
# @return [Hash, nil] a user information hash if the response is valid; +nil+ otherwise.
|
||||
#
|
||||
# @raise any connection errors encountered.
|
||||
def user_info
|
||||
parse_user_info(find_authentication_success(get_service_response_body))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# turns an `<cas:authenticationSuccess>` node into a Hash;
|
||||
# returns nil if given nil
|
||||
def parse_user_info(node)
|
||||
return nil if node.nil?
|
||||
hash = {}
|
||||
node.children.each do |e|
|
||||
unless e.kind_of?(Nokogiri::XML::Text) ||
|
||||
e.name == 'cas:proxies' ||
|
||||
e.name == 'proxies'
|
||||
# There are no child elements
|
||||
if e.element_children.count == 0
|
||||
hash[e.name.sub(/^cas:/, '')] = e.content
|
||||
elsif e.element_children.count
|
||||
hash[e.name.sub(/^cas:/, '')] = [] if hash[e.name.sub(/^cas:/, '')].nil?
|
||||
hash[e.name.sub(/^cas:/, '')].push parse_user_info e
|
||||
end
|
||||
end
|
||||
end
|
||||
hash
|
||||
end
|
||||
|
||||
# finds an `<cas:authenticationSuccess>` node in
|
||||
# a `<cas:serviceResponse>` body if present; returns nil
|
||||
# if the passed body is nil or if there is no such node.
|
||||
def find_authentication_success(body)
|
||||
return nil if body.nil? || body == ''
|
||||
begin
|
||||
doc = Nokogiri::XML(body)
|
||||
begin
|
||||
doc.xpath('/cas:serviceResponse/cas:authenticationSuccess')
|
||||
rescue Nokogiri::XML::XPath::SyntaxError
|
||||
doc.xpath('/serviceResponse/authenticationSuccess')
|
||||
end
|
||||
rescue Nokogiri::XML::XPath::SyntaxError
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
# retrieves the `<cas:serviceResponse>` XML from the CAS server
|
||||
def get_service_response_body
|
||||
result = ''
|
||||
http = ::Net::HTTP.new(@uri.host, @uri.port)
|
||||
http.use_ssl = @uri.port == 443 || @uri.instance_of?(URI::HTTPS)
|
||||
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl? && @configuration.disable_ssl_verification?
|
||||
http.start do |c|
|
||||
response = c.get "#{@uri.path}?#{@uri.query}", VALIDATION_REQUEST_HEADERS.dup
|
||||
result = response.body
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,124 +0,0 @@
|
|||
require 'omniauth/enterprise'
|
||||
require 'net/ldap'
|
||||
require 'sasl/base'
|
||||
require 'sasl'
|
||||
|
||||
module OmniAuth
|
||||
module Strategies
|
||||
class LDAP
|
||||
include OmniAuth::Strategy
|
||||
|
||||
autoload :Adaptor, 'omniauth/strategies/ldap/adaptor'
|
||||
@@config = {
|
||||
'name' => 'cn',
|
||||
'first_name' => 'givenName',
|
||||
'last_name' => 'sn',
|
||||
'email' => ['mail', "email", 'userPrincipalName'],
|
||||
'phone' => ['telephoneNumber', 'homePhone', 'facsimileTelephoneNumber'],
|
||||
'mobile_number' => ['mobile', 'mobileTelephoneNumber'],
|
||||
'nickname' => ['uid', 'userid', 'sAMAccountName'],
|
||||
'title' => 'title',
|
||||
'location' => {"%0, %1, %2, %3 %4" => [['address', 'postalAddress', 'homePostalAddress', 'street', 'streetAddress'], ['l'], ['st'],['co'],['postOfficeBox']]},
|
||||
'uid' => 'dn',
|
||||
'url' => ['wwwhomepage'],
|
||||
'image' => 'jpegPhoto',
|
||||
'description' => 'description'
|
||||
}
|
||||
|
||||
# Initialize the LDAP Middleware
|
||||
#
|
||||
# @param [Rack Application] app Standard Rack middleware argument.
|
||||
# @option options [String, 'LDAP Authentication'] :title A title for the authentication form.
|
||||
def initialize(app, options = {}, &block)
|
||||
super(app, options[:name] || :ldap, options.dup, &block)
|
||||
@name_proc = (@options.delete(:name_proc) || Proc.new {|name| name})
|
||||
@adaptor = OmniAuth::Strategies::LDAP::Adaptor.new(options)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def request_phase
|
||||
if env['REQUEST_METHOD'] == 'GET'
|
||||
get_credentials
|
||||
else
|
||||
session['omniauth.ldap'] = {'username' => request['username'], 'password' => request['password']}
|
||||
redirect callback_path
|
||||
end
|
||||
end
|
||||
|
||||
def get_credentials
|
||||
OmniAuth::Form.build(:title => (options[:title] || "LDAP Authentication")) do
|
||||
text_field 'Login', 'username'
|
||||
password_field 'Password', 'password'
|
||||
end.to_response
|
||||
end
|
||||
|
||||
def callback_phase
|
||||
begin
|
||||
creds = session['omniauth.ldap']
|
||||
session.delete 'omniauth.ldap'
|
||||
@ldap_user_info = {}
|
||||
begin
|
||||
(@adaptor.bind(:allow_anonymous => true) unless @adaptor.bound?)
|
||||
rescue Exception => e
|
||||
puts "failed to bind with the default credentials: " + e.message
|
||||
end
|
||||
@ldap_user_info = @adaptor.search(:filter => Net::LDAP::Filter.eq(@adaptor.uid, @name_proc.call(creds['username'])),:limit => 1) if @adaptor.bound?
|
||||
bind_dn = creds['username']
|
||||
bind_dn = @ldap_user_info[:dn].to_a.first if @ldap_user_info[:dn]
|
||||
@adaptor.bind(:bind_dn => bind_dn, :password => creds['password'])
|
||||
@ldap_user_info = @adaptor.search(:filter => Net::LDAP::Filter.eq(@adaptor.uid, @name_proc.call(creds['username'])),:limit => 1) if @ldap_user_info.empty?
|
||||
@user_info = self.class.map_user(@@config, @ldap_user_info)
|
||||
|
||||
@env['omniauth.auth'] = auth_hash
|
||||
|
||||
rescue Exception => e
|
||||
return fail!(:invalid_credentials, e)
|
||||
end
|
||||
call_app!
|
||||
end
|
||||
|
||||
def auth_hash
|
||||
OmniAuth::Utils.deep_merge(super, {
|
||||
'uid' => @user_info["uid"],
|
||||
'user_info' => @user_info,
|
||||
'extra' => @ldap_user_info
|
||||
})
|
||||
end
|
||||
|
||||
# Use only first value if the field is returned as an Array
|
||||
def self.get_ldap_field(ldap_object, field)
|
||||
value = ldap_object[field.to_sym]
|
||||
case value
|
||||
when Array
|
||||
value.first.to_s
|
||||
else
|
||||
value.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def self.map_user(mapper, object)
|
||||
user = {}
|
||||
mapper.each do |key, value|
|
||||
case value
|
||||
when String
|
||||
user[key] = get_ldap_field(object, value.downcase) if object[value.downcase.to_sym]
|
||||
when Array
|
||||
value.each {|v| (user[key] = get_ldap_field(object, v.downcase); break;) if object[v.downcase.to_sym]}
|
||||
when Hash
|
||||
value.map do |key1, value1|
|
||||
pattern = key1.dup
|
||||
value1.each_with_index do |v,i|
|
||||
part = '';
|
||||
v.each {|v1| (part = get_ldap_field(object, v1.downcase); break;) if object[v1.downcase.to_sym]}
|
||||
pattern.gsub!("%#{i}",part||'')
|
||||
end
|
||||
user[key] = pattern
|
||||
end
|
||||
end
|
||||
end
|
||||
user
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,276 +0,0 @@
|
|||
#this code boughts pieces from activeldap and net-ldap
|
||||
|
||||
require 'rack'
|
||||
require 'net/ldap'
|
||||
require 'net/ntlm'
|
||||
require 'uri'
|
||||
|
||||
module OmniAuth
|
||||
module Strategies
|
||||
class LDAP
|
||||
class Adaptor
|
||||
class LdapError < StandardError; end
|
||||
class ConfigurationError < StandardError; end
|
||||
class AuthenticationError < StandardError; end
|
||||
class ConnectionError < StandardError; end
|
||||
|
||||
VALID_ADAPTER_CONFIGURATION_KEYS = [:host, :port, :method, :bind_dn, :password, :try_sasl, :sasl_mechanisms, :uid, :base, :allow_anonymous]
|
||||
|
||||
MUST_HAVE_KEYS = [:host, :port, :method, :uid, :base]
|
||||
|
||||
METHOD = {
|
||||
:ssl => :simple_tls,
|
||||
:tls => :start_tls,
|
||||
:plain => nil,
|
||||
}
|
||||
|
||||
attr_accessor :bind_dn, :password
|
||||
attr_reader :connection, :uid, :base
|
||||
|
||||
def initialize(configuration={})
|
||||
@connection = nil
|
||||
@disconnected = false
|
||||
@bound = false
|
||||
@configuration = configuration.dup
|
||||
@configuration[:allow_anonymous] ||= false
|
||||
@logger = @configuration.delete(:logger)
|
||||
message = []
|
||||
MUST_HAVE_KEYS.each do |name|
|
||||
message << name if configuration[name].nil?
|
||||
end
|
||||
raise ArgumentError.new(message.join(",") +" MUST be provided") unless message.empty?
|
||||
VALID_ADAPTER_CONFIGURATION_KEYS.each do |name|
|
||||
instance_variable_set("@#{name}", configuration[name])
|
||||
end
|
||||
end
|
||||
|
||||
def connect(options={})
|
||||
host = options[:host] || @host
|
||||
method = ensure_method(options[:method] || @method || :plain)
|
||||
port = options[:port] || @port || ensure_port(method)
|
||||
@disconnected = false
|
||||
@bound = false
|
||||
@bind_tried = false
|
||||
|
||||
config = {
|
||||
:host => host,
|
||||
:port => port,
|
||||
}
|
||||
|
||||
config[:encryption] = {:method => method} if method
|
||||
|
||||
@connection, @uri, @with_start_tls = begin
|
||||
uri = construct_uri(host, port, method == :simple_tls)
|
||||
with_start_tls = method == :start_tls
|
||||
[Net::LDAP::Connection.new(config), uri, with_start_tls]
|
||||
rescue Net::LDAP::LdapError
|
||||
raise ConnectionError, $!.message
|
||||
end
|
||||
end
|
||||
|
||||
def unbind(options={})
|
||||
@connection.close # Net::LDAP doesn't implement unbind.
|
||||
end
|
||||
|
||||
def bind(options={})
|
||||
connect(options) unless connecting?
|
||||
begin
|
||||
@bind_tried = true
|
||||
|
||||
bind_dn = (options[:bind_dn] || @bind_dn).to_s
|
||||
try_sasl = options.has_key?(:try_sasl) ? options[:try_sasl] : @try_sasl
|
||||
if options.has_key?(:allow_anonymous)
|
||||
allow_anonymous = options[:allow_anonymous]
|
||||
else
|
||||
allow_anonymous = @allow_anonymous
|
||||
end
|
||||
# Rough bind loop:
|
||||
# Attempt 1: SASL if available
|
||||
# Attempt 2: SIMPLE with credentials if password block
|
||||
# Attempt 3: SIMPLE ANONYMOUS if 1 and 2 fail and allow anonymous is set to true
|
||||
if try_sasl and sasl_bind(bind_dn, options)
|
||||
puts "bound with sasl"
|
||||
elsif simple_bind(bind_dn, options)
|
||||
puts "bound with simple"
|
||||
elsif allow_anonymous and bind_as_anonymous(options)
|
||||
puts "bound as anonymous"
|
||||
else
|
||||
message = yield if block_given?
|
||||
message ||= ('All authentication methods for %s exhausted.') % target
|
||||
raise AuthenticationError, message
|
||||
end
|
||||
@bound = true
|
||||
rescue Net::LDAP::LdapError
|
||||
raise AuthenticationError, $!.message
|
||||
end
|
||||
end
|
||||
|
||||
def disconnect!(options={})
|
||||
unbind(options)
|
||||
@connection = @uri = @with_start_tls = nil
|
||||
@disconnected = true
|
||||
end
|
||||
|
||||
def rebind(options={})
|
||||
unbind(options) if bound?
|
||||
connect(options)
|
||||
end
|
||||
|
||||
def connecting?
|
||||
!@connection.nil? and !@disconnected
|
||||
end
|
||||
|
||||
def bound?
|
||||
connecting? and @bound
|
||||
end
|
||||
|
||||
def search(options={}, &block)
|
||||
base = options[:base] || @base
|
||||
filter = options[:filter]
|
||||
limit = options[:limit]
|
||||
|
||||
args = {
|
||||
:base => base,
|
||||
:filter => filter,
|
||||
:size => limit
|
||||
}
|
||||
|
||||
attributes = {}
|
||||
execute(:search, args) do |entry|
|
||||
entry.attribute_names.each do |name|
|
||||
attributes[name] = entry[name]
|
||||
end
|
||||
end
|
||||
attributes
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def execute(method, *args, &block)
|
||||
result = @connection.send(method, *args, &block)
|
||||
message = nil
|
||||
|
||||
if result.is_a?(Hash)
|
||||
message = result[:errorMessage]
|
||||
result = result[:resultCode]
|
||||
end
|
||||
|
||||
unless result.zero?
|
||||
message = [Net::LDAP.result2string(result), message].compact.join(": ")
|
||||
raise LdapError, message
|
||||
end
|
||||
end
|
||||
|
||||
def ensure_port(method)
|
||||
if method == :ssl
|
||||
URI::LDAPS::DEFAULT_PORT
|
||||
else
|
||||
URI::LDAP::DEFAULT_PORT
|
||||
end
|
||||
end
|
||||
|
||||
def prepare_connection(options)
|
||||
end
|
||||
|
||||
def ensure_method(method)
|
||||
method ||= "plain"
|
||||
normalized_method = method.to_s.downcase.to_sym
|
||||
return METHOD[normalized_method] if METHOD.has_key?(normalized_method)
|
||||
|
||||
available_methods = METHOD.keys.collect {|m| m.inspect}.join(", ")
|
||||
format = "%s is not one of the available connect methods: %s"
|
||||
raise ConfigurationError, format % [method.inspect, available_methods]
|
||||
end
|
||||
|
||||
def sasl_bind(bind_dn, options={})
|
||||
sasl_mechanisms = options[:sasl_mechanisms] || @sasl_mechanisms
|
||||
sasl_mechanisms.each do |mechanism|
|
||||
begin
|
||||
normalized_mechanism = mechanism.downcase.gsub(/-/, '_')
|
||||
sasl_bind_setup = "sasl_bind_setup_#{normalized_mechanism}"
|
||||
next unless respond_to?(sasl_bind_setup, true)
|
||||
initial_credential, challenge_response = send(sasl_bind_setup, bind_dn, options)
|
||||
|
||||
args = {
|
||||
:method => :sasl,
|
||||
:initial_credential => initial_credential,
|
||||
:mechanism => mechanism,
|
||||
:challenge_response => challenge_response,
|
||||
}
|
||||
|
||||
info = {
|
||||
:name => "bind: SASL", :dn => bind_dn, :mechanism => mechanism,
|
||||
}
|
||||
|
||||
execute(:bind, args)
|
||||
return true
|
||||
|
||||
rescue Exception => e
|
||||
puts e.message
|
||||
end
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
def sasl_bind_setup_digest_md5(bind_dn, options)
|
||||
initial_credential = ""
|
||||
challenge_response = Proc.new do |cred|
|
||||
pref = SASL::Preferences.new :digest_uri => "ldap/#{@host}", :username => bind_dn, :has_password? => true, :password => options[:password]||@password
|
||||
sasl = SASL.new("DIGEST-MD5", pref)
|
||||
response = sasl.receive("challenge", cred)
|
||||
response[1]
|
||||
end
|
||||
[initial_credential, challenge_response]
|
||||
end
|
||||
|
||||
def sasl_bind_setup_gss_spnego(bind_dn, options)
|
||||
user,psw = [bind_dn, options[:password]||@password]
|
||||
raise LdapError.new( "invalid binding information" ) unless (user && psw)
|
||||
|
||||
nego = proc {|challenge|
|
||||
t2_msg = Net::NTLM::Message.parse( challenge )
|
||||
user, domain = user.split('\\').reverse
|
||||
t2_msg.target_name = Net::NTLM::encode_utf16le(domain) if domain
|
||||
t3_msg = t2_msg.response( {:user => user, :password => psw}, {:ntlmv2 => true} )
|
||||
t3_msg.serialize
|
||||
}
|
||||
[Net::NTLM::Message::Type1.new.serialize, nego]
|
||||
end
|
||||
|
||||
def simple_bind(bind_dn, options={})
|
||||
args = {
|
||||
:method => :simple,
|
||||
:username => bind_dn,
|
||||
:password => (options[:password]||@password).to_s,
|
||||
}
|
||||
begin
|
||||
raise AuthenticationError if args[:password] == ""
|
||||
execute(:bind, args)
|
||||
true
|
||||
rescue Exception
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def bind_as_anonymous(options={})
|
||||
execute(:bind, {:method => :anonymous})
|
||||
true
|
||||
end
|
||||
|
||||
def construct_uri(host, port, ssl)
|
||||
protocol = ssl ? "ldaps" : "ldap"
|
||||
URI.parse("#{protocol}://#{host}:#{port}").to_s
|
||||
end
|
||||
|
||||
def target
|
||||
return nil if @uri.nil?
|
||||
if @with_start_tls
|
||||
"#{@uri}(StartTLS)"
|
||||
else
|
||||
@uri
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,50 +0,0 @@
|
|||
require 'omniauth/enterprise'
|
||||
|
||||
module OmniAuth
|
||||
module Strategies
|
||||
class SAML
|
||||
include OmniAuth::Strategy
|
||||
autoload :AuthRequest, 'omniauth/strategies/saml/auth_request'
|
||||
autoload :AuthResponse, 'omniauth/strategies/saml/auth_response'
|
||||
autoload :ValidationError, 'omniauth/strategies/saml/validation_error'
|
||||
autoload :XMLSecurity, 'omniauth/strategies/saml/xml_security'
|
||||
|
||||
@@settings = {}
|
||||
|
||||
def initialize(app, options={})
|
||||
super(app, :saml)
|
||||
@@settings = {
|
||||
:assertion_consumer_service_url => options[:assertion_consumer_service_url],
|
||||
:issuer => options[:issuer],
|
||||
:idp_sso_target_url => options[:idp_sso_target_url],
|
||||
:idp_cert_fingerprint => options[:idp_cert_fingerprint],
|
||||
:name_identifier_format => options[:name_identifier_format] || "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
||||
}
|
||||
end
|
||||
|
||||
def request_phase
|
||||
request = OmniAuth::Strategies::SAML::AuthRequest.new
|
||||
redirect(request.create(@@settings))
|
||||
end
|
||||
|
||||
def callback_phase
|
||||
begin
|
||||
response = OmniAuth::Strategies::SAML::AuthResponse.new(request.params['SAMLResponse'])
|
||||
response.settings = @@settings
|
||||
@name_id = response.name_id
|
||||
return fail!(:invalid_ticket, 'Invalid SAML Ticket') if @name_id.nil? || @name_id.empty?
|
||||
super
|
||||
rescue ArgumentError => e
|
||||
fail!(:invalid_ticket, 'Invalid SAML Response')
|
||||
end
|
||||
end
|
||||
|
||||
def auth_hash
|
||||
OmniAuth::Utils.deep_merge(super, {
|
||||
'uid' => @name_id
|
||||
})
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,38 +0,0 @@
|
|||
require "base64"
|
||||
require "uuid"
|
||||
require "zlib"
|
||||
require "cgi"
|
||||
|
||||
module OmniAuth
|
||||
module Strategies
|
||||
class SAML
|
||||
class AuthRequest
|
||||
|
||||
def create(settings, params = {})
|
||||
uuid = "_" + UUID.new.generate
|
||||
time = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
request =
|
||||
"<samlp:AuthnRequest xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" ID=\"#{uuid}\" Version=\"2.0\" IssueInstant=\"#{time}\" ProtocolBinding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" AssertionConsumerServiceURL=\"#{settings[:assertion_consumer_service_url]}\">" +
|
||||
"<saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">#{settings[:issuer]}</saml:Issuer>\n" +
|
||||
"<samlp:NameIDPolicy xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" Format=\"#{settings[:name_identifier_format]}\" AllowCreate=\"true\"></samlp:NameIDPolicy>\n" +
|
||||
"<samlp:RequestedAuthnContext xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" Comparison=\"exact\">" +
|
||||
"<saml:AuthnContextClassRef xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef></samlp:RequestedAuthnContext>\n" +
|
||||
"</samlp:AuthnRequest>"
|
||||
|
||||
deflated_request = Zlib::Deflate.deflate(request, 9)[2..-5]
|
||||
base64_request = Base64.encode64(deflated_request)
|
||||
encoded_request = CGI.escape(base64_request)
|
||||
request_params = "?SAMLRequest=" + encoded_request
|
||||
|
||||
params.each_pair do |key, value|
|
||||
request_params << "&#{key}=#{CGI.escape(value.to_s)}"
|
||||
end
|
||||
|
||||
settings[:idp_sso_target_url] + request_params
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,141 +0,0 @@
|
|||
require "time"
|
||||
|
||||
module OmniAuth
|
||||
module Strategies
|
||||
class SAML
|
||||
class AuthResponse
|
||||
|
||||
ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
|
||||
PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
|
||||
DSIG = "http://www.w3.org/2000/09/xmldsig#"
|
||||
|
||||
attr_accessor :options, :response, :document, :settings
|
||||
|
||||
def initialize(response, options = {})
|
||||
raise ArgumentError.new("Response cannot be nil") if response.nil?
|
||||
self.options = options
|
||||
self.response = response
|
||||
self.document = OmniAuth::Strategies::SAML::XMLSecurity::SignedDocument.new(Base64.decode64(response))
|
||||
end
|
||||
|
||||
def is_valid?
|
||||
validate(soft = true)
|
||||
end
|
||||
|
||||
def validate!
|
||||
validate(soft = false)
|
||||
end
|
||||
|
||||
# The value of the user identifier as designated by the initialization request response
|
||||
def name_id
|
||||
@name_id ||= begin
|
||||
node = REXML::XPath.first(document, "/p:Response/a:Assertion[@ID='#{document.signed_element_id[1,document.signed_element_id.size]}']/a:Subject/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
|
||||
node ||= REXML::XPath.first(document, "/p:Response[@ID='#{document.signed_element_id[1,document.signed_element_id.size]}']/a:Assertion/a:Subject/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
|
||||
node.nil? ? nil : node.text
|
||||
end
|
||||
end
|
||||
|
||||
# A hash of alle the attributes with the response. Assuming there is only one value for each key
|
||||
def attributes
|
||||
@attr_statements ||= begin
|
||||
result = {}
|
||||
|
||||
stmt_element = REXML::XPath.first(document, "/p:Response/a:Assertion/a:AttributeStatement", { "p" => PROTOCOL, "a" => ASSERTION })
|
||||
return {} if stmt_element.nil?
|
||||
|
||||
stmt_element.elements.each do |attr_element|
|
||||
name = attr_element.attributes["Name"]
|
||||
value = attr_element.elements.first.text
|
||||
|
||||
result[name] = value
|
||||
end
|
||||
|
||||
result.keys.each do |key|
|
||||
result[key.intern] = result[key]
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
# When this user session should expire at latest
|
||||
def session_expires_at
|
||||
@expires_at ||= begin
|
||||
node = REXML::XPath.first(document, "/p:Response/a:Assertion/a:AuthnStatement", { "p" => PROTOCOL, "a" => ASSERTION })
|
||||
parse_time(node, "SessionNotOnOrAfter")
|
||||
end
|
||||
end
|
||||
|
||||
# Conditions (if any) for the assertion to run
|
||||
def conditions
|
||||
@conditions ||= begin
|
||||
REXML::XPath.first(document, "/p:Response/a:Assertion[@ID='#{document.signed_element_id[1,document.signed_element_id.size]}']/a:Conditions", { "p" => PROTOCOL, "a" => ASSERTION })
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validation_error(message)
|
||||
raise OmniAuth::Strategies::SAML::ValidationError.new(message)
|
||||
end
|
||||
|
||||
def validate(soft = true)
|
||||
validate_response_state(soft) &&
|
||||
validate_conditions(soft) &&
|
||||
document.validate(get_fingerprint, soft)
|
||||
end
|
||||
|
||||
def validate_response_state(soft = true)
|
||||
if response.empty?
|
||||
return soft ? false : validation_error("Blank response")
|
||||
end
|
||||
|
||||
if settings.nil?
|
||||
return soft ? false : validation_error("No settings on response")
|
||||
end
|
||||
|
||||
if settings.idp_cert_fingerprint.nil? && settings.idp_cert.nil?
|
||||
return soft ? false : validation_error("No fingerprint or certificate on settings")
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def get_fingerprint
|
||||
if settings.idp_cert
|
||||
cert = OpenSSL::X509::Certificate.new(settings.idp_cert)
|
||||
Digest::SHA1.hexdigest(cert.to_der).upcase.scan(/../).join(":")
|
||||
else
|
||||
settings.idp_cert_fingerprint
|
||||
end
|
||||
end
|
||||
|
||||
def validate_conditions(soft = true)
|
||||
return true if conditions.nil?
|
||||
return true if options[:skip_conditions]
|
||||
|
||||
if not_before = parse_time(conditions, "NotBefore")
|
||||
if Time.now.utc < not_before
|
||||
return soft ? false : validation_error("Current time is earlier than NotBefore condition")
|
||||
end
|
||||
end
|
||||
|
||||
if not_on_or_after = parse_time(conditions, "NotOnOrAfter")
|
||||
if Time.now.utc >= not_on_or_after
|
||||
return soft ? false : validation_error("Current time is on or after NotOnOrAfter condition")
|
||||
end
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def parse_time(node, attribute)
|
||||
if node && node.attributes[attribute]
|
||||
Time.parse(node.attributes[attribute])
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,8 +0,0 @@
|
|||
module OmniAuth
|
||||
module Strategies
|
||||
class SAML
|
||||
class ValidationError < Exception
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,126 +0,0 @@
|
|||
# The contents of this file are subject to the terms
|
||||
# of the Common Development and Distribution License
|
||||
# (the License). You may not use this file except in
|
||||
# compliance with the License.
|
||||
#
|
||||
# You can obtain a copy of the License at
|
||||
# https://opensso.dev.java.net/public/CDDLv1.0.html or
|
||||
# opensso/legal/CDDLv1.0.txt
|
||||
# See the License for the specific language governing
|
||||
# permission and limitations under the License.
|
||||
#
|
||||
# When distributing Covered Code, include this CDDL
|
||||
# Header Notice in each file and include the License file
|
||||
# at opensso/legal/CDDLv1.0.txt.
|
||||
# If applicable, add the following below the CDDL Header,
|
||||
# with the fields enclosed by brackets [] replaced by
|
||||
# your own identifying information:
|
||||
# "Portions Copyrighted [year] [name of copyright owner]"
|
||||
#
|
||||
# $Id: xml_sec.rb,v 1.6 2007/10/24 00:28:41 todddd Exp $
|
||||
#
|
||||
# Copyright 2007 Sun Microsystems Inc. All Rights Reserved
|
||||
# Portions Copyrighted 2007 Todd W Saxton.
|
||||
|
||||
require 'rubygems'
|
||||
require "rexml/document"
|
||||
require "rexml/xpath"
|
||||
require "openssl"
|
||||
require "xmlcanonicalizer"
|
||||
require "digest/sha1"
|
||||
|
||||
module OmniAuth
|
||||
module Strategies
|
||||
class SAML
|
||||
|
||||
module XMLSecurity
|
||||
|
||||
class SignedDocument < REXML::Document
|
||||
DSIG = "http://www.w3.org/2000/09/xmldsig#"
|
||||
|
||||
attr_accessor :signed_element_id
|
||||
|
||||
def initialize(response)
|
||||
super(response)
|
||||
extract_signed_element_id
|
||||
end
|
||||
|
||||
def validate(idp_cert_fingerprint, soft = true)
|
||||
# get cert from response
|
||||
base64_cert = self.elements["//ds:X509Certificate"].text
|
||||
cert_text = Base64.decode64(base64_cert)
|
||||
cert = OpenSSL::X509::Certificate.new(cert_text)
|
||||
|
||||
# check cert matches registered idp cert
|
||||
fingerprint = Digest::SHA1.hexdigest(cert.to_der)
|
||||
|
||||
if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
|
||||
return soft ? false : (raise OmniAuth::Strategies::SAML::ValidationError.new("Fingerprint mismatch"))
|
||||
end
|
||||
|
||||
validate_doc(base64_cert, soft)
|
||||
end
|
||||
|
||||
def validate_doc(base64_cert, soft = true)
|
||||
# validate references
|
||||
|
||||
# check for inclusive namespaces
|
||||
|
||||
inclusive_namespaces = []
|
||||
inclusive_namespace_element = REXML::XPath.first(self, "//ec:InclusiveNamespaces")
|
||||
|
||||
if inclusive_namespace_element
|
||||
prefix_list = inclusive_namespace_element.attributes.get_attribute('PrefixList').value
|
||||
inclusive_namespaces = prefix_list.split(" ")
|
||||
end
|
||||
|
||||
# remove signature node
|
||||
sig_element = REXML::XPath.first(self, "//ds:Signature", {"ds"=>"http://www.w3.org/2000/09/xmldsig#"})
|
||||
sig_element.remove
|
||||
|
||||
# check digests
|
||||
REXML::XPath.each(sig_element, "//ds:Reference", {"ds"=>"http://www.w3.org/2000/09/xmldsig#"}) do |ref|
|
||||
uri = ref.attributes.get_attribute("URI").value
|
||||
hashed_element = REXML::XPath.first(self, "//[@ID='#{uri[1,uri.size]}']")
|
||||
canoner = XML::Util::XmlCanonicalizer.new(false, true)
|
||||
canoner.inclusive_namespaces = inclusive_namespaces if canoner.respond_to?(:inclusive_namespaces) && !inclusive_namespaces.empty?
|
||||
canon_hashed_element = canoner.canonicalize(hashed_element)
|
||||
hash = Base64.encode64(Digest::SHA1.digest(canon_hashed_element)).chomp
|
||||
digest_value = REXML::XPath.first(ref, "//ds:DigestValue", {"ds"=>"http://www.w3.org/2000/09/xmldsig#"}).text
|
||||
|
||||
if hash != digest_value
|
||||
return soft ? false : (raise OmniAuth::Strategies::SAML::ValidationError.new("Digest mismatch"))
|
||||
end
|
||||
end
|
||||
|
||||
# verify signature
|
||||
canoner = XML::Util::XmlCanonicalizer.new(false, true)
|
||||
signed_info_element = REXML::XPath.first(sig_element, "//ds:SignedInfo", {"ds"=>"http://www.w3.org/2000/09/xmldsig#"})
|
||||
canon_string = canoner.canonicalize(signed_info_element)
|
||||
|
||||
base64_signature = REXML::XPath.first(sig_element, "//ds:SignatureValue", {"ds"=>"http://www.w3.org/2000/09/xmldsig#"}).text
|
||||
signature = Base64.decode64(base64_signature)
|
||||
|
||||
# get certificate object
|
||||
cert_text = Base64.decode64(base64_cert)
|
||||
cert = OpenSSL::X509::Certificate.new(cert_text)
|
||||
|
||||
if !cert.public_key.verify(OpenSSL::Digest::SHA1.new, signature, canon_string)
|
||||
return soft ? false : (raise OmniAuth::Strategies::SAML::ValidationError.new("Key validation error"))
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def extract_signed_element_id
|
||||
reference_element = REXML::XPath.first(self, "//ds:Signature/ds:SignedInfo/ds:Reference", {"ds"=>DSIG})
|
||||
self.signed_element_id = reference_element.attribute("URI").value unless reference_element.nil?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,19 +0,0 @@
|
|||
module OmniAuth
|
||||
module Version
|
||||
unless defined?(::OmniAuth::Version::MAJOR)
|
||||
MAJOR = 0
|
||||
end
|
||||
unless defined?(::OmniAuth::Version::MINOR)
|
||||
MINOR = 3
|
||||
end
|
||||
unless defined?(::OmniAuth::Version::PATCH)
|
||||
PATCH = 0
|
||||
end
|
||||
unless defined?(::OmniAuth::Version::PRE)
|
||||
PRE = "rc3"
|
||||
end
|
||||
unless defined?(::OmniAuth::Version::STRING)
|
||||
STRING = [MAJOR, MINOR, PATCH, PRE].compact.join('.')
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,31 +0,0 @@
|
|||
# encoding: utf-8
|
||||
require File.expand_path('../lib/omniauth/version', __FILE__)
|
||||
|
||||
Gem::Specification.new do |gem|
|
||||
gem.add_dependency 'addressable', '~> 2.2.6'
|
||||
gem.add_dependency 'net-ldap', '~> 0.2.2'
|
||||
gem.add_dependency 'nokogiri', '~> 1.5.0'
|
||||
gem.add_dependency 'oa-core', OmniAuth::Version::STRING
|
||||
gem.add_dependency 'pyu-ruby-sasl', '~> 0.0.3.1'
|
||||
gem.add_dependency 'rubyntlm', '~> 0.1.1'
|
||||
gem.add_dependency 'uuid'
|
||||
gem.add_dependency 'XMLCanonicalizer', '~> 1.0.1'
|
||||
gem.add_development_dependency 'rack-test', '~> 0.5'
|
||||
gem.add_development_dependency 'rake', '~> 0.8'
|
||||
gem.add_development_dependency 'rdiscount', '~> 1.6'
|
||||
gem.add_development_dependency 'rspec', '~> 2.5'
|
||||
gem.add_development_dependency 'simplecov', '~> 0.4'
|
||||
gem.add_development_dependency 'webmock', '~> 1.7'
|
||||
gem.add_development_dependency 'yard', '~> 0.7'
|
||||
gem.authors = ['James A. Rosen', 'Ping Yu', 'Michael Bleigh', 'Erik Michaels-Ober', 'Raecoo Cao']
|
||||
gem.description = %q{Enterprise strategies for OmniAuth.}
|
||||
gem.email = ['james.a.rosen@gmail.com', 'ping@intridea.com', 'michael@intridea.com', 'sferik@gmail.com', 'raecoo@intridea.com']
|
||||
gem.files = `git ls-files`.split("\n")
|
||||
gem.homepage = 'http://github.com/intridea/omniauth'
|
||||
gem.name = 'oa-enterprise'
|
||||
gem.require_paths = ['lib']
|
||||
gem.required_rubygems_version = Gem::Requirement.new('>= 1.3.6') if gem.respond_to? :required_rubygems_version=
|
||||
gem.summary = gem.description
|
||||
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
||||
gem.version = OmniAuth::Version::STRING
|
||||
end
|
4
oa-enterprise/spec/fixtures/cas_failure.xml
vendored
4
oa-enterprise/spec/fixtures/cas_failure.xml
vendored
|
@ -1,4 +0,0 @@
|
|||
<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
|
||||
<cas:authenticationFailure>
|
||||
</cas:authenticationFailure>
|
||||
</cas:serviceResponse>
|
8
oa-enterprise/spec/fixtures/cas_success.xml
vendored
8
oa-enterprise/spec/fixtures/cas_success.xml
vendored
|
@ -1,8 +0,0 @@
|
|||
<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
|
||||
<cas:authenticationSuccess>
|
||||
<cas:user>psegel</cas:user>
|
||||
<cas:first-name>Peter</cas:first-name>
|
||||
<cas:last-name>Segel</cas:last-name>
|
||||
<hire-date>2004-07-13</hire-date>
|
||||
</cas:authenticationSuccess>
|
||||
</cas:serviceResponse>
|
|
@ -1,94 +0,0 @@
|
|||
require File.expand_path('../../../spec_helper', __FILE__)
|
||||
require 'cgi'
|
||||
|
||||
describe OmniAuth::Strategies::CAS, :type => :strategy do
|
||||
|
||||
include OmniAuth::Test::StrategyTestCase
|
||||
|
||||
def strategy
|
||||
@cas_server ||= 'https://cas.example.org'
|
||||
[OmniAuth::Strategies::CAS, {:cas_server => @cas_server}]
|
||||
end
|
||||
|
||||
describe 'GET /auth/cas' do
|
||||
before do
|
||||
get '/auth/cas'
|
||||
end
|
||||
|
||||
it 'should redirect to the CAS server' do
|
||||
last_response.should be_redirect
|
||||
return_to = CGI.escape(last_request.url + '/callback')
|
||||
last_response.headers['Location'].should == @cas_server + '/login?service=' + return_to
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /auth/cas/callback without a ticket' do
|
||||
before do
|
||||
get '/auth/cas/callback'
|
||||
end
|
||||
it 'should fail' do
|
||||
last_response.should be_redirect
|
||||
last_response.headers['Location'].should =~ /no_ticket/
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /auth/cas/callback with an invalid ticket' do
|
||||
before do
|
||||
stub_request(:get, /^https:\/\/cas.example.org(:443)?\/serviceValidate\?([^&]+&)?ticket=9391d/).
|
||||
to_return(:body => File.read(File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'cas_failure.xml')))
|
||||
get '/auth/cas/callback?ticket=9391d'
|
||||
end
|
||||
it 'should fail' do
|
||||
last_response.should be_redirect
|
||||
last_response.headers['Location'].should =~ /invalid_ticket/
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /auth/cas/callback with a valid ticket' do
|
||||
before do
|
||||
stub_request(:get, /^https:\/\/cas.example.org(:443)?\/serviceValidate\?([^&]+&)?ticket=593af/).
|
||||
with { |request| @request_uri = request.uri.to_s }.
|
||||
to_return(:body => File.read(File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'cas_success.xml')))
|
||||
get '/auth/cas/callback?ticket=593af'
|
||||
end
|
||||
|
||||
it 'should strip the ticket parameter from the callback URL before sending it to the CAS server' do
|
||||
@request_uri.scan('ticket=').length.should == 1
|
||||
end
|
||||
|
||||
sets_an_auth_hash
|
||||
sets_provider_to 'cas'
|
||||
sets_uid_to 'psegel'
|
||||
|
||||
it 'should set additional user information' do
|
||||
extra = (last_request.env['omniauth.auth'] || {})['extra']
|
||||
extra.should be_kind_of(Hash)
|
||||
extra['first-name'].should == 'Peter'
|
||||
extra['last-name'].should == 'Segel'
|
||||
extra['hire-date'].should == '2004-07-13'
|
||||
end
|
||||
|
||||
it 'should call through to the master app' do
|
||||
last_response.body.should == 'true'
|
||||
end
|
||||
end
|
||||
|
||||
unless RUBY_VERSION =~ /^1\.8\.\d$/
|
||||
describe 'GET /auth/cas/callback with a valid ticket and gzipped response from the server on ruby >1.8' do
|
||||
before do
|
||||
zipped = StringIO.new
|
||||
Zlib::GzipWriter.wrap zipped do |io|
|
||||
io.write File.read(File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'cas_success.xml'))
|
||||
end
|
||||
stub_request(:get, /^https:\/\/cas.example.org(:443)?\/serviceValidate\?([^&]+&)?ticket=593af/).
|
||||
with { |request| @request_uri = request.uri.to_s }.
|
||||
to_return(:body => zipped.string, :headers => { 'content-encoding' => 'gzip' })
|
||||
get '/auth/cas/callback?ticket=593af'
|
||||
end
|
||||
|
||||
it 'should call through to the master app when response is gzipped' do
|
||||
last_response.body.should == 'true'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,41 +0,0 @@
|
|||
require File.expand_path('../../../spec_helper', __FILE__)
|
||||
require 'cgi'
|
||||
|
||||
describe OmniAuth::Strategies::LDAP, :type => :strategy do
|
||||
|
||||
include OmniAuth::Test::StrategyTestCase
|
||||
|
||||
def strategy
|
||||
@ldap_server ||= 'ldap.example.org'
|
||||
[OmniAuth::Strategies::LDAP, {
|
||||
:host => @ldap_server,
|
||||
:port => 636,
|
||||
:method => :ssl,
|
||||
:uid => 'jeremyf',
|
||||
:base => 'o="University of OmniAuth", st=Sublime, c=RubyNation',
|
||||
}]
|
||||
end
|
||||
|
||||
describe 'GET /auth/ldap' do
|
||||
before do
|
||||
get '/auth/ldap'
|
||||
end
|
||||
|
||||
# TODO: Add checks that page has authentication form; I attempted
|
||||
# to use `should have_tag` but that was not working.
|
||||
it 'should get authentication page' do
|
||||
last_response.status.should == 200
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /auth/ldap' do
|
||||
before do
|
||||
post '/auth/ldap', {:username => 'jeremy', :password => 'valid_password' }
|
||||
end
|
||||
|
||||
it 'should redirect us to /auth/ldap/callback' do
|
||||
last_response.should be_redirect
|
||||
last_response.location.should == '/auth/ldap/callback'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,37 +0,0 @@
|
|||
require File.expand_path('../../../spec_helper', __FILE__)
|
||||
|
||||
describe OmniAuth::Strategies::SAML, :type => :strategy do
|
||||
|
||||
include OmniAuth::Test::StrategyTestCase
|
||||
|
||||
def strategy
|
||||
[OmniAuth::Strategies::SAML, {
|
||||
:assertion_consumer_service_url => "http://consumer.service.url/auth/saml/callback",
|
||||
:issuer => "https://saml.issuer.url/issuers/29490",
|
||||
:idp_sso_target_url => "https://idp.sso.target_url/signon/29490",
|
||||
:idp_cert_fingerprint => "E7:91:B2:E1:4C:65:2C:49:F3:33:74:0A:58:5A:7E:55:F7:15:7A:33",
|
||||
:name_identifier_format => "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
||||
}]
|
||||
end
|
||||
|
||||
describe 'GET /auth/saml' do
|
||||
before do
|
||||
get '/auth/saml'
|
||||
end
|
||||
|
||||
it 'should get authentication page' do
|
||||
last_response.should be_redirect
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /auth/saml/callback' do
|
||||
|
||||
it 'should raise ArgumentError exception without the SAMLResponse parameter' do
|
||||
post '/auth/saml/callback'
|
||||
last_response.should be_redirect
|
||||
last_response.location.should == '/auth/failure?message=invalid_ticket'
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
|
@ -1,14 +0,0 @@
|
|||
require 'simplecov'
|
||||
SimpleCov.start
|
||||
require 'rspec'
|
||||
require 'rack/test'
|
||||
require 'webmock/rspec'
|
||||
require 'omniauth/core'
|
||||
require 'omniauth/test'
|
||||
require 'omniauth/enterprise'
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.include WebMock::API
|
||||
config.include Rack::Test::Methods
|
||||
config.extend OmniAuth::Test::StrategyMacros, :type => :strategy
|
||||
end
|
|
@ -1,2 +0,0 @@
|
|||
--format=nested
|
||||
--colour
|
|
@ -1,90 +0,0 @@
|
|||
# OmniAuth Identity
|
||||
|
||||
The OmniAuth Identity gem provides a way for applications to utilize a
|
||||
traditional login/password based authentication system without the need
|
||||
to give up the simple authentication flow provided by OmniAuth. Identity
|
||||
is designed on purpose to be as featureless as possible: it provides the
|
||||
basic construct for user management and then gets out of the way.
|
||||
|
||||
## Usage
|
||||
|
||||
You use `oa-identity` just like you would any other OmniAuth provider: as a
|
||||
Rack middleware. The basic setup for a email/password authentication would
|
||||
look something like this:
|
||||
|
||||
use OmniAuth::Builder do
|
||||
provider :identity, :fields => [:email]
|
||||
end
|
||||
|
||||
Next, you need to create a model (called `Identity by default`) that will be
|
||||
able to persist the information provided by the user. Luckily for you, there
|
||||
are pre-built models for popular ORMs that make this dead simple. You just
|
||||
need to subclass the relevant class:
|
||||
|
||||
class Identity < OmniAuth::Identity::Models::ActiveRecord
|
||||
# Add whatever you like!
|
||||
end
|
||||
|
||||
Adapters are provided for `ActiveRecord` and `MongoMapper` and are
|
||||
autoloaded on request (but not loaded by default so no dependencies are
|
||||
injected).
|
||||
|
||||
Once you've got an Identity persistence model and the strategy up and
|
||||
running, you can point users to `/auth/identity` and it will request
|
||||
that they log in or give them the opportunity to sign up for an account.
|
||||
Once they have authenticated with their identity, OmniAuth will call
|
||||
through to `/auth/identity/callback` with the same kinds of information
|
||||
it would had the user authenticated through an external provider.
|
||||
Simple!
|
||||
|
||||
## Custom Auth Model
|
||||
|
||||
To use a class other than the default, specify the <tt>:model</tt> option to a
|
||||
different class.
|
||||
|
||||
use OmniAuth::Builder do
|
||||
provider :identity, :fields => [:email], :model => MyCustomClass
|
||||
end
|
||||
|
||||
## Customizing Registration Failure
|
||||
|
||||
To use your own custom registration form, create a form that POSTs to
|
||||
'/auth/identity/register' with 'password', 'password_confirmation', and your
|
||||
other fields.
|
||||
|
||||
<%= form_tag '/auth/identity/register' do |f| %>
|
||||
<h1>Create an Account</h1>
|
||||
<%= text_field_tag :email %>
|
||||
<%= password_field_tag, :password %>
|
||||
<%= password_field_tag, :password_confirmation %>
|
||||
<%= submit_tag %>
|
||||
<% end %>
|
||||
|
||||
Beware not to nest your form parameters within a namespace. This strategy
|
||||
looks for the form parameters at the top level of the post params. If you are
|
||||
using [simple\_form](https://github.com/plataformatec/simple_form), then you
|
||||
can avoid the params nesting by specifying <tt>:input_html</tt>.
|
||||
|
||||
<%= simple_form_for @identity, :url => '/auth/identity/register' do |f| %>
|
||||
<h1>Create an Account</h1>
|
||||
<%# specify :input_html to avoid params nesting %>
|
||||
<%= f.input :email, :input_html => {:name => 'email'} %>
|
||||
<%= f.input :password, :as => 'password', :input_html => {:name => 'password'} %>
|
||||
<%= f.input :password_confirmation, :label => "Confirm Password", :as => 'password', :input_html => {:name => 'password_confirmation'} %>
|
||||
<button type='submit'>Sign Up</button>
|
||||
<% end %>
|
||||
|
||||
Next you'll need to let OmniAuth know what action to call when a registration
|
||||
fails. In your OmniAuth configuration, specify any valid rack endpoint in the
|
||||
<tt>:on_failed_registration</tt> option.
|
||||
|
||||
use OmniAuth::Builder do
|
||||
provider :identity,
|
||||
:fields => [:email],
|
||||
:on_failed_registration => UsersController.action(:new)
|
||||
end
|
||||
|
||||
For more information on rack endpoints, check out [this
|
||||
introduction](http://library.edgecase.com/Rails/2011/01/04/rails-routing-and-rack-endpoints.html)
|
||||
and
|
||||
[ActionController::Metal](http://rubydoc.info/docs/rails/ActionController/Metal)
|
|
@ -1,8 +0,0 @@
|
|||
require 'bundler'
|
||||
Bundler::GemHelper.install_tasks
|
||||
|
||||
require 'rspec/core/rake_task'
|
||||
RSpec::Core::RakeTask.new(:spec)
|
||||
|
||||
task :default => :spec
|
||||
task :test => :spec
|
|
@ -1 +0,0 @@
|
|||
require 'omniauth/identity'
|
|
@ -1,17 +0,0 @@
|
|||
require 'omniauth/core'
|
||||
|
||||
module OmniAuth
|
||||
module Strategies
|
||||
autoload :Identity, 'omniauth/strategies/identity'
|
||||
end
|
||||
|
||||
module Identity
|
||||
autoload :Model, 'omniauth/identity/model'
|
||||
autoload :SecurePassword, 'omniauth/identity/secure_password'
|
||||
module Models
|
||||
autoload :ActiveRecord, 'omniauth/identity/models/active_record'
|
||||
# autoload :MongoMapper, 'omniauth/identity/models/mongo_mapper'
|
||||
# autoload :Mongoid, 'omniauth/identity/models/mongoid'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,119 +0,0 @@
|
|||
module OmniAuth
|
||||
module Identity
|
||||
# This module provides an includable interface for implementing the
|
||||
# necessary API for OmniAuth Identity to properly locate identities
|
||||
# and provide all necessary information. All methods marked as
|
||||
# abstract must be implemented in the including class for things to
|
||||
# work properly.
|
||||
module Model
|
||||
def self.included(base)
|
||||
base.extend ClassMethods
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Locate an identity given its unique login key.
|
||||
#
|
||||
# @abstract
|
||||
# @param [String] key The unique login key.
|
||||
# @return [Model] An instance of the identity model class.
|
||||
def locate(key)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
# Authenticate a user with the given key and password.
|
||||
#
|
||||
# @param [String] key The unique login key provided for a given identity.
|
||||
# @param [String] password The presumed password for the identity.
|
||||
# @return [Model] An instance of the identity model class.
|
||||
def authenticate(key, password)
|
||||
instance = locate(key)
|
||||
return false unless instance
|
||||
instance.authenticate(password)
|
||||
end
|
||||
|
||||
# Used to set or retrieve the method that will be used to get
|
||||
# and set the user-supplied authentication key.
|
||||
# @return [String] The method name.
|
||||
def auth_key(method = false)
|
||||
@auth_key = method.to_s unless method == false
|
||||
@auth_key = nil if @auth_key == ''
|
||||
|
||||
@auth_key || 'email'
|
||||
end
|
||||
end
|
||||
|
||||
# Returns self if the provided password is correct, false
|
||||
# otherwise.
|
||||
#
|
||||
# @abstract
|
||||
# @param [String] password The password to check.
|
||||
# @return [self or false] Self if authenticated, false if not.
|
||||
def authenticate(password)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
SCHEMA_ATTRIBUTES = %w(name email nickname first_name last_name location description image phone)
|
||||
# A hash of as much of the standard OmniAuth schema as is stored
|
||||
# in this particular model. By default, this will call instance
|
||||
# methods for each of the attributes it needs in turn, ignoring
|
||||
# any for which `#respond_to?` is `false`.
|
||||
#
|
||||
# If `first_name`, `nickname`, and/or `last_name` is provided but
|
||||
# `name` is not, it will be automatically calculated.
|
||||
#
|
||||
# @return [Hash] A string-keyed hash of user information.
|
||||
def user_info
|
||||
info = SCHEMA_ATTRIBUTES.inject({}) do |hash,attribute|
|
||||
hash[attribute] = send(attribute) if respond_to?(attribute)
|
||||
hash
|
||||
end
|
||||
|
||||
info['name'] ||= [info['first_name'], info['last_name']].join(' ').strip if info['first_name'] || info['last_name']
|
||||
info['name'] ||= info['nickname']
|
||||
|
||||
info
|
||||
end
|
||||
|
||||
# An identifying string that must be globally unique to the
|
||||
# application. Defaults to stringifying the `id` method.
|
||||
#
|
||||
# @return [String] An identifier string unique to this identity.
|
||||
def uid
|
||||
if respond_to?('id')
|
||||
return nil if self.id.nil?
|
||||
self.id.to_s
|
||||
else
|
||||
raise NotImplementedError
|
||||
end
|
||||
end
|
||||
|
||||
# Used to retrieve the user-supplied authentication key (e.g. a
|
||||
# username or email). Determined using the class method of the same name,
|
||||
# defaults to `:email`.
|
||||
#
|
||||
# @return [String] An identifying string that will be entered by
|
||||
# users upon sign in.
|
||||
def auth_key
|
||||
if respond_to?(self.class.auth_key)
|
||||
send(self.class.auth_key)
|
||||
else
|
||||
raise NotImplementedError
|
||||
end
|
||||
end
|
||||
|
||||
# Used to set the user-supplied authentication key (e.g. a
|
||||
# username or email. Determined using the `.auth_key` class
|
||||
# method.
|
||||
#
|
||||
# @param [String] value The value to which the auth key should be
|
||||
# set.
|
||||
def auth_key=(value)
|
||||
if respond_to?(self.class.auth_key + '=')
|
||||
send(self.class.auth_key + '=', value)
|
||||
else
|
||||
raise NotImplementedError
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,24 +0,0 @@
|
|||
require 'active_record'
|
||||
|
||||
module OmniAuth
|
||||
module Identity
|
||||
module Models
|
||||
class ActiveRecord < ::ActiveRecord::Base
|
||||
include OmniAuth::Identity::Model
|
||||
include OmniAuth::Identity::SecurePassword
|
||||
|
||||
self.abstract_class = true
|
||||
has_secure_password
|
||||
|
||||
def self.auth_key=(key)
|
||||
super
|
||||
validates_uniqueness_of key, :case_sensitive => false
|
||||
end
|
||||
|
||||
def self.locate(key)
|
||||
where(auth_key => key).first
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,78 +0,0 @@
|
|||
require 'bcrypt'
|
||||
|
||||
module OmniAuth
|
||||
module Identity
|
||||
# This is taken directly from Rails 3.1 code and is used if
|
||||
# the version of ActiveModel that's being used does not
|
||||
# include SecurePassword. The only difference is that instead of
|
||||
# using ActiveSupport::Concern, it checks to see if there is already
|
||||
# a has_secure_password method.
|
||||
module SecurePassword
|
||||
def self.included(base)
|
||||
unless base.respond_to?(:has_secure_password)
|
||||
base.extend ClassMethods
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Adds methods to set and authenticate against a BCrypt password.
|
||||
# This mechanism requires you to have a password_digest attribute.
|
||||
#
|
||||
# Validations for presence of password, confirmation of password (using
|
||||
# a "password_confirmation" attribute) are automatically added.
|
||||
# You can add more validations by hand if need be.
|
||||
#
|
||||
# Example using Active Record (which automatically includes ActiveModel::SecurePassword):
|
||||
#
|
||||
# # Schema: User(name:string, password_digest:string)
|
||||
# class User < ActiveRecord::Base
|
||||
# has_secure_password
|
||||
# end
|
||||
#
|
||||
# user = User.new(:name => "david", :password => "", :password_confirmation => "nomatch")
|
||||
# user.save # => false, password required
|
||||
# user.password = "mUc3m00RsqyRe"
|
||||
# user.save # => false, confirmation doesn't match
|
||||
# user.password_confirmation = "mUc3m00RsqyRe"
|
||||
# user.save # => true
|
||||
# user.authenticate("notright") # => false
|
||||
# user.authenticate("mUc3m00RsqyRe") # => user
|
||||
# User.find_by_name("david").try(:authenticate, "notright") # => nil
|
||||
# User.find_by_name("david").try(:authenticate, "mUc3m00RsqyRe") # => user
|
||||
def has_secure_password
|
||||
attr_reader :password
|
||||
|
||||
validates_confirmation_of :password
|
||||
validates_presence_of :password_digest
|
||||
|
||||
include InstanceMethodsOnActivation
|
||||
|
||||
if respond_to?(:attributes_protected_by_default)
|
||||
def self.attributes_protected_by_default
|
||||
super + ['password_digest']
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethodsOnActivation
|
||||
# Returns self if the password is correct, otherwise false.
|
||||
def authenticate(unencrypted_password)
|
||||
if BCrypt::Password.new(password_digest) == unencrypted_password
|
||||
self
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# Encrypts the password into the password_digest attribute.
|
||||
def password=(unencrypted_password)
|
||||
@password = unencrypted_password
|
||||
unless unencrypted_password.blank?
|
||||
self.password_digest = BCrypt::Password.create(unencrypted_password)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,96 +0,0 @@
|
|||
module OmniAuth
|
||||
module Strategies
|
||||
# The identity strategy allows you to provide simple internal
|
||||
# user authentication using the same process flow that you
|
||||
# use for external OmniAuth providers.
|
||||
class Identity
|
||||
include OmniAuth::Strategy
|
||||
|
||||
# @option options [Symbol] :name The name you want to use for this strategy.
|
||||
# @option options [Symbol] :model The class you wish to use as the identity model.
|
||||
# @option options [Array] :fields ([:name, :email]) Required information at identity registration.
|
||||
def initialize(app, options = {})
|
||||
options[:fields] ||= [:name, :email]
|
||||
super(app, options[:name] || :identity, options.dup)
|
||||
end
|
||||
|
||||
def request_phase
|
||||
OmniAuth::Form.build(
|
||||
:title => (options[:title] || "Identity Verification"),
|
||||
:url => callback_path
|
||||
) do |f|
|
||||
f.text_field 'Login', 'auth_key'
|
||||
f.password_field 'Password', 'password'
|
||||
f.html "<p align='center'><a href='#{registration_path}'>Create an Identity</a></p>"
|
||||
end.to_response
|
||||
end
|
||||
|
||||
def callback_phase
|
||||
return fail!(:invalid_credentials) unless identity
|
||||
super
|
||||
end
|
||||
|
||||
def other_phase
|
||||
if on_registration_path?
|
||||
if request.get?
|
||||
registration_form
|
||||
elsif request.post?
|
||||
registration_phase
|
||||
end
|
||||
else
|
||||
call_app!
|
||||
end
|
||||
end
|
||||
|
||||
def registration_form
|
||||
OmniAuth::Form.build(:title => 'Register Identity') do |f|
|
||||
options[:fields].each do |field|
|
||||
f.text_field field.to_s.capitalize, field.to_s
|
||||
end
|
||||
f.password_field 'Password', 'password'
|
||||
f.password_field 'Confirm Password', 'password_confirmation'
|
||||
end.to_response
|
||||
end
|
||||
|
||||
def registration_phase
|
||||
attributes = (options[:fields] + [:password, :password_confirmation]).inject({}){|h,k| h[k] = request[k.to_s]; h}
|
||||
@identity = model.create(attributes)
|
||||
if @identity.persisted?
|
||||
env['PATH_INFO'] = callback_path
|
||||
callback_phase
|
||||
else
|
||||
if options[:on_failed_registration]
|
||||
self.env['omniauth.identity'] = @identity
|
||||
options[:on_failed_registration].call(self.env)
|
||||
else
|
||||
registration_form
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def auth_hash
|
||||
{
|
||||
'provider' => name.to_s,
|
||||
'uid' => identity.uid,
|
||||
'user_info' => identity.user_info
|
||||
}
|
||||
end
|
||||
|
||||
def registration_path
|
||||
options[:registration_path] || "#{path_prefix}/#{name}/register"
|
||||
end
|
||||
|
||||
def on_registration_path?
|
||||
on_path?(registration_path)
|
||||
end
|
||||
|
||||
def identity
|
||||
@identity ||= model.authenticate(request['auth_key'], request['password'])
|
||||
end
|
||||
|
||||
def model
|
||||
options[:model] || ::Identity
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,27 +0,0 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
require File.expand_path('../../lib/omniauth/version', __FILE__)
|
||||
|
||||
Gem::Specification.new do |gem|
|
||||
gem.add_runtime_dependency 'oa-core', OmniAuth::Version::STRING
|
||||
gem.add_development_dependency 'maruku', '~> 0.6'
|
||||
gem.add_development_dependency 'simplecov', '~> 0.4'
|
||||
gem.add_development_dependency 'rack-test', '~> 0.5'
|
||||
gem.add_development_dependency 'rake', '~> 0.8'
|
||||
gem.add_development_dependency 'rspec', '~> 2.5'
|
||||
gem.add_development_dependency 'yard', '~> 0.6'
|
||||
gem.add_development_dependency 'ZenTest', '~> 4.5'
|
||||
gem.add_development_dependency 'bcrypt-ruby', '~> 2.1.4'
|
||||
gem.add_development_dependency 'activerecord', '~> 3.0'
|
||||
gem.name = 'oa-identity'
|
||||
gem.version = OmniAuth::Version::STRING
|
||||
gem.description = %q{Internal authentication handlers for OmniAuth.}
|
||||
gem.summary = gem.description
|
||||
gem.email = ['michael@intridea.com', 'sferik@gmail.com']
|
||||
gem.homepage = 'http://github.com/intridea/omniauth'
|
||||
gem.authors = ['Michael Bleigh', 'Erik Michaels-Ober']
|
||||
gem.executables = `git ls-files -- bin/*`.split("\n").map{|f| File.basename(f)}
|
||||
gem.files = `git ls-files`.split("\n")
|
||||
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
||||
gem.require_paths = ['lib']
|
||||
gem.required_rubygems_version = Gem::Requirement.new('>= 1.3.6') if gem.respond_to? :required_rubygems_version=
|
||||
end
|
|
@ -1,123 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
class ExampleModel
|
||||
include OmniAuth::Identity::Model
|
||||
end
|
||||
|
||||
describe OmniAuth::Identity::Model do
|
||||
context 'Class Methods' do
|
||||
subject{ ExampleModel }
|
||||
|
||||
describe '.locate' do
|
||||
it('should be abstract'){ lambda{ subject.locate('abc') }.should raise_error(NotImplementedError) }
|
||||
end
|
||||
|
||||
describe '.authenticate' do
|
||||
it 'should call locate and then authenticate' do
|
||||
mocked_instance = mock('ExampleModel', :authenticate => 'abbadoo')
|
||||
subject.should_receive(:locate).with('example').and_return(mocked_instance)
|
||||
subject.authenticate('example','pass').should == 'abbadoo'
|
||||
end
|
||||
|
||||
it 'should recover gracefully if locate is nil' do
|
||||
subject.stub!(:locate).and_return(nil)
|
||||
subject.authenticate('blah','foo').should be_false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'Instance Methods' do
|
||||
subject{ ExampleModel.new }
|
||||
|
||||
describe '#authenticate' do
|
||||
it('should be abstract'){ lambda{ subject.authenticate('abc') }.should raise_error(NotImplementedError) }
|
||||
end
|
||||
|
||||
describe '#uid' do
|
||||
it 'should default to #id' do
|
||||
subject.should_receive(:respond_to?).with('id').and_return(true)
|
||||
subject.stub!(:id).and_return 'wakka-do'
|
||||
subject.uid.should == 'wakka-do'
|
||||
end
|
||||
|
||||
it 'should stringify it' do
|
||||
subject.stub!(:id).and_return 123
|
||||
subject.uid.should == '123'
|
||||
end
|
||||
|
||||
it 'should raise NotImplementedError if #id is not defined' do
|
||||
subject.should_receive(:respond_to?).with('id').and_return(false)
|
||||
lambda{ subject.uid }.should raise_error(NotImplementedError)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#auth_key' do
|
||||
it 'should default to #email' do
|
||||
subject.should_receive(:respond_to?).with('email').and_return(true)
|
||||
subject.stub!(:email).and_return('bob@bob.com')
|
||||
subject.auth_key.should == 'bob@bob.com'
|
||||
end
|
||||
|
||||
it 'should use the class .auth_key' do
|
||||
subject.class.auth_key 'login'
|
||||
subject.stub!(:login).and_return 'bob'
|
||||
subject.auth_key.should == 'bob'
|
||||
subject.class.auth_key nil
|
||||
end
|
||||
|
||||
it 'should raise a NotImplementedError if the auth_key method is not defined' do
|
||||
lambda{ subject.auth_key }.should raise_error(NotImplementedError)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#auth_key=' do
|
||||
it 'should default to setting email' do
|
||||
subject.should_receive(:respond_to?).with('email=').and_return(true)
|
||||
subject.should_receive(:email=).with 'abc'
|
||||
|
||||
subject.auth_key = 'abc'
|
||||
end
|
||||
|
||||
it 'should use a custom .auth_key if one is provided' do
|
||||
subject.class.auth_key 'login'
|
||||
subject.should_receive(:respond_to?).with('login=').and_return(true)
|
||||
subject.should_receive('login=').with('abc')
|
||||
|
||||
subject.auth_key = 'abc'
|
||||
end
|
||||
|
||||
it 'should raise a NotImplementedError if the autH_key method is not defined' do
|
||||
lambda{ subject.auth_key = 'broken' }.should raise_error(NotImplementedError)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#user_info' do
|
||||
it 'should include attributes that are set' do
|
||||
subject.stub!(:name).and_return('Bob Bobson')
|
||||
subject.stub!(:nickname).and_return('bob')
|
||||
|
||||
subject.user_info.should == {
|
||||
'name' => 'Bob Bobson',
|
||||
'nickname' => 'bob'
|
||||
}
|
||||
end
|
||||
|
||||
it 'should automatically set name off of first and last name' do
|
||||
subject.stub!(:first_name).and_return('Bob')
|
||||
subject.stub!(:last_name).and_return('Bobson')
|
||||
subject.user_info['name'].should == 'Bob Bobson'
|
||||
end
|
||||
|
||||
it 'should automatically set name off of nickname' do
|
||||
subject.stub!(:nickname).and_return('bob')
|
||||
subject.user_info['name'] == 'bob'
|
||||
end
|
||||
|
||||
it 'should not overwrite a provided name' do
|
||||
subject.stub!(:name).and_return('Awesome Dude')
|
||||
subject.stub!(:first_name).and_return('Frank')
|
||||
subject.user_info['name'].should == 'Awesome Dude'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,16 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe(OmniAuth::Identity::Models::ActiveRecord, :db => true) do
|
||||
class TestIdentity < OmniAuth::Identity::Models::ActiveRecord
|
||||
auth_key :ham_sandwich
|
||||
end
|
||||
|
||||
it 'should locate using the auth key using a where query' do
|
||||
TestIdentity.should_receive(:where).with('ham_sandwich' => 'open faced').and_return(['wakka'])
|
||||
TestIdentity.locate('open faced').should == 'wakka'
|
||||
end
|
||||
|
||||
it 'should not use STI rules for its table name' do
|
||||
TestIdentity.table_name.should == 'test_identities'
|
||||
end
|
||||
end
|
|
@ -1,27 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
class HasTheMethod
|
||||
def self.has_secure_password; end
|
||||
end
|
||||
|
||||
class DoesNotHaveTheMethod
|
||||
end
|
||||
|
||||
describe OmniAuth::Identity::SecurePassword do
|
||||
it 'should extend with the class methods if it does not have the method' do
|
||||
DoesNotHaveTheMethod.should_receive(:extend).with(OmniAuth::Identity::SecurePassword::ClassMethods)
|
||||
DoesNotHaveTheMethod.send(:include, OmniAuth::Identity::SecurePassword)
|
||||
end
|
||||
|
||||
it 'should not extend if the method is already defined' do
|
||||
HasTheMethod.should_not_receive(:extend)
|
||||
HasTheMethod.send(:include, OmniAuth::Identity::SecurePassword)
|
||||
end
|
||||
|
||||
it 'should respond to has_secure_password afterwards' do
|
||||
[HasTheMethod,DoesNotHaveTheMethod].each do |klass|
|
||||
klass.send(:include, OmniAuth::Identity::SecurePassword)
|
||||
klass.should be_respond_to(:has_secure_password)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,130 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
class MockIdentity; end
|
||||
|
||||
describe OmniAuth::Strategies::Identity do
|
||||
attr_accessor :app
|
||||
|
||||
let(:auth_hash){ last_response.headers['env']['omniauth.auth'] }
|
||||
let(:identity_hash){ last_response.headers['env']['omniauth.identity'] }
|
||||
|
||||
# customize rack app for testing, if block is given, reverts to default
|
||||
# rack app after testing is done
|
||||
def set_app!(identity_options = {})
|
||||
identity_options.reverse_merge!({:model => MockIdentity})
|
||||
old_app = self.app
|
||||
self.app = Rack::Builder.app do
|
||||
use Rack::Session::Cookie
|
||||
use OmniAuth::Strategies::Identity, identity_options
|
||||
run lambda{|env| [404, {'env' => env}, ["HELLO!"]]}
|
||||
end
|
||||
if block_given?
|
||||
yield
|
||||
self.app = old_app
|
||||
end
|
||||
self.app
|
||||
end
|
||||
|
||||
before(:all) do
|
||||
set_app!
|
||||
end
|
||||
|
||||
describe '#request_phase' do
|
||||
it 'should display a form' do
|
||||
get '/auth/identity'
|
||||
last_response.body.should be_include("<form")
|
||||
end
|
||||
end
|
||||
|
||||
describe '#callback_phase' do
|
||||
let(:user){ mock(:uid => 'user1', :user_info => {'name' => 'Rockefeller'})}
|
||||
|
||||
context 'with valid credentials' do
|
||||
before do
|
||||
MockIdentity.should_receive('authenticate').with('john','awesome').and_return(user)
|
||||
post '/auth/identity/callback', :auth_key => 'john', :password => 'awesome'
|
||||
end
|
||||
|
||||
it 'should populate the auth hash' do
|
||||
auth_hash.should be_kind_of(Hash)
|
||||
end
|
||||
|
||||
it 'should populate the uid' do
|
||||
auth_hash['uid'].should == 'user1'
|
||||
end
|
||||
|
||||
it 'should populate the user_info hash' do
|
||||
auth_hash['user_info'].should == {'name' => 'Rockefeller'}
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid credentials' do
|
||||
before do
|
||||
OmniAuth.config.on_failure = lambda{|env| [401, {}, [env['omniauth.error.type'].inspect]]}
|
||||
MockIdentity.should_receive(:authenticate).with('wrong','login').and_return(false)
|
||||
post '/auth/identity/callback', :auth_key => 'wrong', :password => 'login'
|
||||
end
|
||||
|
||||
it 'should fail with :invalid_credentials' do
|
||||
last_response.body.should == ':invalid_credentials'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#registration_form' do
|
||||
it 'should trigger from /auth/identity/register by default' do
|
||||
get '/auth/identity/register'
|
||||
last_response.body.should be_include("Register Identity")
|
||||
end
|
||||
end
|
||||
|
||||
describe '#registration_phase' do
|
||||
context 'with successful creation' do
|
||||
let(:properties){ {
|
||||
:name => 'Awesome Dude',
|
||||
:email => 'awesome@example.com',
|
||||
:password => 'face',
|
||||
:password_confirmation => 'face'
|
||||
} }
|
||||
|
||||
before do
|
||||
m = mock(:uid => 'abc', :name => 'Awesome Dude', :email => 'awesome@example.com', :user_info => {:name => 'DUUUUDE!'}, :persisted? => true)
|
||||
MockIdentity.should_receive(:create).with(properties).and_return(m)
|
||||
end
|
||||
|
||||
it 'should set the auth hash' do
|
||||
post '/auth/identity/register', properties
|
||||
auth_hash['uid'].should == 'abc'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid identity' do
|
||||
let(:properties) { {
|
||||
:name => 'Awesome Dude',
|
||||
:email => 'awesome@example.com',
|
||||
:password => 'NOT',
|
||||
:password_confirmation => 'MATCHING'
|
||||
} }
|
||||
|
||||
before do
|
||||
MockIdentity.should_receive(:create).with(properties).and_return(mock(:persisted? => false))
|
||||
end
|
||||
|
||||
context 'default' do
|
||||
it 'should show registration form' do
|
||||
post '/auth/identity/register', properties
|
||||
last_response.body.should be_include("Register Identity")
|
||||
end
|
||||
end
|
||||
|
||||
context 'custom on_failed_registration endpoint' do
|
||||
it 'should set the identity hash' do
|
||||
set_app!(:on_failed_registration => lambda{|env| [404, {'env' => env}, ["HELLO!"]]}) do
|
||||
post '/auth/identity/register', properties
|
||||
identity_hash.should_not be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,14 +0,0 @@
|
|||
require 'rubygems'
|
||||
require 'bundler'
|
||||
Bundler.setup :default, :development, :test
|
||||
|
||||
require 'simplecov'
|
||||
SimpleCov.start
|
||||
|
||||
require 'rack/test'
|
||||
require 'omniauth/identity'
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.include Rack::Test::Methods
|
||||
end
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
--color
|
||||
--format=nested
|
||||
--backtrace
|
|
@ -1,4 +0,0 @@
|
|||
--markup markdown
|
||||
--markup-provider maruku
|
||||
-
|
||||
LICENSE
|
|
@ -1,11 +0,0 @@
|
|||
require File.expand_path('../lib/omniauth/version', __FILE__)
|
||||
|
||||
source 'http://rubygems.org'
|
||||
|
||||
gem 'oa-core', OmniAuth::Version::STRING, :path => '../oa-core'
|
||||
|
||||
platforms :jruby do
|
||||
gem 'jruby-openssl', '~> 0.7'
|
||||
end
|
||||
|
||||
gemspec
|
|
@ -1,19 +0,0 @@
|
|||
Copyright (c) 2010-2011 Michael Bleigh and Intridea, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -1,22 +0,0 @@
|
|||
= OmniAuth::More
|
||||
|
||||
OmniAuth stratgies for authentication providers that do not
|
||||
fit into one of the other authentication gems.
|
||||
|
||||
== Installation
|
||||
|
||||
To install omniauth as a suite of gems:
|
||||
|
||||
gem install omniauth
|
||||
|
||||
To install just the providers in the "more" gem:
|
||||
|
||||
gem install oa-more
|
||||
|
||||
== OmniAuth Builder
|
||||
|
||||
If you want to allow multiple providers, use the OmniAuth Builder:
|
||||
|
||||
use OmniAuth::Builder do
|
||||
provider :flickr, 'api_key', 'secret_key', :scope => 'read'
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
require 'bundler'
|
||||
Bundler::GemHelper.install_tasks
|
||||
require 'rspec/core/rake_task'
|
||||
RSpec::Core::RakeTask.new(:spec)
|
||||
task :default => :spec
|
||||
task :test => :spec
|
|
@ -1 +0,0 @@
|
|||
require 'omniauth/more'
|
|
@ -1,12 +0,0 @@
|
|||
require 'omniauth/core'
|
||||
|
||||
module OmniAuth
|
||||
module Strategies
|
||||
autoload :Draugiem, 'omniauth/strategies/draugiem'
|
||||
autoload :Flickr, 'omniauth/strategies/flickr'
|
||||
autoload :HttpBasic, 'omniauth/strategies/http_basic'
|
||||
autoload :Ign, 'omniauth/strategies/ign'
|
||||
autoload :LastFm, 'omniauth/strategies/last_fm'
|
||||
autoload :WindowsLive, 'omniauth/strategies/windows_live'
|
||||
end
|
||||
end
|
|
@ -1,104 +0,0 @@
|
|||
require 'omniauth/core'
|
||||
require 'digest/md5'
|
||||
require 'rest-client'
|
||||
require 'multi_json'
|
||||
|
||||
module OmniAuth
|
||||
module Strategies
|
||||
#
|
||||
# Authenticate to draugiem.lv and frype.com and others.
|
||||
#
|
||||
# @example Basic Rails Usage
|
||||
#
|
||||
# Add this to config/initializers/omniauth.rb
|
||||
#
|
||||
# Rails.application.config.middleware.use OmniAuth::Builder do
|
||||
# provider :draugiem, 'App id', 'API Key'
|
||||
# end
|
||||
#
|
||||
# @example Basic Rack example
|
||||
#
|
||||
# use Rack::Session::Cookie
|
||||
# use OmniAuth::Strategies::Draugiem, 'App id', 'API Key'
|
||||
#
|
||||
class Draugiem
|
||||
include OmniAuth::Strategy
|
||||
attr_accessor :app_id, :api_key
|
||||
|
||||
def initialize(app, app_id, api_key)
|
||||
super(app, :draugiem)
|
||||
@app_id = app_id
|
||||
@api_key = api_key
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def request_phase
|
||||
params = {
|
||||
:app => @app_id,
|
||||
:redirect => callback_url,
|
||||
:hash => Digest::MD5.hexdigest("#{@api_key}#{callback_url}")
|
||||
}
|
||||
query_string = params.collect{ |key,value| "#{key}=#{Rack::Utils.escape(value)}" }.join('&')
|
||||
redirect "http://api.draugiem.lv/authorize/?#{query_string}"
|
||||
end
|
||||
|
||||
def callback_phase
|
||||
if request.params['dr_auth_status'] == 'ok' && request.params['dr_auth_code']
|
||||
response = RestClient.get('http://api.draugiem.lv/json/', { :params => draugiem_authorize_params(request.params['dr_auth_code']) })
|
||||
auth = MultiJson.decode(response.to_s)
|
||||
unless auth['error']
|
||||
@auth_data = auth
|
||||
super
|
||||
else
|
||||
fail!(auth['error']['code'].to_s,auth["error"]["description"].to_s)
|
||||
end
|
||||
else
|
||||
fail!(:invalid_request)
|
||||
end
|
||||
rescue Exception => e
|
||||
fail!(:invalid_response, e)
|
||||
end
|
||||
|
||||
def auth_hash
|
||||
OmniAuth::Utils.deep_merge(super, {
|
||||
'uid' => @auth_data['uid'],
|
||||
'user_info' => get_user_info,
|
||||
'credentials' => {
|
||||
'apikey' => @auth_data['apikey']
|
||||
},
|
||||
'extra' => { 'user_hash' => @auth_data }
|
||||
})
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_user_info
|
||||
if @auth_data['users'] && @auth_data['users'][@auth_data['uid']]
|
||||
user = @auth_data['users'][@auth_data['uid']]
|
||||
{
|
||||
'name' => "#{user['name']} #{user['surname']}",
|
||||
'nickname' => user['nick'],
|
||||
'first_name' => user['name'],
|
||||
'last_name' => user['surname'],
|
||||
'location' => user['place'],
|
||||
'age' => user['age'] =~ /^0-9$/ ? user['age'] : nil,
|
||||
'adult' => user['adult'] == '1' ? true : false,
|
||||
'image' => user['img'],
|
||||
'sex' => user['sex']
|
||||
}
|
||||
else
|
||||
{}
|
||||
end
|
||||
end
|
||||
|
||||
def draugiem_authorize_params code
|
||||
{
|
||||
:action => 'authorize',
|
||||
:app => @api_key,
|
||||
:code => code
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,56 +0,0 @@
|
|||
require 'rest-client'
|
||||
require 'omniauth/more'
|
||||
|
||||
module OmniAuth
|
||||
module Strategies
|
||||
class HttpBasic
|
||||
include OmniAuth::Strategy
|
||||
|
||||
def initialize(app, name, endpoint = nil, headers = {}, &block)
|
||||
super
|
||||
@endpoint = endpoint
|
||||
@request_headers = headers
|
||||
end
|
||||
|
||||
attr_reader :endpoint, :request_headers
|
||||
|
||||
def request_phase
|
||||
if env['REQUEST_METHOD'] == 'GET'
|
||||
get_credentials
|
||||
else
|
||||
perform
|
||||
end
|
||||
end
|
||||
|
||||
def title
|
||||
name.split('_').map{|s| s.capitalize}.join(' ')
|
||||
end
|
||||
|
||||
def get_credentials
|
||||
OmniAuth::Form.build(:title => title) do
|
||||
text_field 'Username', 'username'
|
||||
password_field 'Password', 'password'
|
||||
end.to_response
|
||||
end
|
||||
|
||||
def perform
|
||||
@response = perform_authentication(endpoint)
|
||||
@env['omniauth.auth'] = auth_hash
|
||||
@env['REQUEST_METHOD'] = 'GET'
|
||||
@env['PATH_INFO'] = "#{OmniAuth.config.path_prefix}/#{name}/callback"
|
||||
|
||||
call_app!
|
||||
rescue RestClient::Request::Unauthorized => e
|
||||
fail!(:invalid_credentials, e)
|
||||
end
|
||||
|
||||
def perform_authentication(uri, headers = request_headers)
|
||||
RestClient.get(uri, headers)
|
||||
end
|
||||
|
||||
def callback_phase
|
||||
fail!(:invalid_credentials)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,93 +0,0 @@
|
|||
require 'omniauth/core'
|
||||
require 'openssl'
|
||||
|
||||
module OmniAuth
|
||||
module Strategies
|
||||
class Ign
|
||||
include OmniAuth::Strategy
|
||||
IDENTIFIER_URL_PARAMETER = ""
|
||||
|
||||
class CallbackError < StandardError
|
||||
attr_accessor :error, :error_reason
|
||||
def initialize(error, error_reason)
|
||||
self.error = error
|
||||
self.error_reason = error_reason
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(app, api_key, hostname=nil, options = {})
|
||||
options[:name] ||= "ign"
|
||||
super(app, :ign)
|
||||
@api_key = api_key
|
||||
@hostname = hostname
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def request_phase
|
||||
OmniAuth::Form.build(:title => 'IGN Authentication', :header_info=>js) do
|
||||
label_field('Identifying you with the IGN server', IDENTIFIER_URL_PARAMETER)
|
||||
end.to_response
|
||||
end
|
||||
|
||||
def callback_phase
|
||||
signature = OpenSSL::HMAC.hexdigest('sha1', @api_key, ("#{request.params["username"]}::#{request.params["timestamp"]}"))
|
||||
|
||||
raise CallbackError.new("Invalid Signature","The supplied and calculated signature did not match, user not approved.") if signature != request.params["signature"]
|
||||
|
||||
super
|
||||
rescue CallbackError => e
|
||||
fail!(:invalid_response, e)
|
||||
end
|
||||
|
||||
def auth_hash
|
||||
OmniAuth::Utils.deep_merge(super, {
|
||||
'uid' => "ign-" + request.params["username"],
|
||||
'credentials' => { 'token' => request.params["signature"] },
|
||||
'user_info' => user_info,
|
||||
'extra' => { 'user_hash' => request.params }
|
||||
})
|
||||
end
|
||||
|
||||
def user_info
|
||||
{
|
||||
'nickname' => request.params["username"],
|
||||
}
|
||||
end
|
||||
|
||||
def js
|
||||
@js = <<-JS
|
||||
$(document).ready(function() {
|
||||
$.ajax({
|
||||
url: "http://#{@hostname}/users/current.json?callback=z33k",
|
||||
type: "get",
|
||||
dataType:"jsonp",
|
||||
success: function(data) {
|
||||
if(typeof data.error == 'undefined'){
|
||||
// There is a current My IGN user
|
||||
var username = data.my_ign_username;
|
||||
var signature = data.signature;
|
||||
var timestamp = data.timestamp;
|
||||
window.location = "/auth/ign/callback?username=" +username+"&signature="+signature+"×tamp=" + timestamp;
|
||||
}
|
||||
else{
|
||||
nouser();
|
||||
}
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
function nouser() {
|
||||
var url = "http://my.ign.com/login?r="+window.location;
|
||||
top.location = url;
|
||||
window.location = url;
|
||||
}
|
||||
JS
|
||||
"\n<script src='https://ajax.googleapis.com/ajax/libs/jquery/1.5.2/jquery.min.js' type='text/javascript'></script>" +
|
||||
"\n<script type='text/javascript'>#{@js}</script>" +
|
||||
"\n<style type='text/css'>button {visibility:hidden;}</style>"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,93 +0,0 @@
|
|||
require 'omniauth/core'
|
||||
require 'digest/md5'
|
||||
require 'rest-client'
|
||||
require 'multi_json'
|
||||
|
||||
module OmniAuth
|
||||
module Strategies
|
||||
#
|
||||
# Authenticate to LastFM
|
||||
#
|
||||
# @example Basic Usage
|
||||
#
|
||||
# use OmniAuth::Strategies::LastFm, 'API Key', 'Secret Key'
|
||||
class LastFm
|
||||
include OmniAuth::Strategy
|
||||
attr_accessor :api_key, :secret_key, :options
|
||||
|
||||
# error catching, based on OAuth2 callback
|
||||
class CallbackError < StandardError
|
||||
attr_accessor :error, :error_reason
|
||||
def initialize(error, error_reason)
|
||||
self.error = error
|
||||
self.error_reason = error_reason
|
||||
end
|
||||
end
|
||||
|
||||
# @param [Rack Application] app standard middleware application parameter
|
||||
# @param [String] api_key the application id as [registered on LastFM](http://www.last.fm/api/account)
|
||||
# @param [String] secret_key the application secret as [registered on LastFM](http://www.last.fm/api/account)
|
||||
# @option options, You can optionally specify a callback URL that is different to your API Account callback url. Include this as a query param cb
|
||||
def initialize(app, api_key, secret_key, options = {})
|
||||
super(app, :last_fm)
|
||||
@api_key = api_key
|
||||
@secret_key = secret_key
|
||||
@options = options
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def request_phase
|
||||
params = { :api_key => api_key, :cb => options[:cb] }
|
||||
query_string = params.collect{ |key,value| "#{key}=#{Rack::Utils.escape(value)}" }.join('&')
|
||||
redirect "http://www.last.fm/api/auth/?#{query_string}"
|
||||
end
|
||||
|
||||
def callback_phase
|
||||
token = request.params['token']
|
||||
params = { :api_key => api_key, :method => 'auth.getSession', :format => 'json' }
|
||||
params[:token] = token
|
||||
params[:api_sig] = signature(token)
|
||||
|
||||
response = RestClient.get('http://ws.audioscrobbler.com/2.0/', { :params => params })
|
||||
@auth = MultiJson.decode(response.to_s)
|
||||
raise CallbackError.new(@auth['error'],@auth['message']) if @auth['error']
|
||||
|
||||
user_params = { :method => "user.getInfo", :user => @auth['session']['name'], :api_key => api_key, :format => "json" }
|
||||
user_response = RestClient.get('http://ws.audioscrobbler.com/2.0/', { :params => user_params })
|
||||
@user_auth = MultiJson.decode(user_response.to_s)
|
||||
raise CallbackError.new(@user_auth['error'],@user_auth['message']) if @user_auth['error']
|
||||
|
||||
super
|
||||
rescue CallbackError => e
|
||||
fail!(:invalid_response, e)
|
||||
end
|
||||
|
||||
def auth_hash
|
||||
OmniAuth::Utils.deep_merge(super, {
|
||||
'uid' => @user_auth['user']['id'],
|
||||
'credentials' => { 'token' => @auth['session']['key'] },
|
||||
'user_info' => user_info,
|
||||
'extra' => { 'user_hash' => @user_auth }
|
||||
})
|
||||
end
|
||||
|
||||
def user_info
|
||||
{
|
||||
'name' => @user_auth['user']['realname'],
|
||||
'nickname' => @user_auth['user']['name'],
|
||||
'location' => @user_auth['user']['country'],
|
||||
'image' => @user_auth['user']['image'],
|
||||
'urls' => {
|
||||
'Profile' => @user_auth['user']['url']
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def signature(token)
|
||||
sign = "api_key#{api_key}methodauth.getSessiontoken#{token}#{secret_key}"
|
||||
Digest::MD5.hexdigest(sign)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,39 +0,0 @@
|
|||
require 'omniauth/core'
|
||||
require 'omniauth/strategies/windows_live/windowslivelogin'
|
||||
|
||||
module OmniAuth
|
||||
module Strategies
|
||||
class WindowsLive
|
||||
include OmniAuth::Strategy
|
||||
|
||||
attr_accessor :app_id, :app_secret
|
||||
|
||||
# Initialize the strategy by providing
|
||||
#
|
||||
# @param app_id [String] The application ID from your registered app with Microsoft.
|
||||
# @param app_secret [String] The secret from your registered app with Microsoft.
|
||||
# @option options [String] :locale A localization string for the login, should be in the form `en-us` or similar.
|
||||
# @option options [String] :state Some state information that is serialized into the query string upon callback.
|
||||
# @option options [Boolean] :ssl Whether or not to use SSL for login. Defaults to `true`.
|
||||
# @option options [Boolean] :force_nonprovisioned When true, forces a non-provisioned (i.e. no app id or secret) mode.
|
||||
def initialize(app, app_id = nil, app_secret = nil, options = {})
|
||||
self.app_id = app_id
|
||||
self.app_secret = app_secret
|
||||
super(app, :windows_live, app_id, app_secret, options)
|
||||
options[:ssl] ||= true
|
||||
options[:locale] ||= 'en-us'
|
||||
options[:force_nonprovisioned] = true unless app_id
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def consumer
|
||||
WindowsLiveLogin.new app_id, app_secret, options[:security_algorithm], options[:force_nonprovisioned], options[:policy_url], callback_url
|
||||
end
|
||||
|
||||
def request_phase
|
||||
redirect consumer.getLoginUrl(options[:state], options[:locale])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
File diff suppressed because it is too large
Load diff
|
@ -1,67 +0,0 @@
|
|||
require 'omniauth/core'
|
||||
require 'digest/md5'
|
||||
require 'rest-client'
|
||||
require 'multi_json'
|
||||
|
||||
module OmniAuth
|
||||
module Strategies
|
||||
class Yupoo
|
||||
include OmniAuth::Strategy
|
||||
attr_accessor :api_key, :secret_key, :options
|
||||
|
||||
|
||||
class CallbackError < StandardError
|
||||
attr_accessor :error, :error_reason
|
||||
def initialize(error, error_reason)
|
||||
self.error = error
|
||||
self.error_reason = error_reason
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(app, api_key, secret_key, options = {})
|
||||
super(app, :yupoo)
|
||||
@api_key = api_key
|
||||
@secret_key = secret_key
|
||||
@options = {:scope => 'read'}.merge(options)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def request_phase
|
||||
params = { :api_key => api_key, :perms => options[:scope] }
|
||||
params[:api_sig] = yupoo_sign(params)
|
||||
query_string = params.collect{ |key,value| "#{key}=#{Rack::Utils.escape(value)}" }.join('&')
|
||||
redirect "http://www.yupoo.com/services/auth/?#{query_string}"
|
||||
end
|
||||
|
||||
def callback_phase
|
||||
params = { :api_key => api_key, :method => 'yupoo.auth.getToken', :frob => request.params['frob'], :format => 'json', :nojsoncallback => '1' }
|
||||
params[:api_sig] = yupoo_sign(params)
|
||||
|
||||
response = RestClient.get('http://www.yupoo.com/api/rest/', { :params => params })
|
||||
auth = MultiJson.decode(response.to_s)
|
||||
raise CallbackError.new(auth['code'],auth['message']) if auth['stat'] == 'fail'
|
||||
|
||||
@user = auth['auth']['user']
|
||||
@access_token = auth['auth']['token']['_content']
|
||||
|
||||
super
|
||||
rescue CallbackError => e
|
||||
fail!(:invalid_response, e)
|
||||
end
|
||||
|
||||
def auth_hash
|
||||
OmniAuth::Utils.deep_merge(super, {
|
||||
'uid' => @user['nsid'],
|
||||
'credentials' => { 'token' => @access_token },
|
||||
'user_info' => @user,
|
||||
'extra' => { 'user_hash' => @user }
|
||||
})
|
||||
end
|
||||
|
||||
def yupoo_sign(params)
|
||||
Digest::MD5.hexdigest(secret_key + params.sort{|a,b| a[0].to_s <=> b[0].to_s }.flatten.join)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,19 +0,0 @@
|
|||
module OmniAuth
|
||||
module Version
|
||||
unless defined?(::OmniAuth::Version::MAJOR)
|
||||
MAJOR = 0
|
||||
end
|
||||
unless defined?(::OmniAuth::Version::MINOR)
|
||||
MINOR = 3
|
||||
end
|
||||
unless defined?(::OmniAuth::Version::PATCH)
|
||||
PATCH = 0
|
||||
end
|
||||
unless defined?(::OmniAuth::Version::PRE)
|
||||
PRE = "rc3"
|
||||
end
|
||||
unless defined?(::OmniAuth::Version::STRING)
|
||||
STRING = [MAJOR, MINOR, PATCH, PRE].compact.join('.')
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,27 +0,0 @@
|
|||
# encoding: utf-8
|
||||
require File.expand_path('../lib/omniauth/version', __FILE__)
|
||||
|
||||
Gem::Specification.new do |gem|
|
||||
gem.add_dependency 'multi_json', '~> 1.0.0'
|
||||
gem.add_dependency 'oa-core', OmniAuth::Version::STRING
|
||||
gem.add_dependency 'rest-client', '~> 1.6.0'
|
||||
gem.add_development_dependency 'json_pure', '~> 1.5'
|
||||
gem.add_development_dependency 'rack-test', '~> 0.5'
|
||||
gem.add_development_dependency 'rake', '~> 0.8'
|
||||
gem.add_development_dependency 'rdiscount', '~> 1.6'
|
||||
gem.add_development_dependency 'rspec', '~> 2.5'
|
||||
gem.add_development_dependency 'simplecov', '~> 0.4'
|
||||
gem.add_development_dependency 'webmock', '~> 1.7'
|
||||
gem.add_development_dependency 'yard', '~> 0.7'
|
||||
gem.authors = ['Michael Bleigh', 'Erik Michaels-Ober']
|
||||
gem.description = %q{Additional strategies for OmniAuth.}
|
||||
gem.email = 'michael@intridea.com'
|
||||
gem.files = `git ls-files`.split("\n")
|
||||
gem.homepage = 'http://github.com/intridea/omniauth'
|
||||
gem.name = 'oa-more'
|
||||
gem.require_paths = ['lib']
|
||||
gem.required_rubygems_version = Gem::Requirement.new('>= 1.3.6') if gem.respond_to? :required_rubygems_version=
|
||||
gem.summary = gem.description
|
||||
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
||||
gem.version = OmniAuth::Version::STRING
|
||||
end
|
|
@ -1,51 +0,0 @@
|
|||
require File.expand_path('../../../spec_helper', __FILE__)
|
||||
|
||||
describe 'OmniAuth::Strategies::Draugiem', :type => :strategy do
|
||||
|
||||
include OmniAuth::Test::StrategyTestCase
|
||||
|
||||
def strategy
|
||||
[OmniAuth::Strategies::Draugiem, '123', "abc"]
|
||||
end
|
||||
|
||||
it 'should initialize with api key and app id' do
|
||||
lambda{OmniAuth::Strategies::Draugiem.new({},'123','abc')}.should_not raise_error
|
||||
end
|
||||
|
||||
describe '/auth/draugiem' do
|
||||
|
||||
it 'should redirect to api.draugiem.lv' do
|
||||
get '/auth/draugiem'
|
||||
last_response.should be_redirect
|
||||
last_response.headers['Location'].should match %r{http://api\.draugiem\.lv/authorize/}
|
||||
end
|
||||
|
||||
it 'should gather user data after success authorization' do
|
||||
stub_request(:get, "http://api.draugiem.lv/json/?action=authorize&app=abc&code=123456").
|
||||
to_return(:body => MultiJson.encode({
|
||||
'apikey'=>"123456789",
|
||||
'uid'=>"100",
|
||||
'language'=>"lv",
|
||||
'users'=>{
|
||||
'100'=>{
|
||||
'uid'=>"100",
|
||||
'name'=>"John",
|
||||
'surname'=>"Lenon",
|
||||
'nick'=>"johnybravo",
|
||||
'place'=>"Durbe",
|
||||
'age'=>"false",
|
||||
'adult'=>"1",
|
||||
'img'=>"http://4.bp.blogspot.com/_ZmXOoYjxXog/Sg2jby1RFSI/AAAAAAAAE_Q/1LpfjimAz50/s400/JohnnyBravo3.gif",
|
||||
'sex'=>"M"
|
||||
}
|
||||
}
|
||||
}))
|
||||
get '/auth/draugiem/callback?dr_auth_status=ok&dr_auth_code=123456'
|
||||
|
||||
last_request.env['omniauth.auth']['credentials']['apikey'].should == "123456789"
|
||||
last_request.env['omniauth.auth']['user_info']['location'].should == "Durbe"
|
||||
last_request.env['omniauth.auth']['user_info']['age'].should be_nil
|
||||
last_request.env['omniauth.auth']['user_info']['adult'].should be_true
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,7 +0,0 @@
|
|||
require File.expand_path('../../../spec_helper', __FILE__)
|
||||
|
||||
describe OmniAuth::Strategies::HttpBasic do
|
||||
it 'should exist' do
|
||||
# do nothing
|
||||
end
|
||||
end
|
|
@ -1,7 +0,0 @@
|
|||
require File.expand_path('../../../spec_helper', __FILE__)
|
||||
|
||||
describe 'OmniAuth::Strategies::LastFm' do
|
||||
it 'should initialize with a consumer key and secret' do
|
||||
lambda{OmniAuth::Strategies::LastFm.new({},'abc','def')}.should_not raise_error
|
||||
end
|
||||
end
|
|
@ -1,11 +0,0 @@
|
|||
require 'simplecov'
|
||||
SimpleCov.start
|
||||
require 'rspec'
|
||||
require 'rack/test'
|
||||
require 'webmock/rspec'
|
||||
require 'omniauth/more'
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.include Rack::Test::Methods
|
||||
config.include WebMock::API
|
||||
end
|
|
@ -1,3 +0,0 @@
|
|||
--color
|
||||
--format=nested
|
||||
--backtrace
|
|
@ -1,4 +0,0 @@
|
|||
--markup markdown
|
||||
--markup-provider maruku
|
||||
-
|
||||
LICENSE
|
|
@ -1,11 +0,0 @@
|
|||
require File.expand_path('../lib/omniauth/version', __FILE__)
|
||||
|
||||
source 'http://rubygems.org'
|
||||
|
||||
gem 'oa-core', OmniAuth::Version::STRING, :path => '../oa-core'
|
||||
|
||||
platforms :jruby do
|
||||
gem 'jruby-openssl', '~> 0.7'
|
||||
end
|
||||
|
||||
gemspec
|
|
@ -1,19 +0,0 @@
|
|||
Copyright (c) 2010-2011 Michael Bleigh and Intridea, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue