cul_omniauth 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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,6 @@
1
+ module Cul::Omniauth::RemoteIpAbility
2
+ extend ActiveSupport::Concern
3
+ def current_ability
4
+ @current_ability ||= Ability.new(current_user, remote_ip:request.remote_ip)
5
+ end
6
+ end
@@ -0,0 +1,4 @@
1
+ module Cul::Omniauth
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,3 @@
1
+ class Cul::Omniauth::CallbacksController < Devise::OmniauthCallbacksController
2
+ include Cul::Omniauth::Callbacks
3
+ end
@@ -0,0 +1,4 @@
1
+ module Cul::Omniauth
2
+ module ApplicationHelper
3
+ end
4
+ 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,2 @@
1
+ Cul::Omniauth::Engine.routes.draw do
2
+ end
@@ -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,5 @@
1
+ module Cul::Omniauth
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Cul::Omniauth
4
+ end
5
+ 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
@@ -0,0 +1,5 @@
1
+ module Cul
2
+ module Omniauth
3
+ VERSION = "0.2.0"
4
+ end
5
+ end
@@ -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'
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :cul_omniauth do
3
+ # # Task goes here
4
+ # end
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: []