Skip to content

Commit

Permalink
[ci skip] IdP Config will initialize for each requests no need to use…
Browse files Browse the repository at this point in the history
… extra options.
  • Loading branch information
zogoo committed Jan 5, 2024
1 parent 5021c34 commit 5fa6eba
Show file tree
Hide file tree
Showing 13 changed files with 242 additions and 251 deletions.
28 changes: 13 additions & 15 deletions lib/saml_idp/assertion_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,21 @@ class AssertionBuilder
attr_accessor :session_expiry
attr_accessor :name_id_formats_opts
attr_accessor :asserted_attributes_opts
attr_accessor :idp_config

def initialize(
idp_config,
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:,
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 Down
33 changes: 33 additions & 0 deletions lib/saml_idp/certificate_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
class CertificateGenerator
attr_accessor :rsa_key, :cert, :common_name,
:private_key, :pv_key_password

def initialize(common_name = nil)
self.common_name = common_name
self.pv_key_password = SecureRandom.hex(12)
build_certificate
self.private_key = rsa_key.to_pem(OpenSSL::Cipher.new('AES-128-CBC'), pv_key_password)
end

def certificate
cert.to_pem
end

private

def build_certificate
self.rsa_key = OpenSSL::PKey::RSA.new(2048)
self.common_name ||= 'SAMLCertificate'
subject = "/C=MN/OU=SAMLIdP"

self.cert = OpenSSL::X509::Certificate.new
cert.subject = cert.issuer = OpenSSL::X509::Name.parse(subject)
cert.not_before = Time.now
cert.not_after = Time.now.since(10.years)
cert.public_key = rsa_key.public_key
cert.serial = OpenSSL::BN.rand(160)
cert.version = 2

cert.sign rsa_key, OpenSSL::Digest.new('SHA256')
end
end
96 changes: 26 additions & 70 deletions lib/saml_idp/controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,58 +41,26 @@ def validate_saml_request(raw_saml_request = params[:SAMLRequest])
end

def decode_request(raw_saml_request)
@saml_request = Request.from_deflated_request(raw_saml_request)
@saml_request = Request.from_deflated_request(raw_saml_request, sp_config)
end

def authn_context_classref
Saml::XML::Namespaces::AuthnContext::ClassRef::PASSWORD
end

def encode_authn_response(principal, opts = {})
response_id = get_saml_response_id
reference_id = opts[:reference_id] || get_saml_reference_id
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
acs_url = opts[:acs_url] || saml_acs_url
expiry = opts[:expiry] || 60*60
session_expiry = opts[:session_expiry]
encryption_opts = opts[:encryption] || nil
name_id_formats_opts = opts[:name_id_formats] || nil
asserted_attributes_opts = opts[:attributes] || nil
signed_message_opts = opts[:signed_message] || false
signed_assertion_opts = opts[:signed_assertion] || true
compress_opts = opts[:compress] || false

SamlResponse.new(
idp_config,
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_assertion_opts,
signed_message_opts,
compress_opts
def encode_authn_response(principal)
idp_config.load_saml_request(saml_request)
SamlIdp::SamlResponse.new(
principal: principal,
idp_config: idp_config,
saml_request: saml_request
).build
end

def encode_logout_response(_principal, opts = {})
def encode_logout_response(_principal)
SamlIdp::LogoutResponseBuilder.new(
get_saml_response_id,
(opts[:issuer_uri] || issuer_uri),
saml_logout_url,
saml_request_id,
(opts[:algorithm] || algorithm || default_algorithm)
idp_config: idp_config,
saml_request: saml_request,
).signed
end

Expand All @@ -106,46 +74,34 @@ def encode_response(principal, opts = {})
end
end

def issuer_uri
(SamlIdp.config.base_saml_location.present? && SamlIdp.config.base_saml_location) ||
(defined?(request) && request.url.to_s.split("?").first) ||
"http://example.com"
end

def valid_saml_request?
saml_request.valid?
end

def saml_request_id
saml_request.request_id
end

def saml_acs_url
saml_request.acs_url
end

def saml_logout_url
saml_request.logout_url
def sp_config
@sp_config ||= if sp_config_hash.present?
SamlIdp::SpConfig.new(sp_config_hash)
elsif sp_raw_metadata.present?
SamlIdp::SpConfig.load_from_sp_metadata(sp_raw_metadata)
else
raise "Missing SP configuration"
end
end

def get_saml_response_id
SecureRandom.uuid
def sp_config_hash
nil
end

def get_saml_reference_id
SecureRandom.uuid
end
def sp_raw_metadata
nil
end

def default_algorithm
OpenSSL::Digest::SHA256
def idp_config_hash
raise "Missing IdP configuration"
end

def idp_config
@idp_config ||= SamlIdp::IdPConfig.new
end

def configure_sp
yield idp_config
@idp_config ||= SamlIdp::IdPConfig.new(idp_config_hash)
end

def idp_metadata
Expand Down
1 change: 0 additions & 1 deletion lib/saml_idp/fingerprint.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
module SamlIdp
module Fingerprint
def self.certificate_digest(cert, sha_size = nil)
sha_size ||= SamlIdp.config.algorithm
digest_sha_class(sha_size).hexdigest(OpenSSL::X509::Certificate.new(cert).to_der).scan(/../).join(':')
end

Expand Down
106 changes: 77 additions & 29 deletions lib/saml_idp/idp_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,64 @@

module SamlIdp
class IdPConfig
attr_accessor :x509_certificate
attr_accessor :secret_key
attr_accessor :password
attr_accessor :algorithm
attr_accessor :organization_name
attr_accessor :organization_url
attr_accessor :base_saml_location
attr_accessor :entity_id
attr_accessor :reference_id_generator
attr_accessor :attribute_service_location
attr_accessor :single_service_post_location
attr_accessor :single_service_redirect_location
attr_accessor :single_logout_service_post_location
attr_accessor :single_logout_service_redirect_location
attr_accessor :attributes
attr_accessor :sp_config
attr_accessor :assertion_consumer_service_hosts
attr_accessor :session_expiry
attr_accessor :logger

def initialize
self.x509_certificate = Default::X509_CERTIFICATE
self.secret_key = Default::SECRET_KEY
self.algorithm = :sha1
self.reference_id_generator = ->() { SecureRandom.uuid }
self.sp_config = sp_config
self.session_expiry = 0
self.attributes = {}
self.logger = defined?(::Rails) ? Rails.logger : ->(msg) { puts msg }
IDP_REQUIRED_ATTR = [
:entity_id,
:audience_uri,
:issuer_uri,
:saml_acs_url,
:x509_certificate,
:secret_key,
:password,
:organization_name,
:organization_url,
:attribute_service_location,
:single_service_post_location,
:single_service_redirect_location,
:single_logout_service_post_location,
:single_logout_service_redirect_location
].freeze

IDP_OPTIONAL_ATTR = [
:reference_id,
:response_id,
:algorithm,
:attributes,
:session_expiry,
:authn_context_classref,
:expiry,
:encryption,
:name_id_format,
:asserted_attributes,
:signed_message,
:signed_assertion,
:compress
].freeze

ALL_ATTRIBUTES = (IDP_REQUIRED_ATTR + IDP_OPTIONAL_ATTR).freeze

DEFAULT_VALUES = {
encryption: nil,
signed_message: false,
signed_assertion: true,
compress: false,
algorithm: :sha256,
authn_context_classref: Saml::XML::Namespaces::AuthnContext::ClassRef::PASSWORD,
attributes: {},
session_expiry: 0,
expiry: 60 * 60
}.freeze

attr_reader(*ALL_ATTRIBUTES)

def initialize(attributes = {})
check_required_attributes(attributes)

ALL_ATTRIBUTES.each do |attr|
instance_variable_set("@#{attr}", attributes.key?(attr) ? attributes[attr] : DEFAULT_VALUES[attr])
end

self.reference_id ||= SecureRandom.uuid
self.response_id ||= SecureRandom.uuid
end

# formats
Expand All @@ -50,5 +79,24 @@ def mail_to_string
"mailto:#{email_address}" if email_address.to_s.length > 0
end
end

def load_saml_request(saml_request)
self.audience_uri = idp_config.audience_uri || saml_request.issuer || saml_request.acs_url[/^(.*?\/\/.*?\/)/, 1]
self.issuer_uri = idp_config.issuer_uri || saml_request.to_s.split("?").first || "http://example.com"
self.acs_url = idp_config.acs_url || saml_request.acs_url
end

def to_hash
instance_variables.each_with_object({}) do |var, hash|
hash[var.to_s.delete("@").to_sym] = instance_variable_get(var)
end
end

private

def check_required_attributes(attributes)
missing_attributes = IDP_REQUIRED_ATTR - attributes.keys
raise ArgumentError, "Missing required attributes: #{missing_attributes.join(', ')}" unless missing_attributes.empty?
end
end
end
12 changes: 7 additions & 5 deletions lib/saml_idp/logout_response_builder.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
require 'saml_idp/logout_builder'
module SamlIdp
class LogoutResponseBuilder < LogoutBuilder
attr_accessor :saml_request_id
attr_accessor :idp_config, :saml_request

def initialize(response_id, issuer_uri, saml_slo_url, saml_request_id, algorithm)
super(response_id, issuer_uri, saml_slo_url, algorithm)
self.saml_request_id = saml_request_id
def initialize(idp_config:, saml_request:, response_id: nil)
@response_id = response_id || SecureRandom.uuid
self.saml_request = saml_request
self.idp_config = idp_config
super(@response_id, idp_config.issuer_uri, saml_slo_url, idp_config.algorithm)
end

def build
Expand All @@ -14,7 +16,7 @@ def build
Version: "2.0",
IssueInstant: now_iso,
Destination: saml_slo_url,
InResponseTo: saml_request_id,
InResponseTo: saml_request.request_id,
xmlns: Saml::XML::Namespaces::PROTOCOL do |response|
response.Issuer issuer_uri, xmlns: Saml::XML::Namespaces::ASSERTION
sign response
Expand Down
7 changes: 6 additions & 1 deletion lib/saml_idp/metadata_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@ def build_contact(el)
end
private :build_contact

def reference_id
configurator.reference_id
end
private :reference_id

def reference_string
"_#{reference_id}"
end
Expand Down Expand Up @@ -152,7 +157,7 @@ def raw_algorithm
private :raw_algorithm

def x509_certificate
SamlIdp.config.x509_certificate
configurator.x509_certificate
.to_s
.gsub(/-----BEGIN CERTIFICATE-----/,"")
.gsub(/-----END CERTIFICATE-----/,"")
Expand Down
Loading

0 comments on commit 5fa6eba

Please sign in to comment.