Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gems/omniauth-cas3/lib')
-rw-r--r--vendor/gems/omniauth-cas3/lib/omniauth-cas3.rb1
-rw-r--r--vendor/gems/omniauth-cas3/lib/omniauth/cas3.rb2
-rw-r--r--vendor/gems/omniauth-cas3/lib/omniauth/cas3/version.rb5
-rw-r--r--vendor/gems/omniauth-cas3/lib/omniauth/strategies/cas3.rb222
-rw-r--r--vendor/gems/omniauth-cas3/lib/omniauth/strategies/cas3/logout_request.rb73
-rw-r--r--vendor/gems/omniauth-cas3/lib/omniauth/strategies/cas3/service_ticket_validator.rb103
6 files changed, 406 insertions, 0 deletions
diff --git a/vendor/gems/omniauth-cas3/lib/omniauth-cas3.rb b/vendor/gems/omniauth-cas3/lib/omniauth-cas3.rb
new file mode 100644
index 00000000000..58509b933c8
--- /dev/null
+++ b/vendor/gems/omniauth-cas3/lib/omniauth-cas3.rb
@@ -0,0 +1 @@
+require 'omniauth/cas3'
diff --git a/vendor/gems/omniauth-cas3/lib/omniauth/cas3.rb b/vendor/gems/omniauth-cas3/lib/omniauth/cas3.rb
new file mode 100644
index 00000000000..80460aa1f31
--- /dev/null
+++ b/vendor/gems/omniauth-cas3/lib/omniauth/cas3.rb
@@ -0,0 +1,2 @@
+require 'omniauth/cas3/version'
+require 'omniauth/strategies/cas3' \ No newline at end of file
diff --git a/vendor/gems/omniauth-cas3/lib/omniauth/cas3/version.rb b/vendor/gems/omniauth-cas3/lib/omniauth/cas3/version.rb
new file mode 100644
index 00000000000..9508dd69125
--- /dev/null
+++ b/vendor/gems/omniauth-cas3/lib/omniauth/cas3/version.rb
@@ -0,0 +1,5 @@
+module Omniauth
+ module Cas3
+ VERSION = '1.1.4'
+ end
+end
diff --git a/vendor/gems/omniauth-cas3/lib/omniauth/strategies/cas3.rb b/vendor/gems/omniauth-cas3/lib/omniauth/strategies/cas3.rb
new file mode 100644
index 00000000000..7271621c564
--- /dev/null
+++ b/vendor/gems/omniauth-cas3/lib/omniauth/strategies/cas3.rb
@@ -0,0 +1,222 @@
+require 'omniauth'
+require 'addressable/uri'
+
+module OmniAuth
+ module Strategies
+ class CAS3
+ include OmniAuth::Strategy
+
+ # Custom Exceptions
+ class MissingCASTicket < StandardError; end
+ class InvalidCASTicket < StandardError; end
+
+ autoload :ServiceTicketValidator, 'omniauth/strategies/cas3/service_ticket_validator'
+ autoload :LogoutRequest, 'omniauth/strategies/cas3/logout_request'
+
+ attr_accessor :raw_info
+ alias_method :user_info, :raw_info
+
+ option :name, :cas3 # Required property by OmniAuth::Strategy
+
+ option :host, nil
+ option :port, nil
+ option :path, nil
+ option :ssl, true
+ option :service_validate_url, '/p3/serviceValidate'
+ option :login_url, '/login'
+ option :logout_url, '/logout'
+ option :on_single_sign_out, Proc.new {}
+ # A Proc or lambda that returns a Hash of additional user info to be
+ # merged with the info returned by the CAS server.
+ #
+ # @param [Object] An instance of OmniAuth::Strategies::CAS for the current request
+ # @param [String] The user's Service Ticket value
+ # @param [Hash] The user info for the Service Ticket returned by the CAS server
+ #
+ # @return [Hash] Extra user info
+ option :fetch_raw_info, Proc.new { Hash.new }
+ # Make all the keys configurable with some defaults set here
+ option :uid_field, 'user'
+ option :name_key, 'name'
+ option :email_key, 'email'
+ option :nickname_key, 'user'
+ option :first_name_key, 'first_name'
+ option :last_name_key, 'last_name'
+ option :location_key, 'location'
+ option :image_key, 'image'
+ option :phone_key, 'phone'
+
+ # As required by https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema
+ AuthHashSchemaKeys = %w{name email nickname first_name last_name location image phone}
+ info do
+ prune!({
+ name: raw_info[options[:name_key].to_s],
+ email: raw_info[options[:email_key].to_s],
+ nickname: raw_info[options[:nickname_key].to_s],
+ first_name: raw_info[options[:first_name_key].to_s],
+ last_name: raw_info[options[:last_name_key].to_s],
+ location: raw_info[options[:location_key].to_s],
+ image: raw_info[options[:image_key].to_s],
+ phone: raw_info[options[:phone_key].to_s]
+ })
+ end
+
+ extra do
+ prune!(
+ raw_info.delete_if{ |k,v| AuthHashSchemaKeys.include?(k) }
+ )
+ end
+
+ uid do
+ raw_info[options[:uid_field].to_s]
+ end
+
+ credentials do
+ prune!({ ticket: @ticket })
+ end
+
+ def callback_phase
+ if on_sso_path?
+ single_sign_out_phase
+ else
+ @ticket = request.params['ticket']
+ return fail!(:no_ticket, MissingCASTicket.new('No CAS Ticket')) unless @ticket
+ fetch_raw_info(@ticket)
+ return fail!(:invalid_ticket, InvalidCASTicket.new('Invalid CAS Ticket')) if raw_info.empty?
+ super
+ end
+ end
+
+ def request_phase
+ service_url = append_params(callback_url, return_url)
+
+ [
+ 302,
+ {
+ 'Location' => login_url(service_url),
+ 'Content-Type' => 'text/plain'
+ },
+ ["You are being redirected to CAS for sign-in."]
+ ]
+ end
+
+ def on_sso_path?
+ request.post? && request.params.has_key?('logoutRequest')
+ end
+
+ def single_sign_out_phase
+ logout_request_service.new(self, request).call(options)
+ end
+
+ # Build a CAS host with protocol and port
+ #
+ #
+ def cas_url
+ extract_url if options['url']
+ validate_cas_setup
+ @cas_url ||= begin
+ uri = Addressable::URI.new
+ uri.host = options.host
+ uri.scheme = options.ssl ? 'https' : 'http'
+ uri.port = options.port
+ uri.path = options.path
+ uri.to_s
+ end
+ end
+
+ def extract_url
+ url = Addressable::URI.parse(options.delete('url'))
+ options.merge!(
+ 'host' => url.host,
+ 'port' => url.port,
+ 'path' => url.path,
+ 'ssl' => url.scheme == 'https'
+ )
+ end
+
+ def validate_cas_setup
+ if options.host.nil? || options.login_url.nil?
+ raise ArgumentError.new(":host and :login_url MUST be provided")
+ end
+ end
+
+ # Build a service-validation URL from +service+ and +ticket+.
+ # If +service+ has a ticket param, first remove it. URL-encode
+ # +service+ and add it and the +ticket+ as paraemters to the
+ # CAS serviceValidate URL.
+ #
+ # @param [String] service the service (a.k.a. return-to) URL
+ # @param [String] ticket the ticket to validate
+ #
+ # @return [String] a URL like `http://cas.mycompany.com/serviceValidate?service=...&ticket=...`
+ def service_validate_url(service_url, ticket)
+ service_url = Addressable::URI.parse(service_url)
+ service_url.query_values = service_url.query_values.tap { |qs| qs.delete('ticket') }
+ cas_url + append_params(options.service_validate_url, {
+ service: service_url.to_s,
+ ticket: ticket
+ })
+ end
+
+ # Build a CAS login URL from +service+.
+ #
+ # @param [String] service the service (a.k.a. return-to) URL
+ #
+ # @return [String] a URL like `http://cas.mycompany.com/login?service=...`
+ def login_url(service)
+ cas_url + append_params(options.login_url, { service: service })
+ end
+
+ # Adds URL-escaped +parameters+ to +base+.
+ #
+ # @param [String] base the base URL
+ # @param [String] params the parameters to append to the URL
+ #
+ # @return [String] the new joined URL.
+ def append_params(base, params)
+ params = params.each { |k,v| v = Rack::Utils.escape(v) }
+ Addressable::URI.parse(base).tap do |base_uri|
+ base_uri.query_values = (base_uri.query_values || {}).merge(params)
+ end.to_s
+ end
+
+ # Validate the Service Ticket
+ # @return [Object] the validated Service Ticket
+ def validate_service_ticket(ticket)
+ ServiceTicketValidator.new(self, options, callback_url, ticket).call
+ end
+
+ private
+
+ def fetch_raw_info(ticket)
+ ticket_user_info = validate_service_ticket(ticket).user_info
+ custom_user_info = options.fetch_raw_info.call(self, options, ticket, ticket_user_info)
+ self.raw_info = ticket_user_info.merge(custom_user_info)
+ end
+
+ # Deletes Hash pairs with `nil` values.
+ # From https://github.com/mkdynamic/omniauth-facebook/blob/972ed5e3456bcaed7df1f55efd7c05c216c8f48e/lib/omniauth/strategies/facebook.rb#L122-127
+ def prune!(hash)
+ hash.delete_if do |_, value|
+ prune!(value) if value.is_a?(Hash)
+ value.nil? || (value.respond_to?(:empty?) && value.empty?)
+ end
+ end
+
+ def return_url
+ # If the request already has a `url` parameter, then it will already be appended to the callback URL.
+ if request.params && request.params['url']
+ {}
+ else
+ { url: request.referer }
+ end
+ end
+
+ def logout_request_service
+ LogoutRequest
+ end
+ end
+ end
+end
+
+OmniAuth.config.add_camelization 'cas3', 'CAS3'
diff --git a/vendor/gems/omniauth-cas3/lib/omniauth/strategies/cas3/logout_request.rb b/vendor/gems/omniauth-cas3/lib/omniauth/strategies/cas3/logout_request.rb
new file mode 100644
index 00000000000..72978227edb
--- /dev/null
+++ b/vendor/gems/omniauth-cas3/lib/omniauth/strategies/cas3/logout_request.rb
@@ -0,0 +1,73 @@
+module OmniAuth
+ module Strategies
+ class CAS3
+ class LogoutRequest
+ def initialize(strategy, request)
+ @strategy, @request = strategy, request
+ end
+
+ def call(options = {})
+ @options = options
+
+ begin
+ result = single_sign_out_callback.call(*logout_request)
+ rescue StandardError => err
+ return @strategy.fail! :logout_request, err
+ else
+ result = [200,{},'OK'] if result == true || result.nil?
+ ensure
+ return unless result
+
+ # TODO: Why does ActionPack::Response return [status,headers,body]
+ # when Rack::Response#new wants [body,status,headers]? Additionally,
+ # why does Rack::Response differ in argument order from the usual
+ # Rack-like [status,headers,body] array?
+ return Rack::Response.new(result[2],result[0],result[1]).finish
+ end
+ end
+
+ private
+
+ def logout_request
+ @logout_request ||= begin
+ saml = parse_and_ensure_namespaces(@request.params['logoutRequest'])
+ ns = saml.collect_namespaces
+ name_id = saml.xpath('//saml:NameID', ns).text
+ sess_idx = saml.xpath('//samlp:SessionIndex', ns).text
+ inject_params(name_id:name_id, session_index:sess_idx)
+ @request
+ end
+ end
+
+ def parse_and_ensure_namespaces(logout_request_xml)
+ doc = Nokogiri.parse(logout_request_xml)
+ ns = doc.collect_namespaces
+ if ns.include?('xmlns:samlp') && ns.include?('xmlns:saml')
+ doc
+ else
+ add_namespaces(doc)
+ end
+ end
+
+ def add_namespaces(logout_request_doc)
+ root = logout_request_doc.root
+ root.add_namespace('samlp', 'urn:oasis:names:tc:SAML:2.0:protocol')
+ root.add_namespace('saml', 'urn:oasis:names:tc:SAML:2.0:assertion\\')
+
+ # In order to add namespaces properly we need to re-parse the document
+ Nokogiri.parse(logout_request_doc.to_s)
+ end
+
+ def inject_params(new_params)
+ new_params.each do |key, val|
+ @request.update_param(key, val)
+ end
+ end
+
+ def single_sign_out_callback
+ @options[:on_single_sign_out]
+ end
+ end
+ end
+ end
+end
diff --git a/vendor/gems/omniauth-cas3/lib/omniauth/strategies/cas3/service_ticket_validator.rb b/vendor/gems/omniauth-cas3/lib/omniauth/strategies/cas3/service_ticket_validator.rb
new file mode 100644
index 00000000000..4f9a61c5216
--- /dev/null
+++ b/vendor/gems/omniauth-cas3/lib/omniauth/strategies/cas3/service_ticket_validator.rb
@@ -0,0 +1,103 @@
+require 'net/http'
+require 'net/https'
+require 'nokogiri'
+
+module OmniAuth
+ module Strategies
+ class CAS3
+ class ServiceTicketValidator
+ VALIDATION_REQUEST_HEADERS = { 'Accept' => '*/*' }
+
+ # Build a validator from a +configuration+, a
+ # +return_to+ URL, and a +ticket+.
+ #
+ # @param [Hash] options the OmniAuth Strategy options
+ # @param [String] return_to_url the URL of this CAS client service
+ # @param [String] ticket the service ticket to validate
+ def initialize(strategy, options, return_to_url, ticket)
+ @options = options
+ @uri = URI.parse(strategy.service_validate_url(return_to_url, ticket))
+ end
+
+ # Executes a network request to process the CAS Service Response
+ def call
+ @response_body = get_service_response_body
+ @success_body = find_authentication_success(@response_body)
+ self
+ end
+
+ # Request validation of the ticket from the CAS server's
+ # serviceValidate (CAS 2.0) function.
+ #
+ # Swallows all XML parsing errors (and returns +nil+ in those cases).
+ #
+ # @return [Hash, nil] a user information hash if the response is valid; +nil+ otherwise.
+ #
+ # @raise any connection errors encountered.
+ def user_info
+ parse_user_info(@success_body)
+ end
+
+ private
+
+ # turns an `<cas:authenticationSuccess>` node into a Hash;
+ # returns nil if given nil
+ def parse_user_info(node)
+ return nil if node.nil?
+ {}.tap do |hash|
+ node.children.each do |e|
+ node_name = e.name.sub(/^cas:/, '')
+ unless e.kind_of?(Nokogiri::XML::Text) || node_name == 'proxies'
+ # There are no child elements
+ if e.element_children.count == 0
+ hash[node_name] = e.content
+ elsif e.element_children.count
+ # JASIG style extra attributes
+ if node_name == 'attributes'
+ hash.merge!(parse_user_info(e))
+ else
+ hash[node_name] = [] if hash[node_name].nil?
+ hash[node_name].push(parse_user_info(e))
+ end
+ end
+ end
+ end
+ end
+ end
+
+ # finds an `<cas:authenticationSuccess>` node in
+ # a `<cas:serviceResponse>` body if present; returns nil
+ # if the passed body is nil or if there is no such node.
+ def find_authentication_success(body)
+ return nil if body.nil? || body == ''
+ begin
+ doc = Nokogiri::XML(body)
+ begin
+ doc.xpath('/cas:serviceResponse/cas:authenticationSuccess')
+ rescue Nokogiri::XML::XPath::SyntaxError
+ doc.xpath('/serviceResponse/authenticationSuccess')
+ end
+ rescue Nokogiri::XML::XPath::SyntaxError
+ nil
+ end
+ end
+
+ # retrieves the `<cas:serviceResponse>` XML from the CAS server
+ def get_service_response_body
+ result = ''
+ http = Net::HTTP.new(@uri.host, @uri.port)
+ http.use_ssl = @uri.port == 443 || @uri.instance_of?(URI::HTTPS)
+ if http.use_ssl?
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @options.disable_ssl_verification?
+ http.ca_path = @options.ca_path
+ end
+ http.start do |c|
+ response = c.get "#{@uri.path}?#{@uri.query}", VALIDATION_REQUEST_HEADERS.dup
+ result = response.body
+ end
+ result
+ end
+ end
+ end
+ end
+end