saml-kit 0.2.1 → 0.2.2

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.
@@ -0,0 +1,182 @@
1
+ module Saml
2
+ module Kit
3
+ module Builders
4
+ class Response
5
+ attr_reader :user, :request
6
+ attr_accessor :id, :reference_id, :now
7
+ attr_accessor :version, :status_code
8
+ attr_accessor :issuer, :sign, :destination, :encrypt
9
+
10
+ def initialize(user, request)
11
+ @user = user
12
+ @request = request
13
+ @id = Id.generate
14
+ @reference_id = Id.generate
15
+ @now = Time.now.utc
16
+ @version = "2.0"
17
+ @status_code = Namespaces::SUCCESS
18
+ @issuer = configuration.issuer
19
+ @destination = destination_for(request)
20
+ @sign = want_assertions_signed
21
+ @encrypt = false
22
+ end
23
+
24
+ def want_assertions_signed
25
+ request.provider.want_assertions_signed
26
+ rescue => error
27
+ Saml::Kit.logger.error(error)
28
+ true
29
+ end
30
+
31
+ def to_xml
32
+ Signature.sign(sign: sign) do |xml, signature|
33
+ xml.Response response_options do
34
+ xml.Issuer(issuer, xmlns: Namespaces::ASSERTION)
35
+ signature.template(id)
36
+ xml.Status do
37
+ xml.StatusCode Value: status_code
38
+ end
39
+ assertion(xml, signature)
40
+ end
41
+ end
42
+ end
43
+
44
+ def build
45
+ Saml::Kit::Response.new(to_xml, request_id: request.id)
46
+ end
47
+
48
+ private
49
+
50
+ def assertion(xml, signature)
51
+ with_encryption(xml) do |xml|
52
+ xml.Assertion(assertion_options) do
53
+ xml.Issuer issuer
54
+ signature.template(reference_id) unless encrypt
55
+ xml.Subject do
56
+ xml.NameID user.name_id_for(request.name_id_format), Format: request.name_id_format
57
+ xml.SubjectConfirmation Method: Namespaces::BEARER do
58
+ xml.SubjectConfirmationData "", subject_confirmation_data_options
59
+ end
60
+ end
61
+ xml.Conditions conditions_options do
62
+ xml.AudienceRestriction do
63
+ xml.Audience request.issuer
64
+ end
65
+ end
66
+ xml.AuthnStatement authn_statement_options do
67
+ xml.AuthnContext do
68
+ xml.AuthnContextClassRef Namespaces::PASSWORD
69
+ end
70
+ end
71
+ assertion_attributes = user.assertion_attributes_for(request)
72
+ if assertion_attributes.any?
73
+ xml.AttributeStatement do
74
+ assertion_attributes.each do |key, value|
75
+ xml.Attribute Name: key, NameFormat: Namespaces::URI, FriendlyName: key do
76
+ xml.AttributeValue value.to_s
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ def with_encryption(xml)
86
+ if encrypt
87
+ temp = ::Builder::XmlMarkup.new
88
+ yield temp
89
+ raw_xml_to_encrypt = temp.target!
90
+
91
+ encryption_certificate = request.provider.encryption_certificates.first
92
+ public_key = encryption_certificate.public_key
93
+
94
+ cipher = OpenSSL::Cipher.new('AES-256-CBC')
95
+ cipher.encrypt
96
+ key = cipher.random_key
97
+ iv = cipher.random_iv
98
+ encrypted = cipher.update(raw_xml_to_encrypt) + cipher.final
99
+
100
+ Saml::Kit.logger.debug ['+iv', iv].inspect
101
+ Saml::Kit.logger.debug ['+key', key].inspect
102
+
103
+ xml.EncryptedAssertion xmlns: Namespaces::ASSERTION do
104
+ xml.EncryptedData xmlns: Namespaces::XMLENC do
105
+ xml.EncryptionMethod Algorithm: "http://www.w3.org/2001/04/xmlenc#aes256-cbc"
106
+ xml.KeyInfo xmlns: Namespaces::XMLDSIG do
107
+ xml.EncryptedKey xmlns: Namespaces::XMLENC do
108
+ xml.EncryptionMethod Algorithm: "http://www.w3.org/2001/04/xmlenc#rsa-1_5"
109
+ xml.CipherData do
110
+ xml.CipherValue Base64.encode64(public_key.public_encrypt(key))
111
+ end
112
+ end
113
+ end
114
+ xml.CipherData do
115
+ xml.CipherValue Base64.encode64(iv + encrypted)
116
+ end
117
+ end
118
+ end
119
+ else
120
+ yield xml
121
+ end
122
+ end
123
+
124
+ def destination_for(request)
125
+ if request.signed? && request.trusted?
126
+ request.assertion_consumer_service_url || request.provider.assertion_consumer_service_for(binding: :http_post).try(:location)
127
+ else
128
+ request.provider.assertion_consumer_service_for(binding: :http_post).try(:location)
129
+ end
130
+ end
131
+
132
+ def configuration
133
+ Saml::Kit.configuration
134
+ end
135
+
136
+ def response_options
137
+ {
138
+ ID: id,
139
+ Version: version,
140
+ IssueInstant: now.iso8601,
141
+ Destination: destination,
142
+ Consent: Namespaces::UNSPECIFIED,
143
+ InResponseTo: request.id,
144
+ xmlns: Namespaces::PROTOCOL,
145
+ }
146
+ end
147
+
148
+ def assertion_options
149
+ {
150
+ ID: reference_id,
151
+ IssueInstant: now.iso8601,
152
+ Version: "2.0",
153
+ xmlns: Namespaces::ASSERTION,
154
+ }
155
+ end
156
+
157
+ def subject_confirmation_data_options
158
+ {
159
+ InResponseTo: request.id,
160
+ NotOnOrAfter: 3.hours.since(now).utc.iso8601,
161
+ Recipient: request.assertion_consumer_service_url,
162
+ }
163
+ end
164
+
165
+ def conditions_options
166
+ {
167
+ NotBefore: now.utc.iso8601,
168
+ NotOnOrAfter: Saml::Kit.configuration.session_timeout.from_now.utc.iso8601,
169
+ }
170
+ end
171
+
172
+ def authn_statement_options
173
+ {
174
+ AuthnInstant: now.iso8601,
175
+ SessionIndex: assertion_options[:ID],
176
+ SessionNotOnOrAfter: 3.hours.since(now).utc.iso8601,
177
+ }
178
+ end
179
+ end
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,89 @@
1
+ module Saml
2
+ module Kit
3
+ module Builders
4
+ class ServiceProviderMetadata
5
+ attr_accessor :id, :entity_id, :acs_urls, :logout_urls, :name_id_formats, :sign
6
+ attr_accessor :want_assertions_signed
7
+
8
+ def initialize(configuration = Saml::Kit.configuration)
9
+ @id = Id.generate
10
+ @configuration = configuration
11
+ @entity_id = configuration.issuer
12
+ @acs_urls = []
13
+ @logout_urls = []
14
+ @name_id_formats = [Namespaces::PERSISTENT]
15
+ @sign = true
16
+ @want_assertions_signed = true
17
+ end
18
+
19
+ def add_assertion_consumer_service(url, binding: :http_post)
20
+ @acs_urls.push(location: url, binding: Bindings.binding_for(binding))
21
+ end
22
+
23
+ def add_single_logout_service(url, binding: :http_post)
24
+ @logout_urls.push(location: url, binding: Bindings.binding_for(binding))
25
+ end
26
+
27
+ def to_xml
28
+ Signature.sign(sign: sign) do |xml, signature|
29
+ xml.instruct!
30
+ xml.EntityDescriptor entity_descriptor_options do
31
+ signature.template(id)
32
+ xml.SPSSODescriptor descriptor_options do
33
+ if @configuration.signing_certificate_pem.present?
34
+ xml.KeyDescriptor use: "signing" do
35
+ xml.KeyInfo "xmlns": Namespaces::XMLDSIG do
36
+ xml.X509Data do
37
+ xml.X509Certificate @configuration.stripped_signing_certificate
38
+ end
39
+ end
40
+ end
41
+ end
42
+ if @configuration.encryption_certificate_pem.present?
43
+ xml.KeyDescriptor use: "encryption" do
44
+ xml.KeyInfo "xmlns": Namespaces::XMLDSIG do
45
+ xml.X509Data do
46
+ xml.X509Certificate @configuration.stripped_encryption_certificate
47
+ end
48
+ end
49
+ end
50
+ end
51
+ logout_urls.each do |item|
52
+ xml.SingleLogoutService Binding: item[:binding], Location: item[:location]
53
+ end
54
+ name_id_formats.each do |format|
55
+ xml.NameIDFormat format
56
+ end
57
+ acs_urls.each_with_index do |item, index|
58
+ xml.AssertionConsumerService Binding: item[:binding], Location: item[:location], index: index, isDefault: index == 0 ? true : false
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ def build
66
+ Saml::Kit::ServiceProviderMetadata.new(to_xml)
67
+ end
68
+
69
+ private
70
+
71
+ def entity_descriptor_options
72
+ {
73
+ 'xmlns': Namespaces::METADATA,
74
+ ID: id,
75
+ entityID: entity_id,
76
+ }
77
+ end
78
+
79
+ def descriptor_options
80
+ {
81
+ AuthnRequestsSigned: sign,
82
+ WantAssertionsSigned: want_assertions_signed,
83
+ protocolSupportEnumeration: Namespaces::PROTOCOL,
84
+ }
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -5,6 +5,7 @@ module Saml
5
5
  include XsdValidatable
6
6
  include ActiveModel::Validations
7
7
  include Trustable
8
+ include Buildable
8
9
  validates_presence_of :content
9
10
  validates_presence_of :id
10
11
  validate :must_match_xsd
@@ -76,6 +77,21 @@ module Saml
76
77
  Saml::Kit.logger.error(error)
77
78
  InvalidDocument.new(xml)
78
79
  end
80
+
81
+ def builder_class
82
+ case name
83
+ when Saml::Kit::Response.to_s
84
+ Saml::Kit::Builders::Response
85
+ when Saml::Kit::LogoutResponse.to_s
86
+ Saml::Kit::Builders::LogoutResponse
87
+ when Saml::Kit::AuthenticationRequest.to_s
88
+ Saml::Kit::Builders::AuthenticationRequest
89
+ when Saml::Kit::LogoutRequest.to_s
90
+ Saml::Kit::Builders::LogoutRequest
91
+ else
92
+ raise ArgumentError.new("Unknown SAML Document #{name}")
93
+ end
94
+ end
79
95
  end
80
96
 
81
97
  private
@@ -0,0 +1,9 @@
1
+ module Saml
2
+ module Kit
3
+ class Id
4
+ def self.generate
5
+ "_#{SecureRandom.uuid}"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -29,105 +29,20 @@ module Saml
29
29
  end
30
30
  end
31
31
 
32
- private
33
-
34
- class Builder
35
- attr_accessor :id, :organization_name, :organization_url, :contact_email, :entity_id, :attributes, :name_id_formats
36
- attr_accessor :want_authn_requests_signed, :sign
37
- attr_reader :logout_urls, :single_sign_on_urls
38
-
39
- def initialize(configuration = Saml::Kit.configuration)
40
- @id = SecureRandom.uuid
41
- @entity_id = configuration.issuer
42
- @attributes = []
43
- @name_id_formats = [Namespaces::PERSISTENT]
44
- @single_sign_on_urls = []
45
- @logout_urls = []
46
- @configuration = configuration
47
- @sign = true
48
- @want_authn_requests_signed = true
49
- end
50
-
51
- def add_single_sign_on_service(url, binding: :http_post)
52
- @single_sign_on_urls.push(location: url, binding: Bindings.binding_for(binding))
53
- end
54
-
55
- def add_single_logout_service(url, binding: :http_post)
56
- @logout_urls.push(location: url, binding: Bindings.binding_for(binding))
57
- end
58
-
59
- def to_xml
60
- Signature.sign(sign: sign) do |xml, signature|
61
- xml.instruct!
62
- xml.EntityDescriptor entity_descriptor_options do
63
- signature.template(id)
64
- xml.IDPSSODescriptor idp_sso_descriptor_options do
65
- if @configuration.signing_certificate_pem.present?
66
- xml.KeyDescriptor use: "signing" do
67
- xml.KeyInfo "xmlns": Namespaces::XMLDSIG do
68
- xml.X509Data do
69
- xml.X509Certificate @configuration.stripped_signing_certificate
70
- end
71
- end
72
- end
73
- end
74
- if @configuration.encryption_certificate_pem.present?
75
- xml.KeyDescriptor use: "encryption" do
76
- xml.KeyInfo "xmlns": Namespaces::XMLDSIG do
77
- xml.X509Data do
78
- xml.X509Certificate @configuration.stripped_encryption_certificate
79
- end
80
- end
81
- end
82
- end
83
- logout_urls.each do |item|
84
- xml.SingleLogoutService Binding: item[:binding], Location: item[:location]
85
- end
86
- name_id_formats.each do |format|
87
- xml.NameIDFormat format
88
- end
89
- single_sign_on_urls.each do |item|
90
- xml.SingleSignOnService Binding: item[:binding], Location: item[:location]
91
- end
92
- attributes.each do |attribute|
93
- xml.tag! 'saml:Attribute', Name: attribute
94
- end
95
- end
96
- xml.Organization do
97
- xml.OrganizationName organization_name, 'xml:lang': "en"
98
- xml.OrganizationDisplayName organization_name, 'xml:lang': "en"
99
- xml.OrganizationURL organization_url, 'xml:lang': "en"
100
- end
101
- xml.ContactPerson contactType: "technical" do
102
- xml.Company "mailto:#{contact_email}"
103
- end
104
- end
105
- end
106
- end
107
-
108
- def build
109
- IdentityProviderMetadata.new(to_xml)
110
- end
111
-
112
- private
113
-
114
- def entity_descriptor_options
115
- {
116
- 'xmlns': Namespaces::METADATA,
117
- 'xmlns:ds': Namespaces::XMLDSIG,
118
- 'xmlns:saml': Namespaces::ASSERTION,
119
- ID: "_#{id}",
120
- entityID: entity_id,
121
- }
32
+ def login_request_for(binding:, relay_state: nil)
33
+ builder = Saml::Kit::AuthenticationRequest.builder do |x|
34
+ x.sign = want_authn_requests_signed
35
+ yield x if block_given?
122
36
  end
37
+ request_binding = single_sign_on_service_for(binding: binding)
38
+ request_binding.serialize(builder, relay_state: relay_state)
39
+ end
123
40
 
124
- def idp_sso_descriptor_options
125
- {
126
- WantAuthnRequestsSigned: want_authn_requests_signed,
127
- protocolSupportEnumeration: Namespaces::PROTOCOL,
128
- }
129
- end
41
+ def self.builder_class
42
+ Saml::Kit::Builders::IdentityProviderMetadata
130
43
  end
44
+
45
+ Builder = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Saml::Kit::IdentityProviderMetadata::Builder', 'Saml::Kit::Builders::IdentityProviderMetadata')
131
46
  end
132
47
  end
133
48
  end
@@ -18,61 +18,15 @@ module Saml
18
18
  urls.first
19
19
  end
20
20
 
21
- def response_for(user)
22
- LogoutResponse::Builder.new(user, self)
23
- end
24
-
25
- private
26
-
27
- class Builder
28
- attr_accessor :id, :destination, :issuer, :name_id_format, :now
29
- attr_accessor :sign, :version
30
- attr_reader :user
31
-
32
- def initialize(user, configuration: Saml::Kit.configuration, sign: true)
33
- @user = user
34
- @id = SecureRandom.uuid
35
- @issuer = configuration.issuer
36
- @name_id_format = Saml::Kit::Namespaces::PERSISTENT
37
- @now = Time.now.utc
38
- @version = "2.0"
39
- @sign = sign
40
- end
41
-
42
- def to_xml
43
- Signature.sign(sign: sign) do |xml, signature|
44
- xml.instruct!
45
- xml.LogoutRequest logout_request_options do
46
- xml.Issuer({ xmlns: Namespaces::ASSERTION }, issuer)
47
- signature.template(id)
48
- xml.NameID name_id_options, user.name_id_for(name_id_format)
49
- end
50
- end
51
- end
52
-
53
- def build
54
- Saml::Kit::LogoutRequest.new(to_xml)
55
- end
56
-
57
- private
58
-
59
- def logout_request_options
60
- {
61
- ID: "_#{id}",
62
- Version: version,
63
- IssueInstant: now.utc.iso8601,
64
- Destination: destination,
65
- xmlns: Namespaces::PROTOCOL,
66
- }
67
- end
68
-
69
- def name_id_options
70
- {
71
- Format: name_id_format,
72
- xmlns: Namespaces::ASSERTION,
73
- }
21
+ def response_for(user, binding:, relay_state: nil)
22
+ builder = Saml::Kit::LogoutResponse.builder(user, self) do |x|
23
+ yield x if block_given?
74
24
  end
25
+ response_binding = provider.single_logout_service_for(binding: binding)
26
+ response_binding.serialize(builder, relay_state: relay_state)
75
27
  end
28
+
29
+ Builder = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Saml::Kit::LogoutRequest::Builder', 'Saml::Kit::Builders::LogoutRequest')
76
30
  end
77
31
  end
78
32
  end