#!/usr/bin/env ruby # Start the server by running: # # $ ruby main.rb require "bundler/inline" gemfile do source "https://rubygems.org" gem "base64", "~> 0.1" gem "erb", "~> 4.0" gem "rack", "~> 3.0" gem "rackup", "~> 2.0" gem "saml-kit", "~> 1.0" gem "webrick", "~> 1.0" end class OnDemandRegistry < Saml::Kit::DefaultRegistry def metadata_for(entity_id) found = super(entity_id) return found if found register_url(entity_id, verify_ssl: false) super(entity_id) end end Saml::Kit.configure do |x| x.entity_id = "http://localhost:8283/metadata.xml" x.registry = OnDemandRegistry.new x.logger = Logger.new("/dev/stderr") end class ServiceProvider def initialize @storage = {} end # Download IDP Metadata # # GET /metadata.xml def metadata xml = Saml::Kit::Metadata.build_xml do |builder| builder.embed_signature = false builder.contact_email = 'hi@example.com' builder.organization_name = "Acme, Inc" builder.organization_url = "https://example.com" builder.build_service_provider do |x| x.name_id_formats = [Saml::Kit::Namespaces::EMAIL_ADDRESS] x.add_assertion_consumer_service("http://localhost:8283/assertions", binding: :http_post) end end [200, { 'Content-Type' => "application/samlmetadata+xml" }, [xml]] end def call(env) path = env['PATH_INFO'] case env['REQUEST_METHOD'] when 'GET' case path when "/metadata.xml" return metadata else # TODO Generate a post to the IdP return post_to_idp(Rack::Request.new(env)) end when 'POST' case path when "/assertions" # TODO:: Render the SAMLResponse from the IdP return assertions(Rack::Request.new(env)) else return not_found end end not_found end private def not_found [404, {}, []] end def post_to_idp(request) idp = Saml::Kit.registry.metadata_for('http://localhost:8282/metadata.xml') relay_state = Base64.strict_encode64(JSON.generate(redirect_to: '/dashboard')) @saml_builder = nil uri, saml_params = idp.login_request_for(binding: :http_post, relay_state: relay_state) do |builder| @saml_builder = builder end template = <<~ERB