scashin133-rsaml 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. data/.autotest +10 -0
  2. data/.gitignore +2 -0
  3. data/LICENSE +0 -0
  4. data/README +13 -0
  5. data/Rakefile +141 -0
  6. data/lib/rsaml/action.rb +57 -0
  7. data/lib/rsaml/action_namespace.rb +63 -0
  8. data/lib/rsaml/advice.rb +34 -0
  9. data/lib/rsaml/assertion.rb +192 -0
  10. data/lib/rsaml/attribute.rb +76 -0
  11. data/lib/rsaml/audience.rb +19 -0
  12. data/lib/rsaml/authentication_context.rb +34 -0
  13. data/lib/rsaml/authn_context/README +1 -0
  14. data/lib/rsaml/authn_context/authentication_context_declaration.rb +42 -0
  15. data/lib/rsaml/authn_context/identification.rb +10 -0
  16. data/lib/rsaml/authn_context/physical_verification.rb +24 -0
  17. data/lib/rsaml/condition.rb +13 -0
  18. data/lib/rsaml/conditions.rb +107 -0
  19. data/lib/rsaml/encrypted.rb +12 -0
  20. data/lib/rsaml/errors.rb +16 -0
  21. data/lib/rsaml/evidence.rb +21 -0
  22. data/lib/rsaml/ext/string.rb +5 -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/identifier.rb +9 -0
  27. data/lib/rsaml/parser.rb +23 -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/attribute_query.rb +56 -0
  36. data/lib/rsaml/protocol/query/authn_query.rb +30 -0
  37. data/lib/rsaml/protocol/query/authz_decision_query.rb +40 -0
  38. data/lib/rsaml/protocol/query/subject_query.rb +22 -0
  39. data/lib/rsaml/protocol/query.rb +12 -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/protocol.rb +21 -0
  47. data/lib/rsaml/proxy_restriction.rb +30 -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/statement.rb +10 -0
  53. data/lib/rsaml/subject.rb +37 -0
  54. data/lib/rsaml/subject_confirmation.rb +34 -0
  55. data/lib/rsaml/subject_confirmation_data.rb +45 -0
  56. data/lib/rsaml/subject_locality.rb +27 -0
  57. data/lib/rsaml/validatable.rb +21 -0
  58. data/lib/rsaml/version.rb +9 -0
  59. data/lib/rsaml.rb +51 -0
  60. data/lib/xml_enc.rb +3 -0
  61. data/lib/xml_sig/canonicalization_method.rb +43 -0
  62. data/lib/xml_sig/key_info.rb +55 -0
  63. data/lib/xml_sig/reference.rb +57 -0
  64. data/lib/xml_sig/signature.rb +29 -0
  65. data/lib/xml_sig/signature_method.rb +20 -0
  66. data/lib/xml_sig/signed_info.rb +27 -0
  67. data/lib/xml_sig/transform.rb +37 -0
  68. data/lib/xml_sig.rb +11 -0
  69. data/scashin133-rsaml.gemspec +180 -0
  70. data/test/action_namespace_test.rb +93 -0
  71. data/test/action_test.rb +51 -0
  72. data/test/advice_test.rb +25 -0
  73. data/test/assertion_test.rb +192 -0
  74. data/test/attribute_test.rb +60 -0
  75. data/test/authentication_context_test.rb +26 -0
  76. data/test/conditions_test.rb +84 -0
  77. data/test/evidence_test.rb +33 -0
  78. data/test/identifier_test.rb +22 -0
  79. data/test/issuer_test.rb +32 -0
  80. data/test/name_test.rb +32 -0
  81. data/test/parser_test.rb +32 -0
  82. data/test/protocol/assertion_id_request_test.rb +19 -0
  83. data/test/protocol/attribute_query_test.rb +30 -0
  84. data/test/protocol/authn_query_test.rb +20 -0
  85. data/test/protocol/authn_request_test.rb +56 -0
  86. data/test/protocol/authz_decision_query_test.rb +31 -0
  87. data/test/protocol/idp_list_test.rb +15 -0
  88. data/test/protocol/request_test.rb +66 -0
  89. data/test/protocol/response_test.rb +68 -0
  90. data/test/protocol/scoping_test.rb +20 -0
  91. data/test/protocol/status_code_test.rb +34 -0
  92. data/test/protocol/status_test.rb +16 -0
  93. data/test/proxy_restriction_test.rb +20 -0
  94. data/test/rsaml_test.rb +12 -0
  95. data/test/sample_data/attribute_query.xml +8 -0
  96. data/test/statement_test.rb +101 -0
  97. data/test/subject_locality_test.rb +27 -0
  98. data/test/subject_test.rb +44 -0
  99. data/test/test_helper.rb +16 -0
  100. data/test/xml_sig/canonicalization_test.rb +19 -0
  101. data/test/xml_sig/iso-8859-1.txt +1 -0
  102. metadata +206 -0
data/.autotest ADDED
@@ -0,0 +1,10 @@
1
+ module Autotest::CustomTestMatch
2
+ Autotest.add_hook :initialize do |at|
3
+ at.add_mapping(/test/) do |f, _|
4
+ at.files_matching(/_test\.rb$/)
5
+ end
6
+ at.add_mapping(/lib\/.*/) do |f, _|
7
+ at.files_matching(/_test\.rb$/)
8
+ end
9
+ end
10
+ end
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ uuid.state
2
+ rdoc
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.
data/Rakefile ADDED
@@ -0,0 +1,141 @@
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
+
17
+ begin
18
+ require 'jeweler'
19
+ Jeweler::Tasks.new do |gemspec|
20
+ gemspec.name = "scashin133-rsaml"
21
+ gemspec.summary = "Ruby implementation of the SAML 2.0 Specification"
22
+ gemspec.description = %Q{RSAML is a SAML implementation in Ruby. RSAML currently implements the elements defined in the SAML-Core 2.0
23
+ specification by defining an object model that mimics the structure of SAML. Method names and attributes have been made
24
+ ruby-friendly and documentation is provided for each class and method. In certain cases the SAML specification is
25
+ referenced directly and should be considered the final say whenever a question arises regarding SAML implementation.
26
+ }
27
+ gemspec.email = ["anthonyeden@gmail.com", "scashin133@gmail.com"]
28
+ gemspec.homepage = "http://github.com/scashin133/rsaml"
29
+ gemspec.authors = ["Anthony Eden"]
30
+ gemspec.add_dependency('activesupport', '>=2.3.4')
31
+ gemspec.add_dependency('uuid', '>=2.1.1')
32
+ gemspec.version = PKG_VERSION
33
+ end
34
+ Jeweler::GemcutterTasks.new
35
+ rescue LoadError
36
+ puts "Jeweler not available. Install it with: gem install jeweler"
37
+ end
38
+
39
+
40
+ desc 'Default: run unit tests.'
41
+ task :default => :test
42
+
43
+ desc 'Test the library.'
44
+ Rake::TestTask.new(:test) do |t|
45
+ t.libs << 'lib'
46
+ test_files = FileList['test/**/*_test.rb']
47
+ t.test_files = test_files
48
+ t.verbose = true
49
+ end
50
+
51
+ desc 'Generate documentation for the library.'
52
+ Rake::RDocTask.new(:rdoc) do |rdoc|
53
+ rdoc.rdoc_dir = 'rdoc'
54
+ rdoc.title = 'RSAML'
55
+ rdoc.options << '--line-numbers' << '--inline-source'
56
+ rdoc.rdoc_files.include('README')
57
+ rdoc.rdoc_files.include('lib/**/*.rb')
58
+ end
59
+
60
+ namespace :rcov do
61
+ desc 'Measures test coverage'
62
+ task :test do
63
+ rm_f 'coverage.data'
64
+ mkdir 'coverage' unless File.exist?('coverage')
65
+ rcov = "rcov --aggregate coverage.data --text-summary -Ilib"
66
+ system("#{rcov} test/*_test.rb")
67
+ #system("open coverage/index.html") if PLATFORM['darwin']
68
+ end
69
+ end
70
+
71
+ PKG_FILES = FileList[
72
+ #'CHANGELOG',
73
+ #'LICENSE',
74
+ 'README',
75
+ #'TODO',
76
+ 'Rakefile',
77
+ 'bin/**/*',
78
+ 'doc/**/*',
79
+ 'lib/**/*',
80
+ ] - [ 'test' ]
81
+
82
+ spec = Gem::Specification.new do |s|
83
+ s.name = 'rsaml'
84
+ s.version = PKG_VERSION
85
+ s.summary = "RSAML - SAML implementation in Ruby."
86
+ s.description = <<-EOF
87
+ An implementation of SAML in Ruby.
88
+ EOF
89
+
90
+ s.add_dependency('rake', '>= 0.7.1')
91
+ s.add_dependency('uuid', '>= 1.0.4')
92
+
93
+ s.rdoc_options << '--exclude' << '.'
94
+ s.has_rdoc = false
95
+
96
+ s.files = PKG_FILES.to_a.delete_if {|f| f.include?('.svn')}
97
+ s.require_path = 'lib'
98
+
99
+ s.author = "Anthony Eden"
100
+ s.email = "anthonyeden@gmail.com"
101
+ end
102
+
103
+ Rake::GemPackageTask.new(spec) do |pkg|
104
+ pkg.gem_spec = spec
105
+ pkg.need_tar = true
106
+ pkg.need_zip = true
107
+ end
108
+
109
+ desc "Generate code statistics"
110
+ task :lines do
111
+ lines, codelines, total_lines, total_codelines = 0, 0, 0, 0
112
+
113
+ for file_name in FileList["lib/**/*.rb"]
114
+ next if file_name =~ /vendor/
115
+ f = File.open(file_name)
116
+
117
+ while line = f.gets
118
+ lines += 1
119
+ next if line =~ /^\s*$/
120
+ next if line =~ /^\s*#/
121
+ codelines += 1
122
+ end
123
+ puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}"
124
+
125
+ total_lines += lines
126
+ total_codelines += codelines
127
+
128
+ lines, codelines = 0, 0
129
+ end
130
+
131
+ puts "Total: Lines #{total_lines}, LOC #{total_codelines}"
132
+ end
133
+
134
+ desc "Reinstall the gem from a local package copy"
135
+ task :reinstall => [:package] do
136
+ windows = RUBY_PLATFORM =~ /mswin/
137
+ sudo = windows ? '' : 'sudo'
138
+ gem = windows ? 'gem.bat' : 'gem'
139
+ `#{sudo} #{gem} uninstall -x -i #{PKG_NAME}`
140
+ `#{sudo} #{gem} install pkg/#{PKG_NAME}-#{PKG_VERSION}`
141
+ end
@@ -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
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
@@ -0,0 +1,76 @@
1
+ module RSAML #:nodoc:
2
+ # Identifies an attribute by name and optionally includes its value(s).
3
+ class Attribute
4
+ # The name of the attribute.
5
+ attr_accessor :name
6
+
7
+ # A URI reference representing the classification of the attribute name for purposes of
8
+ # interpreting the name
9
+ attr_accessor :name_format
10
+
11
+ # A string that provides a more human-readable form of the attribute's name, which may
12
+ # be useful in cases in which the actual Name is complex or opaque, such as an OID or a UUID.
13
+ attr_accessor :friendly_name
14
+
15
+ # Initialize the attribute with the given name. Optionally pass an array of values.
16
+ def initialize(name, *values)
17
+ @name = name
18
+ @values = values
19
+ end
20
+
21
+ # An array of values for the attribute.
22
+ def values
23
+ @values ||= []
24
+ end
25
+
26
+ # Validate the structure of the attribute.
27
+ def validate
28
+ raise ValidationError, "Name is required" unless name
29
+ end
30
+
31
+ # extension point to allow arbitrary XML attributes to be added to <Attribute> constructs
32
+ # without the need for an explicit schema extension. This allows additional fields to be
33
+ # added as needed to supply additional parameters to be used, for example, in an attribute
34
+ # query. This attribute is a Hash of name/value pairs that is output directly with the
35
+ # <Attribute> XML element.
36
+ def extra_xml_attributes
37
+ @extra_xml_attributes ||= {}
38
+ end
39
+
40
+ # Construct an XML fragment representing the attribute
41
+ def to_xml(xml=Builder::XmlMarkup.new)
42
+ xml_attributes = {'Name' => name}
43
+ xml_attributes['NameFormat'] = name_format unless name_format.nil?
44
+ xml_attributes['FriendlyName'] = friendly_name unless friendly_name.nil?
45
+ xml.tag!('saml:Attribute', xml_attributes.merge(extra_xml_attributes)) {
46
+ values.each { |value| xml.tag!('saml:AttributeValue', value.to_s) }
47
+ }
48
+ end
49
+
50
+ def self.from_xml(element)
51
+ element = REXML::Document.new(element).root if element.is_a?(String)
52
+ attribute = Attribute.new(element.attribute('Name').value)
53
+ if element.attribute('NameFormat')
54
+ attribute.name_format = element.attribute('NameFormat').value
55
+ end
56
+ attribute
57
+ end
58
+ end
59
+
60
+ # An encrypted attribute.
61
+ class EncryptedAttribute < Encrypted
62
+
63
+ # Validate the structure
64
+ def validate
65
+ raise ValidationError, "Encrypted data is required" if encrypted_data.nil?
66
+ end
67
+
68
+ # Construct an XML fragment representing the encrypted attribute
69
+ def to_xml(xml=Builder::XmlMarkup.new)
70
+ xml.tag!('saml:EncryptedAttribute') {
71
+ xml.tag!('xenc:EncryptedData', encrypted_data)
72
+ encrypted_keys.each { |key| xml << key.to_xml }
73
+ }
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,19 @@
1
+ module RSAML #:nodoc:
2
+ # A URI reference that identifies an intended audience. The URI reference MAY identify a document
3
+ # that describes the terms and conditions of audience membership. It MAY also contain the unique
4
+ # identifier URI from a SAML name identifier that describes a system entity.
5
+ class Audience
6
+ # URI for the audience
7
+ attr_accessor :uri
8
+
9
+ # Initialize the Audience instance with the given URI
10
+ def initialize(uri)
11
+ @uri = uri
12
+ end
13
+
14
+ # Construct an XML fragment representing the audience
15
+ def to_xml(xml=Builder::XmlMarkup.new)
16
+ xml.tag!('saml:Audience', uri)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,34 @@
1
+ module RSAML #:nodoc:
2
+ # Specifies the context of an authentication event. The element can contain
3
+ # an authentication context class reference, an authentication context declaration
4
+ # or declaration reference, or both.
5
+ class AuthenticationContext
6
+ # A URI reference identifying an authentication context class that describes the authentication context
7
+ # declaration that follows.
8
+ attr_accessor :class_reference
9
+
10
+ # An authentication context declaration provided by value.
11
+ attr_accessor :context_declaration
12
+
13
+ # A URI reference that identifies a declaration. The URI reference MAY directly resolve into
14
+ # an XML document containing the referenced declaration.
15
+ attr_accessor :context_declaration_ref
16
+
17
+ # Zero or more unique identifiers of authentication authorities that were involved in the
18
+ # authentication of the principal
19
+ def authenticating_authority
20
+ @authenticating_authority ||= []
21
+ end
22
+
23
+ # Construct an XML fragment representing the authentication statement
24
+ def to_xml(xml=Builder::XmlMarkup.new)
25
+ xml.tag!('saml:AuthnContext') {
26
+ xml.tag!('saml:AuthnContextClassRef', class_reference) unless class_reference.nil?
27
+ xml.tag!('saml:AuthnContextDecl', context_declaration) unless context_declaration.nil?
28
+ xml.tag!('saml:AuthnContextDeclRef', context_declaration_ref) unless context_declaration_ref.nil?
29
+ }
30
+ end
31
+ end
32
+ end
33
+
34
+ require 'rsaml/authn_context/authentication_context_declaration'
@@ -0,0 +1 @@
1
+ Implementation of the AuthnContext 2.0 specification.