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
data/LICENSE ADDED
File without changes
data/README ADDED
@@ -0,0 +1,13 @@
1
+ == About
2
+
3
+ RSAML is a SAML implementation in Ruby. RSAML currently implements the elements defined in the SAML-Core 2.0 specification by defining an object model that mimics the structure of SAML. Method names and attributes have been made ruby-friendly and documentation is provided for each class and method. In certain cases the SAML specification is referenced directly and should be considered the final say whenever a question arises regarding SAML implementation.
4
+
5
+ Concrete requests:
6
+
7
+ * RSAML::Protocol::Query::AuthnQuery (Authentication query)
8
+ * RSAML::Protocol::Query::AttributeQuery (Attribute query)
9
+ * RSAML::Protocol::Query::AuthzDecisionQuery (Authorization query)
10
+
11
+ == A note on the implementation
12
+
13
+ RSAML is implemented in a very verbose fashion. While there are probably ways to reduce the code footprint using meta programming and other Rubyisms, I've attempted to stick to an implementation style that is easy to follow for non-rubyists and rubyists alike. Additionally I am striving for a comprehensive test suite that can be used to verify conformance to the SAML 2.0 specification.
@@ -0,0 +1,136 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+ require 'rake/packagetask'
5
+ require 'rake/gempackagetask'
6
+
7
+ require File.join(File.dirname(__FILE__), '/lib/rsaml/version')
8
+
9
+ PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
10
+ PKG_NAME = 'rsaml'
11
+ PKG_VERSION = RSAML::VERSION::STRING + PKG_BUILD
12
+ PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
13
+ PKG_DESTINATION = ENV["PKG_DESTINATION"] || "../#{PKG_NAME}"
14
+
15
+ RELEASE_NAME = "REL #{PKG_VERSION}"
16
+ PKG_FILES = FileList[
17
+ #'CHANGELOG',
18
+ #'LICENSE',
19
+ 'README',
20
+ #'TODO',
21
+ 'Rakefile',
22
+ 'bin/**/*',
23
+ 'doc/**/*',
24
+ 'lib/**/*',
25
+ ] - [ 'test' ]
26
+
27
+ spec = Gem::Specification.new do |s|
28
+ s.name = 'rsaml'
29
+ s.version = PKG_VERSION
30
+ s.summary = "RSAML - SAML implementation in Ruby."
31
+ s.description = <<-EOF
32
+ An implementation of SAML in Ruby.
33
+ EOF
34
+
35
+ s.add_dependency('rake', '>= 0.7.1')
36
+ s.add_dependency('uuid', '>= 1.0.4')
37
+
38
+ s.rdoc_options << '--exclude' << '.'
39
+ s.has_rdoc = false
40
+
41
+ s.files = PKG_FILES.to_a.delete_if {|f| f.include?('.svn')}
42
+ s.require_path = 'lib'
43
+
44
+ s.author = "Anthony Eden"
45
+ s.email = "anthonyeden@gmail.com"
46
+ end
47
+
48
+ begin
49
+ require 'jeweler'
50
+ Jeweler::Tasks.new do |gemspec|
51
+ gemspec.name = "rsaml"
52
+ gemspec.summary = "Ruby implementation of the SAML 2.0 Specification"
53
+ gemspec.description = %Q{RSAML is a SAML implementation in Ruby. RSAML currently implements the elements defined in the SAML-Core 2.0
54
+ specification by defining an object model that mimics the structure of SAML. Method names and attributes have been made
55
+ ruby-friendly and documentation is provided for each class and method. In certain cases the SAML specification is
56
+ referenced directly and should be considered the final say whenever a question arises regarding SAML implementation.
57
+ }
58
+ gemspec.email = ["anthonyeden@gmail.com", "scashin133@gmail.com", "elise@elisehuard.be"]
59
+ gemspec.homepage = "http://github.com/aeden/rsaml"
60
+ gemspec.authors = ["Anthony Eden"]
61
+ gemspec.add_dependency('activesupport', '>=2.3.4')
62
+ gemspec.add_dependency('uuid', '>=2.1.1')
63
+ gemspec.version = PKG_VERSION
64
+ gemspec.files = PKG_FILES.to_a.delete_if {|f| f.include?('.svn')}
65
+ gemspec.require_path = 'lib'
66
+ end
67
+ Jeweler::GemcutterTasks.new
68
+ rescue LoadError
69
+ puts "Jeweler not available. Install it with: gem install jeweler"
70
+ end
71
+
72
+
73
+ desc 'Default: run unit tests.'
74
+ task :default => :test
75
+
76
+ desc 'Test the library.'
77
+ Rake::TestTask.new(:test) do |t|
78
+ t.libs << 'lib'
79
+ test_files = FileList['test/**/*_test.rb']
80
+ t.test_files = test_files
81
+ t.verbose = true
82
+ end
83
+
84
+ desc 'Generate documentation for the library.'
85
+ Rake::RDocTask.new(:rdoc) do |rdoc|
86
+ rdoc.rdoc_dir = 'rdoc'
87
+ rdoc.title = 'RSAML'
88
+ rdoc.options << '--line-numbers' << '--inline-source'
89
+ rdoc.rdoc_files.include('README')
90
+ rdoc.rdoc_files.include('lib/**/*.rb')
91
+ end
92
+
93
+ namespace :rcov do
94
+ desc 'Measures test coverage'
95
+ task :test do
96
+ rm_f 'coverage.data'
97
+ mkdir 'coverage' unless File.exist?('coverage')
98
+ rcov = "rcov --aggregate coverage.data --text-summary -Ilib"
99
+ system("#{rcov} test/*_test.rb")
100
+ #system("open coverage/index.html") if PLATFORM['darwin']
101
+ end
102
+ end
103
+
104
+ desc "Generate code statistics"
105
+ task :lines do
106
+ lines, codelines, total_lines, total_codelines = 0, 0, 0, 0
107
+
108
+ for file_name in FileList["lib/**/*.rb"]
109
+ next if file_name =~ /vendor/
110
+ f = File.open(file_name)
111
+
112
+ while line = f.gets
113
+ lines += 1
114
+ next if line =~ /^\s*$/
115
+ next if line =~ /^\s*#/
116
+ codelines += 1
117
+ end
118
+ puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}"
119
+
120
+ total_lines += lines
121
+ total_codelines += codelines
122
+
123
+ lines, codelines = 0, 0
124
+ end
125
+
126
+ puts "Total: Lines #{total_lines}, LOC #{total_codelines}"
127
+ end
128
+
129
+ desc "Reinstall the gem from a local package copy"
130
+ task :reinstall => [:package] do
131
+ windows = RUBY_PLATFORM =~ /mswin/
132
+ sudo = windows ? '' : 'sudo'
133
+ gem = windows ? 'gem.bat' : 'gem'
134
+ `#{sudo} #{gem} uninstall -x -i #{PKG_NAME}`
135
+ `#{sudo} #{gem} install pkg/#{PKG_NAME}-#{PKG_VERSION}`
136
+ end
@@ -0,0 +1,57 @@
1
+ $KCODE = 'UTF-8'
2
+
3
+ module RSAML
4
+ def saml_namespaces
5
+ {
6
+ 'saml' => 'urn:oasis:names:tc:SAML:2.0:assertion',
7
+ 'samlp' => 'urn:oasis:names:tc:SAML:2.0:protocol',
8
+ 'ds' => 'http://www.w3.org/2000/09/xmldsig#',
9
+ 'xenc' => 'http://www.w3.org/2001/04/xmlenc#',
10
+ 'xs' => 'http://www.w3.org/2001/XMLSchema',
11
+ 'xsi' => 'http://www.w3.org/2001/XMLSchema-instance'
12
+ }
13
+ end
14
+ end
15
+
16
+ require 'rubygems'
17
+ require 'uuid'
18
+ begin
19
+ require 'active_support'
20
+ rescue LoadError
21
+ require 'activesupport'
22
+ end
23
+
24
+ require "rexml/document"
25
+
26
+ $:.unshift(File.dirname(__FILE__))
27
+
28
+ require 'xml_sig'
29
+ require 'xml_enc'
30
+
31
+ require 'rsaml/ext/string'
32
+
33
+ require 'rsaml/encrypted'
34
+ require 'rsaml/validatable'
35
+ require 'rsaml/errors'
36
+
37
+ require 'rsaml/action'
38
+ require 'rsaml/action_namespace'
39
+ require 'rsaml/advice'
40
+ require 'rsaml/assertion'
41
+ require 'rsaml/attribute'
42
+ require 'rsaml/audience'
43
+ require 'rsaml/authentication_context'
44
+ require 'rsaml/condition'
45
+ require 'rsaml/conditions'
46
+ require 'rsaml/evidence'
47
+ require 'rsaml/identifier'
48
+ require 'rsaml/proxy_restriction'
49
+ require 'rsaml/statement'
50
+ require 'rsaml/subject'
51
+ require 'rsaml/subject_confirmation'
52
+ require 'rsaml/subject_confirmation_data'
53
+ require 'rsaml/subject_locality'
54
+
55
+ require 'rsaml/protocol'
56
+
57
+ require 'rsaml/parser'
@@ -0,0 +1,57 @@
1
+ module RSAML #:nodoc:
2
+ # Specifies an action on the specified resource for which permission is sought. Its value provides the
3
+ # label for an action sought to be performed on the specified resource.
4
+ class Action
5
+ include Validatable
6
+
7
+ # Identifiers that MAY be used in the namespace attribute of the Action element to refer to
8
+ # common sets of actions to perform on resources.
9
+ #
10
+ # Each namespace provides a defined set of actions. Please refer to the SAML 2.0 specification
11
+ # for additional information.
12
+ def self.namespaces
13
+ ActionNamespace.namespaces
14
+ end
15
+
16
+ # A URI reference representing the namespace in which the name of the specified action is to be
17
+ # interpreted. If this element is absent, the namespace
18
+ # urn:oasis:names:tc:SAML:1.0:action:rwedc-negation is in effect.
19
+ attr_accessor :namespace
20
+
21
+ # The action value
22
+ attr_accessor :value
23
+
24
+ # Initialize the action with the given value.
25
+ def initialize(value)
26
+ @value = value
27
+ end
28
+
29
+ # The action namespace.
30
+ def namespace
31
+ @namespace ||= Action.namespaces[:rwedc_negation]
32
+ end
33
+
34
+ # Validate the structure
35
+ def validate
36
+ raise ValidationError, "Action value must be specified" if value.nil?
37
+ raise ValidationError, "Action value not in given namespace" unless namespace.valid_action?(value)
38
+ end
39
+
40
+ # Construct an XML fragment representing the action.
41
+ def to_xml(xml=Builder::XmlMarkup.new)
42
+ attributes = {}
43
+ attributes['Namespace'] = namespace unless namespace.nil?
44
+ xml.tag!('saml:Action', attributes, value)
45
+ end
46
+
47
+ # Construct an Action instance from the given XML Element or fragment.
48
+ def self.from_xml(element)
49
+ element = REXML::Document.new(element).root if element.is_a?(String)
50
+ action = Action.new(element.text)
51
+ if (namespace_attribute = element.attribute('Namespace'))
52
+ action.namespace = ActionNamespace.namespace_for_uri(namespace_attribute.value)
53
+ end
54
+ action
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,63 @@
1
+ module RSAML #:nodoc:
2
+ # Namespaces for actions.
3
+ class ActionNamespace
4
+ # A Hash of predefined namespaces from the SAML 2.0 specification. The value for each
5
+ # key/value pair is an ActionNamespace instance with a URI and set of action names.
6
+ def self.namespaces
7
+ @namespaces ||= {
8
+ :rwedc => ActionNamespace.new('urn:oasis:names:tc:SAML:1.0:action:rwedc', [
9
+ 'Read','Write','Execute','Delete','Control'
10
+ ]),
11
+ :rwedc_negation => ActionNamespace.new('urn:oasis:names:tc:SAML:1.0:action:rwedc-negation', [
12
+ 'Read','Write','Execute','Delete','Control','~Read','~Write','~Execute','~Delete','~Control'
13
+ ]),
14
+ :ghpp => ActionNamespace.new('urn:oasis:names:tc:SAML:1.0:action:ghpp', [
15
+ 'GET','HEAD','PUT','POST'
16
+ ]),
17
+ :unix => UnixActionNamespace.new
18
+ }
19
+ end
20
+
21
+ # Get an ActionNamespace instance for the given namespace URI. This method will
22
+ # return nil if no namespace is found for the given URI
23
+ def self.namespace_for_uri(uri)
24
+ namespaces.values.find { |ns| ns.uri == uri }
25
+ end
26
+
27
+ # URI identifying this action namespace
28
+ attr_accessor :uri
29
+
30
+ # Common sets of actions to perform on resources.
31
+ attr_accessor :action_names
32
+
33
+ # Initialize the action namespace with the given URI and action names
34
+ def initialize(uri, action_names)
35
+ @uri = uri
36
+ @action_names = action_names
37
+ end
38
+
39
+ # Return true if the given value is a valid action in the namespace.
40
+ def valid_action?(value)
41
+ action_names.include?(value)
42
+ end
43
+
44
+ # Return a string representation, specifically the URI for the namespace.
45
+ def to_s
46
+ uri
47
+ end
48
+ end
49
+
50
+ # Unix Action namespace implementation
51
+ class UnixActionNamespace < ActionNamespace
52
+ # Initialize
53
+ def initialize
54
+ super('urn:oasis:names:tc:SAML:1.0:action:unix', [])
55
+ end
56
+
57
+ # Return true if the given value is a valid action
58
+ def valid_action?(value)
59
+ # TODO: implement octal check
60
+ false
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,34 @@
1
+ module RSAML #:nodoc:
2
+ # Contains any additional information that the SAML authority wishes to provide. This information MAY be
3
+ # ignored by applications without affecting either the semantics or the validity of the assertion.
4
+ class Advice
5
+ include Validatable
6
+ # Contains a mixture of zero or more Assertion, EncryptedAssertion, assertion IDs, and assertion URIs.
7
+ # May also contain custom objects that produce namespace-qualified XML for non-SAML elements.
8
+ def assertions
9
+ @assertions ||= []
10
+ end
11
+
12
+ # Validate the advice structure.
13
+ def validate
14
+ assertions.each { |assertion| assertion.validate }
15
+ end
16
+
17
+ # Construct an XML fragment representing the assertion
18
+ def to_xml(xml=Builder::XmlMarkup.new)
19
+ xml.tag!('saml:Advice') {
20
+ assertions.each { |assertion| xml << assertion.to_xml }
21
+ }
22
+ end
23
+
24
+ # Construct an Advice instance from the given XML Element or fragment
25
+ def self.from_xml(element)
26
+ element = REXML::Document.new(element).root if element.is_a?(String)
27
+ advice = Advice.new
28
+ element.get_elements('saml:Assertion').each do |assertion_element|
29
+ advice.assertions << Assertion.from_xml(assertion_element)
30
+ end
31
+ advice
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,192 @@
1
+ module RSAML #:nodoc:
2
+ # Reference to an assertion via URI
3
+ class AssertionURIRef
4
+ include Validatable
5
+
6
+ # The URI reference
7
+ attr_accessor :uri
8
+
9
+ # Initialize the AssertionURIRef with the given URI
10
+ def initialize(uri)
11
+ @uri = uri
12
+ end
13
+
14
+ # Validate that the AssertionURIRef is structurally valid
15
+ def validate
16
+ raise ValidationError, "A URI is required" if uri.nil?
17
+ end
18
+
19
+ # Construct an XML fragment representing the assertion uri ref
20
+ def to_xml(xml=Builder::XmlMarkup.new)
21
+ xml.tag!('saml:AssertionURIRef', uri)
22
+ end
23
+
24
+ # Construct an Action instance from the given XML Element or fragment.
25
+ def self.from_xml(element)
26
+ element = REXML::Document.new(element).root if element.is_a?(String)
27
+ AssertionURIRef.new(element.text)
28
+ end
29
+ end
30
+
31
+ # Reference to an assertion via ID
32
+ class AssertionIDRef
33
+ include Validatable
34
+
35
+ # The ID reference
36
+ attr_accessor :id
37
+
38
+ # Initialize the AssertionIDRef with the given assertion ID
39
+ def initialize(id)
40
+ @id = id
41
+ end
42
+
43
+ # Validate that the AssertionIDRef is structurally valid
44
+ def validate
45
+ raise ValidationError, "An id is required" if id.nil?
46
+ end
47
+
48
+ # Construct an XML fragment representing the assertion ID ref
49
+ def to_xml(xml=Builder::XmlMarkup.new)
50
+ xml.tag!('saml:AssertionIDRef', id)
51
+ end
52
+
53
+ # Construct an Action instance from the given XML Element or fragment.
54
+ def self.from_xml(element)
55
+ element = REXML::Document.new(element).root if element.is_a?(String)
56
+ AssertionIDRef.new(element.text)
57
+ end
58
+ end
59
+
60
+ # An encrypted assertion
61
+ class EncryptedAssertion < Encrypted
62
+ # Construct an XML fragment representing the encrypted assertion
63
+ def to_xml(xml=Builder::XmlMarkup.new)
64
+ xml.tag!('saml:EncryptedAssertion') {
65
+ xml.tag!('xenc:EncryptedData', encrypted_data)
66
+ encrypted_keys.each { |key| xml << encrypted_key.to_xml }
67
+ }
68
+ end
69
+ end
70
+
71
+ # An assertion is a package of information that supplies zero or more statements made by a SAML
72
+ # authority.
73
+ class Assertion
74
+ include Validatable
75
+
76
+ # SAML assertions are usually made about a subject, however the subject is optional
77
+ attr_accessor :subject
78
+
79
+ # The version of this assertion.
80
+ attr_accessor :version
81
+
82
+ # The identifier for this assertion.
83
+ attr_accessor :id
84
+
85
+ # The time instant of issue in UTC
86
+ attr_accessor :issue_instant
87
+
88
+ # The SAML authority that is making the claim(s) in the assertion. The issuer SHOULD be unambiguous
89
+ # to the intended relying parties.
90
+ attr_accessor :issuer
91
+
92
+ # A signature that protects the integrity of and authenticates the issuer of the assertion.
93
+ attr_accessor :signature
94
+
95
+ # The subject of the statement(s) in the assertion.
96
+ attr_accessor :subject
97
+
98
+ # Conditions that MUST be evaluated when assessing the validity of and/or when using the assertion.
99
+ # Note: conditions should contain a single Conditions instance, not an array of Condition instances.
100
+ attr_accessor :conditions
101
+
102
+ # Construct a new assertion from the given issuer
103
+ def initialize(issuer)
104
+ @issuer = issuer
105
+ @version = "2.0"
106
+ @id = UUID.new.generate
107
+ @issue_instant = Time.now.utc
108
+ end
109
+
110
+ # Conditions collection
111
+ def conditions
112
+ @conditions ||= Conditions.new
113
+ end
114
+
115
+ # Assertion statements
116
+ def statements
117
+ @statements ||= []
118
+ end
119
+
120
+ # Additional information related to the assertion that assists processing in certain situations but which
121
+ # MAY be ignored by applications that do not understand the advice or do not wish to make use of it.
122
+ def advice
123
+ @advice ||= []
124
+ end
125
+
126
+ # Assert the assertion.
127
+ def assert
128
+ # rule: if there is a signature it must be asserted
129
+ signature.assert if signature
130
+
131
+ # rule: if there are conditions then they must be asserted
132
+ if conditions
133
+ # rule: an assertion cache should be kept if conditions allow it
134
+ assertion_cache << self unless conditions.cache?
135
+ conditions.assert
136
+ end
137
+ end
138
+
139
+ # Validate the assertion. This validates the structural integrity of the assertion, not the
140
+ # validity of the assertion itself. To "assert" the assertion use the assert method.
141
+ def validate
142
+ # rule: if there are no statements there must be a subject
143
+ if statements.length == 0 && subject.nil?
144
+ raise ValidationError, "An assertion with no statements must have a subject"
145
+ end
146
+
147
+ # rule: if there is an authentication then there must be a subject
148
+ statements.each do |statement|
149
+ if statement_classes.include?(statement.class)
150
+ if subject.nil?
151
+ raise ValidationError, "An assertion with an #{statement.class.name} must have a subject"
152
+ else
153
+ break
154
+ end
155
+ end
156
+ end
157
+ end
158
+
159
+ # Construct an XML fragment representing the assertion
160
+ def to_xml(xml=Builder::XmlMarkup.new)
161
+ attributes = {'Version' => version, 'ID' => id, 'IssueInstant' => issue_instant.xmlschema}
162
+ xml.tag!('saml:Assertion', attributes) {
163
+ xml << issuer.to_xml
164
+ xml << signature.to_xml unless signature.nil?
165
+ xml << subject.to_xml unless subject.nil?
166
+ xml << conditions.to_xml unless conditions.nil? || conditions.empty?
167
+ advice.each { |a| xml << a.to_xml }
168
+ statements.each { |s| xml << s.to_xml }
169
+ }
170
+ end
171
+
172
+ # Construct an Action instance from the given XML Element or fragment.
173
+ def self.from_xml(element)
174
+ element = REXML::Document.new(element).root if element.is_a?(String)
175
+ issuer = Identifier::Issuer.from_xml(element.get_elements('saml:Issuer').first)
176
+ assertion = Assertion.new(issuer)
177
+ if (subject = element.get_elements('saml:Subject').first)
178
+ assertion.subject = Subject.from_xml(subject)
179
+ end
180
+ assertion
181
+ end
182
+
183
+ protected
184
+ def assertion_cache
185
+ @assertion_cache ||= []
186
+ end
187
+
188
+ def statement_classes
189
+ [AuthenticationStatement, AttributeStatement, AuthorizationDecisionStatement]
190
+ end
191
+ end
192
+ end