From e758496795f00094d1f9ebe4e5f12bf90da1651c Mon Sep 17 00:00:00 2001 From: mo khan Date: Wed, 26 Mar 2025 14:46:20 -0600 Subject: feat: exchange a SAML assertion for an access token --- bin/idp | 20 ++++++++++---------- bin/ui | 17 +++++++++-------- 2 files changed, 19 insertions(+), 18 deletions(-) (limited to 'bin') diff --git a/bin/idp b/bin/idp index 9c2ff1df..eca046c2 100755 --- a/bin/idp +++ b/bin/idp @@ -5,6 +5,7 @@ require "bundler/inline" gemfile do source "https://rubygems.org" + gem "base64", "~> 0.1" gem "bcrypt", "~> 3.0" gem "csv", "~> 3.0" gem "declarative_policy", "~> 1.0" @@ -14,7 +15,7 @@ gemfile do gem "rack", "~> 3.0" gem "rack-session", "~> 2.0" gem "rackup", "~> 2.0" - gem "saml-kit", "~> 1.0" + gem "saml-kit", "~> 1.0", git: "github.com:xlgmokha/saml-kit", branch: "main" gem "twirp", "~> 1.0" gem "warden", "~> 1.0" gem "webrick", "~> 1.0" @@ -444,7 +445,6 @@ module Authz @all ||= [] end - # TODO:: Look up saml_assertion def find_by(params) case params[:grant_type] when 'authorization_code' @@ -483,16 +483,11 @@ module Authz def saml_assertion_grant(encoded_saml_assertion) xml = Base64.decode64(encoded_saml_assertion) - saml_response = Saml::Kit::Document.to_saml_document(xml) - saml_assertion = saml_response.assertion # TODO:: Validate signature and prevent assertion reuse + saml_assertion = Saml::Kit::Document.to_saml_document(xml) - user = case saml_assertion.name_id_format - when Saml::Kit::Namespaces::EMAIL_ADDRESS - ::Authn::User.find_by_email(saml_assertion.name_id) - when Saml::Kit::Namespaces::PERSISTENT + user = ::Authn::User.find_by_email(saml_assertion.name_id) || ::Authn::User.find(saml_assertion.name_id) - end new(user, saml_assertion: saml_assertion) end @@ -544,6 +539,11 @@ module Authz body[:id_token] = user.create_id_token.to_jwt end end + rescue StandardError => error + { + error: error.message, + error_description: error.backtrace, + } end end @@ -582,7 +582,7 @@ module Authz params = request.content_type == "application/json" ? JSON.parse(request.body.read, symbolize_names: true) : Hash[URI.decode_www_form(request.body.read)].transform_keys(&:to_sym) grant = AuthorizationGrant.find_by(params) - return [404, { "Content-Type" => "application/json" }, [JSON.pretty_generate(error: 404, message: "Not Found")]] if grant.nil? || grant.inactive? + return [404, { "Content-Type" => "application/json" }, [JSON.pretty_generate(error: 404, error_description: "Not Found")]] if grant.nil? || grant.inactive? return [200, { "Content-Type" => "application/json" }, [JSON.pretty_generate(grant.exchange)]] when "/oauth/revoke" # RFC-7009 # TODO:: Revoke the JWT token and make it ineligible for usage diff --git a/bin/ui b/bin/ui index e5eb5488..14b934d6 100755 --- a/bin/ui +++ b/bin/ui @@ -123,7 +123,7 @@ end module HTTPHelpers def current_user?(request) - request.session[:id_token] + request.session[:access_token] end def not_found @@ -393,14 +393,14 @@ class UI saml_response = saml_binding.deserialize(request.params) raise saml_response.errors unless saml_response.valid? + assertion = Base64.strict_encode64(saml_response.assertion.to_xml) response = oauth_client.exchange( "urn:ietf:params:oauth:grant-type:saml2-bearer", - assertion: request.params["SAMLResponse"], + assertion: assertion, ) if response.code == "200" tokens = JSON.parse(response.body, symbolize_names: true) request.session[:access_token] = tokens[:access_token] - request.session[:id_token] = tokens[:id_token] request.session[:refresh_token] = tokens[:access_token] template = <<~ERB @@ -410,13 +410,14 @@ class UI -

Received SAML Response

- -
<%= request.params["SAMLResponse"] %>
-
<%= JSON.pretty_generate(request.session[:access_token]) %>
- Home Groups + +

Received SAML Response

+ + + + ERB -- cgit v1.2.3