rsaml 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. data/LICENSE +0 -0
  2. data/README +13 -0
  3. data/Rakefile +136 -0
  4. data/lib/rsaml.rb +57 -0
  5. data/lib/rsaml/action.rb +57 -0
  6. data/lib/rsaml/action_namespace.rb +63 -0
  7. data/lib/rsaml/advice.rb +34 -0
  8. data/lib/rsaml/assertion.rb +192 -0
  9. data/lib/rsaml/attribute.rb +76 -0
  10. data/lib/rsaml/audience.rb +19 -0
  11. data/lib/rsaml/authentication_context.rb +34 -0
  12. data/lib/rsaml/authn_context/README +1 -0
  13. data/lib/rsaml/authn_context/authentication_context_declaration.rb +42 -0
  14. data/lib/rsaml/authn_context/identification.rb +10 -0
  15. data/lib/rsaml/authn_context/physical_verification.rb +24 -0
  16. data/lib/rsaml/condition.rb +13 -0
  17. data/lib/rsaml/conditions.rb +107 -0
  18. data/lib/rsaml/encrypted.rb +12 -0
  19. data/lib/rsaml/errors.rb +16 -0
  20. data/lib/rsaml/evidence.rb +21 -0
  21. data/lib/rsaml/ext/string.rb +5 -0
  22. data/lib/rsaml/identifier.rb +9 -0
  23. data/lib/rsaml/identifier/base.rb +23 -0
  24. data/lib/rsaml/identifier/issuer.rb +28 -0
  25. data/lib/rsaml/identifier/name.rb +55 -0
  26. data/lib/rsaml/parser.rb +23 -0
  27. data/lib/rsaml/protocol.rb +21 -0
  28. data/lib/rsaml/protocol/artifact_resolve.rb +14 -0
  29. data/lib/rsaml/protocol/assertion_id_request.rb +18 -0
  30. data/lib/rsaml/protocol/authn_request.rb +91 -0
  31. data/lib/rsaml/protocol/idp_entry.rb +18 -0
  32. data/lib/rsaml/protocol/idp_list.rb +28 -0
  33. data/lib/rsaml/protocol/message.rb +65 -0
  34. data/lib/rsaml/protocol/name_id_policy.rb +31 -0
  35. data/lib/rsaml/protocol/query.rb +12 -0
  36. data/lib/rsaml/protocol/query/attribute_query.rb +56 -0
  37. data/lib/rsaml/protocol/query/authn_query.rb +30 -0
  38. data/lib/rsaml/protocol/query/authz_decision_query.rb +40 -0
  39. data/lib/rsaml/protocol/query/subject_query.rb +22 -0
  40. data/lib/rsaml/protocol/request.rb +27 -0
  41. data/lib/rsaml/protocol/requested_authn_context.rb +34 -0
  42. data/lib/rsaml/protocol/response.rb +56 -0
  43. data/lib/rsaml/protocol/scoping.rb +33 -0
  44. data/lib/rsaml/protocol/status.rb +38 -0
  45. data/lib/rsaml/protocol/status_code.rb +84 -0
  46. data/lib/rsaml/proxy_restriction.rb +30 -0
  47. data/lib/rsaml/statement.rb +10 -0
  48. data/lib/rsaml/statement/attribute_statement.rb +27 -0
  49. data/lib/rsaml/statement/authentication_statement.rb +57 -0
  50. data/lib/rsaml/statement/authorization_decision_statement.rb +53 -0
  51. data/lib/rsaml/statement/base.rb +9 -0
  52. data/lib/rsaml/subject.rb +37 -0
  53. data/lib/rsaml/subject_confirmation.rb +35 -0
  54. data/lib/rsaml/subject_confirmation_data.rb +55 -0
  55. data/lib/rsaml/subject_locality.rb +27 -0
  56. data/lib/rsaml/validatable.rb +21 -0
  57. data/lib/rsaml/version.rb +9 -0
  58. data/lib/xml_enc.rb +3 -0
  59. data/lib/xml_sig.rb +11 -0
  60. data/lib/xml_sig/canonicalization_method.rb +43 -0
  61. data/lib/xml_sig/key_info.rb +55 -0
  62. data/lib/xml_sig/reference.rb +57 -0
  63. data/lib/xml_sig/signature.rb +29 -0
  64. data/lib/xml_sig/signature_method.rb +20 -0
  65. data/lib/xml_sig/signed_info.rb +27 -0
  66. data/lib/xml_sig/transform.rb +37 -0
  67. data/test/action_namespace_test.rb +93 -0
  68. data/test/action_test.rb +51 -0
  69. data/test/advice_test.rb +25 -0
  70. data/test/assertion_test.rb +192 -0
  71. data/test/attribute_test.rb +60 -0
  72. data/test/authentication_context_test.rb +26 -0
  73. data/test/conditions_test.rb +84 -0
  74. data/test/evidence_test.rb +33 -0
  75. data/test/identifier_test.rb +22 -0
  76. data/test/issuer_test.rb +33 -0
  77. data/test/name_test.rb +33 -0
  78. data/test/parser_test.rb +32 -0
  79. data/test/protocol/assertion_id_request_test.rb +19 -0
  80. data/test/protocol/attribute_query_test.rb +30 -0
  81. data/test/protocol/authn_query_test.rb +20 -0
  82. data/test/protocol/authn_request_test.rb +56 -0
  83. data/test/protocol/authz_decision_query_test.rb +31 -0
  84. data/test/protocol/idp_list_test.rb +15 -0
  85. data/test/protocol/request_test.rb +66 -0
  86. data/test/protocol/response_test.rb +68 -0
  87. data/test/protocol/scoping_test.rb +20 -0
  88. data/test/protocol/status_code_test.rb +34 -0
  89. data/test/protocol/status_test.rb +16 -0
  90. data/test/proxy_restriction_test.rb +20 -0
  91. data/test/rsaml_test.rb +12 -0
  92. data/test/statement_test.rb +101 -0
  93. data/test/subject_locality_test.rb +27 -0
  94. data/test/subject_test.rb +44 -0
  95. data/test/test_helper.rb +16 -0
  96. data/test/xml_sig/canonicalization_test.rb +19 -0
  97. metadata +187 -0
@@ -0,0 +1,55 @@
1
+ module RSAML #:nodoc:
2
+ module Identifier #:nodoc:
3
+ # A Name identifier.
4
+ class Name < Base
5
+ # The following identifiers MAY be used to refer to the classification of the attribute name
6
+ # for purposes of interpreting the name.
7
+ def self.formats
8
+ {
9
+ :unspecified => 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified',
10
+ :email_address => 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
11
+ :x509_subject_name => 'urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName',
12
+ :windows_domain_qualified_name => 'urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName',
13
+ :kerberos => 'urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos',
14
+ :entity => 'urn:oasis:names:tc:SAML:2.0:nameid-format:entity',
15
+ :persistent => 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
16
+ :transient => 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
17
+ }
18
+ end
19
+
20
+ # A URI reference representing the classification of string-based identifier information.
21
+ attr_accessor :format
22
+
23
+ # A name identifier established by a service provider or affiliation of providers for the entity, if
24
+ # different from the primary name identifier given
25
+ attr_accessor :sp_provided_id
26
+
27
+ # The value of the identifier
28
+ attr_accessor :value
29
+
30
+ # Initialize the identifier with the given value
31
+ def initialize(value)
32
+ @value = value
33
+ end
34
+
35
+ # The format of the name.
36
+ def format
37
+ @format ||= Name.formats[:unspecified]
38
+ end
39
+
40
+ # Construct an XML fragment representing the name
41
+ def to_xml(xml=Builder::XmlMarkup.new)
42
+ attributes = {'Format' => format}
43
+ attributes['NameQualifier'] = name_qualifier unless name_qualifier.nil?
44
+ attributes['SPNameQualifier'] = sp_name_qualifier unless sp_name_qualifier.nil?
45
+ attributes['SPProvidedID'] = sp_provided_id unless sp_provided_id.nil?
46
+ xml.tag!('saml:NameID', value, attributes)
47
+ end
48
+
49
+ def self.from_xml(element)
50
+ Name.new(element.text)
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,23 @@
1
+ module RSAML #:nodoc:
2
+ class Parser
3
+ # Parse the given SAML message and return a Ruby object structure representing
4
+ # the message. This may include protocol and core classes.
5
+ def parse(xml)
6
+ messages = []
7
+ xml = REXML::Document.new(xml) if xml.is_a?(String)
8
+
9
+ if attribute_query_elements = xml.get_elements('samlp:AttributeQuery')
10
+ attribute_query_elements.each do |attribute_query_element|
11
+ messages << Protocol::Query::AttributeQuery.from_xml(attribute_query_element)
12
+ end
13
+ end
14
+
15
+ case messages.length
16
+ when 1: messages.first
17
+ when 0: nil
18
+ else
19
+ messages
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ module RSAML #:nodoc:
2
+ # The protocol module contains request and response classes for the SAML protocol implementation
3
+ module Protocol
4
+ end
5
+ end
6
+
7
+ require 'rsaml/protocol/message'
8
+ require 'rsaml/protocol/status_code'
9
+ require 'rsaml/protocol/status'
10
+ require 'rsaml/protocol/request'
11
+ require 'rsaml/protocol/response'
12
+
13
+ require 'rsaml/protocol/name_id_policy'
14
+ require 'rsaml/protocol/scoping'
15
+ require 'rsaml/protocol/idp_list'
16
+ require 'rsaml/protocol/idp_entry'
17
+
18
+ require 'rsaml/protocol/assertion_id_request'
19
+ require 'rsaml/protocol/authn_request'
20
+
21
+ require 'rsaml/protocol/query'
@@ -0,0 +1,14 @@
1
+ module RSAML #:nodoc:
2
+ module Protocol #:nodoc:
3
+ # The ArtifactResolve message is used to request that a SAML protocol message be returned in an
4
+ # <ArtifactResponse> message by specifying an artifact that represents the SAML protocol message.
5
+ # The original transmission of the artifact is governed by the specific protocol binding that is
6
+ # being used; see [SAMLBind] for more information on the use of artifacts in bindings.
7
+ #
8
+ # The <ArtifactResolve> message SHOULD be signed or otherwise authenticated and integrity
9
+ # protected by the protocol binding used to deliver the message.
10
+ class ArtifactResolve
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,18 @@
1
+ module RSAML #:nodoc:
2
+ module Protocol #:nodoc:
3
+ # Request to return assertions with the given ids
4
+ class AssertionIDRequest
5
+ # Specify each assertion to return.
6
+ def assertion_id_refs
7
+ @assertion_id_refs ||= []
8
+ end
9
+
10
+ # Construct an XML fragment representing the assertion id request
11
+ def to_xml(xml=Builder::XmlMarkup.new)
12
+ xml.tag!('samlp:AssertionIDRequest') {
13
+ assertion_id_refs.each { |assertion_id_ref| xml << assertion_id_ref.to_xml }
14
+ }
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,91 @@
1
+ module RSAML #:nodoc:
2
+ module Protocol #:nodoc:
3
+ # To request that an identity provider issue an assertion with an authentication statement, a presenter
4
+ # authenticates to that identity provider (or relies on an existing security context) and sends it an
5
+ # <AuthnRequest> message that describes the properties that the resulting assertion needs to have to
6
+ # satisfy its purpose. Among these properties may be information that relates to the content of the assertion
7
+ # and/or information that relates to how the resulting <Response> message should be delivered to the
8
+ # requester. The process of authentication of the presenter may take place before, during, or after the initial
9
+ # delivery of the <AuthnRequest> message.
10
+ #
11
+ # The requester might not be the same as the presenter of the request if, for example, the requester is a
12
+ # relying party that intends to use the resulting assertion to authenticate or authorize the requested subject
13
+ # so that the relying party can decide whether to provide a service.
14
+ class AuthnRequest < Request
15
+ # Specifies the requested subject of the resulting assertion(s).
16
+ attr_accessor :subject
17
+
18
+ # Specifies constraints on the name identifier to be used to represent the requested subject. If omitted,
19
+ # then any type of identifier supported by the identity provider for the requested subject can be used,
20
+ # constrained by any relevant deployment-specific policies, with respect to privacy, for example.
21
+ attr_accessor :name_id_policy
22
+
23
+ # Specifies the SAML conditions the requester expects to limit the validity and/or use of the resulting
24
+ # assertion(s). The responder MAY modify or supplement this set as it deems necessary. The
25
+ # information in this element is used as input to the process of constructing the assertion, rather than as
26
+ # conditions on the use of the request itself.
27
+ attr_accessor :conditions
28
+
29
+ # Specifies the requirements, if any, that the requester places on the authentication context that applies
30
+ # to the responding provider's authentication of the presenter.
31
+ attr_accessor :requested_authn_context
32
+
33
+ # Specifies a set of identity providers trusted by the requester to authenticate the presenter, as well as
34
+ # limitations and context related to proxying of the <Au message to subsequent identity providers by the
35
+ # responder.
36
+ attr_accessor :scoping
37
+
38
+ # A Boolean value. If "true", the identity provider MUST authenticate the presenter directly rather than
39
+ # rely on a previous security context. If a value is not provided, the default is "false". However, if both
40
+ # ForceAuthn and IsPassive are "true", the identity provider MUST NOT freshly authenticate the
41
+ # presenter unless the constraints of IsPassive can be met.
42
+ attr_accessor :force_authn
43
+
44
+ # A Boolean value. If "true", the identity provider and the user agent itself MUST NOT visibly take control
45
+ # of the user interface from the requester and interact with the presenter in a noticeable fashion. If a
46
+ # value is not provided, the default is "false".
47
+ attr_accessor :is_passive
48
+
49
+ attr_accessor :assertion_consumer_service_index
50
+
51
+ attr_accessor :assertion_consumer_service_url
52
+
53
+ # A URI reference that identifies a SAML protocol binding to be used when returning the response message.
54
+ attr_accessor :protocol_binding
55
+
56
+ # Indirectly identifies information associated with the requester describing the SAML attributes the
57
+ # requester desires or requires to be supplied by the identity provider in the <Response> message. The
58
+ # identity provider MUST have a trusted means to map the index value in the attribute to information
59
+ # associated with the requester.
60
+ attr_accessor :attribute_consuming_service_url
61
+
62
+ # Specifies the human-readable name of the requester for use by the presenter's user agent or the
63
+ # identity provider
64
+ attr_accessor :provider_name
65
+
66
+ # Validate the authentication request.
67
+ def validate
68
+ raise ValidationError, "Conditions must be of type Conditions" if conditions && !conditions.is_a?(Conditions)
69
+ end
70
+
71
+ # Construct an XML fragment representing the authentication request
72
+ def to_xml(xml=Builder::XmlMarkup.new)
73
+ attributes = {}
74
+ attributes['ForceAuthn'] = force_authn unless force_authn.nil?
75
+ attributes['IsPassive'] = is_passive unless is_passive.nil?
76
+ # TODO implement assertion consumer service index
77
+ # TODO implement assertion consumer service URL
78
+ attributes['ProtocolBinding'] = protocol_binding unless protocol_binding.nil?
79
+ attributes['AttributeConsumingServiceURL'] = attribute_consuming_service_url unless attribute_consuming_service_url.nil?
80
+ attributes['ProviderName'] = provider_name unless provider_name.nil?
81
+ xml.tag!('samlp:AuthnRequest', attributes) {
82
+ xml << subject.to_xml unless subject.nil?
83
+ xml << name_id_policy.to_xml unless name_id_policy.nil?
84
+ xml << conditions.to_xml unless conditions.nil?
85
+ xml << requested_authn_context unless requested_authn_context.nil?
86
+ xml << scoping.to_xml unless scoping.nil?
87
+ }
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,18 @@
1
+ module RSAML #:nodoc:
2
+ module Protocol #:nodoc:
3
+ class IDPEntry
4
+ attr_accessor :provider_id
5
+
6
+ # Initialize the IDP entry
7
+ def initialize(provider_id)
8
+ @provider_id = provider_id
9
+ end
10
+
11
+ # Construct an XML fragment representing the idp list
12
+ def to_xml(xml=Builder::XmlMarkup.new)
13
+ attributes = {'ProviderID' => provider_id}
14
+ xml.tag!('samlp:IDPEntry', attributes)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,28 @@
1
+ module RSAML #:nodoc:
2
+ module Protocol #:nodoc:
3
+ # Specifies the identity providers trusted by the requester to authenticate the presenter.
4
+ class IDPList
5
+ # Initialize the IDP list
6
+ def initialize(*idp_entries)
7
+ @idp_entries = idp_entries
8
+ end
9
+
10
+ # Information about identity providers.
11
+ def idp_entries
12
+ @idp_entries ||= []
13
+ end
14
+
15
+ # Validate the IDPList structure.
16
+ def validate
17
+ raise ValidationError, "At least one IDP entry is required" if idp_entries.empty?
18
+ end
19
+
20
+ # Construct an XML fragment representing the idp list
21
+ def to_xml(xml=Builder::XmlMarkup.new)
22
+ xml.tag!('samlp:IDPList') {
23
+ idp_entries.each { |idp_entry| xml << idp_entry.to_xml }
24
+ }
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,65 @@
1
+ module RSAML #:nodoc:
2
+ module Protocol #:nodoc:
3
+ # Base class for messages. This class should not be instantiated directly, rather the Request and Response
4
+ # classes should be used.
5
+ class Message
6
+
7
+ # An identifier for the message. It is of type xs:ID and MUST follow the requirements specified in Section
8
+ # 1.3.4 of the SAML 2.0 specification for identifier uniqueness.
9
+ attr_accessor :id
10
+
11
+ # The version of this message. The identifier for the version of SAML defined in this specification is "2.0".
12
+ attr_accessor :version
13
+
14
+ # The time instant of issue of the message. The time value must be encoded in UTC.
15
+ attr_accessor :issue_instant
16
+
17
+ # A URI reference indicating the address to which this message has been sent. This is useful to prevent
18
+ # malicious forwarding of messages to unintended recipients, a protection that is required by some
19
+ # protocol bindings. If it is present, the actual recipient MUST check that the URI reference identifies the
20
+ # location at which the message was sent or received. If it does not, the response MUST be discarded. Some
21
+ # protocol bindings may require the use of this attribute.
22
+ attr_accessor :destination
23
+
24
+ # Indicates whether or not (and under what conditions) consent has been obtained from a principal in
25
+ # the sending of this message. If no Consent value is provided, the identifier
26
+ # urn:oasis:names:tc:SAML:2.0:consent:unspecified is in effect.
27
+ attr_accessor :consent
28
+
29
+ # Identifies the entity that generated the message.
30
+ attr_accessor :issuer
31
+
32
+ # An XML Signature that authenticates the requestor or responder and provides message integrity.
33
+ attr_accessor :signature
34
+
35
+ # Initialize the message instance
36
+ def initialize
37
+ @id = UUID.new.generate
38
+ @version = "2.0"
39
+ @issue_instant = Time.now.utc
40
+ end
41
+
42
+ # This extension point contains optional protocol message extension elements that are agreed on
43
+ # between the communicating parties.
44
+ def extensions
45
+ @extionsion ||= []
46
+ end
47
+
48
+ # Validate the request structure
49
+ def validate
50
+ raise ValidationError, "ID is required" if id.nil?
51
+ raise ValidationError, "Version is required" if version.nil?
52
+ raise ValidationError, "Issue instant is required" if issue_instant.nil?
53
+ raise ValidationError, "Issue instant must be UTC" unless issue_instant.utc?
54
+ end
55
+
56
+ protected
57
+ # Add XML Namespace attributes
58
+ def add_xmlns(attributes)
59
+ attributes['xmlns:samlp'] = "urn:oasis:names:tc:SAML:2.0:protocol"
60
+ attributes['xmlns:saml'] = "urn:oasis:names:tc:SAML:2.0:assertion"
61
+ attributes
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,31 @@
1
+ module RSAML #:nodoc:
2
+ module Protocol #:nodoc:
3
+ # Tailors the name identifier in the subjects of assertions resulting from an authentication request.
4
+ class NameIdPolicy
5
+ # Specifies the URI reference corresponding to a name identifier format
6
+ attr_accessor :format
7
+
8
+ # Optionally specifies that the assertion subject's identifier be returned (or created) in the namespace of
9
+ # a service provider other than the requester, or in the namespace of an affiliation group of service
10
+ # providers.
11
+ attr_accessor :sp_name_qualifier
12
+
13
+ # A Boolean value used to indicate whether the identity provider is allowed, in the course of fulfilling the
14
+ # request, to create a new identifier to represent the principal. Defaults to "false". When "false", the
15
+ # requester constrains the identity provider to only issue an assertion to it if an acceptable identifier for
16
+ # the principal has already been established. Note that this does not prevent the identity provider from
17
+ # creating such identifiers outside the context of this specific request (for example, in advance for a
18
+ # large number of principals).
19
+ attr_accessor :allow_create
20
+
21
+ # Construct an XML fragment representing the name id policy
22
+ def to_xml(xml=Builder::XmlMarkup.new)
23
+ attributes = {}
24
+ attributes['Format'] = format unless format.nil?
25
+ attributes['SPNameQualifier'] = sp_name_qualifier unless sp_name_qualifier.nil?
26
+ attributes['AllowCreate'] = allow_create unless allow_create.nil?
27
+ xml.tag!('samlp:NameIDPolicy', attributes)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,12 @@
1
+ module RSAML #:nodoc:
2
+ module Protocol #:nodoc:
3
+ # Module containing SAML query classes.
4
+ module Query
5
+ end
6
+ end
7
+ end
8
+
9
+ require 'rsaml/protocol/query/subject_query'
10
+ require 'rsaml/protocol/query/authn_query'
11
+ require 'rsaml/protocol/query/attribute_query'
12
+ require 'rsaml/protocol/query/authz_decision_query'
@@ -0,0 +1,56 @@
1
+ module RSAML #:nodoc:
2
+ module Protocol #:nodoc:
3
+ module Query #:nodoc:
4
+ # used to make the query "Return the requested attributes for this subject." A successful response
5
+ # will be in the form of assertions containing attribute statements, to the extent allowed by policy.
6
+ class AttributeQuery < SubjectQuery
7
+ # Each attribute element specifies an attribute whose value(s) are to be returned. If no
8
+ # attributes are specified, it indicates that all attributes allowed by policy are requested.
9
+ # If a given attribute element contains one or more AttributeValue elements, then if that attribute
10
+ # is returned in the response, it MUST NOT contain any values that are not equal to the values
11
+ # specified in the query. In the absence of equality rules specified by particular profiles or
12
+ # attributes, equality is defined as an identical XML representation of the value. Each value in
13
+ # the array MUST be an Attribute instance.
14
+ def attributes
15
+ @attributes ||= []
16
+ end
17
+
18
+ # Validate the structure of the attribute query.
19
+ def validate
20
+ matched = {}
21
+ duplicated_attributes = []
22
+ attributes.each do |attribute|
23
+ if matched.has_key?(attribute.name) && matched[attribute.name] == attribute.name_format
24
+ duplicated_attributes << attribute.name unless duplicated_attributes.include?(attribute.name)
25
+ else
26
+ matched[attribute.name] = attribute.name_format
27
+ end
28
+ end
29
+ if !duplicated_attributes.empty?
30
+ raise ValidationError, "An attribute with the same name and name format may only be specified once. The following attributes were specified multiple times: #{duplicated_attributes.join(',')}"
31
+ end
32
+ end
33
+
34
+ # Construct an XML fragment representing the attribute query
35
+ def to_xml(xml=Builder::XmlMarkup.new)
36
+ xml_attributes = {}
37
+ xml.tag!('samlp:AttributeQuery', xml_attributes) {
38
+ xml << subject.to_xml unless subject.nil?
39
+ attributes.each { |attribute| xml << attribute.to_xml }
40
+ }
41
+ end
42
+
43
+ # Construct an AttributeQuery instance from the XML Element.
44
+ def self.from_xml(element)
45
+ element = REXML::Document.new(element).root if element.is_a?(String)
46
+ subject = Subject.from_xml(element.get_elements('saml:Subject').first)
47
+ attribute_query = AttributeQuery.new(subject)
48
+ element.get_elements('saml:Attribute').each do |attribute_element|
49
+ attribute_query.attributes << Attribute.from_xml(attribute_element)
50
+ end
51
+ attribute_query
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end