Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include SLO request builder #232

Draft
wants to merge 17 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,6 @@ KEY DATA
-----END RSA PRIVATE KEY-----
CERT

# x509_certificate, secret_key, and password may also be set from within a proc, for example:
# config.x509_certificate = -> { File.read("cert.pem") }
# config.secret_key = -> { SecretKeyFinder.key_for(id: 1) }
# config.password = -> { "password" }

# config.password = "secret_key_password"
# config.algorithm = :sha256 # Default: sha1 only for development.
# config.organization_name = "Your Organization"
Expand Down
1 change: 1 addition & 0 deletions gemfiles/rails_5.2.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ source "https://rubygems.org"

gem "rails", "~> 5.2.4"
gem "activeresource", "~> 5.1.0"
gem 'concurrent-ruby', '1.3.4'

gemspec path: "../"
1 change: 1 addition & 0 deletions gemfiles/rails_6.1.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ source "https://rubygems.org"

gem "rails", "~> 6.1.0"
gem "activeresource", "~> 5.1.0"
gem 'concurrent-ruby', '1.3.4'

gemspec path: "../"
1 change: 1 addition & 0 deletions gemfiles/rails_7.0.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ source "https://rubygems.org"

gem "rails", "~> 7.0.0"
gem "activeresource", "~> 6.0.0"
gem 'concurrent-ruby', '1.3.4'

gemspec path: "../"
5 changes: 2 additions & 3 deletions lib/saml_idp/algorithmable.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
module SamlIdp
module Algorithmable
def algorithm
algorithm_check = raw_algorithm || SamlIdp.config.algorithm
return algorithm_check if algorithm_check.respond_to?(:digest)
return raw_algorithm if raw_algorithm.respond_to?(:digest)
begin
OpenSSL::Digest.const_get(algorithm_check.to_s.upcase)
OpenSSL::Digest.const_get(raw_algorithm.to_s.upcase)
rescue NameError
OpenSSL::Digest::SHA1
end
Expand Down
66 changes: 31 additions & 35 deletions lib/saml_idp/assertion_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,29 @@ class AssertionBuilder
attr_accessor :session_expiry
attr_accessor :name_id_formats_opts
attr_accessor :asserted_attributes_opts
attr_accessor :public_cert
attr_accessor :private_key
attr_accessor :pv_key_password

delegate :config, to: :SamlIdp

def initialize(
reference_id,
issuer_uri,
principal,
audience_uri,
saml_request_id,
saml_acs_url,
raw_algorithm,
authn_context_classref,
expiry=60*60,
encryption_opts=nil,
session_expiry=nil,
name_id_formats_opts = nil,
asserted_attributes_opts = nil
reference_id:,
issuer_uri:,
principal:,
audience_uri:,
saml_request_id:,
saml_acs_url:,
raw_algorithm:,
authn_context_classref:,
public_cert:,
private_key:,
pv_key_password:,
expiry: 60*60,
encryption_opts: nil,
session_expiry: nil,
name_id_formats_opts: nil,
asserted_attributes_opts: nil
)
self.reference_id = reference_id
self.issuer_uri = issuer_uri
Expand All @@ -49,6 +55,17 @@ def initialize(
self.session_expiry = session_expiry.nil? ? config.session_expiry : session_expiry
self.name_id_formats_opts = name_id_formats_opts
self.asserted_attributes_opts = asserted_attributes_opts
self.public_cert = public_cert
self.private_key = private_key
self.pv_key_password = pv_key_password
end

def encrypt(opts = {})
raise "Must set encryption_opts to encrypt" unless encryption_opts
raw_xml = opts[:sign] ? signed : raw
require 'saml_idp/encryptor'
encryptor = Encryptor.new encryption_opts
encryptor.encrypt(raw_xml)
end

def fresh
Expand Down Expand Up @@ -105,15 +122,8 @@ def fresh
end
end
alias_method :raw, :fresh
private :fresh

def encrypt(opts = {})
raise "Must set encryption_opts to encrypt" unless encryption_opts
raw_xml = opts[:sign] ? signed : raw
require 'saml_idp/encryptor'
encryptor = Encryptor.new encryption_opts
encryptor.encrypt(raw_xml)
end
private

def asserted_attributes
if asserted_attributes_opts.present? && !asserted_attributes_opts.empty?
Expand All @@ -124,7 +134,6 @@ def asserted_attributes
config.attributes
end
end
private :asserted_attributes

def get_values_for(friendly_name, getter)
result = nil
Expand All @@ -141,12 +150,10 @@ def get_values_for(friendly_name, getter)
end
Array(result)
end
private :get_values_for

def name_id
name_id_getter.call principal
end
private :name_id

def name_id_getter
getter = name_id_format[:getter]
Expand All @@ -156,56 +163,45 @@ def name_id_getter
->(principal) { principal.public_send getter.to_s }
end
end
private :name_id_getter

def name_id_format
@name_id_format ||= NameIdFormatter.new(name_id_formats).chosen
end
private :name_id_format

def name_id_formats
@name_id_formats ||= (name_id_formats_opts || config.name_id.formats)
end
private :name_id_formats

def reference_string
"_#{reference_id}"
end
private :reference_string

def now
@now ||= Time.now.utc
end
private :now

def now_iso
iso { now }
end
private :now_iso

def not_before
iso { now - 5 }
end
private :not_before

def not_on_or_after_condition
iso { now + expiry }
end
private :not_on_or_after_condition

def not_on_or_after_subject
iso { now + 3 * 60 }
end
private :not_on_or_after_subject

def session_not_on_or_after
iso { now + session_expiry }
end
private :session_not_on_or_after

def iso
yield.iso8601
end
private :iso
end
end
4 changes: 2 additions & 2 deletions lib/saml_idp/configurator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ class Configurator
attr_accessor :logger

def initialize
self.x509_certificate = -> { Default::X509_CERTIFICATE }
self.secret_key = -> { Default::SECRET_KEY }
self.x509_certificate = Default::X509_CERTIFICATE
self.secret_key = Default::SECRET_KEY
self.algorithm = :sha1
self.reference_id_generator = ->() { SecureRandom.uuid }
self.service_provider = OpenStruct.new
Expand Down
62 changes: 35 additions & 27 deletions lib/saml_idp/controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
require 'securerandom'
require 'saml_idp/request'
require 'saml_idp/logout_response_builder'
require 'saml_idp/logout_request_builder'

module SamlIdp
module Controller
extend ActiveSupport::Concern
Expand Down Expand Up @@ -34,10 +36,7 @@ def acs_url

def validate_saml_request(raw_saml_request = params[:SAMLRequest])
decode_request(raw_saml_request, params[:Signature], params[:SigAlg], params[:RelayState])
return true if valid_saml_request?

head :forbidden if defined?(::Rails)
false
valid_saml_request?
end

def decode_request(raw_saml_request, signature, sig_algorithm, relay_state)
Expand All @@ -60,6 +59,9 @@ def encode_authn_response(principal, opts = {})
audience_uri = opts[:audience_uri] || saml_request.issuer || saml_acs_url[/^(.*?\/\/.*?\/)/, 1]
opt_issuer_uri = opts[:issuer_uri] || issuer_uri
my_authn_context_classref = opts[:authn_context_classref] || authn_context_classref
public_cert = opts[:public_cert] || SamlIdp.config.x509_certificate
private_key = opts[:private_key] || SamlIdp.config.secret_key
pv_key_password = opts[:pv_key_password] || SamlIdp.config.password
acs_url = opts[:acs_url] || saml_acs_url
expiry = opts[:expiry] || 60*60
session_expiry = opts[:session_expiry]
Expand All @@ -69,37 +71,43 @@ def encode_authn_response(principal, opts = {})
signed_message_opts = opts[:signed_message] || false
name_id_formats_opts = opts[:name_id_formats] || nil
asserted_attributes_opts = opts[:attributes] || nil
signed_assertion_opts = opts[:signed_assertion] || true
signed_assertion_opts = opts[:signed_assertion].nil? ? true : opts[:signed_assertion]
compress_opts = opts[:compress] || false

SamlResponse.new(
reference_id,
response_id,
opt_issuer_uri,
principal,
audience_uri,
saml_request_id,
acs_url,
(opts[:algorithm] || algorithm || default_algorithm),
my_authn_context_classref,
expiry,
encryption_opts,
session_expiry,
name_id_formats_opts,
asserted_attributes_opts,
signed_message_opts,
signed_assertion_opts,
compress_opts
reference_id: reference_id,
response_id: response_id,
issuer_uri: opt_issuer_uri,
principal: principal,
audience_uri: audience_uri,
saml_request_id: saml_request_id,
saml_acs_url: acs_url,
algorithm: (opts[:algorithm] || algorithm || default_algorithm),
authn_context_classref: my_authn_context_classref,
public_cert: public_cert,
private_key: private_key,
pv_key_password: pv_key_password,
expiry: expiry,
encryption_opts: encryption_opts,
session_expiry: session_expiry,
name_id_formats_opts: name_id_formats_opts,
asserted_attributes_opts: asserted_attributes_opts,
signed_message_opts: signed_message_opts,
signed_assertion_opts: signed_assertion_opts,
compression_opts: compress_opts
).build
end

def encode_logout_response(_principal, opts = {})
SamlIdp::LogoutResponseBuilder.new(
get_saml_response_id,
(opts[:issuer_uri] || issuer_uri),
saml_logout_url,
saml_request_id,
(opts[:algorithm] || algorithm || default_algorithm)
response_id: get_saml_response_id,
issuer_uri: (opts[:issuer_uri] || issuer_uri),
saml_slo_url: saml_logout_url,
saml_request_id: saml_request_id,
algorithm: (opts[:algorithm] || algorithm || default_algorithm),
public_cert: opts[:public_cert] || SamlIdp.config.x509_certificate,
private_key: opts[:private_key] || SamlIdp.config.secret_key,
pv_key_password: opts[:pv_key_password] || SamlIdp.config.password
).signed
end

Expand Down
16 changes: 15 additions & 1 deletion lib/saml_idp/logout_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,26 @@ class LogoutBuilder
attr_accessor :issuer_uri
attr_accessor :saml_slo_url
attr_accessor :algorithm
attr_accessor :public_cert
attr_accessor :private_key
attr_accessor :pv_key_password

def initialize(response_id, issuer_uri, saml_slo_url, algorithm)
def initialize(
response_id:,
issuer_uri:,
saml_slo_url:,
algorithm:,
public_cert:,
private_key:,
pv_key_password:
)
self.response_id = response_id
self.issuer_uri = issuer_uri
self.saml_slo_url = saml_slo_url
self.algorithm = algorithm
self.public_cert = public_cert
self.private_key = private_key
self.pv_key_password = pv_key_password
end

# this is an abstract base class.
Expand Down
21 changes: 19 additions & 2 deletions lib/saml_idp/logout_request_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,25 @@ module SamlIdp
class LogoutRequestBuilder < LogoutBuilder
attr_accessor :name_id

def initialize(response_id, issuer_uri, saml_slo_url, name_id, algorithm)
super(response_id, issuer_uri, saml_slo_url, algorithm)
def initialize(
response_id:,
issuer_uri:,
saml_slo_url:,
name_id:,
algorithm:,
public_cert:,
private_key:,
pv_key_password: nil
)
super(
response_id: response_id,
issuer_uri: issuer_uri,
saml_slo_url: saml_slo_url,
algorithm: algorithm,
public_cert: public_cert,
private_key: private_key,
pv_key_password: pv_key_password
)
self.name_id = name_id
end

Expand Down
21 changes: 19 additions & 2 deletions lib/saml_idp/logout_response_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,25 @@ module SamlIdp
class LogoutResponseBuilder < LogoutBuilder
attr_accessor :saml_request_id

def initialize(response_id, issuer_uri, saml_slo_url, saml_request_id, algorithm)
super(response_id, issuer_uri, saml_slo_url, algorithm)
def initialize(
response_id:,
issuer_uri:,
saml_slo_url:,
saml_request_id:,
algorithm:,
public_cert:,
private_key:,
pv_key_password: nil
)
super(
response_id: response_id,
issuer_uri: issuer_uri,
saml_slo_url: saml_slo_url,
algorithm: algorithm,
public_cert: public_cert,
private_key: private_key,
pv_key_password: pv_key_password
)
self.saml_request_id = saml_request_id
end

Expand Down
Loading
Loading