saml2 1.0.4 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a3df3b121cbc505cf89bd09bf2551c4bf169522e
4
- data.tar.gz: bee5074779df61a8d6decfc85b1137f407f3bafb
3
+ metadata.gz: b77541aa53787757d03865855dbbb84da27edaba
4
+ data.tar.gz: f53032507b4c09f95e1e0f2def917e8760a5f41a
5
5
  SHA512:
6
- metadata.gz: f960b992502f20a9a518b53a34e7c50d5d3fae14452d51704f1eb36308accf5f3a48e3f5810d35a31bb3a64cb605ba2a5a49a18e6d01d8f36037a467ab36bf0a
7
- data.tar.gz: 050953212fed3515aa2a329b24e1adcf1fc7f57bed49d86e700db5d0e47bae8f6c6cc346e1cb02bae366302bcb5c1ce414e32fc7d97adb6b1ad49ee7205a9a3f
6
+ metadata.gz: 2658dc70caec44d8a6b2d8b3e014f419a2887a82a43dc6e2280c1f43437d5d162bc86b9197f144d386709317f4f39b1d692d8bb03d0bd340943fd460ec8d430f
7
+ data.tar.gz: 5fcd948501794284e79ba05f8296c4d5e9492f4624210b80fafde2edd760f47de26476bed1f67b8f6e8607c682e5f6e732b40a796ba03a47503c324c8d037cfc
@@ -1,5 +1,7 @@
1
1
  require 'date'
2
2
 
3
+ require 'active_support/core_ext/array/wrap'
4
+
3
5
  require 'saml2/base'
4
6
  require 'saml2/namespaces'
5
7
 
@@ -53,7 +55,7 @@ module SAML2
53
55
  builder['saml'].Attribute('Name' => name) do |builder|
54
56
  builder.parent['FriendlyName'] = friendly_name if friendly_name
55
57
  builder.parent['NameFormat'] = name_format if name_format
56
- Array(value).each do |val|
58
+ Array.wrap(value).each do |value|
57
59
  xsi_type, val = convert_to_xsi(value)
58
60
  builder['saml'].AttributeValue(val) do |builder|
59
61
  builder.parent['xsi:type'] = xsi_type if xsi_type
@@ -67,7 +69,7 @@ module SAML2
67
69
  @friendly_name = node['FriendlyName']
68
70
  @name_format = node['NameFormat']
69
71
  values = node.xpath('saml:AttributeValue', Namespaces::ALL).map do |node|
70
- convert_from_xsi(node['xsi:type'], node.content && node.content.strip)
72
+ convert_from_xsi(node.attribute_with_ns('type', Namespaces::XSI), node.content && node.content.strip)
71
73
  end
72
74
  @value = case values.length
73
75
  when 0; nil
@@ -78,27 +80,36 @@ module SAML2
78
80
  end
79
81
 
80
82
  private
81
- XSI_TYPES = {
82
- 'xsd:string' => [String, nil, nil],
83
- nil => [DateTime, ->(v) { v.iso8601 }, ->(v) { DateTime.parse(v) if v }]
83
+ XS_TYPES = {
84
+ lookup_qname('xs:boolean', Namespaces::ALL) =>
85
+ [[TrueClass, FalseClass], nil, ->(v) { %w{true 1}.include?(v) ? true : false }],
86
+ lookup_qname('xs:string', Namespaces::ALL) =>
87
+ [String, nil, nil],
88
+ lookup_qname('xs:date', Namespaces::ALL) =>
89
+ [Date, nil, ->(v) { Date.parse(v) if v }],
90
+ lookup_qname('xs:dateTime', Namespaces::ALL) =>
91
+ [Time, ->(v) { v.iso8601 }, ->(v) { Time.parse(v) if v }]
84
92
  }.freeze
85
93
 
86
94
  def convert_to_xsi(value)
87
- xsi_type = nil
95
+ xs_type = nil
88
96
  converter = nil
89
- XSI_TYPES.each do |type, (klass, to_xsi, from_xsi)|
90
- if klass === value
91
- xsi_type = type
97
+ XS_TYPES.each do |type, (klasses, to_xsi, _from_xsi)|
98
+ if Array.wrap(klasses).any? { |klass| klass === value }
99
+ xs_type = "xs:#{type.last}"
92
100
  converter = to_xsi
93
101
  break
94
102
  end
95
103
  end
96
104
  value = converter.call(value) if converter
97
- [xsi_type, value]
105
+ [xs_type, value]
98
106
  end
99
107
 
100
108
  def convert_from_xsi(type, value)
101
- info = XSI_TYPES[type]
109
+ return value unless type
110
+ qname = self.class.lookup_qname(type.value, type.namespaces)
111
+
112
+ info = XS_TYPES[qname]
102
113
  if info && info.last
103
114
  value = info.last.call(value)
104
115
  end
@@ -106,15 +117,24 @@ module SAML2
106
117
  end
107
118
  end
108
119
 
109
- class AttributeStatement
120
+ class AttributeStatement < Base
110
121
  attr_reader :attributes
111
122
 
112
- def initialize(attributes)
123
+ def initialize(attributes = [])
113
124
  @attributes = attributes
114
125
  end
115
126
 
127
+ def from_xml(node)
128
+ @attributes = node.xpath('saml:Attribute', Namespaces::ALL).map do |attr|
129
+ Attribute.from_xml(attr)
130
+ end
131
+
132
+ super
133
+ end
134
+
116
135
  def build(builder)
117
- builder['saml'].AttributeStatement('xmlns:xsi' => Namespaces::XSI) do |builder|
136
+ builder['saml'].AttributeStatement('xmlns:xs' => Namespaces::XS,
137
+ 'xmlns:xsi' => Namespaces::XSI) do |builder|
118
138
  @attributes.each { |attr| attr.build(builder) }
119
139
  end
120
140
  end
@@ -1,3 +1,5 @@
1
+ require 'active_support/core_ext/array/wrap'
2
+
1
3
  require 'saml2/attribute'
2
4
  require 'saml2/indexed_object'
3
5
  require 'saml2/namespaces'
@@ -34,7 +36,7 @@ module SAML2
34
36
  def initialize(requested_attribute, provided_value)
35
37
  super("Attribute #{requested_attribute.name} is provided value " \
36
38
  "#{provided_value.inspect}, but only allows " \
37
- "#{Array(requested_attribute.value).inspect}")
39
+ "#{Array.wrap(requested_attribute.value).inspect}")
38
40
  @requested_attribute, @provided_value = requested_attribute, provided_value
39
41
  end
40
42
  end
@@ -76,7 +78,7 @@ module SAML2
76
78
  end
77
79
  if attr
78
80
  if requested_attr.value &&
79
- !Array(requested_attr.value).include?(attr.value)
81
+ !Array.wrap(requested_attr.value).include?(attr.value)
80
82
  raise InvalidAttributeValue.new(requested_attr, attr.value)
81
83
  end
82
84
  attributes << attr
data/lib/saml2/base.rb CHANGED
@@ -41,6 +41,11 @@ module SAML2
41
41
  end
42
42
  end
43
43
 
44
+ def self.lookup_qname(qname, namespaces)
45
+ prefix, local_name = split_qname(qname)
46
+ [lookup_namespace(prefix, namespaces), local_name]
47
+ end
48
+
44
49
  protected
45
50
  def load_string_array(node, element)
46
51
  self.class.load_string_array(node, element)
@@ -49,5 +54,18 @@ module SAML2
49
54
  def load_object_array(node, element, klass)
50
55
  self.class.load_object_array(node, element, klass)
51
56
  end
57
+
58
+ def self.split_qname(qname)
59
+ if qname.include?(':')
60
+ qname.split(':', 2)
61
+ else
62
+ [nil, qname]
63
+ end
64
+ end
65
+
66
+ def self.lookup_namespace(prefix, namespaces)
67
+ return nil if namespaces.empty?
68
+ namespaces[prefix.empty? ? 'xmlns' : "xmlns:#{prefix}"]
69
+ end
52
70
  end
53
71
  end
@@ -1,3 +1,5 @@
1
+ require 'active_support/core_ext/array/wrap'
2
+
1
3
  module SAML2
2
4
  class Conditions < Array
3
5
  attr_accessor :not_before, :not_on_or_after
@@ -49,12 +51,12 @@ module SAML2
49
51
  end
50
52
 
51
53
  def valid?(options)
52
- Array(audience).include?(options[:audience]) ? :valid : :invalid
54
+ Array.wrap(audience).include?(options[:audience]) ? :valid : :invalid
53
55
  end
54
56
 
55
57
  def build(builder)
56
58
  builder['saml'].AudienceRestriction do |builder|
57
- Array(audience).each do |single_audience|
59
+ Array.wrap(audience).each do |single_audience|
58
60
  builder['saml'].Audience(single_audience)
59
61
  end
60
62
  end
@@ -5,17 +5,19 @@ module SAML2
5
5
  SAML = "urn:oasis:names:tc:SAML:2.0:assertion".freeze
6
6
  SAMLP = "urn:oasis:names:tc:SAML:2.0:protocol".freeze
7
7
  XENC = "http://www.w3.org/2001/04/xmlenc#".freeze
8
+ XS = "http://www.w3.org/2001/XMLSchema".freeze
8
9
  XSI = "http://www.w3.org/2001/XMLSchema-instance".freeze
9
10
  X500 = "urn:oasis:names:tc:SAML:2.0:profiles:attribute:X500".freeze
10
11
 
11
12
  ALL = {
12
- 'dsig' => DSIG,
13
- 'md' => METADATA,
14
- 'saml' => SAML,
15
- 'samlp' => SAMLP,
16
- 'x500' => X500,
17
- 'xenc' => XENC,
18
- 'xsi' => XSI
13
+ 'xmlns:dsig' => DSIG,
14
+ 'xmlns:md' => METADATA,
15
+ 'xmlns:saml' => SAML,
16
+ 'xmlns:samlp' => SAMLP,
17
+ 'xmlns:x500' => X500,
18
+ 'xmlns:xenc' => XENC,
19
+ 'xmlns:xs' => XS,
20
+ 'xmlns:xsi' => XSI,
19
21
  }.freeze
20
22
  end
21
23
  end
data/lib/saml2/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module SAML2
2
- VERSION = '1.0.4'
2
+ VERSION = '1.0.5'
3
3
  end
@@ -9,15 +9,15 @@
9
9
  <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
10
10
  </Transforms>
11
11
  <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
12
- <DigestValue>b9jIvyyPEhWc/kHYHUA8LTCz4pwYSRCE+CL+SNqmiEk=</DigestValue>
12
+ <DigestValue>PTIbPIph/60z5i+AdxU32f7w2jiAD904pC3QNTs263E=</DigestValue>
13
13
  </Reference>
14
14
  </SignedInfo>
15
- <SignatureValue>eHHTFNYA1VNbzFOtsoTGn4A+BXdKFXHfJNvWWU52L1kBUfCyeFNVLby03yQRw+CO
16
- 71JomuTy/jpWU50h27sOfXjWjPkiULRHNPWuDIglrOgnUpu2HeD+i9IoA69/vF9J
17
- Bmwm2IubzhBAxc70Dm/oXZ238bN0E+c2u4B9jp6MdVXSL7RDmfKAU19ABAZlYTmJ
18
- 4xXlS0fjrGPOn+BszBR9BNLDvNST/MgaGmwAU0esP5dYzC0Pwi9lOGD5bO+MOxTp
19
- TBx9e0KY3BTEVVOxl8G6M8w9nx5mnKMgHpPq5P/uMHv7m8gUVdMQ8lDvPqyEibPw
20
- ABIureP4iKebJVl7JTeoYA==</SignatureValue>
15
+ <SignatureValue>BmxWVd1P+2L/qJgV5RYoktBkkXHamXHa4JAX7O2M6GSlCN9qwWaPe5fLqCcpNaMs
16
+ HLQF/cNEsY0ntgKlxI231Q6p/+ZYUwzMXXBTixYHgMzwvtqMwRLIP60c/oI3pQTY
17
+ 420Psr3WmZMnpmouQB9fcSwO33BbppbMv2mwHaVp00BvQcnwS4SU1wr1XCSKr4kc
18
+ TocP7R7zK+2FlVJB5wPzgUo9kIbjRTupVj6NOKjjp42Gv8GRg+fY02JZ98zaOnJk
19
+ PCNICiQpxsN5XWYxfdtsHRpES/Y36lPYkIPDmJhuTxmKoTl0apq9Pg4m9oM6RRQW
20
+ zI1YBtwCWrEjk8eW/Wrz3w==</SignatureValue>
21
21
  <KeyInfo>
22
22
  <X509Data>
23
23
  <X509Certificate>MIID+jCCAuKgAwIBAgIJAIz/He5UafnhMA0GCSqGSIb3DQEBBQUAMFsxCzAJBgNV
@@ -44,4 +44,4 @@ Cg8Yo62X9vWW6PaKXHs3N+g1Ig16NwjdVIYvcxLc2KY0vrqu/R5c8RbmCxMZyss9
44
44
  ZtltN+yN40INHGRWnHc=</X509Certificate>
45
45
  </X509Data>
46
46
  </KeyInfo>
47
- </Signature><saml:Subject><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent">jacob</saml:NameID><saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><saml:SubjectConfirmationData NotOnOrAfter="2015-02-12T22:54:29Z" Recipient="https://siteadmin.test.instructure.com/saml_consume" InResponseTo="_bec424fa5103428909a30ff1e31168327f79474984"/></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore="2015-02-12T22:51:24Z" NotOnOrAfter="2015-02-12T22:51:59Z"><saml:AudienceRestriction><saml:Audience>http://siteadmin.instructure.com/saml2</saml:Audience></saml:AudienceRestriction></saml:Conditions><saml:AuthnStatement AuthnInstant="2015-02-12T22:51:29Z"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement><saml:AttributeStatement xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><saml:Attribute xmlns:x500="urn:oasis:names:tc:SAML:2.0:profiles:attribute:X500" Name="urn:oid:2.5.4.42" FriendlyName="givenName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" x500:Encoding="LDAP"><saml:AttributeValue xsi:type="xsd:string">cody</saml:AttributeValue></saml:Attribute></saml:AttributeStatement></saml:Assertion></samlp:Response>
47
+ </Signature><saml:Subject><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent">jacob</saml:NameID><saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><saml:SubjectConfirmationData NotOnOrAfter="2015-02-12T22:54:29Z" Recipient="https://siteadmin.test.instructure.com/saml_consume" InResponseTo="_bec424fa5103428909a30ff1e31168327f79474984"/></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore="2015-02-12T22:51:24Z" NotOnOrAfter="2015-02-12T22:51:59Z"><saml:AudienceRestriction><saml:Audience>http://siteadmin.instructure.com/saml2</saml:Audience></saml:AudienceRestriction></saml:Conditions><saml:AuthnStatement AuthnInstant="2015-02-12T22:51:29Z"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement><saml:AttributeStatement xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><saml:Attribute xmlns:x500="urn:oasis:names:tc:SAML:2.0:profiles:attribute:X500" Name="urn:oid:2.5.4.42" FriendlyName="givenName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" x500:Encoding="LDAP"><saml:AttributeValue xsi:type="xs:string">cody</saml:AttributeValue></saml:Attribute></saml:AttributeStatement></saml:Assertion></samlp:Response>
@@ -2,9 +2,19 @@ require_relative '../spec_helper'
2
2
 
3
3
  module SAML2
4
4
  describe Attribute do
5
+ def serialize(attribute)
6
+ doc = Nokogiri::XML::Builder.new do |builder|
7
+ builder['saml'].Root('xmlns:saml' => Namespaces::SAML) do |builder|
8
+ attribute.build(builder)
9
+ builder.parent.child['xmlns:saml'] = Namespaces::SAML
10
+ end
11
+ end.doc
12
+ doc.root.child.to_s
13
+ end
14
+
5
15
  let(:eduPersonPrincipalNameXML) { <<XML.strip
6
16
  <saml:Attribute xmlns:x500="urn:oasis:names:tc:SAML:2.0:profiles:attribute:X500" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.6" FriendlyName="eduPersonPrincipalName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" x500:Encoding="LDAP" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
7
- <saml:AttributeValue xsi:type="xsd:string">user@domain</saml:AttributeValue>
17
+ <saml:AttributeValue xsi:type="xs:string">user@domain</saml:AttributeValue>
8
18
  </saml:Attribute>
9
19
  XML
10
20
  }
@@ -26,14 +36,62 @@ XML
26
36
  attr.friendly_name.must_equal 'eduPersonPrincipalName'
27
37
  attr.name_format.must_equal Attribute::NameFormats::URI
28
38
 
29
- doc = Nokogiri::XML::Builder.new do |builder|
30
- builder['saml'].Root('xmlns:saml' => Namespaces::SAML) do |builder|
31
- attr.build(builder)
32
- builder.parent.child['xmlns:saml'] = Namespaces::SAML
33
- end
34
- end.doc
35
- xml = doc.root.child.to_s
36
- xml.must_equal eduPersonPrincipalNameXML
39
+ serialize(attr).must_equal eduPersonPrincipalNameXML
40
+ end
41
+
42
+ it "should parse and serialize boolean values" do
43
+ xml = <<XML.strip
44
+ <saml:AttributeStatement xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
45
+ <saml:Attribute Name="attr">
46
+ <saml:AttributeValue xsi:type="xs:boolean">1</saml:AttributeValue>
47
+ </saml:Attribute>
48
+ </saml:AttributeStatement>
49
+ XML
50
+
51
+ stmt = AttributeStatement.from_xml(Nokogiri::XML(xml).root)
52
+ stmt.attributes.first.value.must_equal true
53
+
54
+ # serializes canonically
55
+ serialize(stmt).must_equal(xml.sub('>1<', '>true<'))
37
56
  end
57
+
58
+ it "should parse and serialize dateTime values" do
59
+ xml = <<XML.strip
60
+ <saml:AttributeStatement xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
61
+ <saml:Attribute Name="attr">
62
+ <saml:AttributeValue xsi:type="xs:dateTime">2015-06-29T18:37:03Z</saml:AttributeValue>
63
+ </saml:Attribute>
64
+ </saml:AttributeStatement>
65
+ XML
66
+
67
+ stmt = AttributeStatement.from_xml(Nokogiri::XML(xml).root)
68
+ stmt.attributes.first.value.must_equal Time.at(1435603023)
69
+
70
+ # serializes canonically
71
+ serialize(stmt).must_equal(xml)
72
+ end
73
+
74
+ it "should parse values with different namespace prefixes" do
75
+ xml = <<XML.strip
76
+ <saml:Attribute Name="attr" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xssi="http://www.w3.org/2001/XMLSchema-instance">
77
+ <saml:AttributeValue xssi:type="xsd:boolean">0</saml:AttributeValue>
78
+ </saml:Attribute>
79
+ XML
80
+
81
+ attr = Attribute.from_xml(Nokogiri::XML(xml).root)
82
+ attr.value.must_equal false
83
+ end
84
+
85
+ it "should parse untagged values" do
86
+ xml = <<XML.strip
87
+ <saml:Attribute Name="attr" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
88
+ <saml:AttributeValue>something</saml:AttributeValue>
89
+ </saml:Attribute>
90
+ XML
91
+
92
+ attr = Attribute.from_xml(Nokogiri::XML(xml).root)
93
+ attr.value.must_equal "something"
94
+ end
95
+
38
96
  end
39
97
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: saml2
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 1.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cody Cutrer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-05-15 00:00:00.000000000 Z
11
+ date: 2015-07-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -50,6 +50,26 @@ dependencies:
50
50
  - - ">="
51
51
  - !ruby/object:Gem::Version
52
52
  version: 0.9.2
53
+ - !ruby/object:Gem::Dependency
54
+ name: activesupport
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '3.2'
60
+ - - "<"
61
+ - !ruby/object:Gem::Version
62
+ version: '5.0'
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '3.2'
70
+ - - "<"
71
+ - !ruby/object:Gem::Version
72
+ version: '5.0'
53
73
  - !ruby/object:Gem::Dependency
54
74
  name: rake
55
75
  requirement: !ruby/object:Gem::Requirement