cul_omniauth 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|