libsaml 2.0.5

Sign up to get free protection for your applications and to get access to all the features.
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