cul_omniauth 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/Rakefile +35 -0
- data/app/assets/javascripts/cul/omniauth/application.js +13 -0
- data/app/assets/stylesheets/cul/omniauth/application.css +15 -0
- data/app/controllers/concerns/cul/omniauth/callbacks.rb +36 -0
- data/app/controllers/concerns/cul/omniauth/remote_ip_ability.rb +6 -0
- data/app/controllers/cul/omniauth/application_controller.rb +4 -0
- data/app/controllers/cul/omniauth/callbacks_controller.rb +3 -0
- data/app/helpers/cul/omniauth/application_helper.rb +4 -0
- data/app/models/concerns/cul/omniauth/abilities.rb +79 -0
- data/app/models/concerns/cul/omniauth/users.rb +73 -0
- data/app/views/layouts/cul/omniauth/application.html.erb +14 -0
- data/config/routes.rb +2 -0
- data/lib/cul/omniauth/ability_proxy.rb +21 -0
- data/lib/cul/omniauth/engine.rb +5 -0
- data/lib/cul/omniauth/failure_app.rb +18 -0
- data/lib/cul/omniauth/file_configurable.rb +43 -0
- data/lib/cul/omniauth/version.rb +5 -0
- data/lib/cul_omniauth.rb +59 -0
- data/lib/omni_auth/strategies/saml/service_ticket_validator.rb +81 -0
- data/lib/omni_auth/strategies/saml.rb +207 -0
- data/lib/omni_auth/strategies/wind/logout_request.rb +62 -0
- data/lib/omni_auth/strategies/wind/service_ticket_validator.rb +49 -0
- data/lib/omni_auth/strategies/wind.rb +201 -0
- data/lib/tasks/cul_omniauth_tasks.rake +4 -0
- metadata +153 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 12e145d88c005c6cab678e19eb93b17cff6e09e9
|
4
|
+
data.tar.gz: 262436496792653e2a1cf76e3bc76be6b94ede92
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ff406c2659ce8e17f061647727f46541acf000c080eef7b13ecaf807b338c39dc012424f132d7e44c30d1b61384fcaf76770bd10d938942c835ac8201ef999ba
|
7
|
+
data.tar.gz: 57078dcf843567d0dce62a6bd1bd06619a099036d59384907c2888170725b0d819b6e396b039fcfbc955e4e877bff4e33fb130fa20e70985ad207a13109ae36c
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2015 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'Cul::Omniauth'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
Bundler::GemHelper.install_tasks
|
23
|
+
|
24
|
+
require 'rake/testtask'
|
25
|
+
|
26
|
+
require 'rspec/core'
|
27
|
+
require 'rspec/core/rake_task'
|
28
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
29
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
30
|
+
spec.pattern += FileList['spec/*_spec.rb']
|
31
|
+
spec.rspec_opts = []
|
32
|
+
spec.rspec_opts << ['--backtrace'] if ENV['CI']
|
33
|
+
end
|
34
|
+
|
35
|
+
task default: :spec
|
@@ -0,0 +1,13 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// compiled file.
|
9
|
+
//
|
10
|
+
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
|
11
|
+
// about supported directives.
|
12
|
+
//
|
13
|
+
//= require_tree .
|
@@ -0,0 +1,15 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any styles
|
10
|
+
* defined in the other CSS/SCSS files in this directory. It is generally better to create a new
|
11
|
+
* file per style scope.
|
12
|
+
*
|
13
|
+
*= require_tree .
|
14
|
+
*= require_self
|
15
|
+
*/
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Cul::Omniauth::Callbacks
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
def cas
|
4
|
+
find_user('CAS')
|
5
|
+
end
|
6
|
+
def saml
|
7
|
+
find_user('SAML')
|
8
|
+
end
|
9
|
+
def wind
|
10
|
+
find_user('WIND')
|
11
|
+
end
|
12
|
+
|
13
|
+
def ssl
|
14
|
+
find_user('ssl')
|
15
|
+
end
|
16
|
+
|
17
|
+
def find_user(auth_type)
|
18
|
+
find_method = "find_for_#{auth_type.downcase}".to_sym
|
19
|
+
current_user ||= User.send(find_method,request.env["omniauth.auth"], current_user)
|
20
|
+
affils = ["#{request.env["omniauth.auth"].uid}:users.cul.columbia.edu"]
|
21
|
+
affils << "staff:cul.columbia.edu" if current_user.cul_staff?
|
22
|
+
affils += (request.env["omniauth.auth"].extra.affiliations || [])
|
23
|
+
affiliations(current_user,affils)
|
24
|
+
session["devise.roles"] = affils
|
25
|
+
if current_user.persisted?
|
26
|
+
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => auth_type
|
27
|
+
sign_in_and_redirect current_user, :event => :authentication
|
28
|
+
else
|
29
|
+
session["devise.#{auth_type.downcase}_data"] = request.env["omniauth.auth"]
|
30
|
+
redirect_to root_url
|
31
|
+
end
|
32
|
+
end
|
33
|
+
def affiliations(user, affils)
|
34
|
+
end
|
35
|
+
protected :find_user
|
36
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Cul::Omniauth::Abilities
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
EMPTY = [].freeze
|
4
|
+
def initialize(user, opts={})
|
5
|
+
@user = user || User.new
|
6
|
+
self.class.config.select {|role,config| user.role? role }.each do |role, config|
|
7
|
+
config.fetch(:can,EMPTY).each do |action, conditions|
|
8
|
+
if conditions.blank?
|
9
|
+
can action, :all
|
10
|
+
else
|
11
|
+
can action, Cul::Omniauth::AbilityProxy do |proxy|
|
12
|
+
r = !!proxy
|
13
|
+
if r
|
14
|
+
conditions.fetch(:if,EMPTY).each do |property, comparisons|
|
15
|
+
p = value_for_property(proxy, property, opts)
|
16
|
+
r &= !!p
|
17
|
+
r &= comparisons.detect {|c,v| Comparisons.send(c, p, v)}
|
18
|
+
end
|
19
|
+
conditions.fetch(:unless,EMPTY).each do |property, comparisons|
|
20
|
+
p = value_for_property(proxy, property, opts)
|
21
|
+
if p
|
22
|
+
r &= !comparisons.detect {|c,v| Comparisons.send(c, p, v)}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
r
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
private
|
33
|
+
def value_for_property(proxy, property_handle, opts)
|
34
|
+
if proxy.respond_to? property_handle.to_sym
|
35
|
+
property = proxy.send property_handle
|
36
|
+
end
|
37
|
+
property = opts.fetch(property_handle,EMPTY) if property.blank?
|
38
|
+
property
|
39
|
+
end
|
40
|
+
module Comparisons
|
41
|
+
def self.include?(context, value)
|
42
|
+
context.include? value
|
43
|
+
end
|
44
|
+
def self.eql?(context, value)
|
45
|
+
context.eql? value
|
46
|
+
end
|
47
|
+
def self.in?(context, value)
|
48
|
+
(Array(value) & Array(context)).size > 0
|
49
|
+
end
|
50
|
+
end
|
51
|
+
public
|
52
|
+
module ClassMethods
|
53
|
+
def config
|
54
|
+
@role_proxy_config ||= begin
|
55
|
+
root = (Rails.root.blank?) ? '.' : Rails.root
|
56
|
+
path = File.join(root,'config','roles.yml')
|
57
|
+
_opts = YAML.load_file(path)
|
58
|
+
all_config = _opts.fetch("_all_environments", {})
|
59
|
+
env_config = _opts.fetch(Rails.env, {})
|
60
|
+
symbolize_hash_keys(all_config.merge(env_config))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
def self.included mod
|
64
|
+
mod.config.each do |k,v|
|
65
|
+
if v[:includes]
|
66
|
+
v[:includes].each do |included|
|
67
|
+
Role.role(k).includes(included.to_sym)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
private
|
73
|
+
def symbolize_hash_keys(hash)
|
74
|
+
hash.symbolize_keys!
|
75
|
+
hash.values.select{|v| v.is_a? Hash}.each{|h| symbolize_hash_keys(h)}
|
76
|
+
hash
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Cul::Omniauth::Users
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do |mod|
|
5
|
+
# Include default devise modules and the omniauthable module
|
6
|
+
# Others available are:
|
7
|
+
# :confirmable, :lockable, :timeoutable and :omniauthable
|
8
|
+
mod.devise :registerable, :recoverable,
|
9
|
+
:rememberable, :trackable, :validatable, :omniauthable
|
10
|
+
|
11
|
+
#mod.attr_accessible :username, :uid, :provider
|
12
|
+
#mod.attr_accessible :email, :guest
|
13
|
+
|
14
|
+
mod.delegate :can?, :cannot?, :to => :ability
|
15
|
+
end
|
16
|
+
|
17
|
+
def role? role_sym
|
18
|
+
role_sym == :guest
|
19
|
+
end
|
20
|
+
|
21
|
+
def ability
|
22
|
+
@ability ||= Ability.new(self)
|
23
|
+
end
|
24
|
+
|
25
|
+
module ClassMethods
|
26
|
+
def find_for_cas(token, resource=nil)
|
27
|
+
user = where(:login => token.uid).first
|
28
|
+
# create new user if necessary
|
29
|
+
unless user
|
30
|
+
user = create(whitelist(:login => token.uid))
|
31
|
+
# can we add groups or roles here?
|
32
|
+
end
|
33
|
+
user
|
34
|
+
end
|
35
|
+
|
36
|
+
def find_for_saml(token, resource=nil)
|
37
|
+
user = where(:login => token.uid).first
|
38
|
+
# create new user if necessary
|
39
|
+
unless user
|
40
|
+
user = create(whitelist(:login => token.uid))
|
41
|
+
# can we add groups or roles here?
|
42
|
+
end
|
43
|
+
|
44
|
+
user
|
45
|
+
end
|
46
|
+
|
47
|
+
def find_for_wind(token, resource=nil)
|
48
|
+
user = where(:login => token.uid).first
|
49
|
+
# create new user if necessary
|
50
|
+
unless user
|
51
|
+
user = create(whitelist(:login => token.uid))
|
52
|
+
# can we add groups or roles here?
|
53
|
+
end
|
54
|
+
|
55
|
+
user
|
56
|
+
end
|
57
|
+
|
58
|
+
def from_omniauth(auth)
|
59
|
+
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
|
60
|
+
user.email = auth.info.email
|
61
|
+
user.password = Devise.friendly_token[0,20]
|
62
|
+
user.name = auth.info.name # assuming the user model has a name
|
63
|
+
user.image = auth.info.image # assuming the user model has an image
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
def whitelist(params=nil)
|
69
|
+
params.permit(:login,:uid,:provider,:email,:guest) if params.respond_to? :permit
|
70
|
+
params || {}
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Cul::Omniauth</title>
|
5
|
+
<%= stylesheet_link_tag "cul/omniauth/application", media: "all" %>
|
6
|
+
<%= javascript_include_tag "cul/omniauth/application" %>
|
7
|
+
<%= csrf_meta_tags %>
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
|
11
|
+
<%= yield %>
|
12
|
+
|
13
|
+
</body>
|
14
|
+
</html>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module Cul::Omniauth
|
2
|
+
class AbilityProxy
|
3
|
+
attr_accessor :mime_type, :context, :content_models, :publisher, :remote_ip
|
4
|
+
def initialize(opts = {})
|
5
|
+
self.mime_type = opts[:mime_type]
|
6
|
+
self.context = opts[:context]
|
7
|
+
self.content_models = opts[:content_models] || []
|
8
|
+
self.publisher = opts[:publisher] || []
|
9
|
+
self.remote_ip = opts[:remote_ip] || []
|
10
|
+
end
|
11
|
+
def to_h
|
12
|
+
return {
|
13
|
+
mime_type: mime_type(),
|
14
|
+
context: context(),
|
15
|
+
content_models: content_models(),
|
16
|
+
publisher: publisher(),
|
17
|
+
remote_ip: remote_ip()
|
18
|
+
}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'devise'
|
2
|
+
class Cul::Omniauth::FailureApp < Devise::FailureApp
|
3
|
+
DEFAULT_PROVIDER = :saml
|
4
|
+
def self.provider=(provider)
|
5
|
+
@provider = provider
|
6
|
+
end
|
7
|
+
def self.provider
|
8
|
+
@provider || DEFAULT_PROVIDER
|
9
|
+
end
|
10
|
+
def self.for(provider=nil)
|
11
|
+
r = Class.new(self)
|
12
|
+
r.provider = provider || self.provider
|
13
|
+
r
|
14
|
+
end
|
15
|
+
def redirect_url
|
16
|
+
user_omniauth_authorize_path(self.class.provider)
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'omniauth-cas'
|
3
|
+
module Cul::Omniauth::FileConfigurable
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
included do |mod|
|
6
|
+
#OmniAuth::Strategies::CAS.configure(mod.cas_configuration_opts)
|
7
|
+
end
|
8
|
+
module ClassMethods
|
9
|
+
def cas_configuration_opts
|
10
|
+
@cas_opts ||= begin
|
11
|
+
_opts = YAML.load_file(File.join(Rails.root,'config','cas.yml'))[Rails.env] || {}
|
12
|
+
_opts = _opts.symbolize_keys
|
13
|
+
_opts
|
14
|
+
end
|
15
|
+
@cas_opts
|
16
|
+
end
|
17
|
+
def configure_devise_omniauth(config,opts=nil)
|
18
|
+
opts ||= cas_configuration_opts
|
19
|
+
opts = opts.dup
|
20
|
+
provider = opts.delete(:provider)
|
21
|
+
fetch_raw_info = opts.delete(:fetch_raw_info)
|
22
|
+
fetch_raw_info = fetch_raw_info.to_sym if fetch_raw_info.is_a? String
|
23
|
+
if fetch_raw_info.is_a? Symbol
|
24
|
+
method = fetch_raw_info
|
25
|
+
fetch_raw_info = lambda do |strategy, options, ticket, ticket_user_info|
|
26
|
+
send(method, strategy, options, ticket, ticket_user_info)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
opts[:fetch_raw_info] = fetch_raw_info if fetch_raw_info
|
30
|
+
config.omniauth provider, opts
|
31
|
+
config.warden do |manager|
|
32
|
+
manager.failure_app = Cul::Omniauth::FailureApp.for(provider)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
def print_raw_info(strategy, options, ticket, ticket_user_info)
|
36
|
+
puts "strategy: #{strategy.inspect}"
|
37
|
+
puts "options: #{options.inspect}"
|
38
|
+
puts "ticket: #{ticket.inspect}"
|
39
|
+
puts "ticket_user_info: #{ticket_user_info.inspect}"
|
40
|
+
{} # for merge
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/cul_omniauth.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'omniauth-cas'
|
2
|
+
module Cul
|
3
|
+
module Omniauth
|
4
|
+
autoload :FailureApp, 'cul/omniauth/failure_app'
|
5
|
+
autoload :FileConfigurable, 'cul/omniauth/file_configurable'
|
6
|
+
autoload :AbilityProxy, 'cul/omniauth/ability_proxy'
|
7
|
+
require "cul/omniauth/engine"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
module OmniAuth
|
11
|
+
module Strategies
|
12
|
+
require 'omni_auth/strategies/saml'
|
13
|
+
require 'omni_auth/strategies/wind'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
OmniAuth::Strategies::CAS::ServiceTicketValidator.class
|
17
|
+
class OmniAuth::Strategies::CAS::ServiceTicketValidator
|
18
|
+
alias defunct_parse parse_user_info
|
19
|
+
alias defunct_success find_authentication_success
|
20
|
+
# turns an `<cas:authenticationSuccess>` node into a Hash;
|
21
|
+
# returns nil if given nil
|
22
|
+
def parse_user_info(node)
|
23
|
+
return nil if node.nil?
|
24
|
+
{}.tap do |hash|
|
25
|
+
node.children.each do |e|
|
26
|
+
node_name = e.name.sub(/^cas:/, '')
|
27
|
+
unless e.kind_of?(Nokogiri::XML::Text) || node_name == 'proxies'
|
28
|
+
# There are no child elements
|
29
|
+
if e.element_children.count == 0
|
30
|
+
hash[node_name] = e.content
|
31
|
+
elsif e.element_children.count
|
32
|
+
# JASIG style extra attributes
|
33
|
+
if node_name == 'attributes'
|
34
|
+
hash.merge!(parse_user_info(e))
|
35
|
+
elsif node_name == 'affiliations'
|
36
|
+
hash.merge!(affiliations: e.xpath('cas:affil',NS).collect {|x| x.text})
|
37
|
+
else
|
38
|
+
hash[node_name] = [] if hash[node_name].nil?
|
39
|
+
hash[node_name].push(parse_user_info(e))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
def find_authentication_success(body)
|
47
|
+
return nil if body.nil? || body == ''
|
48
|
+
begin
|
49
|
+
doc = Nokogiri::XML(body)
|
50
|
+
begin
|
51
|
+
doc.xpath('/cas:serviceResponse/cas:authenticationSuccess')
|
52
|
+
rescue Nokogiri::XML::XPath::SyntaxError
|
53
|
+
doc.xpath('/serviceResponse/authenticationSuccess')
|
54
|
+
end
|
55
|
+
rescue Nokogiri::XML::XPath::SyntaxError
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# This is a clone of the OmniAuth CAS ServiceTicketValidator for WIND
|
2
|
+
# Copyright (c) 2011 Derek Lindahl and CustomInk, LLC
|
3
|
+
# distributed under the MIT license
|
4
|
+
# https://github.com/dlindahl/omniauth-cas
|
5
|
+
module OmniAuth
|
6
|
+
module Strategies
|
7
|
+
class SAML
|
8
|
+
class ServiceTicketValidator < OmniAuth::Strategies::CAS::ServiceTicketValidator
|
9
|
+
ART_TEMPLATE = "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">" +
|
10
|
+
"<SOAP-ENV:Header/><SOAP-ENV:Body>" +
|
11
|
+
"<samlp:Request IssueInstant=\"%s\" MajorVersion=\"1\" MinorVersion=\"1\" xmlns:samlp=\"urn:oasis:names:tc:SAML:1.0:protocol\">" +
|
12
|
+
"<samlp:AssertionArtifact>%s</samlp:AssertionArtifact>" +
|
13
|
+
"</samlp:Request>" +
|
14
|
+
"</SOAP-ENV:Body>" +
|
15
|
+
"</SOAP-ENV:Envelope>"
|
16
|
+
NAME_ID_XPATH = './samla:Assertion/samla:AuthenticationStatement/samla:Subject/samla:NameIdentifier'
|
17
|
+
AFFIL_VALUE_XPATH = './samla:Assertion/samla:AttributeStatement/samla:Attribute[@AttributeName=\'affiliation\']/samla:AttributeValue'
|
18
|
+
def initialize(strategy, options, return_to_url, ticket)
|
19
|
+
super
|
20
|
+
@ticket = ticket
|
21
|
+
@ticket_host = URI(return_to_url).host
|
22
|
+
end
|
23
|
+
def parse_user_info(node)
|
24
|
+
return nil if node.nil?
|
25
|
+
{}.tap do |hash|
|
26
|
+
node.xpath(NAME_ID_XPATH, SAML_NS).each {|n| hash['user'] = n.text }
|
27
|
+
hash['affiliations'] = node.xpath(AFFIL_VALUE_XPATH, SAML_NS).inject([]) {|m,v| m << v.text; m}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
def find_authentication_success(body)
|
31
|
+
return nil if body.nil? || body == ''
|
32
|
+
begin
|
33
|
+
doc = Nokogiri::XML(body)
|
34
|
+
begin
|
35
|
+
prefix = nil
|
36
|
+
doc.xpath('//sprot:Response',SAML_NS).each do |n|
|
37
|
+
n.namespace_definitions.each do |ns|
|
38
|
+
if ns.href == 'urn:oasis:names:tc:SAML:1.0:protocol'
|
39
|
+
prefix = ns.prefix
|
40
|
+
end
|
41
|
+
prefix ||= n.namespace.prefix
|
42
|
+
end
|
43
|
+
end
|
44
|
+
prefix = prefix + ':' if prefix
|
45
|
+
xpath = '//sprot:Response/sprot:Status/sprot:StatusCode[@Value=\'' + prefix + 'Success\']/../..'
|
46
|
+
doc.xpath(xpath, SAML_NS)
|
47
|
+
rescue Nokogiri::XML::XPath::SyntaxError
|
48
|
+
doc.xpath('//Response/Status/StatusCode[@Value=\'Success\']/../..')
|
49
|
+
end
|
50
|
+
rescue Nokogiri::XML::XPath::SyntaxError
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
def get_service_request_body
|
55
|
+
ART_TEMPLATE % [Time.now.utc.iso8601(3), @ticket]
|
56
|
+
end
|
57
|
+
# retrieves the `<sprot:Response>` XML from the CAS server
|
58
|
+
def get_service_response_body
|
59
|
+
result = ''
|
60
|
+
http = Net::HTTP.new(@uri.host, @uri.port)
|
61
|
+
http.use_ssl = @uri.port == 443 || @uri.instance_of?(URI::HTTPS)
|
62
|
+
if http.use_ssl?
|
63
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @options.disable_ssl_verification?
|
64
|
+
http.ca_path = @options.ca_path
|
65
|
+
end
|
66
|
+
http.start do |c|
|
67
|
+
body = get_service_request_body
|
68
|
+
headers = {
|
69
|
+
"Content-Type"=>"text/xml",
|
70
|
+
"Content-Length" => body.size.to_s,
|
71
|
+
'SOAPAction' => "http://www.oasis-open.org/committees/security"
|
72
|
+
}
|
73
|
+
response = c.post "#{@uri.path}?#{@uri.query}", body, headers
|
74
|
+
result = response.body
|
75
|
+
end
|
76
|
+
result
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,207 @@
|
|
1
|
+
# This is a clone of the OmniAuth CAS Strategy for SAML
|
2
|
+
# Copyright (c) 2011 Derek Lindahl and CustomInk, LLC
|
3
|
+
# distributed under the MIT license
|
4
|
+
# https://github.com/dlindahl/omniauth-cas
|
5
|
+
require 'omniauth'
|
6
|
+
require 'addressable/uri'
|
7
|
+
|
8
|
+
module OmniAuth
|
9
|
+
module Strategies
|
10
|
+
class SAML
|
11
|
+
include OmniAuth::Strategy
|
12
|
+
# Custom Exceptions
|
13
|
+
class MissingCASTicket < StandardError; end
|
14
|
+
class InvalidCASTicket < StandardError; end
|
15
|
+
autoload :ServiceTicketValidator, 'omni_auth/strategies/saml/service_ticket_validator'
|
16
|
+
autoload :LogoutRequest, 'omni_auth/strategies/saml/logout_request'
|
17
|
+
|
18
|
+
attr_accessor :raw_info
|
19
|
+
alias_method :user_info, :raw_info
|
20
|
+
|
21
|
+
SAML_NS = {
|
22
|
+
samla: "urn:oasis:names:tc:SAML:1.0:assertion",
|
23
|
+
sprot: "urn:oasis:names:tc:SAML:1.0:protocol",
|
24
|
+
}
|
25
|
+
option :name, :saml # Required property by OmniAuth::Strategy
|
26
|
+
|
27
|
+
option :host, 'cas.columbia.edu'
|
28
|
+
option :port, nil
|
29
|
+
option :path, nil
|
30
|
+
option :ssl, true
|
31
|
+
option :service_validate_url, '/samlValidate'
|
32
|
+
option :login_url, '/login'
|
33
|
+
option :service, nil
|
34
|
+
option :logout_url, '/logout'
|
35
|
+
option :on_single_sign_out, Proc.new {}
|
36
|
+
# A Proc or lambda that returns a Hash of additional user info to be
|
37
|
+
# merged with the info returned by the CAS server.
|
38
|
+
#
|
39
|
+
# @param [Object] An instance of OmniAuth::Strategies::CAS for the current request
|
40
|
+
# @param [String] The user's Service Ticket value
|
41
|
+
# @param [Hash] The user info for the Service Ticket returned by the CAS server
|
42
|
+
#
|
43
|
+
# @return [Hash] Extra user info
|
44
|
+
option :fetch_raw_info, Proc.new { Hash.new }
|
45
|
+
# Make all the keys configurable with some defaults set here
|
46
|
+
option :uid_field, 'user'
|
47
|
+
option :name_key, 'name'
|
48
|
+
option :email_key, 'email'
|
49
|
+
option :nickname_key, 'user'
|
50
|
+
option :first_name_key, 'first_name'
|
51
|
+
option :last_name_key, 'last_name'
|
52
|
+
option :location_key, 'location'
|
53
|
+
|
54
|
+
# As required by https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema
|
55
|
+
AuthHashSchemaKeys = %w{name email nickname first_name last_name location}
|
56
|
+
info do
|
57
|
+
prune!({
|
58
|
+
name: raw_info[options[:name_key].to_s],
|
59
|
+
email: raw_info[options[:email_key].to_s],
|
60
|
+
nickname: raw_info[options[:nickname_key].to_s],
|
61
|
+
first_name: raw_info[options[:first_name_key].to_s],
|
62
|
+
last_name: raw_info[options[:last_name_key].to_s],
|
63
|
+
location: raw_info[options[:location_key].to_s],
|
64
|
+
})
|
65
|
+
end
|
66
|
+
|
67
|
+
extra do
|
68
|
+
prune!(
|
69
|
+
raw_info.delete_if{ |k,v| AuthHashSchemaKeys.include?(k) }
|
70
|
+
)
|
71
|
+
end
|
72
|
+
|
73
|
+
uid do
|
74
|
+
raw_info[options[:uid_field].to_s]
|
75
|
+
end
|
76
|
+
|
77
|
+
credentials do
|
78
|
+
prune!({ ticket: @ticket })
|
79
|
+
end
|
80
|
+
|
81
|
+
def login_url(service)
|
82
|
+
cas_url + append_params(options.login_url, { TARGET: service })
|
83
|
+
end
|
84
|
+
def logout_url(service)
|
85
|
+
cas_url + append_params(options.logout_url, { service: service})
|
86
|
+
end
|
87
|
+
# Build a CAS host with protocol and port
|
88
|
+
#
|
89
|
+
#
|
90
|
+
def cas_url
|
91
|
+
extract_url if options['url']
|
92
|
+
validate_cas_setup
|
93
|
+
@cas_url ||= begin
|
94
|
+
uri = Addressable::URI.new
|
95
|
+
uri.host = options.host
|
96
|
+
uri.scheme = options.ssl ? 'https' : 'http'
|
97
|
+
uri.port = options.port
|
98
|
+
uri.path = options.path
|
99
|
+
uri.to_s
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def extract_url
|
104
|
+
url = Addressable::URI.parse(options.delete('url'))
|
105
|
+
options.merge!(
|
106
|
+
'host' => url.host,
|
107
|
+
'port' => url.port,
|
108
|
+
'path' => url.path,
|
109
|
+
'ssl' => url.scheme == 'https'
|
110
|
+
)
|
111
|
+
end
|
112
|
+
|
113
|
+
def validate_cas_setup
|
114
|
+
if options.host.nil? || options.login_url.nil?
|
115
|
+
raise ArgumentError.new(":host and :login_url MUST be provided")
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def service_validate_url(service_url, ticket)
|
120
|
+
service_url = Addressable::URI.parse(service_url).origin
|
121
|
+
parms = {
|
122
|
+
TARGET: service_url,
|
123
|
+
# service: service_url,
|
124
|
+
# ticket: ticket
|
125
|
+
}
|
126
|
+
r = cas_url + append_params(options.service_validate_url, parms)
|
127
|
+
r
|
128
|
+
end
|
129
|
+
|
130
|
+
def callback_phase
|
131
|
+
if on_sso_path?
|
132
|
+
single_sign_out_phase
|
133
|
+
else
|
134
|
+
@ticket = request.params['SAMLart']
|
135
|
+
return fail!(:no_ticket, MissingCASTicket.new('No CAS Ticket')) unless @ticket
|
136
|
+
fetch_raw_info(@ticket)
|
137
|
+
return fail!(:invalid_ticket, InvalidCASTicket.new('Invalid CAS Ticket')) if raw_info.empty?
|
138
|
+
super
|
139
|
+
end
|
140
|
+
end
|
141
|
+
def request_phase
|
142
|
+
service_url = append_params(callback_url, return_url)
|
143
|
+
|
144
|
+
[
|
145
|
+
302,
|
146
|
+
{
|
147
|
+
'Location' => login_url(service_url),
|
148
|
+
'Content-Type' => 'text/plain'
|
149
|
+
},
|
150
|
+
["You are being redirected to CAS for sign-in."]
|
151
|
+
]
|
152
|
+
end
|
153
|
+
|
154
|
+
def on_sso_path?
|
155
|
+
request.post? && request.params.has_key?('logoutRequest')
|
156
|
+
end
|
157
|
+
|
158
|
+
def single_sign_out_phase
|
159
|
+
logout_request_service.new(self, request).call(options)
|
160
|
+
end
|
161
|
+
|
162
|
+
def append_params(base, params)
|
163
|
+
params = params.each { |k,v| v = Rack::Utils.escape(v) }
|
164
|
+
Addressable::URI.parse(base).tap do |base_uri|
|
165
|
+
base_uri.query_values = (base_uri.query_values || {}).merge(params)
|
166
|
+
end.to_s
|
167
|
+
end
|
168
|
+
|
169
|
+
# Validate the Service Ticket
|
170
|
+
# @return [Object] the validated Service Ticket
|
171
|
+
def validate_service_ticket(ticket)
|
172
|
+
OmniAuth::Strategies::SAML::ServiceTicketValidator.new(self, options, callback_url, ticket).call
|
173
|
+
end
|
174
|
+
|
175
|
+
private
|
176
|
+
|
177
|
+
def fetch_raw_info(ticket)
|
178
|
+
ticket_user_info = validate_service_ticket(ticket).user_info
|
179
|
+
custom_user_info = options.fetch_raw_info.call(self, options, ticket, ticket_user_info)
|
180
|
+
self.raw_info = ticket_user_info.merge(custom_user_info)
|
181
|
+
end
|
182
|
+
|
183
|
+
# Deletes Hash pairs with `nil` values.
|
184
|
+
# From https://github.com/mkdynamic/omniauth-facebook/blob/972ed5e3456bcaed7df1f55efd7c05c216c8f48e/lib/omniauth/strategies/facebook.rb#L122-127
|
185
|
+
def prune!(hash)
|
186
|
+
hash.delete_if do |_, value|
|
187
|
+
prune!(value) if value.is_a?(Hash)
|
188
|
+
value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def return_url
|
193
|
+
# If the request already has a `url` parameter, then it will already be appended to the callback URL.
|
194
|
+
if request.params && request.params['url']
|
195
|
+
{}
|
196
|
+
else
|
197
|
+
{ url: request.referer }
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def logout_request_service
|
202
|
+
LogoutRequest
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
OmniAuth.config.add_camelization 'saml', 'SAML'
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# This is a clone of the OmniAuth CAS LogoutRequest for WIND
|
2
|
+
# Copyright (c) 2011 Derek Lindahl and CustomInk, LLC
|
3
|
+
# distributed under the MIT license
|
4
|
+
# https://github.com/dlindahl/omniauth-cas
|
5
|
+
module OmniAuth
|
6
|
+
module Strategies
|
7
|
+
class WIND
|
8
|
+
class LogoutRequest
|
9
|
+
def initialize(strategy, request)
|
10
|
+
@strategy, @request = strategy, request
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(options = {})
|
14
|
+
@options = options
|
15
|
+
|
16
|
+
begin
|
17
|
+
result = single_sign_out_callback.call(*logout_request)
|
18
|
+
rescue StandardError => err
|
19
|
+
return @strategy.fail! :logout_request, err
|
20
|
+
else
|
21
|
+
result = [200,{},'OK'] if result == true || result.nil?
|
22
|
+
ensure
|
23
|
+
return unless result
|
24
|
+
|
25
|
+
# TODO: Why does ActionPack::Response return [status,headers,body]
|
26
|
+
# when Rack::Response#new wants [body,status,headers]? Additionally,
|
27
|
+
# why does Rack::Response differ in argument order from the usual
|
28
|
+
# Rack-like [status,headers,body] array?
|
29
|
+
return Rack::Response.new(result[2],result[0],result[1]).finish
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def logout_request
|
36
|
+
@logout_request ||= begin
|
37
|
+
saml = Nokogiri.parse(@request.params['logoutRequest'])
|
38
|
+
name_id = saml.xpath('//saml:NameID').text
|
39
|
+
sess_idx = saml.xpath('//samlp:SessionIndex').text
|
40
|
+
inject_params(name_id:name_id, session_index:sess_idx)
|
41
|
+
@request
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def inject_params(new_params)
|
46
|
+
rack_input = @request.env['rack.input'].read
|
47
|
+
params = Rack::Utils.parse_query(rack_input, '&').merge new_params
|
48
|
+
@request.env['rack.input'] = StringIO.new(Rack::Utils.build_query(params))
|
49
|
+
rescue
|
50
|
+
# A no-op intended to ensure that the ensure block is run
|
51
|
+
raise
|
52
|
+
ensure
|
53
|
+
@request.env['rack.input'].rewind
|
54
|
+
end
|
55
|
+
|
56
|
+
def single_sign_out_callback
|
57
|
+
@options[:on_single_sign_out]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# This is a clone of the OmniAuth CAS ServiceTicketValidator for WIND
|
2
|
+
# Copyright (c) 2011 Derek Lindahl and CustomInk, LLC
|
3
|
+
# distributed under the MIT license
|
4
|
+
# https://github.com/dlindahl/omniauth-cas
|
5
|
+
module OmniAuth
|
6
|
+
module Strategies
|
7
|
+
class WIND
|
8
|
+
class ServiceTicketValidator < OmniAuth::Strategies::CAS::ServiceTicketValidator
|
9
|
+
NS = {wind: 'http://www.columbia.edu/acis/rad/authmethods/wind'}
|
10
|
+
def parse_user_info(node)
|
11
|
+
return nil if node.nil?
|
12
|
+
|
13
|
+
{}.tap do |hash|
|
14
|
+
node.children.each do |e|
|
15
|
+
node_name = e.name.sub(/^wind:/, '')
|
16
|
+
unless e.kind_of?(Nokogiri::XML::Text) || node_name == 'proxies'
|
17
|
+
# There are no child elements
|
18
|
+
if e.element_children.count == 0
|
19
|
+
hash[node_name] = e.content
|
20
|
+
elsif e.element_children.count
|
21
|
+
# WIND style affiliations
|
22
|
+
if node_name == 'affiliations'
|
23
|
+
hash.merge!(affiliations: e.xpath('wind:affil',NS).collect {|x| x.text})
|
24
|
+
else
|
25
|
+
hash[node_name] = [] if hash[node_name].nil?
|
26
|
+
hash[node_name].push(parse_user_info(e))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
def find_authentication_success(body)
|
34
|
+
return nil if body.nil? || body == ''
|
35
|
+
begin
|
36
|
+
doc = Nokogiri::XML(body)
|
37
|
+
begin
|
38
|
+
doc.xpath('/wind:serviceResponse/wind:authenticationSuccess', NS)
|
39
|
+
rescue Nokogiri::XML::XPath::SyntaxError
|
40
|
+
doc.xpath('/serviceResponse/authenticationSuccess')
|
41
|
+
end
|
42
|
+
rescue Nokogiri::XML::XPath::SyntaxError
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,201 @@
|
|
1
|
+
# This is a clone of the OmniAuth CAS Strategy for WIND
|
2
|
+
# Copyright (c) 2011 Derek Lindahl and CustomInk, LLC
|
3
|
+
# distributed under the MIT license
|
4
|
+
# https://github.com/dlindahl/omniauth-cas
|
5
|
+
require 'omniauth'
|
6
|
+
require 'addressable/uri'
|
7
|
+
|
8
|
+
module OmniAuth
|
9
|
+
module Strategies
|
10
|
+
class WIND
|
11
|
+
include OmniAuth::Strategy
|
12
|
+
# Custom Exceptions
|
13
|
+
class MissingWINDTicket < StandardError; end
|
14
|
+
class InvalidWINDTicket < StandardError; end
|
15
|
+
autoload :ServiceTicketValidator, 'omni_auth/strategies/wind/service_ticket_validator'
|
16
|
+
autoload :LogoutRequest, 'omni_auth/strategies/wind/logout_request'
|
17
|
+
|
18
|
+
attr_accessor :raw_info
|
19
|
+
alias_method :user_info, :raw_info
|
20
|
+
|
21
|
+
option :name, :wind # Required property by OmniAuth::Strategy
|
22
|
+
|
23
|
+
option :host, 'wind.columbia.edu'
|
24
|
+
option :port, nil
|
25
|
+
option :path, nil
|
26
|
+
option :ssl, true
|
27
|
+
option :service_validate_url, '/validate'
|
28
|
+
option :login_url, '/login'
|
29
|
+
option :service, nil
|
30
|
+
option :logout_url, '/logout'
|
31
|
+
option :on_single_sign_out, Proc.new {}
|
32
|
+
# A Proc or lambda that returns a Hash of additional user info to be
|
33
|
+
# merged with the info returned by the CAS server.
|
34
|
+
#
|
35
|
+
# @param [Object] An instance of OmniAuth::Strategies::CAS for the current request
|
36
|
+
# @param [String] The user's Service Ticket value
|
37
|
+
# @param [Hash] The user info for the Service Ticket returned by the CAS server
|
38
|
+
#
|
39
|
+
# @return [Hash] Extra user info
|
40
|
+
option :fetch_raw_info, Proc.new { Hash.new }
|
41
|
+
# Make all the keys configurable with some defaults set here
|
42
|
+
option :uid_field, 'user'
|
43
|
+
option :name_key, 'name'
|
44
|
+
option :email_key, 'email'
|
45
|
+
option :nickname_key, 'user'
|
46
|
+
option :first_name_key, 'first_name'
|
47
|
+
option :last_name_key, 'last_name'
|
48
|
+
option :location_key, 'location'
|
49
|
+
|
50
|
+
# As required by https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema
|
51
|
+
AuthHashSchemaKeys = %w{name email nickname first_name last_name location}
|
52
|
+
info do
|
53
|
+
prune!({
|
54
|
+
name: raw_info[options[:name_key].to_s],
|
55
|
+
email: raw_info[options[:email_key].to_s],
|
56
|
+
nickname: raw_info[options[:nickname_key].to_s],
|
57
|
+
first_name: raw_info[options[:first_name_key].to_s],
|
58
|
+
last_name: raw_info[options[:last_name_key].to_s],
|
59
|
+
location: raw_info[options[:location_key].to_s],
|
60
|
+
})
|
61
|
+
end
|
62
|
+
|
63
|
+
extra do
|
64
|
+
prune!(
|
65
|
+
raw_info.delete_if{ |k,v| AuthHashSchemaKeys.include?(k) }
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
uid do
|
70
|
+
raw_info[options[:uid_field].to_s]
|
71
|
+
end
|
72
|
+
|
73
|
+
credentials do
|
74
|
+
prune!({ ticket: @ticket })
|
75
|
+
end
|
76
|
+
|
77
|
+
def login_url(service)
|
78
|
+
wind_url + append_params(options.login_url, { destination: service, service: options.service })
|
79
|
+
end
|
80
|
+
def logout_url(service)
|
81
|
+
wind_url + append_params(options.logout_url, { destination: service})
|
82
|
+
end
|
83
|
+
# Build a WIND host with protocol and port
|
84
|
+
#
|
85
|
+
#
|
86
|
+
def wind_url
|
87
|
+
extract_url if options['url']
|
88
|
+
validate_wind_setup
|
89
|
+
@wind_url ||= begin
|
90
|
+
uri = Addressable::URI.new
|
91
|
+
uri.host = options.host
|
92
|
+
uri.scheme = options.ssl ? 'https' : 'http'
|
93
|
+
uri.port = options.port
|
94
|
+
uri.path = options.path
|
95
|
+
uri.to_s
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def extract_url
|
100
|
+
url = Addressable::URI.parse(options.delete('url'))
|
101
|
+
options.merge!(
|
102
|
+
'host' => url.host,
|
103
|
+
'port' => url.port,
|
104
|
+
'path' => url.path,
|
105
|
+
'ssl' => url.scheme == 'https'
|
106
|
+
)
|
107
|
+
end
|
108
|
+
|
109
|
+
def validate_wind_setup
|
110
|
+
if options.host.nil? || options.login_url.nil?
|
111
|
+
raise ArgumentError.new(":host and :login_url MUST be provided")
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def service_validate_url(service_url, ticket)
|
116
|
+
service_url = Addressable::URI.parse(service_url)
|
117
|
+
service_url.query_values = service_url.query_values.tap { |qs| qs.delete('ticketid') }
|
118
|
+
r = wind_url + append_params(options.service_validate_url, {
|
119
|
+
ticketid: ticket
|
120
|
+
})
|
121
|
+
r
|
122
|
+
end
|
123
|
+
|
124
|
+
def callback_phase
|
125
|
+
if on_sso_path?
|
126
|
+
single_sign_out_phase
|
127
|
+
else
|
128
|
+
@ticket = request.params['ticketid']
|
129
|
+
return fail!(:no_ticket, MissingWINDTicket.new('No WIND Ticket')) unless @ticket
|
130
|
+
fetch_raw_info(@ticket)
|
131
|
+
return fail!(:invalid_ticket, InvalidWINDTicket.new('Invalid WIND Ticket')) if raw_info.empty?
|
132
|
+
super
|
133
|
+
end
|
134
|
+
end
|
135
|
+
def request_phase
|
136
|
+
service_url = append_params(callback_url, return_url)
|
137
|
+
|
138
|
+
[
|
139
|
+
302,
|
140
|
+
{
|
141
|
+
'Location' => login_url(service_url),
|
142
|
+
'Content-Type' => 'text/plain'
|
143
|
+
},
|
144
|
+
["You are being redirected to WIND for sign-in."]
|
145
|
+
]
|
146
|
+
end
|
147
|
+
|
148
|
+
def on_sso_path?
|
149
|
+
request.post? && request.params.has_key?('logoutRequest')
|
150
|
+
end
|
151
|
+
|
152
|
+
def single_sign_out_phase
|
153
|
+
logout_request_service.new(self, request).call(options)
|
154
|
+
end
|
155
|
+
|
156
|
+
def append_params(base, params)
|
157
|
+
params = params.each { |k,v| v = Rack::Utils.escape(v) }
|
158
|
+
Addressable::URI.parse(base).tap do |base_uri|
|
159
|
+
base_uri.query_values = (base_uri.query_values || {}).merge(params)
|
160
|
+
end.to_s
|
161
|
+
end
|
162
|
+
|
163
|
+
# Validate the Service Ticket
|
164
|
+
# @return [Object] the validated Service Ticket
|
165
|
+
def validate_service_ticket(ticket)
|
166
|
+
OmniAuth::Strategies::WIND::ServiceTicketValidator.new(self, options, callback_url, ticket).call
|
167
|
+
end
|
168
|
+
|
169
|
+
private
|
170
|
+
|
171
|
+
def fetch_raw_info(ticket)
|
172
|
+
ticket_user_info = validate_service_ticket(ticket).user_info
|
173
|
+
custom_user_info = options.fetch_raw_info.call(self, options, ticket, ticket_user_info)
|
174
|
+
self.raw_info = ticket_user_info.merge(custom_user_info)
|
175
|
+
end
|
176
|
+
|
177
|
+
# Deletes Hash pairs with `nil` values.
|
178
|
+
# From https://github.com/mkdynamic/omniauth-facebook/blob/972ed5e3456bcaed7df1f55efd7c05c216c8f48e/lib/omniauth/strategies/facebook.rb#L122-127
|
179
|
+
def prune!(hash)
|
180
|
+
hash.delete_if do |_, value|
|
181
|
+
prune!(value) if value.is_a?(Hash)
|
182
|
+
value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def return_url
|
187
|
+
# If the request already has a `url` parameter, then it will already be appended to the callback URL.
|
188
|
+
if request.params && request.params['url']
|
189
|
+
{}
|
190
|
+
else
|
191
|
+
{ url: request.referer }
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def logout_request_service
|
196
|
+
LogoutRequest
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
OmniAuth.config.add_camelization 'wind', 'WIND'
|
metadata
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cul_omniauth
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- barmintor
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-07-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: devise-guests
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.3'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: omniauth-cas
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: cancan
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: sqlite3
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 3.0.0
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 3.0.0
|
97
|
+
description: Engine and model mixins for Omniauth with CAS and SSL.
|
98
|
+
email:
|
99
|
+
- LASTNAME at gmail
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- MIT-LICENSE
|
105
|
+
- Rakefile
|
106
|
+
- app/assets/javascripts/cul/omniauth/application.js
|
107
|
+
- app/assets/stylesheets/cul/omniauth/application.css
|
108
|
+
- app/controllers/concerns/cul/omniauth/callbacks.rb
|
109
|
+
- app/controllers/concerns/cul/omniauth/remote_ip_ability.rb
|
110
|
+
- app/controllers/cul/omniauth/application_controller.rb
|
111
|
+
- app/controllers/cul/omniauth/callbacks_controller.rb
|
112
|
+
- app/helpers/cul/omniauth/application_helper.rb
|
113
|
+
- app/models/concerns/cul/omniauth/abilities.rb
|
114
|
+
- app/models/concerns/cul/omniauth/users.rb
|
115
|
+
- app/views/layouts/cul/omniauth/application.html.erb
|
116
|
+
- config/routes.rb
|
117
|
+
- lib/cul/omniauth/ability_proxy.rb
|
118
|
+
- lib/cul/omniauth/engine.rb
|
119
|
+
- lib/cul/omniauth/failure_app.rb
|
120
|
+
- lib/cul/omniauth/file_configurable.rb
|
121
|
+
- lib/cul/omniauth/version.rb
|
122
|
+
- lib/cul_omniauth.rb
|
123
|
+
- lib/omni_auth/strategies/saml.rb
|
124
|
+
- lib/omni_auth/strategies/saml/service_ticket_validator.rb
|
125
|
+
- lib/omni_auth/strategies/wind.rb
|
126
|
+
- lib/omni_auth/strategies/wind/logout_request.rb
|
127
|
+
- lib/omni_auth/strategies/wind/service_ticket_validator.rb
|
128
|
+
- lib/tasks/cul_omniauth_tasks.rake
|
129
|
+
homepage: https://github.com/cul/cul_omniauth
|
130
|
+
licenses:
|
131
|
+
- MIT
|
132
|
+
metadata: {}
|
133
|
+
post_install_message:
|
134
|
+
rdoc_options: []
|
135
|
+
require_paths:
|
136
|
+
- lib
|
137
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
138
|
+
requirements:
|
139
|
+
- - ">="
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - ">="
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: '0'
|
147
|
+
requirements: []
|
148
|
+
rubyforge_project:
|
149
|
+
rubygems_version: 2.4.6
|
150
|
+
signing_key:
|
151
|
+
specification_version: 4
|
152
|
+
summary: Omniauth engine for CUL web apps.
|
153
|
+
test_files: []
|