libsaml 2.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. checksums.yaml +15 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +91 -0
  4. data/Rakefile +33 -0
  5. data/lib/saml.rb +142 -0
  6. data/lib/saml/artifact.rb +51 -0
  7. data/lib/saml/artifact_resolve.rb +10 -0
  8. data/lib/saml/artifact_response.rb +9 -0
  9. data/lib/saml/assertion.rb +67 -0
  10. data/lib/saml/authn_request.rb +34 -0
  11. data/lib/saml/base.rb +47 -0
  12. data/lib/saml/bindings/http_artifact.rb +44 -0
  13. data/lib/saml/bindings/http_post.rb +29 -0
  14. data/lib/saml/bindings/http_redirect.rb +100 -0
  15. data/lib/saml/bindings/soap.rb +31 -0
  16. data/lib/saml/complex_types/endpoint_type.rb +17 -0
  17. data/lib/saml/complex_types/indexed_endpoint_type.rb +15 -0
  18. data/lib/saml/complex_types/request_abstract_type.rb +57 -0
  19. data/lib/saml/complex_types/sso_descriptor_type.rb +48 -0
  20. data/lib/saml/complex_types/status_response_type.rb +29 -0
  21. data/lib/saml/config.rb +49 -0
  22. data/lib/saml/elements/attribute.rb +24 -0
  23. data/lib/saml/elements/attribute_statement.rb +26 -0
  24. data/lib/saml/elements/audience_restriction.rb +12 -0
  25. data/lib/saml/elements/authn_context.rb +13 -0
  26. data/lib/saml/elements/authn_statement.rb +25 -0
  27. data/lib/saml/elements/conditions.rb +24 -0
  28. data/lib/saml/elements/contact_person.rb +33 -0
  29. data/lib/saml/elements/entities_descriptor.rb +27 -0
  30. data/lib/saml/elements/entity_descriptor.rb +37 -0
  31. data/lib/saml/elements/idp_sso_descriptor.rb +23 -0
  32. data/lib/saml/elements/key_descriptor.rb +34 -0
  33. data/lib/saml/elements/key_descriptor/key_info.rb +30 -0
  34. data/lib/saml/elements/key_descriptor/key_info/x509_data.rb +34 -0
  35. data/lib/saml/elements/name_id.rb +14 -0
  36. data/lib/saml/elements/organization.rb +16 -0
  37. data/lib/saml/elements/requested_authn_context.rb +28 -0
  38. data/lib/saml/elements/signature.rb +33 -0
  39. data/lib/saml/elements/signature/canonicalization_method.rb +19 -0
  40. data/lib/saml/elements/signature/digest_method.rb +19 -0
  41. data/lib/saml/elements/signature/inclusive_namespaces.rb +20 -0
  42. data/lib/saml/elements/signature/key_info.rb +14 -0
  43. data/lib/saml/elements/signature/reference.rb +23 -0
  44. data/lib/saml/elements/signature/signature_method.rb +19 -0
  45. data/lib/saml/elements/signature/signed_info.rb +24 -0
  46. data/lib/saml/elements/signature/transform.rb +19 -0
  47. data/lib/saml/elements/signature/transforms.rb +21 -0
  48. data/lib/saml/elements/sp_sso_descriptor.rb +27 -0
  49. data/lib/saml/elements/status.rb +15 -0
  50. data/lib/saml/elements/status_code.rb +42 -0
  51. data/lib/saml/elements/sub_status_code.rb +14 -0
  52. data/lib/saml/elements/subject.rb +38 -0
  53. data/lib/saml/elements/subject_confirmation.rb +30 -0
  54. data/lib/saml/elements/subject_confirmation_data.rb +23 -0
  55. data/lib/saml/elements/subject_locality.rb +12 -0
  56. data/lib/saml/encoding.rb +35 -0
  57. data/lib/saml/logout_request.rb +10 -0
  58. data/lib/saml/logout_response.rb +11 -0
  59. data/lib/saml/provider.rb +85 -0
  60. data/lib/saml/provider_stores/file.rb +33 -0
  61. data/lib/saml/response.rb +21 -0
  62. data/lib/saml/util.rb +51 -0
  63. data/lib/saml/version.rb +3 -0
  64. data/lib/saml/xml_helpers.rb +34 -0
  65. data/lib/tasks/saml_tasks.rake +4 -0
  66. metadata +195 -0
@@ -0,0 +1,34 @@
1
+ module Saml
2
+ class AuthnRequest
3
+ include Saml::ComplexTypes::RequestAbstractType
4
+
5
+ tag 'AuthnRequest'
6
+ attribute :force_authn, Boolean, :tag => "ForceAuthn"
7
+ attribute :assertion_consumer_service_index, Integer, :tag => "AssertionConsumerServiceIndex"
8
+ attribute :assertion_consumer_service_url, String, :tag => "AssertionConsumerServiceURL"
9
+ attribute :attribute_consuming_service_index, Integer, :tag => "AttributeConsumingServiceIndex"
10
+ attribute :protocol_binding, String, :tag => "ProtocolBinding"
11
+ attribute :provider_name, String, :tag => "ProviderName"
12
+
13
+ has_one :requested_authn_context, Saml::Elements::RequestedAuthnContext
14
+
15
+ validates :force_authn, :inclusion => [true, false, nil]
16
+ validates :assertion_consumer_service_index, :numericality => true, :if => "assertion_consumer_service_index.present?"
17
+
18
+ validate :check_assertion_consumer_service
19
+
20
+ def assertion_url
21
+ return assertion_consumer_service_url if assertion_consumer_service_url
22
+ provider.assertion_consumer_service_url(assertion_consumer_service_index) if assertion_consumer_service_index
23
+ end
24
+
25
+ private
26
+
27
+ def check_assertion_consumer_service
28
+ if assertion_consumer_service_index.present?
29
+ errors.add(:assertion_consumer_service_url, :must_be_blank) if @assertion_consumer_service_url.present?
30
+ errors.add(:protocol_binding, :must_be_blank) if protocol_binding.present?
31
+ end
32
+ end
33
+ end
34
+ end
data/lib/saml/base.rb ADDED
@@ -0,0 +1,47 @@
1
+ require 'happymapper'
2
+
3
+ module Saml
4
+ module Base
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ include ::HappyMapper
9
+ include ::ActiveModel::Validations
10
+
11
+ extend HappyMapperClassMethods
12
+ include HappyMapperInstanceMethods
13
+ end
14
+
15
+ module HappyMapperInstanceMethods
16
+ def initialize(attributes = {})
17
+ attributes.each do |key, value|
18
+ send("#{key}=", value) if respond_to?("#{key}=") && value.present?
19
+ end
20
+ end
21
+
22
+ def from_xml=(bool)
23
+ @from_xml = bool
24
+ end
25
+
26
+ def from_xml?
27
+ @from_xml
28
+ end
29
+ end
30
+
31
+ module HappyMapperClassMethods
32
+ def parse(xml, options = {})
33
+ object = super
34
+ if object.is_a?(Array)
35
+ object.map { |x| x.from_xml = true }
36
+ elsif object
37
+ object.from_xml = true
38
+ end
39
+ object
40
+ rescue Nokogiri::XML::SyntaxError => e
41
+ raise Saml::Errors::UnparseableMessage.new(e.message)
42
+ rescue NoMethodError => e
43
+ raise Saml::Errors::UnparseableMessage.new(e.message)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,44 @@
1
+ module Saml
2
+ module Bindings
3
+ class HTTPArtifact
4
+
5
+ class << self
6
+ # @param [Saml::ArtifactResponse] artifact_response
7
+ def create_response_xml(artifact_response)
8
+ Saml::Util.sign_xml(artifact_response, :soap)
9
+ end
10
+
11
+ def create_url(location, artifact, options = {})
12
+ uri = URI.parse(location)
13
+ query = [uri.query, "SAMLart=#{CGI.escape(artifact.to_s)}"]
14
+
15
+ query << "RelayState=#{CGI.escape(options[:relay_state])}" if options[:relay_state]
16
+
17
+ uri.query = query.compact.join("&")
18
+ uri.to_s
19
+ end
20
+
21
+ def receive_message(request)
22
+ raw_xml = request.body.dup.read
23
+ artifact_resolve = Saml::ArtifactResolve.parse(raw_xml, single: true)
24
+
25
+ Saml::Util.verify_xml(artifact_resolve, raw_xml)
26
+ end
27
+
28
+ def resolve(request, location)
29
+ artifact = request.params["SAMLart"]
30
+ artifact_resolve = Saml::ArtifactResolve.new(artifact: artifact, destination: location)
31
+
32
+ response = Saml::Util.post(location, Saml::Util.sign_xml(artifact_resolve, :soap))
33
+
34
+ if response.code == 200
35
+ artifact_response = Saml::ArtifactResponse.parse(response.body, single: true)
36
+ verified_artifact_response = Saml::Util.verify_xml(artifact_response, response.body)
37
+
38
+ verified_artifact_response.response if artifact_response.success?
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,29 @@
1
+ module Saml
2
+ module Bindings
3
+ class HTTPPost
4
+ class << self
5
+ def create_form_attributes(message, options = {})
6
+ param = message.is_a?(Saml::ComplexTypes::StatusResponseType) ? "SAMLResponse" : "SAMLRequest"
7
+
8
+ xml = Saml::Util.sign_xml(message)
9
+
10
+ variables = {}
11
+ variables[param] = Saml::Encoding.encode_64(xml)
12
+ variables["RelayState"] = options[:relay_state] if options[:relay_state]
13
+
14
+ {
15
+ location: message.destination,
16
+ variables: variables
17
+ }
18
+ end
19
+
20
+ def receive_message(request, type)
21
+ message = Saml::Encoding.decode_64(request.params["SAMLRequest"] || request.params["SAMLResponse"])
22
+ request_or_response = Saml.parse_message(message, type)
23
+
24
+ Saml::Util.verify_xml(request_or_response, message)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,100 @@
1
+ module Saml
2
+ module Bindings
3
+ class HTTPRedirect
4
+ class << self
5
+ def create_url(request_or_response, options = {})
6
+ options[:signature_algorithm] ||= 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
7
+ new(request_or_response, options).create_url
8
+ end
9
+
10
+ def receive_message(http_request, options = {})
11
+ options[:signature] = Saml::Encoding.decode_64(http_request.params["Signature"] || "")
12
+ options[:signature_algorithm] = http_request.params["SigAlg"]
13
+ options[:relay_state] = http_request.params["RelayState"]
14
+
15
+ request_or_response = parse_request_or_response(options.delete(:type), http_request.params)
16
+
17
+ redirect_binding = new(request_or_response, options)
18
+ query_string = URI.parse(http_request.url).query
19
+
20
+ redirect_binding.verify_signature(query_string) if request_or_response.provider.authn_requests_signed?
21
+
22
+ request_or_response
23
+ end
24
+
25
+ private
26
+
27
+ def parse_request_or_response(type, params)
28
+ message = decode_message(params["SAMLRequest"])
29
+
30
+ Saml.parse_message(message, type)
31
+ end
32
+
33
+ def decode_message(message)
34
+ Saml::Encoding.decode_gzip(Saml::Encoding.decode_64(message)).gsub("\n", "")
35
+ end
36
+ end
37
+
38
+ attr_accessor :request_or_response, :signature_algorithm, :relay_state, :signature
39
+
40
+ def initialize(request_or_response, options = {})
41
+ @request_or_response = request_or_response
42
+ @signature_algorithm = options[:signature_algorithm]
43
+ @relay_state = options[:relay_state]
44
+ @signature = options[:signature]
45
+ end
46
+
47
+ def verify_signature(query)
48
+ unless request_or_response.provider.verify(signature_algorithm, signature, parse_signature_params(query))
49
+ raise Saml::Errors::SignatureInvalid.new
50
+ end
51
+ end
52
+
53
+ def create_url
54
+ [request_or_response.destination, signed_params].join("?")
55
+ end
56
+
57
+ private
58
+
59
+
60
+ def parse_signature_params(query)
61
+ params = {}
62
+ query.split(/[&;]/).each do |pairs|
63
+ key, value = pairs.split('=', 2)
64
+ params[key] = value
65
+ end
66
+
67
+ relay_state = params["RelayState"] ? "&RelayState=#{params['RelayState']}" : ""
68
+ "SAMLRequest=#{params['SAMLRequest']}#{relay_state}&SigAlg=#{params['SigAlg']}"
69
+ end
70
+
71
+ def encoded_message
72
+ Saml::Encoding.encode_64(Saml::Encoding.encode_gzip(request_or_response.to_xml))
73
+ end
74
+
75
+ def encoded_params
76
+ params.collect do |key, value|
77
+ "#{key}=#{CGI.escape(value)}"
78
+ end.join('&')
79
+ end
80
+
81
+ def params
82
+ params = {}
83
+
84
+ params["SAMLRequest"] = encoded_message
85
+ params["RelayState"] = relay_state if relay_state
86
+ params["SigAlg"] = signature_algorithm if signature_algorithm
87
+
88
+ params
89
+ end
90
+
91
+ def signed_params
92
+ signature = request_or_response.provider.sign(signature_algorithm, encoded_params)
93
+
94
+ encoded_signature = CGI.escape(Saml::Encoding.encode_64(signature))
95
+
96
+ "#{encoded_params}&Signature=#{encoded_signature}"
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,31 @@
1
+ module Saml
2
+ module Bindings
3
+ class SOAP
4
+ class << self
5
+ def create_response_xml(response)
6
+ Saml::Util.sign_xml(response, :soap)
7
+ end
8
+
9
+ def post_message(message, response_type)
10
+ signed_message = Saml::Util.sign_xml(message, :soap)
11
+
12
+ http_response = Saml::Util.post(message.destination, signed_message)
13
+
14
+ if http_response.code == 200
15
+ response = Saml.parse_message(http_response.body, response_type)
16
+ Saml::Util.verify_xml(response, http_response.body)
17
+ else
18
+ nil
19
+ end
20
+ end
21
+
22
+ def receive_message(request, type)
23
+ raw_xml = request.body.dup.read
24
+ message = Saml.parse_message(raw_xml, type)
25
+
26
+ Saml::Util.verify_xml(message, raw_xml)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,17 @@
1
+ module Saml
2
+ module ComplexTypes
3
+ module EndpointType
4
+ extend ActiveSupport::Concern
5
+ include Saml::Base
6
+
7
+ included do
8
+ namespace 'md'
9
+
10
+ attribute :binding, String, :tag => "Binding"
11
+ attribute :location, String, :tag => "Location"
12
+
13
+ validates :binding, :location, :presence => true
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ module Saml
2
+ module ComplexTypes
3
+ module IndexedEndpointType
4
+ extend ActiveSupport::Concern
5
+ include EndpointType
6
+
7
+ included do
8
+ attribute :index, Integer, :tag => "index"
9
+ attribute :is_default, HappyMapper::Boolean, :tag => "isDefault"
10
+
11
+ validates :index, :presence => true
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,57 @@
1
+ require 'happymapper'
2
+
3
+ module Saml
4
+ module ComplexTypes
5
+ module RequestAbstractType
6
+ extend ActiveSupport::Concern
7
+ include Saml::Base
8
+ include Saml::XMLHelpers
9
+
10
+ included do
11
+ register_namespace 'samlp', Saml::SAMLP_NAMESPACE
12
+ register_namespace 'saml', Saml::SAML_NAMESPACE
13
+ namespace 'samlp'
14
+
15
+ attribute :_id, String, :tag => 'ID'
16
+ attribute :version, String, :tag => "Version"
17
+ attribute :issue_instant, Time, :tag => "IssueInstant", :on_save => lambda { |val| val.utc.xmlschema }
18
+
19
+ attribute :destination, String, :tag => "Destination"
20
+ element :issuer, String, :namespace => 'saml', :tag => "Issuer"
21
+
22
+ has_one :signature, Saml::Elements::Signature, :tag => "Signature"
23
+
24
+ validates :_id, :version, :issue_instant, :presence => true
25
+
26
+ validates :version, inclusion: %w(2.0)
27
+ validate :check_issue_instant, :if => "issue_instant.present?"
28
+ end
29
+
30
+ def initialize(*args)
31
+ super(*args)
32
+ @_id ||= Saml.generate_id
33
+ @issue_instant ||= Time.now
34
+ @issuer ||= Saml::Config.entity_id
35
+ @version ||= Saml::SAML_VERSION
36
+ end
37
+
38
+ def add_signature
39
+ self.signature = Saml::Elements::Signature.new(uri: "##{self._id}")
40
+ x509certificate = OpenSSL::X509::Certificate.new(provider.certificate) rescue nil
41
+ self.signature.key_info = Saml::Elements::KeyDescriptor::KeyInfo.new(x509certificate.to_pem) if x509certificate
42
+ end
43
+
44
+ # @return [Saml::Provider]
45
+ def provider
46
+ Saml.provider(issuer)
47
+ end
48
+
49
+ private
50
+
51
+ def check_issue_instant
52
+ errors.add(:issue_instant, :too_old) if issue_instant < Time.now - Saml::Config.max_issue_instant_offset.minutes
53
+ errors.add(:issue_instant, :too_new) if issue_instant > Time.now + Saml::Config.max_issue_instant_offset.minutes
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,48 @@
1
+ module Saml
2
+ module ComplexTypes
3
+ module SSODescriptorType
4
+ extend ActiveSupport::Concern
5
+ include Saml::Base
6
+
7
+ class ArtifactResolutionService
8
+ include Saml::ComplexTypes::IndexedEndpointType
9
+
10
+ tag 'ArtifactResolutionService'
11
+ namespace 'md'
12
+ end
13
+
14
+ class SingleLogoutService
15
+ include Saml::ComplexTypes::EndpointType
16
+
17
+ tag 'SingleLogoutService'
18
+ namespace 'md'
19
+ end
20
+
21
+ included do
22
+ namespace 'md'
23
+
24
+ PROTOCOL_SUPPORT_ENUMERATION = "urn:oasis:names:tc:SAML:2.0:protocol" unless defined?(PROTOCOL_SUPPORT_ENUMERATION)
25
+
26
+ attribute :protocol_support_enumeration, String, :tag => "protocolSupportEnumeration"
27
+ attribute :valid_until, Time, :tag => "validUntil"
28
+ attribute :cache_duration, Integer, :tag => "cacheDuration"
29
+ attribute :error_url, String, :tag => "errorURL"
30
+
31
+ has_many :key_descriptors, Saml::Elements::KeyDescriptor
32
+
33
+ has_many :artifact_resolution_services, ArtifactResolutionService
34
+ has_many :single_logout_services, SingleLogoutService
35
+
36
+ validates :protocol_support_enumeration, :presence => true, :inclusion => [PROTOCOL_SUPPORT_ENUMERATION]
37
+ end
38
+
39
+ def initialize(*args)
40
+ super(*args)
41
+ @single_logout_services ||= []
42
+ @key_descriptors ||= []
43
+ @artifact_resolution_services ||= []
44
+ @protocol_support_enumeration ||= PROTOCOL_SUPPORT_ENUMERATION
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,29 @@
1
+ require 'happymapper'
2
+
3
+ module Saml
4
+ module ComplexTypes
5
+ module StatusResponseType
6
+ extend ActiveSupport::Concern
7
+
8
+ include RequestAbstractType
9
+
10
+ included do
11
+ attribute :in_response_to, String, :tag => 'InResponseTo'
12
+ has_one :status, Saml::Elements::Status
13
+
14
+ validates :in_response_to, :status, :presence => true
15
+ end
16
+
17
+ def initialize(*args)
18
+ options = args.extract_options!
19
+ @status = Saml::Elements::Status.new(:status_code => Saml::Elements::StatusCode.new(:value => options.delete(:status_value),
20
+ :sub_status_value => options.delete(:sub_status_value)))
21
+ super(*(args << options))
22
+ end
23
+
24
+ def success?
25
+ status.status_code.success?
26
+ end
27
+ end
28
+ end
29
+ end