saml2 1.0.4 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
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