diff options
| author | mo khan <mo@mokhan.ca> | 2025-03-05 11:43:07 -0700 |
|---|---|---|
| committer | mo khan <mo@mokhan.ca> | 2025-03-05 11:43:07 -0700 |
| commit | 20e152182f7137ae2c7f512d0cab1b3c846a4677 (patch) | |
| tree | 9c7a0ca1e52abc1d9fd2a77ec0c3b3b6166c4dd0 /bin/ui | |
| parent | 502228f90f6e3e7b03d2c3165a9b8b8f00e29dce (diff) | |
refactor: extract scheme and provide the appropriate nameid for saml transaction
Diffstat (limited to 'bin/ui')
| -rwxr-xr-x | bin/ui | 163 |
1 files changed, 163 insertions, 0 deletions
@@ -0,0 +1,163 @@ +#!/usr/bin/env ruby + +require "bundler/inline" + +gemfile do + source "https://rubygems.org" + + gem "base64", "~> 0.1" + gem "erb", "~> 4.0" + gem "net-hippie", "~> 1.0" + gem "rack", "~> 3.0" + gem "rackup", "~> 2.0" + gem "saml-kit", "~> 1.0" + gem "webrick", "~> 1.0" +end + +$scheme = ENV.fetch('SCHEME', 'http') +$port = ENV.fetch('PORT', 8283).to_i +$host = ENV.fetch('HOST', "localhost:#{$port}") + +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 = "#{$scheme}://#{$host}/metadata.xml" + x.registry = OnDemandRegistry.new + x.logger = Logger.new("/dev/stderr") +end + +class UI + def metadata + xml = Saml::Kit::Metadata.build_xml do |builder| + builder.embed_signature = false + builder.contact_email = 'ui@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("#{$scheme}://#{$host}/saml/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 + when "/oidc/new" + return redirect_to("http://localhost:8282/oauth/authorize?client_id=service-provider&state=example&redirect_uri=#{$scheme}://#{$host}/oauth/callback&response_type=code&response_mode=query&scope=openid") + when "/saml/new" + return saml_post_to_idp(Rack::Request.new(env)) + when "/oauth/callback" + return oauth_callback(Rack::Request.new(env)) + else + # return saml_post_to_idp(Rack::Request.new(env)) + return redirect_to("/saml/new") + end + when 'POST' + case path + when "/saml/assertions" + return saml_assertions(Rack::Request.new(env)) + else + return not_found + end + end + not_found + end + + private + + def not_found + [404, { 'X-Backend-Server' => 'UI' }, []] + end + + def redirect_to(location) + [302, { 'Location' => location }, []] + end + + def oauth_callback(request) + response = Net::Hippie.default_client.post( + "http://localhost:8282/oauth/token", + headers: { 'Authorization' => Net::Hippie.basic_auth('client_id', 'secret') }, + body: { + grant_type: "authorization_code", + code: request.params['code'], + code_verifier: "not_implemented" + } + ) + [200, { "Content-Type" => "application/json" }, [JSON.pretty_generate(request.params.merge(JSON.parse(response.body)))]] + end + + def saml_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 + html = ERB.new(template, trim_mode: '-').result(binding) + [200, { 'Content-Type' => "text/html" }, [html]] + end + + def saml_assertions(request) + sp = Saml::Kit.registry.metadata_for("#{$scheme}://#{$host}/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, 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 UI.new + end.to_app + + Rackup::Server.start(app: app, Port: $port) +end |
