saml-kit 0.2.1 → 0.2.2

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