Merging master.

This commit is contained in:
Michael Bleigh 2011-09-03 15:08:07 -03:00
commit 3dbfdff127
68 changed files with 2208 additions and 245 deletions

View File

@ -1,9 +0,0 @@
require 'autotest/bundler'
Autotest.add_hook :initialize do |at|
at.clear_mappings
at.add_mapping(%r{^.*/spec/.*_spec.rb$}) do |filename, _|
filename
end
end

View File

@ -1,4 +1,7 @@
rvm:
- 1.8.7
- 1.9.2
- jruby
- rbx
- rbx-2.0
- ree

27
Guardfile Normal file
View File

@ -0,0 +1,27 @@
# 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 Normal file
View File

@ -0,0 +1,19 @@
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.

View File

@ -1,5 +1,4 @@
OmniAuth: Standardized Multi-Provider Authentication
====================================================
# OmniAuth: Standardized Multi-Provider Authentication
OmniAuth is a new Rack-based authentication system for multi-provider external
authentcation. OmniAuth is built from the ground up on the philosophy that
**authentication is not the same as identity**, and is based on two
@ -13,22 +12,20 @@ observations:
to one, three, or twelve other services. Modern authentication systems
should allow a user's identity to be associated with many authentications.
Installation
------------
## <a name="installation">Installation</a>
To install OmniAuth, simply install the gem:
gem install omniauth
Continuous Integration
----------------------
[![Build Status](https://secure.travis-ci.org/intridea/omniauth.png)](http://travis-ci.org/intridea/omniauth)
## <a name="ci">Continuous Integration</a>
[![Build Status](https://travis-ci.org/intridea/omniauth.png)](http://travis-ci.org/intridea/omniauth)
Providers
---------
## <a name="providers">Providers</a>
OmniAuth currently supports the following external providers:
* via OAuth (OAuth 1.0, OAuth 2, and xAuth)
* 37signals ID (credit: [mbleigh](https://github.com/mbleigh))
* AngelList (credit: [joshuaxls](https://github.com/joshuaxls))
* Bit.ly (credit: [philnash](https://github.com/philnash))
* Blogger (credit: [dsueiro-backing](https://github.com/dsueiro-backing))
* Cobot (credit: [kamal](https://github.com/kamal))
@ -51,6 +48,7 @@ OmniAuth currently supports the following external providers:
* Instapaper (credit: [micpringle](https://github.com/micpringle))
* LastFM (credit: [tictoc](https://github.com/tictoc))
* LinkedIn (credit: [mbleigh](https://github.com/mbleigh))
* Mailchimp (via [srbiv](http://github.com/srbiv))
* Mailru (credit: [lexer](https://github.com/lexer))
* Meetup (credit [coderoshi](https://github.com/coderoshi))
* Miso (credit: [rickenharp](https://github.com/rickenharp))
@ -74,6 +72,7 @@ OmniAuth currently supports the following external providers:
* Tsohu (credit: [quake](https://github.com/quake))
* Tumblr (credit: [jamiew](https://github.com/jamiew))
* Twitter (credit: [mbleigh](https://github.com/mbleigh))
* Viadeo (credit: [guillaug](https://github.com/guillaug))
* Vimeo (credit: [jamiew](https://github.com/jamiew))
* Vkontakte (credit: [german](https://github.com/german))
* WePay (credit: [ryanwood](https://github.com/ryanwood))
@ -88,19 +87,7 @@ OmniAuth currently supports the following external providers:
* OpenID (credit: [mbleigh](https://github.com/mbleigh))
* Yupoo (credit: [chouti](https://github.com/chouti))
Compatibility
-------------
OmniAuth is tested against the following Ruby versions:
* 1.8.7
* 1.9.1
* 1.9.2
* JRuby (note, the Evernote strategy is not available for JRuby)
* Rubinius
* REE
Usage
-----
## <a name="usage">Usage</a>
OmniAuth is a collection of Rack middleware. To use a single strategy, you simply need to add the middleware:
require 'oa-oauth'
@ -126,8 +113,7 @@ The hash in question will look something like this:
The `user_info` hash will automatically be populated with as much information about the user as OmniAuth was able to pull from the given API or authentication provider.
Resources
---------
## <a name="resources">Resources</a>
The best place to find more information is the [OmniAuth Wiki](https://github.com/intridea/omniauth/wiki). Some specific information you might be interested in:
* [CI Build Status](http://travis-ci.org/intridea/omniauth)
@ -136,20 +122,20 @@ The best place to find more information is the [OmniAuth Wiki](https://github.co
* [Report Issues](https://github.com/intridea/omniauth/issues)
* [Mailing List](http://groups.google.com/group/omniauth)
OmniAuth Core
-------------
## <a name="core">Core Team</a>
* **Michael Bleigh** ([mbleigh](https://github.com/mbleigh))
* **Erik Michaels-Ober** ([sferik](https://github.com/sferik))
Supported Rubies
----------------
## <a name="rubies">Supported Rubies</a>
This library aims to support and is [tested
against](http://travis-ci.org/intridea/omniauth) the following Ruby
implementations:
* Ruby 1.8.7
* Ruby 1.9.2
* Ruby Enterprise Edition 1.8.7
* [JRuby](http://www.jruby.org/)
* [Rubinius](http://rubini.us/)
* [Ruby Enterprise Edition](http://www.rubyenterpriseedition.com/)
If something doesn't work on one of these interpreters, it should be considered
a bug.
@ -165,6 +151,5 @@ implementation, you will be personally responsible for providing patches in a
timely fashion. If critical issues for a particular implementation exist at the
time of a major release, support for that Ruby version may be dropped.
License
-------
OmniAuth is licensed under the MIT License.
## <a name="license">License</a>
OmniAuth is released under the MIT License.

0
Rakefile Normal file → Executable file
View File

1
lib/oa-core.rb Normal file
View File

@ -0,0 +1 @@
require 'omniauth/core'

33
lib/omniauth/builder.rb Normal file
View File

@ -0,0 +1,33 @@
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

140
lib/omniauth/core.rb Normal file
View File

@ -0,0 +1,140 @@
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'
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

186
lib/omniauth/form.rb Normal file
View File

@ -0,0 +1,186 @@
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)
form.instance_eval(&block)
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

230
lib/omniauth/strategy.rb Normal file
View File

@ -0,0 +1,230 @@
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 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
# 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?
current_path.casecmp(request_path) == 0
end
def on_callback_path?
current_path.casecmp(callback_path) == 0
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
{
'provider' => name.to_s,
'uid' => nil
}
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

12
lib/omniauth/test.rb Normal file
View File

@ -0,0 +1,12 @@
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

View File

@ -0,0 +1,8 @@
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

View File

@ -0,0 +1,34 @@
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

View File

@ -0,0 +1,49 @@
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

View File

@ -56,10 +56,14 @@ module OmniAuth
def add_mock(provider, mock={})
# Stringify keys recursively one level.
mock.stringify_keys!
mock.keys.each do|key|
if mock[key].is_a? Hash
mock[key].stringify_keys!
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
@ -99,7 +103,8 @@ module OmniAuth
'smugmug' => 'SmugMug',
'cas' => 'CAS',
'trademe' => 'TradeMe',
'ldap' => 'LDAP'
'ldap' => 'LDAP',
'google_oauth2' => 'GoogleOAuth2'
}
module_function

View File

@ -110,7 +110,7 @@ module OmniAuth
elsif env['HTTP_REFERER'] && !env['HTTP_REFERER'].match(/#{request_path}$/)
@env['rack.session']['omniauth.origin'] = env['HTTP_REFERER']
end
redirect(script_name + callback_path)
redirect(script_name + callback_path + query_string)
end
def mock_callback_call

View File

@ -1,4 +1,4 @@
# -*- encoding: utf-8 -*-
# encoding: utf-8
require File.expand_path('../lib/omniauth/version', __FILE__)
Gem::Specification.new do |gem|
@ -9,19 +9,18 @@ Gem::Specification.new do |gem|
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 'rdiscount', '~> 1.6'
gem.add_development_dependency 'rspec', '~> 2.5'
gem.add_development_dependency 'yard', '~> 0.7'
gem.add_development_dependency 'ZenTest', '~> 4.5'
gem.name = 'oa-core'
gem.version = OmniAuth::Version::STRING
gem.description = %q{Core strategies 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.description = %q{Core strategies for OmniAuth.}
gem.email = ['michael@intridea.com', 'sferik@gmail.com']
gem.files = `git ls-files`.split("\n")
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
gem.homepage = 'http://github.com/intridea/omniauth'
gem.name = 'oa-core'
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

View File

@ -296,6 +296,11 @@ describe OmniAuth::Strategy 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

View File

@ -66,7 +66,40 @@ 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

View File

@ -4,5 +4,6 @@ module OmniAuth
module Strategies
autoload :CAS, 'omniauth/strategies/cas'
autoload :LDAP, 'omniauth/strategies/ldap'
autoload :SAML, 'omniauth/strategies/saml'
end
end

View File

@ -125,12 +125,12 @@ module OmniAuth
end
def search(options={}, &block)
base = options[:base]
base = options[:base] || @base
filter = options[:filter]
limit = options[:limit]
args = {
:base => @base,
:base => base,
:filter => filter,
:size => limit
}

View File

@ -0,0 +1,50 @@
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

View File

@ -0,0 +1,38 @@
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

View File

@ -0,0 +1,141 @@
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

View File

@ -0,0 +1,8 @@
module OmniAuth
module Strategies
class SAML
class ValidationError < Exception
end
end
end
end

View File

@ -0,0 +1,126 @@
# 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

View File

@ -1,31 +1,31 @@
# -*- encoding: utf-8 -*-
# encoding: utf-8
require File.expand_path('../lib/omniauth/version', __FILE__)
Gem::Specification.new do |gem|
gem.add_runtime_dependency 'addressable', '~> 2.2.6'
gem.add_runtime_dependency 'nokogiri', '~> 1.5.0'
gem.add_runtime_dependency 'net-ldap', '~> 0.2.2'
gem.add_runtime_dependency 'oa-core', OmniAuth::Version::STRING
gem.add_runtime_dependency 'pyu-ruby-sasl', '~> 0.0.3.1'
gem.add_runtime_dependency 'rubyntlm', '~> 0.1.1'
gem.add_development_dependency 'maruku', '~> 0.6'
gem.add_development_dependency 'simplecov', '~> 0.4'
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 'webmock', '~> 1.6'
gem.add_development_dependency 'simplecov', '~> 0.4'
gem.add_development_dependency 'webmock', '~> 1.7'
gem.add_development_dependency 'yard', '~> 0.7'
gem.add_development_dependency 'ZenTest', '~> 4.5'
gem.name = 'oa-enterprise'
gem.version = OmniAuth::Version::STRING
gem.authors = ['James A. Rosen', 'Ping Yu', 'Michael Bleigh', 'Erik Michaels-Ober', 'Raecoo Cao']
gem.description = %q{Enterprise strategies for OmniAuth.}
gem.summary = gem.description
gem.email = ['james.a.rosen@gmail.com', 'ping@intridea.com', 'michael@intridea.com', 'sferik@gmail.com']
gem.homepage = 'http://github.com/intridea/omniauth'
gem.authors = ['James A. Rosen', 'Ping Yu', 'Michael Bleigh', 'Erik Michaels-Ober']
gem.executables = `git ls-files -- bin/*`.split("\n").map{|f| File.basename(f)}
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.test_files = `git ls-files -- {test,spec,features}/*`.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

View File

@ -0,0 +1,37 @@
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

View File

@ -26,4 +26,3 @@ Gem::Specification.new do |gem|
gem.require_paths = ['lib']
gem.required_rubygems_version = Gem::Requirement.new('>= 1.3.6') if gem.respond_to? :required_rubygems_version=
end

View File

@ -2,12 +2,11 @@ require 'omniauth/core'
module OmniAuth
module Strategies
autoload :WindowsLive, 'omniauth/strategies/windows_live'
autoload :Draugiem, 'omniauth/strategies/draugiem'
autoload :Flickr, 'omniauth/strategies/flickr'
autoload :LastFm, 'omniauth/strategies/last_fm'
autoload :Yupoo, 'omniauth/strategies/yupoo'
autoload :Ign, 'omniauth/strategies/ign'
autoload :Draugiem, 'omniauth/strategies/draugiem'
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

View File

@ -1,86 +0,0 @@
require 'omniauth/core'
require 'digest/md5'
require 'rest-client'
require 'multi_json'
module OmniAuth
module Strategies
#
# Authenticate to Flickr
#
# @example Basic Usage
#
# use OmniAuth::Strategies::Flickr, 'API Key', 'Secret Key', :scope => 'read'
class Flickr
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 Flickr](http://www.flickr.com/services/apps/)
# @param [String] secret_key the application secret as [registered on Flickr](http://www.flickr.com/services/apps/)
# @option options ['read','write','delete] :scope ('read') the scope of your authorization request; must be `read` or 'write' or 'delete'
def initialize(app, api_key, secret_key, options = {})
super(app, :flickr)
@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] = flickr_sign(params)
query_string = params.collect{ |key,value| "#{key}=#{Rack::Utils.escape(value)}" }.join('&')
redirect "http://flickr.com/services/auth/?#{query_string}"
end
def callback_phase
params = { :api_key => api_key, :method => 'flickr.auth.getToken', :frob => request.params['frob'], :format => 'json', :nojsoncallback => '1' }
params[:api_sig] = flickr_sign(params)
response = RestClient.get('http://api.flickr.com/services/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_info,
'extra' => { 'user_hash' => @user }
})
end
def user_info
name = @user['fullname']
name = @user['username'] if name.nil? || name.empty?
{
'nickname' => @user['username'],
'name' => name,
}
end
def flickr_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

View File

@ -1,29 +1,27 @@
# -*- encoding: utf-8 -*-
# encoding: utf-8
require File.expand_path('../lib/omniauth/version', __FILE__)
Gem::Specification.new do |gem|
gem.add_runtime_dependency 'multi_json', '~> 1.0.0'
gem.add_runtime_dependency 'oa-core', OmniAuth::Version::STRING
gem.add_runtime_dependency 'rest-client', '~> 1.6.0'
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 'maruku', '~> 0.6'
gem.add_development_dependency 'rake', '~> 0.8'
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.6'
gem.add_development_dependency 'webmock', '~> 1.7'
gem.add_development_dependency 'yard', '~> 0.7'
gem.add_development_dependency 'ZenTest', '~> 4.5'
gem.name = 'oa-more'
gem.version = OmniAuth::Version::STRING
gem.description = %q{Additional strategies for OmniAuth.}
gem.summary = gem.description
gem.email = 'michael@intridea.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.description = %q{Additional strategies for OmniAuth.}
gem.email = 'michael@intridea.com'
gem.files = `git ls-files`.split("\n")
gem.test_files = `git ls-files -- {test,spec,features}/*`.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

View File

@ -1,7 +0,0 @@
require File.expand_path('../../../spec_helper', __FILE__)
describe 'OmniAuth::Strategies::Flickr' do
it 'should initialize with a consumer key and secret' do
lambda{OmniAuth::Strategies::Flickr.new({},'abc','def')}.should_not raise_error
end
end

View File

@ -9,6 +9,7 @@ module OmniAuth
autoload :Dropbox, 'omniauth/strategies/oauth/dropbox'
autoload :Evernote, 'omniauth/strategies/oauth/evernote'
autoload :Flattr, 'omniauth/strategies/oauth/flattr'
autoload :Flickr, 'omniauth/strategies/oauth/flickr'
autoload :Goodreads, 'omniauth/strategies/oauth/goodreads'
autoload :Google, 'omniauth/strategies/oauth/google'
autoload :GoogleHealth, 'omniauth/strategies/oauth/google_health'
@ -24,7 +25,6 @@ module OmniAuth
autoload :Plurk, 'omniauth/strategies/oauth/plurk'
autoload :Rdio, 'omniauth/strategies/oauth/rdio'
autoload :SmugMug, 'omniauth/strategies/oauth/smug_mug'
autoload :SoundCloud, 'omniauth/strategies/oauth/sound_cloud'
autoload :T163, 'omniauth/strategies/oauth/t163'
autoload :Tqq, 'omniauth/strategies/oauth/tqq'
autoload :TradeMe, 'omniauth/strategies/oauth/trade_me'
@ -40,6 +40,7 @@ module OmniAuth
autoload :YouTube, 'omniauth/strategies/oauth/you_tube'
autoload :OAuth2, 'omniauth/strategies/oauth2'
autoload :AngelList, 'omniauth/strategies/oauth2/angellist'
autoload :Bitly, 'omniauth/strategies/oauth2/bitly'
autoload :Cobot, 'omniauth/strategies/oauth2/cobot'
autoload :Dailymile, 'omniauth/strategies/oauth2/dailymile'
@ -48,19 +49,26 @@ module OmniAuth
autoload :Foursquare, 'omniauth/strategies/oauth2/foursquare'
autoload :GitHub, 'omniauth/strategies/oauth2/github'
autoload :Glitch, 'omniauth/strategies/oauth2/glitch'
autoload :GoogleOAuth2, 'omniauth/strategies/google_oauth2'
autoload :Gowalla, 'omniauth/strategies/oauth2/gowalla'
autoload :Instagram, 'omniauth/strategies/oauth2/instagram'
autoload :Liveid, 'omniauth/strategies/oauth2/liveid'
autoload :Mailchimp, 'omniauth/strategies/oauth2/mailchimp'
autoload :Mailru, 'omniauth/strategies/oauth2/mailru'
autoload :Mixi, 'omniauth/strategies/oauth2/mixi'
autoload :Renren, 'omniauth/strategies/oauth2/renren'
autoload :Salesforce, 'omniauth/strategies/oauth2/salesforce'
autoload :TaoBao, 'omniauth/strategies/oauth2/taobao'
autoload :SoundCloud, 'omniauth/strategies/oauth2/sound_cloud'
autoload :Taobao, 'omniauth/strategies/oauth2/taobao'
autoload :Teambox, 'omniauth/strategies/oauth2/teambox'
autoload :ThirtySevenSignals, 'omniauth/strategies/oauth2/thirty_seven_signals'
autoload :Viadeo, 'omniauth/strategies/oauth2/viadeo'
autoload :Vkontakte, 'omniauth/strategies/oauth2/vkontakte'
autoload :WePay, 'omniauth/strategies/oauth2/we_pay'
autoload :XAuth, 'omniauth/strategies/xauth'
autoload :Instapaper, 'omniauth/strategies/xauth/instapaper'
end
end

View File

@ -0,0 +1,59 @@
require 'omniauth/oauth'
require 'multi_json'
module OmniAuth
module Strategies
# OAuth 2.0 based authentication with Google.
class GoogleOAuth2 < OAuth2
# @param [Rack Application] app standard middleware application argument
# @param [String] client_id the application ID for your client
# @param [String] client_secret the application secret
# @option options [String] :scope ('https://www.googleapis.com/auth/userinfo.email') space-separated services that you need.
def initialize(app, client_id = nil, client_secret = nil, options = {}, &block)
client_options = {
:site => 'https://accounts.google.com',
:authorize_url => '/o/oauth2/auth',
:token_url => '/o/oauth2/token'
}
super(app, :google_oauth2, client_id, client_secret, client_options, options, &block)
end
def request_phase
google_email_scope = "www.googleapis.com/auth/userinfo.email"
options[:scope] ||= "https://#{google_email_scope}"
options[:scope] << "https://#{google_email_scope}" unless options[:scope] =~ %r[http[s]?:\/\/#{google_email_scope}]
redirect client.auth_code.authorize_url(
{:redirect_uri => callback_url, :response_type => "code"}.merge(options))
end
def auth_hash
OmniAuth::Utils.deep_merge(super, {
'uid' => user_info['uid'],
'user_info' => user_info,
'credentials' => {'expires_at' => @access_token.expires_at},
'extra' => {'user_hash' => user_data}
})
end
def user_info
if user_data['data']['isVerified']
email = user_data['data']['email']
else
email = nil
end
{
'email' => email,
'uid' => email,
'name' => email
}
end
def user_data
@data ||=
@access_token.get("https://www.googleapis.com/userinfo/email?alt=json").parsed
end
end
end
end

View File

@ -40,7 +40,7 @@ module OmniAuth
rescue ::Timeout::Error => e
fail!(:timeout, e)
rescue ::Net::HTTPFatalError => e
rescue ::Net::HTTPFatalError, ::OpenSSL::SSL::SSLError => e
fail!(:service_unavailable, e)
end
@ -60,7 +60,7 @@ module OmniAuth
super
rescue ::Timeout::Error => e
fail!(:timeout, e)
rescue ::Net::HTTPFatalError => e
rescue ::Net::HTTPFatalError, ::OpenSSL::SSL::SSLError => e
fail!(:service_unavailable, e)
rescue ::OAuth::Unauthorized => e
fail!(:invalid_credentials, e)

View File

@ -31,6 +31,8 @@ module OmniAuth
def user_info
{
'name' => user_data['display_name'],
'uid' => user_data['uid'],
'email' => user_data['email']
}
end
end

View File

@ -0,0 +1,39 @@
require 'omniauth/oauth'
require 'multi_json'
module OmniAuth
module Strategies
class Flickr < OmniAuth::Strategies::OAuth
def initialize(app, consumer_key=nil, consumer_secret=nil, options={}, &block)
scope = options.delete(:scope) || 'read'
options[:authorize_params] ||= {}
options[:authorize_params][:perms] = scope
client_options = {
:access_token_path => "/services/oauth/access_token",
:authorize_path => "/services/oauth/authorize",
:request_token_path => "/services/oauth/request_token",
:site => "http://www.flickr.com"
}
super(app, :flickr, consumer_key, consumer_secret, client_options, options, &block)
end
def auth_hash
OmniAuth::Utils.deep_merge(
super, {
'uid' => @access_token.params["user_nsid"],
'user_info' => user_info
}
)
end
def user_info
{
'username' => @access_token.params["username"],
'full_name' => @access_token.params["fullname"]
}
end
end
end
end

View File

@ -42,6 +42,7 @@ module OmniAuth
'location' => user_hash['location'],
'image' => user_hash['profile_image_url'],
'description' => user_hash['description'],
'email' => user_hash['email'],
'urls' => {
'T163' => 'http://t.163.com',
},

View File

@ -68,8 +68,8 @@ module OmniAuth
# MonkeyPath to symbolize tina parameters
def callback_phase
session[:oauth].symbolize_keys!
session[:oauth][name.to_sym].symbolize_keys! if session[:oauth][name.to_sym]
session[:oauth].stringify_keys!
session[:oauth][name.to_s].stringify_keys! if session[:oauth][name.to_s]
super
end

View File

@ -0,0 +1,57 @@
require 'omniauth/oauth'
require 'multi_json'
module OmniAuth
module Strategies
# Authenticate to AngelList utilizing OAuth 2.0 and retrieve
# basic user information.
#
# @example Basic Usage
# use OmniAuth::Strategies::AngelList, 'API Key', 'Secret Key'
class AngelList < OmniAuth::Strategies::OAuth2
# @param [Rack Application] app standard middleware application parameter
# @param [String] client_id the application id as [registered on AngelList](http://angel.co/api/oauth/faq)
# @param [String] client_secret the application secret as [registered on AngelList](http://bit.ly/api/oauth/faq )
def initialize(app, client_id=nil, client_secret=nil, options={}, &block)
client_options = {
:site => 'https://api.angel.co/',
:authorize_url => 'https://angel.co/api/oauth/authorize',
:token_url => 'https://angel.co/api/oauth/token'
}
super(app, :angellist, client_id, client_secret, client_options, options, &block)
end
def auth_hash
OmniAuth::Utils.deep_merge(
super, {
'uid' => user_data['id'],
'user_info' => user_info,
'extra' => {
'user_hash' => user_data,
}
}
)
end
def user_info
{
'name' => user_data['name'],
'bio' => user_data['bio'],
'image' => user_data['image'],
'urls' => {
'AngelList' => user_data['angellist_url'],
'Website' => user_data['online_bio_url']
},
}
end
def user_data
@data ||= begin
@access_token.options[:mode] = :query
@access_token.get('/1/me').parsed
end
end
end
end
end

View File

@ -34,7 +34,7 @@ module OmniAuth
end
def user_data
@data ||= MultiJson.decode(@access_token.get('https://foursquare.com/users/self', {'oauth_token' => @access_token.token}))
@data ||= MultiJson.decode(@access_token.get('https://api.foursquare.com/v2/users/self', { :params => { 'oauth_token' => @access_token.token } }).body)
end
def request_phase

View File

@ -42,6 +42,7 @@ module OmniAuth
end
def user_data
@access_token.options.merge!({:param_name => 'access_token', :mode => :query})
@data ||= MultiJson.decode(@access_token.get('/v1/users/self'))
end

View File

@ -0,0 +1,67 @@
require 'omniauth/oauth'
require 'multi_json'
module OmniAuth
module Strategies
# Authenticate to Windows Connect utilizing OAuth 2.0 and retrieve
# basic user information.
#
# OAuth 2.0 - MS Documentation
# http://msdn.microsoft.com/en-us/library/hh243647.aspx
#
# Sign-up for account:
# http://go.microsoft.com/fwlink/?LinkId=213332
#
# @example Basic Usage of Liveid
#
# use OmniAuth::Strategies::Liveid, 'client_id', 'client_secret'
class Liveid < OmniAuth::Strategies::OAuth2
# @option options [String] :scope separate the scopes by a space
def initialize(app, client_id=nil, client_secret=nil, options={}, &block)
client_options = {
:authorize_url => 'https://oauth.live.com/authorize',
:token_url => 'https://oauth.live.com/token'
}
super(app, :liveid, client_id, client_secret, client_options, options, &block)
end
def auth_hash
OmniAuth::Utils.deep_merge(
super,
{
'uid' => user_data['id'],
'user_info' => user_info,
'extra' => {
'user_hash' => user_data,
}
}
)
end
def request_phase
options[:scope] ||= 'wl.signin wl.basic'
options[:response_type] ||= 'code'
super
end
def user_data
@data ||= MultiJson.decode(@access_token.get('https://apis.live.net/v5.0/me').body)
end
def user_info
{
'id' => user_data['id'],
'name' => user_data['name'],
'email' => '',
'first_name' => user_data['first_name'],
'last_name' => user_data['last_name'],
'link' => user_data['link'],
'gender' => user_data['gender'],
'locale' => user_data['locale']
}
end
end
end
end

View File

@ -0,0 +1,39 @@
require 'omniauth/oauth'
require 'multi_json'
module OmniAuth
module Strategies
class Mailchimp < OmniAuth::Strategies::OAuth2
def initialize(app, client_id=nil, client_secret=nil, options={}, &block)
client_options = {
:authorize_url => 'https://login.mailchimp.com/oauth2/authorize',
:token_url => 'https://login.mailchimp.com/oauth2/token',
}
super(app, :mailchimp, client_id, client_secret, client_options, options, &block)
end
def auth_hash
data = user_data
OmniAuth::Utils.deep_merge(
super, {
'uid' => @access_token.client.id,
'extra'=> {
'user_hash' => data
}
}
)
end
def user_data
@data ||= MultiJson.decode(@access_token.get("https://login.mailchimp.com/oauth2/metadata").body)
rescue ::OAuth2::Error => e
if e.response.status == 302
@data ||= MultiJson.decode(@access_token.get(e.response.headers['location']))
else
raise e
end
end
end
end
end

View File

@ -18,7 +18,10 @@ module OmniAuth
super, {
'uid' => @access_token['id'],
'credentials' => {
'issued_at' => @access_token['issued_at'],
'refresh_token' => @access_token.refresh_token,
'instance_url' => @access_token['instance_url'],
'signature' => @access_token['signature'],
},
'extra' => {
'user_hash' => data,

View File

@ -3,15 +3,17 @@ require 'multi_json'
module OmniAuth
module Strategies
# Authenticate to SoundCloud via OAuth and retrieve basic
# Authenticate to SoundCloud via OAuth2 and retrieve basic
# user information.
#
# Usage:
# use OmniAuth::Strategies::SoundCloud, 'consumerkey', 'consumersecret'
class SoundCloud < OmniAuth::Strategies::OAuth
class SoundCloud < OmniAuth::Strategies::OAuth2
def initialize(app, consumer_key=nil, consumer_secret=nil, options={}, &block)
client_options = {
:site => 'https://api.soundcloud.com',
:authorize_url => 'https://soundcloud.com/connect',
:token_url => 'https://api.soundcloud.com/oauth2/token'
}
super(app, :soundcloud, consumer_key, consumer_secret, client_options, options, &block)
end
@ -45,6 +47,14 @@ module OmniAuth
def user_hash
@user_hash ||= MultiJson.decode(@access_token.get('/me.json').body)
end
# OAuth2 by default uses 'Bearer %s' in the header
def build_access_token
access_token = super
access_token.options[:header_format] = "OAuth %s"
access_token
end
end
end
end

View File

@ -10,7 +10,7 @@ module OmniAuth
#
# @example Basic Usage
# use OmniAuth::Strategies::TaoBao, 'client_id', 'client_secret'
class TaoBao < OmniAuth::Strategies::OAuth2
class Taobao < OmniAuth::Strategies::OAuth2
# @param [Rack Application] app standard middleware application parameter
# @param [String] client_id the app key at taobao open platform
# @param [String] client_secret the app secret at taobao open platform
@ -71,7 +71,8 @@ module OmniAuth
end
def generate_sign(params)
str = client_secret + (params.sort.collect { |k, v| "#{k}#{v}" }).join + client_secret
# params.sort.collect { |k, v| "#{k}#{v}" }
str = client_secret + params.sort {|a,b| "#{a[0]}"<=>"#{b[0]}"}.flatten.join + client_secret
params['sign'] = Digest::MD5.hexdigest(str).upcase!
params
end

View File

@ -0,0 +1,72 @@
require 'omniauth/oauth'
require 'multi_json'
module OmniAuth
module Strategies
# Authenticate to Viadeo utilizing OAuth 2.0 and retrieve
# basic user information.
#
# @example Basic Usage
# use OmniAuth::Strategies::Viadeo, 'client_id', 'client_secret'
class Viadeo < OmniAuth::Strategies::OAuth2
# @param [Rack Application] app standard middleware application parameter
# @param [String] client_id the application id as [registered on Viadeo](http://dev.viadeo.com/)
# @param [String] client_secret the application secret as registered on Facebook
def initialize(app, client_id=nil, client_secret=nil, options = {}, &block)
client_options = {
:site => 'https://api.viadeo.com/',
:authorize_url => 'https://secure.viadeo.com/oauth-provider/authorize2',
:token_url => 'https://secure.viadeo.com/oauth-provider/access_token2'
}
super(app, :viadeo, client_id, client_secret, client_options, options, &block)
end
def auth_hash
OmniAuth::Utils.deep_merge(
super, {
'uid' => user_data['id'],
'user_info' => user_info,
'extra' => {
'user_hash' => user_data,
},
}
)
end
def user_data
@data ||= MultiJson.decode(@access_token.get('/me').body)
end
def request_phase
options[:response_type] ||= 'code'
super
end
def callback_phase
options[:grant_type] ||= 'authorization_code'
super
end
def user_info
{
'name' => user_data['name'],
'link' => user_data['link'],
'first_name' => user_data['first_name'],
'last_name' => user_data['last_name'],
'gender' => user_data['gender'],
'nickname' => user_data['nickname'],
'has_picture' => user_data['has_picture'] ,
'picture_small' => user_data['picture_small'],
'picture_large' => user_data['picture_large'],
'headline' => user_data['headline'],
'introduction' => user_data['introduction'],
'interests' => user_data['interests'],
'location' => user_data['location'],
'is_premium' => user_data['is_premium'],
'premium_since' => user_data['premium_since']
}
end
end
end
end

View File

@ -1,33 +1,31 @@
# -*- encoding: utf-8 -*-
# encoding: utf-8
require File.expand_path('../lib/omniauth/version', __FILE__)
Gem::Specification.new do |gem|
gem.add_runtime_dependency 'faraday', '~> 0.7.3'
gem.add_runtime_dependency 'multi_json', '~> 1.0.0'
gem.add_runtime_dependency 'multi_xml', '~> 0.2.2'
gem.add_runtime_dependency 'oa-core', OmniAuth::Version::STRING
gem.add_runtime_dependency 'oauth', '~> 0.4.0'
gem.add_runtime_dependency 'oauth2', '~> 0.5.0'
gem.add_dependency 'faraday', '~> 0.7.3'
gem.add_dependency 'multi_json', '~> 1.0.0'
gem.add_dependency 'multi_xml', '~> 0.3.0'
gem.add_dependency 'oa-core', OmniAuth::Version::STRING
gem.add_dependency 'oauth', '~> 0.4.0'
gem.add_dependency 'oauth2', '~> 0.5.0'
gem.add_development_dependency 'evernote', '~> 1.0'
gem.add_development_dependency 'maruku', '~> 0.6'
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.6'
gem.add_development_dependency 'yard', '~> 0.7'
gem.add_development_dependency 'vcr', '~> 1.10'
gem.add_development_dependency 'ZenTest', '~> 4.5'
gem.name = 'oa-oauth'
gem.version = OmniAuth::Version::STRING
gem.description = %q{OAuth strategies for OmniAuth.}
gem.summary = gem.description
gem.email = ['michael@intridea.com', 'sferik@gmail.com']
gem.homepage = 'http://github.com/intridea/omniauth'
gem.add_development_dependency 'webmock', '~> 1.7'
gem.add_development_dependency 'yard', '~> 0.7'
gem.authors = ['Michael Bleigh', 'Erik Michaels-Ober']
gem.executables = `git ls-files -- bin/*`.split("\n").map{|f| File.basename(f)}
gem.description = %q{OAuth strategies for OmniAuth.}
gem.email = ['michael@intridea.com', 'sferik@gmail.com']
gem.files = `git ls-files`.split("\n")
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
gem.homepage = 'http://github.com/intridea/omniauth'
gem.name = 'oa-oauth'
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

View File

@ -0,0 +1,5 @@
require File.expand_path('../../../spec_helper', __FILE__)
describe OmniAuth::Strategies::GoogleOAuth2 do
it_should_behave_like "an oauth2 strategy"
end

View File

@ -0,0 +1,6 @@
require 'spec_helper'
describe OmniAuth::Strategies::Flickr do
it_should_behave_like "an oauth strategy"
end

View File

@ -57,6 +57,19 @@ describe "OmniAuth::Strategies::OAuth" do
last_request.env['omniauth.error'].should be_kind_of(::Net::HTTPFatalError)
last_request.env['omniauth.error.type'] = :service_unavailable
end
context "SSL failure" do
before do
stub_request(:post, 'https://api.example.org/oauth/request_token').
to_raise(::OpenSSL::SSL::SSLError.new("SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed"))
get '/auth/example.org'
end
it 'should call fail! with :service_unavailable' do
last_request.env['omniauth.error'].should be_kind_of(::OpenSSL::SSL::SSLError)
last_request.env['omniauth.error.type'] = :service_unavailable
end
end
end
end
@ -88,6 +101,19 @@ describe "OmniAuth::Strategies::OAuth" do
last_request.env['omniauth.error.type'] = :service_unavailable
end
end
context "SSL failure" do
before do
stub_request(:post, 'https://api.example.org/oauth/access_token').
to_raise(::OpenSSL::SSL::SSLError.new("SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed"))
get '/auth/example.org/callback', {:oauth_verifier => 'dudeman'}, {'rack.session' => {'oauth' => {"example.org" => {'callback_confirmed' => true, 'request_token' => 'yourtoken', 'request_secret' => 'yoursecret'}}}}
end
it 'should call fail! with :service_unavailable' do
last_request.env['omniauth.error'].should be_kind_of(::OpenSSL::SSL::SSLError)
last_request.env['omniauth.error.type'] = :service_unavailable
end
end
end
describe '/auth/{name}/callback with expired session' do

View File

@ -0,0 +1,5 @@
require 'spec_helper'
describe OmniAuth::Strategies::AngelList do
it_should_behave_like "an oauth2 strategy"
end

View File

@ -0,0 +1,5 @@
require 'spec_helper'
describe OmniAuth::Strategies::Instagram do
it_should_behave_like "an oauth2 strategy"
end

View File

@ -0,0 +1,5 @@
require 'spec_helper'
describe OmniAuth::Strategies::Liveid do
it_should_behave_like 'an oauth2 strategy'
end

View File

@ -0,0 +1,6 @@
# oa-oauth/spec/omniauth/strategies/rdio_spec.rb
require File.expand_path('../../../../spec_helper', __FILE__)
describe OmniAuth::Strategies::Mailchimp do
it_should_behave_like "an oauth2 strategy"
end

View File

@ -1,5 +1,5 @@
require 'spec_helper'
describe OmniAuth::Strategies::SoundCloud do
it_should_behave_like 'an oauth strategy'
it_should_behave_like "an oauth2 strategy"
end

View File

@ -1,5 +1,5 @@
require 'spec_helper'
describe OmniAuth::Strategies::TaoBao do
describe OmniAuth::Strategies::Taobao do
it_should_behave_like "an oauth2 strategy"
end

View File

@ -0,0 +1,5 @@
require 'spec_helper'
describe OmniAuth::Strategies::Viadeo do
it_should_behave_like "an oauth2 strategy"
end

View File

@ -56,9 +56,11 @@ module OmniAuth
end
def identifier
options[:identifier] || request[IDENTIFIER_URL_PARAMETER]
i = options[:identifier] || request[IDENTIFIER_URL_PARAMETER]
i = nil if i == ''
i
end
def request_phase
identifier ? start : get_identifier
end

View File

@ -1,28 +1,26 @@
# -*- encoding: utf-8 -*-
# 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_runtime_dependency 'rack-openid', '~> 1.3.1'
gem.add_runtime_dependency 'ruby-openid-apps-discovery', '~> 1.2.0'
gem.add_development_dependency 'maruku', '~> 0.6'
gem.add_dependency 'oa-core', OmniAuth::Version::STRING
gem.add_dependency 'rack-openid', '~> 1.3.1'
gem.add_dependency 'ruby-openid-apps-discovery', '~> 1.2.0'
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.6'
gem.add_development_dependency 'webmock', '~> 1.7'
gem.add_development_dependency 'yard', '~> 0.7'
gem.add_development_dependency 'ZenTest', '~> 4.5'
gem.name = 'oa-openid'
gem.version = OmniAuth::Version::STRING
gem.description = %q{OpenID strategies 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.description = %q{OpenID strategies for OmniAuth.}
gem.email = ['michael@intridea.com', 'sferik@gmail.com']
gem.files = `git ls-files`.split("\n")
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
gem.homepage = 'http://github.com/intridea/omniauth'
gem.name = 'oa-openid'
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

View File

@ -1,4 +1,4 @@
# -*- encoding: utf-8 -*-
# encoding: utf-8
require File.expand_path('../lib/omniauth/version', __FILE__)
Gem::Specification.new do |gem|
@ -6,7 +6,7 @@ Gem::Specification.new do |gem|
gem.add_runtime_dependency subgem, OmniAuth::Version::STRING
end
gem.authors = ['Michael Bleigh', 'Erik Michaels-Ober']
gem.description = %q{OmniAuth is an authentication framework that that separates the concept of authentiation from the concept of identity, providing simple hooks for any application to have one or multiple authentication providers for a user.}
gem.description = %q{OmniAuth is an authentication framework that separates the concept of authentiation from the concept of identity, providing simple hooks for any application to have one or multiple authentication providers for a user.}
gem.email = ['michael@intridea.com', 'sferik@gmail.com']
gem.files = Dir.glob("lib/**/*") + %w(README.md LICENSE.md)
gem.homepage = 'http://github.com/intridea/omniauth'

View File

@ -0,0 +1,20 @@
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

View File

@ -0,0 +1,79 @@
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

View File

@ -0,0 +1,368 @@
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 '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

12
spec/spec_helper.rb Normal file
View File

@ -0,0 +1,12 @@
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