scashin133-rsaml 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +10 -0
- data/.gitignore +2 -0
- data/LICENSE +0 -0
- data/README +13 -0
- data/Rakefile +141 -0
- data/lib/rsaml/action.rb +57 -0
- data/lib/rsaml/action_namespace.rb +63 -0
- data/lib/rsaml/advice.rb +34 -0
- data/lib/rsaml/assertion.rb +192 -0
- data/lib/rsaml/attribute.rb +76 -0
- data/lib/rsaml/audience.rb +19 -0
- data/lib/rsaml/authentication_context.rb +34 -0
- data/lib/rsaml/authn_context/README +1 -0
- data/lib/rsaml/authn_context/authentication_context_declaration.rb +42 -0
- data/lib/rsaml/authn_context/identification.rb +10 -0
- data/lib/rsaml/authn_context/physical_verification.rb +24 -0
- data/lib/rsaml/condition.rb +13 -0
- data/lib/rsaml/conditions.rb +107 -0
- data/lib/rsaml/encrypted.rb +12 -0
- data/lib/rsaml/errors.rb +16 -0
- data/lib/rsaml/evidence.rb +21 -0
- data/lib/rsaml/ext/string.rb +5 -0
- data/lib/rsaml/identifier/base.rb +23 -0
- data/lib/rsaml/identifier/issuer.rb +28 -0
- data/lib/rsaml/identifier/name.rb +55 -0
- data/lib/rsaml/identifier.rb +9 -0
- data/lib/rsaml/parser.rb +23 -0
- data/lib/rsaml/protocol/artifact_resolve.rb +14 -0
- data/lib/rsaml/protocol/assertion_id_request.rb +18 -0
- data/lib/rsaml/protocol/authn_request.rb +91 -0
- data/lib/rsaml/protocol/idp_entry.rb +18 -0
- data/lib/rsaml/protocol/idp_list.rb +28 -0
- data/lib/rsaml/protocol/message.rb +65 -0
- data/lib/rsaml/protocol/name_id_policy.rb +31 -0
- data/lib/rsaml/protocol/query/attribute_query.rb +56 -0
- data/lib/rsaml/protocol/query/authn_query.rb +30 -0
- data/lib/rsaml/protocol/query/authz_decision_query.rb +40 -0
- data/lib/rsaml/protocol/query/subject_query.rb +22 -0
- data/lib/rsaml/protocol/query.rb +12 -0
- data/lib/rsaml/protocol/request.rb +27 -0
- data/lib/rsaml/protocol/requested_authn_context.rb +34 -0
- data/lib/rsaml/protocol/response.rb +56 -0
- data/lib/rsaml/protocol/scoping.rb +33 -0
- data/lib/rsaml/protocol/status.rb +38 -0
- data/lib/rsaml/protocol/status_code.rb +84 -0
- data/lib/rsaml/protocol.rb +21 -0
- data/lib/rsaml/proxy_restriction.rb +30 -0
- data/lib/rsaml/statement/attribute_statement.rb +27 -0
- data/lib/rsaml/statement/authentication_statement.rb +57 -0
- data/lib/rsaml/statement/authorization_decision_statement.rb +53 -0
- data/lib/rsaml/statement/base.rb +9 -0
- data/lib/rsaml/statement.rb +10 -0
- data/lib/rsaml/subject.rb +37 -0
- data/lib/rsaml/subject_confirmation.rb +34 -0
- data/lib/rsaml/subject_confirmation_data.rb +45 -0
- data/lib/rsaml/subject_locality.rb +27 -0
- data/lib/rsaml/validatable.rb +21 -0
- data/lib/rsaml/version.rb +9 -0
- data/lib/rsaml.rb +51 -0
- data/lib/xml_enc.rb +3 -0
- data/lib/xml_sig/canonicalization_method.rb +43 -0
- data/lib/xml_sig/key_info.rb +55 -0
- data/lib/xml_sig/reference.rb +57 -0
- data/lib/xml_sig/signature.rb +29 -0
- data/lib/xml_sig/signature_method.rb +20 -0
- data/lib/xml_sig/signed_info.rb +27 -0
- data/lib/xml_sig/transform.rb +37 -0
- data/lib/xml_sig.rb +11 -0
- data/scashin133-rsaml.gemspec +180 -0
- data/test/action_namespace_test.rb +93 -0
- data/test/action_test.rb +51 -0
- data/test/advice_test.rb +25 -0
- data/test/assertion_test.rb +192 -0
- data/test/attribute_test.rb +60 -0
- data/test/authentication_context_test.rb +26 -0
- data/test/conditions_test.rb +84 -0
- data/test/evidence_test.rb +33 -0
- data/test/identifier_test.rb +22 -0
- data/test/issuer_test.rb +32 -0
- data/test/name_test.rb +32 -0
- data/test/parser_test.rb +32 -0
- data/test/protocol/assertion_id_request_test.rb +19 -0
- data/test/protocol/attribute_query_test.rb +30 -0
- data/test/protocol/authn_query_test.rb +20 -0
- data/test/protocol/authn_request_test.rb +56 -0
- data/test/protocol/authz_decision_query_test.rb +31 -0
- data/test/protocol/idp_list_test.rb +15 -0
- data/test/protocol/request_test.rb +66 -0
- data/test/protocol/response_test.rb +68 -0
- data/test/protocol/scoping_test.rb +20 -0
- data/test/protocol/status_code_test.rb +34 -0
- data/test/protocol/status_test.rb +16 -0
- data/test/proxy_restriction_test.rb +20 -0
- data/test/rsaml_test.rb +12 -0
- data/test/sample_data/attribute_query.xml +8 -0
- data/test/statement_test.rb +101 -0
- data/test/subject_locality_test.rb +27 -0
- data/test/subject_test.rb +44 -0
- data/test/test_helper.rb +16 -0
- data/test/xml_sig/canonicalization_test.rb +19 -0
- data/test/xml_sig/iso-8859-1.txt +1 -0
- metadata +206 -0
data/.autotest
ADDED
data/.gitignore
ADDED
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
|
data/lib/rsaml/action.rb
ADDED
@@ -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
|
data/lib/rsaml/advice.rb
ADDED
@@ -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.
|