class Rack::Saml
As the Shibboleth SP, Rack::Saml::Base adopts :protected_path as an :assertion_consumer_path. It is easy to configure and support omniauth-shibboleth. To establish single path behavior, it currently supports only HTTP Redirect Binding from SP to Idp HTTP POST Binding from IdP to SP
rack-saml uses rack.session to store SAML and Discovery Service status. env = {
'rack_saml' => { 'ds.session' => { 'sid' => temporally_generated_hash, 'expires' => xxxxx # timestamp (string) } 'saml_authreq.session' => { 'sid' => temporally_generated_hash, 'expires' => xxxxx # timestamp (string) } 'saml_res.session' => { 'sid' => temporally_generated_hash, 'expires' => xxxxx, # timestamp (string) 'env' => {} } }
}
Constants
- FILE_NAME
- FILE_TYPE
- VERSION
Public Class Methods
new(app, opts = {})
click to toggle source
# File lib/rack/saml.rb, line 60 def initialize app, opts = {} @app = app @opts = opts FILE_TYPE.each do |type| load_file(type) end if @config['assertion_handler'].nil? raise ArgumentError, "'assertion_handler' parameter should be specified in the :config file" end end
Public Instance Methods
call(env)
click to toggle source
# File lib/rack/saml.rb, line 140 def call env session = Session.new(env) request = Rack::Request.new env # saml_sp: SAML SP's entity_id # generate saml_sp from request uri and default path (rack-saml-sp) saml_sp_prefix = "#{request.scheme}://#{request.host}#{":#{request.port}" if request.port}#{request.script_name}" @config['saml_sp'] ||= "#{saml_sp_prefix}/rack-saml-sp" @config['assertion_consumer_service_uri'] ||= "#{saml_sp_prefix}#{@config['protected_path']}" # for debug #return [ # 403, # { # 'Content-Type' => 'text/plain' # }, # ["Forbidden." + request.inspect] # ["Forbidden." + env.to_a.map {|i| "#{i[0]}: #{i[1]}"}.join("\n")] #] if request.request_method == 'GET' if match_protected_path?(request) # generate AuthnRequest if session.is_valid?('saml_res') # the client already has a valid session ResponseHandler.extract_attrs(env, session) else if !@config['shib_ds'].nil? # use discovery service (ds) if request.params['entityID'].nil? # start ds session session.start('ds') return Rack::Response.new.tap { |r| r.redirect "#{@config['shib_ds']}?entityID=#{URI.encode(@config['saml_sp'], /[^\w]/)}&return=#{URI.encode("#{@config['assertion_consumer_service_uri']}?target=#{session.get_sid('ds')}", /[^\w]/)}" }.finish end if !session.is_valid?('ds', request.params['target']) # confirm ds session current_sid = session.get_sid('ds') session.finish('ds') return create_response(500, 'text/html', "Internal Server Error: Invalid discovery service session current sid=#{current_sid}, request sid=#{request.params['target']}") end session.finish('ds') @config['saml_idp'] = request.params['entityID'] end session.start('saml_authreq') handler = RequestHandler.new(request, @config, @metadata['idp_lists'][@config['saml_idp']]) return Rack::Response.new.tap { |r| r.redirect handler.authn_request.redirect_uri }.finish end elsif match_metadata_path?(request) # generate Metadata handler = MetadataHandler.new(request, @config, @metadata['idp_lists'][@config['saml_idp']]) return create_response(200, 'application/samlmetadata+xml', handler.sp_metadata.generate) end elsif request.request_method == 'POST' && match_protected_path?(request) # process Response if session.is_valid?('saml_authreq') handler = ResponseHandler.new(request, @config, @metadata['idp_lists'][@config['saml_idp']]) begin if handler.response.is_valid? session.finish('saml_authreq') session.start('saml_res', @config['saml_sess_timeout'] || 1800) handler.extract_attrs(env, session, @attribute_map) return Rack::Response.new.tap { |r| r.redirect request.url }.finish else return create_response(403, 'text/html', 'SAML Error: Invalid SAML response.') end rescue ValidationError => e return create_response(403, 'text/html', "SAML Error: Invalid SAML response.<br/>Reason: #{e.message}") end else return create_response(500, 'text/html', 'No valid AuthnRequest session.') end end @app.call env end
create_response(code, content_type, message)
click to toggle source
# File lib/rack/saml.rb, line 220 def create_response(code, content_type, message) return [ code, { 'Content-Type' => content_type }, [message] ] end
default_config_path(config_file)
click to toggle source
# File lib/rack/saml.rb, line 49 def default_config_path(config_file) ::File.expand_path("../../../config/#{config_file}", __FILE__) end
load_file(type)
click to toggle source
# File lib/rack/saml.rb, line 53 def load_file(type) if @opts[type].nil? || !::File.exists?(@opts[type]) @opts[type] = default_config_path(FILE_NAME[type]) end eval "@#{type} = YAML.load_file(@opts[:#{type}])" end
match_metadata_path?(request)
click to toggle source
# File lib/rack/saml.rb, line 216 def match_metadata_path?(request) request.path_info == @config['metadata_path'] end
match_protected_path?(request)
click to toggle source
# File lib/rack/saml.rb, line 212 def match_protected_path?(request) request.path_info == @config['protected_path'] end