Skip to content

Commit

Permalink
Added options to allow IDP to dynamically change values of Name ID an…
Browse files Browse the repository at this point in the history
…d attributes when creating a SAML response. (#88)

* Added options to allow IPD to dynamically change
values of Name ID and attributes when creating a SAML response.
* Add default value for signed message option

Co-authored-by: zogoo <chtsogbadrakh@gmail.com>
Co-authored-by: tsogbadrakh <tsogbadrakh@globalsign.com>
  • Loading branch information
3 people authored Jul 26, 2021
1 parent b34adcb commit 4198dd2
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 19 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@
/Gemfile.lock
/gemfiles/*.gemfile.lock
/.byebug_history
/vendor
31 changes: 28 additions & 3 deletions lib/saml_idp/assertion_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,26 @@ class AssertionBuilder
attr_accessor :expiry
attr_accessor :encryption_opts
attr_accessor :session_expiry
attr_accessor :name_id_formats_opts
attr_accessor :asserted_attributes_opts

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)
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
)
self.reference_id = reference_id
self.issuer_uri = issuer_uri
self.principal = principal
Expand All @@ -31,6 +47,8 @@ def initialize(reference_id, issuer_uri, principal, audience_uri, saml_request_i
self.expiry = expiry
self.encryption_opts = encryption_opts
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
end

def fresh
Expand Down Expand Up @@ -98,7 +116,9 @@ def encrypt(opts = {})
end

def asserted_attributes
if principal.respond_to?(:asserted_attributes)
if asserted_attributes_opts.present? && !asserted_attributes_opts.empty?
asserted_attributes_opts
elsif principal.respond_to?(:asserted_attributes)
principal.send(:asserted_attributes)
elsif !config.attributes.nil? && !config.attributes.empty?
config.attributes
Expand Down Expand Up @@ -139,10 +159,15 @@ def name_id_getter
private :name_id_getter

def name_id_format
@name_id_format ||= NameIdFormatter.new(config.name_id.formats).chosen
@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
Expand Down
6 changes: 5 additions & 1 deletion lib/saml_idp/controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ def encode_authn_response(principal, opts = {})
session_expiry = opts[:session_expiry]
encryption_opts = opts[:encryption] || nil
signed_message_opts = opts[:signed_message] || false
name_id_formats_opts = opts[:name_id_formats] || nil
asserted_attributes_opts = opts[:attributes] || nil

SamlResponse.new(
reference_id,
Expand All @@ -75,7 +77,9 @@ def encode_authn_response(principal, opts = {})
expiry,
encryption_opts,
session_expiry,
signed_message_opts
signed_message_opts,
name_id_formats_opts,
asserted_attributes_opts
).build
end

Expand Down
39 changes: 24 additions & 15 deletions lib/saml_idp/saml_response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,26 @@ class SamlResponse
attr_accessor :encryption_opts
attr_accessor :session_expiry
attr_accessor :signed_message_opts
attr_accessor :name_id_formats_opts
attr_accessor :asserted_attributes_opts

def initialize(reference_id,
response_id,
issuer_uri,
principal,
audience_uri,
saml_request_id,
saml_acs_url,
algorithm,
authn_context_classref,
expiry=60*60,
encryption_opts=nil,
session_expiry=0,
signed_message_opts
)
def initialize(
reference_id,
response_id,
issuer_uri,
principal,
audience_uri,
saml_request_id,
saml_acs_url,
algorithm,
authn_context_classref,
expiry=60*60,
encryption_opts=nil,
session_expiry=0,
signed_message_opts=false,
name_id_formats_opts = nil,
asserted_attributes_opts = nil
)
self.reference_id = reference_id
self.response_id = response_id
self.issuer_uri = issuer_uri
Expand All @@ -48,6 +53,8 @@ def initialize(reference_id,
self.encryption_opts = encryption_opts
self.session_expiry = session_expiry
self.signed_message_opts = signed_message_opts
self.name_id_formats_opts = name_id_formats_opts
self.asserted_attributes_opts = asserted_attributes_opts
end

def build
Expand Down Expand Up @@ -88,7 +95,9 @@ def assertion_builder
authn_context_classref,
expiry,
encryption_opts,
session_expiry
session_expiry,
name_id_formats_opts,
asserted_attributes_opts
end
private :assertion_builder
end
Expand Down
73 changes: 73 additions & 0 deletions spec/lib/saml_idp/assertion_builder_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ module SamlIdp
key_transport: 'rsa-oaep-mgf1p',
}
end
let(:session_expiry) { nil }
let(:name_id_formats_opt) { nil }
let(:asserted_attributes_opt) { nil }
subject { described_class.new(
reference_id,
issuer_uri,
Expand Down Expand Up @@ -103,6 +106,76 @@ module SamlIdp
expect(encrypted_xml).to_not match(audience_uri)
end

describe "with name_id_formats_opt" do
let(:name_id_formats_opt) {
{
persistent: -> (principal) {
principal.unique_identifier
}
}
}
it "delegates name_id_formats to opts" do
UserWithUniqueId = Struct.new(:unique_identifier, :email, :asserted_attributes)
principal = UserWithUniqueId.new('unique_identifier_123456', 'foo@example.com', { emailAddress: { getter: :email } })
builder = described_class.new(
reference_id,
issuer_uri,
principal,
audience_uri,
saml_request_id,
saml_acs_url,
algorithm,
authn_context_classref,
expiry,
encryption_opts,
session_expiry,
name_id_formats_opt,
asserted_attributes_opt
)
Timecop.travel(Time.zone.local(2010, 6, 1, 13, 0, 0)) do
expect(builder.raw).to eq("<Assertion xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"_abc\" IssueInstant=\"2010-06-01T13:00:00Z\" Version=\"2.0\"><Issuer>http://sportngin.com</Issuer><Subject><NameID Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent\">unique_identifier_123456</NameID><SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><SubjectConfirmationData InResponseTo=\"123\" NotOnOrAfter=\"2010-06-01T13:03:00Z\" Recipient=\"http://saml.acs.url\"></SubjectConfirmationData></SubjectConfirmation></Subject><Conditions NotBefore=\"2010-06-01T12:59:55Z\" NotOnOrAfter=\"2010-06-01T16:00:00Z\"><AudienceRestriction><Audience>http://example.com</Audience></AudienceRestriction></Conditions><AuthnStatement AuthnInstant=\"2010-06-01T13:00:00Z\" SessionIndex=\"_abc\"><AuthnContext><AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</AuthnContextClassRef></AuthnContext></AuthnStatement><AttributeStatement><Attribute Name=\"emailAddress\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"emailAddress\"><AttributeValue>foo@example.com</AttributeValue></Attribute></AttributeStatement></Assertion>")
end
end
end

describe "with asserted_attributes_opt" do
let(:asserted_attributes_opt) {
{
'GivenName' => {
getter: :first_name
},
'SurName' => {
getter: -> (principal) {
principal.last_name
}
}
}
}

it "delegates asserted_attributes to opts" do
UserWithName = Struct.new(:email, :first_name, :last_name)
principal = UserWithName.new('foo@example.com', 'George', 'Washington')
builder = described_class.new(
reference_id,
issuer_uri,
principal,
audience_uri,
saml_request_id,
saml_acs_url,
algorithm,
authn_context_classref,
expiry,
encryption_opts,
session_expiry,
name_id_formats_opt,
asserted_attributes_opt
)
Timecop.travel(Time.zone.local(2010, 6, 1, 13, 0, 0)) do
expect(builder.raw).to eq("<Assertion xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"_abc\" IssueInstant=\"2010-06-01T13:00:00Z\" Version=\"2.0\"><Issuer>http://sportngin.com</Issuer><Subject><NameID Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\">foo@example.com</NameID><SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><SubjectConfirmationData InResponseTo=\"123\" NotOnOrAfter=\"2010-06-01T13:03:00Z\" Recipient=\"http://saml.acs.url\"></SubjectConfirmationData></SubjectConfirmation></Subject><Conditions NotBefore=\"2010-06-01T12:59:55Z\" NotOnOrAfter=\"2010-06-01T16:00:00Z\"><AudienceRestriction><Audience>http://example.com</Audience></AudienceRestriction></Conditions><AuthnStatement AuthnInstant=\"2010-06-01T13:00:00Z\" SessionIndex=\"_abc\"><AuthnContext><AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</AuthnContextClassRef></AuthnContext></AuthnStatement><AttributeStatement><Attribute Name=\"GivenName\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"GivenName\"><AttributeValue>George</AttributeValue></Attribute><Attribute Name=\"SurName\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"SurName\"><AttributeValue>Washington</AttributeValue></Attribute></AttributeStatement></Assertion>")
end
end
end

describe "with custom session_expiry configuration" do
let(:config) { SamlIdp::Configurator.new }
before do
Expand Down

0 comments on commit 4198dd2

Please sign in to comment.