Skip to content

Commit

Permalink
Merge branch 'master' of github.com:intridea/omniauth
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Bleigh committed Jul 20, 2010
2 parents 490e233 + 1be1ebb commit 7300af4
Show file tree
Hide file tree
Showing 23 changed files with 524 additions and 29 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -22,6 +22,7 @@ tmtags
coverage
rdoc
pkg
tmp

## PROJECT::SPECIFIC
*.gem
Expand Down
2 changes: 1 addition & 1 deletion Rakefile
Expand Up @@ -4,7 +4,7 @@ require 'term/ansicolor'

include Term::ANSIColor

OMNIAUTH_GEMS = %w(oa-basic oa-core oa-oauth oa-openid omniauth)
OMNIAUTH_GEMS = %w(oa-basic oa-core oa-oauth oa-openid oa-corporate omniauth)

def each_gem(action, &block)
OMNIAUTH_GEMS.each_with_index do |dir, i|
Expand Down
6 changes: 6 additions & 0 deletions oa-core/lib/omniauth/form.rb
Expand Up @@ -60,6 +60,12 @@ class Form
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;
Expand Down
Empty file added oa-corporate/CHANGELOG.rdoc
Empty file.
9 changes: 9 additions & 0 deletions oa-corporate/Gemfile
@@ -0,0 +1,9 @@
source "http://rubygems.org"

gem 'oa-core', :path => File.expand_path('../../oa-core/', __FILE__)

# Will automatically pull in this gem and all its
# dependencies specified in the gemspec
gem 'oa-corporate', :path => File.expand_path("..", __FILE__)

eval File.read(File.join(File.dirname(__FILE__), '../development_dependencies.rb'))
19 changes: 19 additions & 0 deletions oa-corporate/LICENSE.rdoc
@@ -0,0 +1,19 @@
Copyright (c) 2010 James A. Rosen

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.
38 changes: 38 additions & 0 deletions oa-corporate/README.rdoc
@@ -0,0 +1,38 @@
= OmniAuth::Corporate

OmniAuth strategies for use in your intranet.

== Installation

To get just corporate functionality:

gem install oa-corporate

For the full auth suite:

gem install omniauth

== Stand-Alone Example

Use the strategy as a middleware in your application:

require 'omniauth/corporate'

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.


== OmniAuth Builder

If CAS is one of several authentication strategies, use the OmniAuth Builder:

require 'omniauth/corporate'
require 'omniauth/oauth' # for Campfire
require 'openid/store/filesystem'

use OmniAuth::Builder do
provider :cas, :server => 'http://cas.mycompany.com/cas'
provider :campfire
end
15 changes: 15 additions & 0 deletions oa-corporate/Rakefile
@@ -0,0 +1,15 @@
require 'rubygems'
require 'bundler'
Bundler.setup
require 'rake'

require 'mg'
MG.new('oa-corporate.gemspec')

require 'spec/rake/spectask'
Spec::Rake::SpecTask.new(:spec) do |spec|
spec.libs << '../oa-core/lib' << 'lib' << 'spec'
spec.spec_files = FileList['spec/**/*_spec.rb']
end

task :default => :spec
1 change: 1 addition & 0 deletions oa-corporate/VERSION
@@ -0,0 +1 @@
0.0.3
7 changes: 7 additions & 0 deletions oa-corporate/lib/omniauth/corporate.rb
@@ -0,0 +1,7 @@
require 'omniauth/core'

module OmniAuth
module Strategies
autoload :CAS, 'omniauth/strategies/cas'
end
end
47 changes: 47 additions & 0 deletions oa-corporate/lib/omniauth/strategies/cas.rb
@@ -0,0 +1,47 @@
require 'omniauth/corporate'

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 = {})
super(app, options.delete(:name) || :cas)
@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) unless ticket
validator = ServiceTicketValidator.new(@configuration, callback_url, ticket)
@user_info = validator.user_info
return fail!(:invalid_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
88 changes: 88 additions & 0 deletions oa-corporate/lib/omniauth/strategies/cas/configuration.rb
@@ -0,0 +1,88 @@
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 <tt>:cas_server</tt> 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 <tt>:cas_server</tt> is
# specified, requred otherwise.
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+.
#
# @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)
url = append_service @service_validate_url, service
url << '&ticket=' << Rack::Utils.escape(ticket)
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
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
@@ -0,0 +1,80 @@
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)
@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?
node.children.inject({}) do |hash, child|
unless child.kind_of?(Nokogiri::XML::Text) ||
child.name == 'cas:proxies' ||
child.name == 'proxies'
hash[child.name.sub(/^cas:/, '')] = child.content
end
hash
end
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.start do |c|
response = c.get "#{@uri.path}?#{@uri.query}", VALIDATION_REQUEST_HEADERS
result = response.body
end
result
end

end
end
end
end
21 changes: 21 additions & 0 deletions oa-corporate/oa-corporate.gemspec
@@ -0,0 +1,21 @@
require 'rubygems'

version = File.open(File.dirname(__FILE__) + '/../VERSION', 'r').read.strip

Gem::Specification.new do |gem|
gem.name = "oa-corporate"
gem.version = version
gem.summary = %Q{Corporate strategies for OmniAuth.}
gem.description = %Q{Corporate strategies for OmniAuth.}
gem.email = "james.a.rosen@gmail.com"
gem.homepage = "http://github.com/intridea/omniauth"
gem.authors = ["James A. Rosen"]

gem.files = Dir.glob("{lib}/**/*") + %w(README.rdoc LICENSE.rdoc CHANGELOG.rdoc)

gem.add_dependency 'oa-core', version
gem.add_dependency 'rack', '~> 1.1.0'
gem.add_dependency 'nokogiri', '~> 1.4.2'

eval File.read(File.join(File.dirname(__FILE__), '../development_dependencies.rb'))
end
4 changes: 4 additions & 0 deletions oa-corporate/spec/fixtures/cas_failure.xml
@@ -0,0 +1,4 @@
<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
<cas:authenticationFailure>
</cas:authenticationFailure>
</cas:serviceResponse>
8 changes: 8 additions & 0 deletions oa-corporate/spec/fixtures/cas_success.xml
@@ -0,0 +1,8 @@
<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>

0 comments on commit 7300af4

Please sign in to comment.