summaryrefslogtreecommitdiff
path: root/bin
diff options
context:
space:
mode:
Diffstat (limited to 'bin')
-rwxr-xr-xbin/idp171
-rwxr-xr-xbin/sp147
2 files changed, 318 insertions, 0 deletions
diff --git a/bin/idp b/bin/idp
new file mode 100755
index 00000000..daa15cc6
--- /dev/null
+++ b/bin/idp
@@ -0,0 +1,171 @@
+#!/usr/bin/env ruby
+
+# Start the server by running:
+#
+# $ ruby main.rb
+
+require "bundler/inline"
+gemfile do
+ source "https://rubygems.org"
+
+ gem "erb", "~> 4.0"
+ gem "rack", "~> 3.0"
+ gem "rackup", "~> 2.0"
+ gem "saml-kit", "~> 1.0"
+ gem "webrick", "~> 1.0"
+end
+
+class User
+ def initialize(attributes)
+ @attributes = attributes
+ end
+
+ def name_id_for(name_id_format)
+ @attributes[:email]
+ end
+
+ def assertion_attributes_for(request)
+ {
+ custom: 'custom attribute'
+ }
+ end
+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:8282/metadata.xml"
+ x.registry = OnDemandRegistry.new
+ x.logger = Logger.new("/dev/stderr")
+end
+
+class IdentityProvider
+ def initialize
+ @storage = {}
+ end
+
+ # Download IDP Metadata
+ #
+ # GET /metadata.xml
+ def metadata
+ xml = Saml::Kit::Metadata.build_xml do |builder|
+ builder.contact_email = 'hi@example.com'
+ builder.organization_name = "Acme, Inc"
+ builder.organization_url = "https://example.com"
+ builder.build_identity_provider do |x|
+ x.add_single_sign_on_service("http://localhost:8282/sessions/new", binding: :http_post)
+ x.name_id_formats = [Saml::Kit::Namespaces::EMAIL_ADDRESS]
+ x.attributes << :Username
+ 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
+ when "/sessions/new"
+ return post_back(Rack::Request.new(env))
+ else
+ return not_found
+ end
+ when 'POST'
+ case path
+ when "/sessions/new"
+ return post_back(Rack::Request.new(env))
+ else
+ return not_found
+ end
+ end
+ not_found
+ end
+
+ private
+
+ def post_back(request)
+ params = saml_params_from(request)
+ saml_request = binding_for(request).deserialize(params)
+ @builder = nil
+ url, saml_params = saml_request.response_for(
+ User.new({ email: "example@example.com" }),
+ binding: :http_post,
+ relay_state: params[:RelayState]
+ ) do |builder|
+ builder.embed_signature = true
+ @builder = builder
+ end
+ template = <<~ERB
+ <!doctype html>
+ <html>
+ <head><title></title></head>
+ <body>
+ <h2>Recieved SAML Request</h2>
+ <textarea readonly="readonly" disabled="disabled" cols=225 rows=6><%=- saml_request.to_xml(pretty: true) -%></textarea>
+
+ <h2>Sending SAML Response (IdP -> SP)</h2>
+ <textarea readonly="readonly" disabled="disabled" cols=225 rows=30><%=- @builder.build.to_xml(pretty: true) -%></textarea>
+ <form action="<%= url %>" method="post">
+ <%- saml_params.each do |(key, value)| -%>
+ <input type="hidden" name="<%= key %>" value="<%= value %>" />
+ <%- end -%>
+ <input type="submit" value="Submit" />
+ </form>
+ </body>
+ </html>
+ ERB
+ erb = ERB.new(template, nil, trim_mode: '-')
+ html = erb.result(binding)
+ [200, { 'Content-Type' => "text/html" }, [html]]
+ end
+
+
+ def not_found
+ [404, {}, []]
+ end
+
+ def saml_params_from(request)
+ if request.post?
+ {
+ "SAMLRequest" => request.params["SAMLRequest"],
+ "RelayState" => request.params["RelayState"],
+ }
+ else
+ query_string = request.query_string
+ on = query_string.include?("&amp;") ? "&amp;" : "&"
+ Hash[query_string.split(on).map { |x| x.split("=", 2) }].symbolize_keys
+ end
+ end
+
+ def binding_for(request)
+ location = "http://localhost:8282/sessions/new"
+ if request.post?
+ Saml::Kit::Bindings::HttpPost
+ .new(location: location)
+ else
+ Saml::Kit::Bindings::HttpRedirect
+ .new(location: location)
+ end
+ end
+end
+
+if __FILE__ == $0
+ app = Rack::Builder.new do
+ use Rack::Reloader
+ run IdentityProvider.new
+ end.to_app
+
+ Rackup::Server.start(app: app, Port: 8282)
+end
diff --git a/bin/sp b/bin/sp
new file mode 100755
index 00000000..68a0e3db
--- /dev/null
+++ b/bin/sp
@@ -0,0 +1,147 @@
+#!/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
+ <!doctype html>
+ <html>
+ <head><title></title></head>
+ <body style="background-color: pink;">
+ <h2>Sending SAML Request (SP -> IdP)</h2>
+ <textarea readonly="readonly" disabled="disabled" cols=225 rows=6><%=- @saml_builder.to_xml(pretty: true) -%></textarea>
+
+ <form action="<%= uri %>" method="post">
+ <%- saml_params.each do |(key, value)| -%>
+ <input type="hidden" name="<%= key %>" value="<%= value %>" />
+ <%- end -%>
+ <input type="submit" value="Submit" />
+ </form>
+ </body>
+ </html>
+ ERB
+ erb = ERB.new(template, nil, trim_mode: '-')
+ html = erb.result(binding)
+ [200, { 'Content-Type' => "text/html" }, [html]]
+ end
+
+ def assertions(request)
+ sp = Saml::Kit.registry.metadata_for('http://localhost:8283/metadata.xml')
+ saml_binding = sp.assertion_consumer_service_for(binding: :http_post)
+ saml_response = saml_binding.deserialize(request.params)
+ raise saml_response.errors unless saml_response.valid?
+
+ template = <<~ERB
+ <!doctype html>
+ <html>
+ <head><title></title></head>
+ <body style="background-color: pink;">
+ <h2>Received SAML Response</h2>
+ <textarea readonly="readonly" disabled="disabled" cols=220 rows=40><%=- saml_response.to_xml(pretty: true) -%></textarea>
+ </body>
+ </html>
+ ERB
+ erb = ERB.new(template, nil, trim_mode: '-')
+ html = erb.result(binding)
+ [200, { 'Content-Type' => "text/html" }, [html]]
+ end
+end
+
+if __FILE__ == $0
+ app = Rack::Builder.new do
+ use Rack::Reloader
+ run ServiceProvider.new
+ end.to_app
+
+ Rackup::Server.start(app: app, Port: 8283)
+end