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 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: []