ruby-saml-bekk 0.2.4 → 0.3.1

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.
@@ -83,6 +83,23 @@ contains all the saml:AttributeStatement with its 'Name' as a indifferent key an
83
83
 
84
84
  response.attributes[:username]
85
85
 
86
+ Logout is also supported via Onelogin::Saml::Logoutrequest and Onelogin::Saml::Logoutresponse
87
+
88
+ == EntityDescriptor generation
89
+
90
+ To install metadata about your SP(service provider) at the SAML IDP(identity provider) you
91
+ have to install a EntityDescriptor containing some metadata for your SP. To generate
92
+ thise files you can follow this example
93
+
94
+ config = {
95
+ entity_id => "https://someaddress.com/",
96
+ name_id_format => "urn:oasis:names:tc:SAML:2.0:nameid-format:transient
97
+ assertion_consumer_service_location => "https://someaddress/your/saml/consume/action"
98
+ }
99
+ entity_descriptor = Onelogin::Saml::EntityDescription.new
100
+ puts entity_descriptor.generate(config)
101
+
102
+
86
103
 
87
104
  = Full Example
88
105
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.4
1
+ 0.3.1
@@ -1,4 +1,7 @@
1
+ require 'onelogin/saml/codeing'
1
2
  require 'onelogin/saml/authrequest'
3
+ require 'onelogin/saml/logoutrequest'
4
+ require 'onelogin/saml/logoutresponse'
2
5
  require 'onelogin/saml/response'
3
6
  require 'onelogin/saml/settings'
4
7
  require 'onelogin/saml/entity_descriptor'
@@ -1,12 +1,13 @@
1
1
  require "base64"
2
2
  require "uuid"
3
3
  require "zlib"
4
- require "cgi"
4
+
5
5
 
6
6
  module Onelogin::Saml
7
7
  class Authrequest
8
+ include Codeing
8
9
  def create(settings, params = {})
9
- uuid = UUID.new.generate
10
+ uuid = "_".UUID.new.generate
10
11
  time = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
11
12
 
12
13
  request =
@@ -17,13 +18,13 @@ module Onelogin::Saml
17
18
  "<saml:AuthnContextClassRef xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef></samlp:RequestedAuthnContext>\n" +
18
19
  "</samlp:AuthnRequest>"
19
20
 
20
- deflated_request = Zlib::Deflate.deflate(request, 9)[2..-5]
21
- base64_request = Base64.encode64(deflated_request)
22
- encoded_request = CGI.escape(base64_request)
21
+ deflated_request = deflate(request)
22
+ base64_request = encode(deflated_request)
23
+ encoded_request = escape(base64_request)
23
24
  request_params = "?SAMLRequest=" + encoded_request
24
25
 
25
26
  params.each_pair do |key, value|
26
- request_params << "&#{key}=#{CGI.escape(value.to_s)}"
27
+ request_params << "&#{key}=#{escape(value.to_s)}"
27
28
  end
28
29
 
29
30
  settings.idp_sso_target_url + request_params
@@ -0,0 +1,34 @@
1
+ require "cgi"
2
+ require 'zlib'
3
+
4
+ module Onelogin
5
+ module Saml
6
+ module Codeing
7
+ def decode(encoded)
8
+ Base64.decode64(encoded)
9
+ end
10
+
11
+ def encode(encoded)
12
+ Base64.encode64(encoded)
13
+ end
14
+
15
+ def escape(unescaped)
16
+ CGI.escape(unescaped)
17
+ end
18
+
19
+ def unescape(escaped)
20
+ CGI.unescape(escaped)
21
+ end
22
+
23
+ def inflate(deflated)
24
+ zlib = Zlib::Inflate.new(-Zlib::MAX_WBITS)
25
+ zlib.inflate(deflated)
26
+ end
27
+
28
+ def deflate(inflated)
29
+ Zlib::Deflate.deflate(inflated, 9)[2..-5]
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -12,6 +12,9 @@ module Onelogin::Saml
12
12
  name_id_format = values["name_id_format"]
13
13
  assertion_consumer_service_location = values["assertion_consumer_service_location"]
14
14
 
15
+ single_logout_service_location = values["single_logout_service_location"]
16
+ single_logout_service_response_location = values["single_logout_service_response_location"]
17
+
15
18
  erb = ERB.new(@template)
16
19
  erb.result(binding)
17
20
  end
@@ -2,6 +2,12 @@
2
2
 
3
3
  <EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" entityID="<%= entity_id %>">
4
4
  <SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
5
+ <% if single_logout_service_location && single_logout_service_response_location %>
6
+ <SingleLogoutService
7
+ Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
8
+ Location="<%= single_logout_service_location %>"
9
+ ResponseLocation="<%= single_logout_service_response_location %>"/>
10
+ <% end %>
5
11
  <NameIDFormat><%= name_id_format %></NameIDFormat>
6
12
  <AssertionConsumerService
7
13
  isDefault="true"
@@ -0,0 +1,46 @@
1
+ require 'base64'
2
+ require 'uuid'
3
+ require 'cgi'
4
+
5
+ module Onelogin::Saml
6
+ class Logoutrequest
7
+ attr_reader :transaction_id
8
+
9
+ def initialize
10
+ @transaction_id = UUID.new.generate
11
+ end
12
+
13
+ def create(settings,nameid,params={})
14
+ issue_instant = Onelogin::Saml::Logoutrequest.timestamp
15
+
16
+ request = xml(settings, nameid, issue_instant)
17
+
18
+ deflated_request = Zlib::Deflate.deflate(request, 9)[2..-5]
19
+ base64_request = Base64.encode64(deflated_request)
20
+ params["SAMLRequest"] = base64_request
21
+ query_string = params.map {|key, value| "#{key}=#{CGI.escape(value)}"}.join("&")
22
+
23
+ settings.idp_slo_target_url + "?#{query_string}"
24
+ end
25
+
26
+ def xml(settings, nameid, issue_instant)
27
+ request = <<-EOF
28
+ <samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
29
+ ID="#{transaction_id}" Version="2.0" IssueInstant="#{issue_instant}">
30
+ <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">#{settings.issuer}</saml:Issuer>
31
+ <saml:NameID xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
32
+ NameQualifier="#{settings.sp_name_qualifier}"
33
+ Format="#{settings.name_identifier_format}">#{nameid}</saml:NameID>
34
+ </samlp:LogoutRequest>
35
+ EOF
36
+
37
+ request
38
+ end
39
+
40
+ private
41
+
42
+ def self.timestamp
43
+ Time.new().strftime("%Y-%m-%dT%H:%M:%SZ")
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,33 @@
1
+ #encoding: utf-8
2
+
3
+ require "rexml/document"
4
+
5
+ module Onelogin
6
+ module Saml
7
+ class Logoutresponse
8
+ include Codeing
9
+
10
+ def initialize(response)
11
+ begin
12
+ @response = decode(response)
13
+ document
14
+ rescue
15
+ @response = inflate(decode(response))
16
+ end
17
+ end
18
+
19
+ def issuer
20
+ document.elements["/samlp:LogoutResponse/saml:Issuer"].text
21
+ end
22
+
23
+ def in_response_to
24
+ document.elements["/samlp:LogoutResponse"].attributes["InResponseTo"]
25
+ end
26
+
27
+ protected
28
+ def document
29
+ REXML::Document.new(@response)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -3,12 +3,13 @@ require "time"
3
3
 
4
4
  module Onelogin::Saml
5
5
  class Response
6
+ include Codeing
6
7
  attr_accessor :response, :document, :logger, :settings
7
8
 
8
9
  def initialize(response)
9
10
  raise ArgumentError.new("Response cannot be nil") if response.nil?
10
11
  self.response = response
11
- self.document = XMLSecurity::SignedDocument.new(Base64.decode64(response))
12
+ self.document = XMLSecurity::SignedDocument.new(decode(response))
12
13
  end
13
14
 
14
15
  def is_valid?
@@ -2,5 +2,8 @@ module Onelogin::Saml
2
2
  class Settings
3
3
  attr_accessor :assertion_consumer_service_url, :issuer, :sp_name_qualifier
4
4
  attr_accessor :idp_sso_target_url, :idp_cert_fingerprint, :name_identifier_format
5
+ attr_accessor :idp_slo_target_url
6
+
7
+ SLO_ELEMENT_PATH = "/EntityDescriptor/IDPSSODescriptor/SingleLogoutService[@Binding='urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect']"
5
8
  end
6
9
  end
@@ -4,12 +4,12 @@
4
4
  # -*- encoding: utf-8 -*-
5
5
 
6
6
  Gem::Specification.new do |s|
7
- s.name = %q{ruby-saml-bekk}
8
- s.version = "0.2.3"
7
+ s.name = %q{ruby-saml}
8
+ s.version = "0.3.1"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["OneLogin LLC"]
12
- s.date = %q{2011-02-21}
12
+ s.date = %q{2011-03-07}
13
13
  s.description = %q{SAML toolkit for Ruby on Rails}
14
14
  s.email = %q{support@onelogin.com}
15
15
  s.extra_rdoc_files = [
@@ -1,6 +1,9 @@
1
+ #encoding: utf-8
1
2
  require 'test_helper'
2
3
 
3
4
  class RubySamlTest < Test::Unit::TestCase
5
+ include Onelogin::Saml::Codeing
6
+
4
7
 
5
8
  context "Settings" do
6
9
  setup do
@@ -108,6 +111,75 @@ class RubySamlTest < Test::Unit::TestCase
108
111
  end
109
112
  end
110
113
 
114
+ context "Logoutrequest" do
115
+
116
+ setup do
117
+ UUID.expects(:new).returns(stub(:generate => "da64beb0-2ac4-012e-a9c9-48bcc8e9f44d"))
118
+ @settings = Onelogin::Saml::Settings.new
119
+ @settings.issuer = "issuer"
120
+ @settings.sp_name_qualifier ="sp name"
121
+ @settings.name_identifier_format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
122
+ @settings.idp_slo_target_url ="http://slotarget.com/"
123
+
124
+ Onelogin::Saml::Logoutrequest.stubs(:timestamp).returns("timestamb")
125
+ end
126
+
127
+ should "generate a correct logout request" do
128
+ logoutrequest = Onelogin::Saml::Logoutrequest.new
129
+
130
+ logout_xml = logoutrequest.xml(@settings, "demo", "test")
131
+
132
+ expected_xml = <<-EOF
133
+ <samlp:LogoutRequest xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"
134
+ ID=\"da64beb0-2ac4-012e-a9c9-48bcc8e9f44d\" Version=\"2.0\" IssueInstant=\"test\">
135
+ <saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">issuer</saml:Issuer>
136
+ <saml:NameID xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\"
137
+ NameQualifier=\"sp name\"
138
+ Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:transient\">demo</saml:NameID>
139
+ </samlp:LogoutRequest>
140
+ EOF
141
+
142
+ assert_equal expected_xml.strip, logout_xml.strip
143
+ end
144
+
145
+
146
+ should "generate a correct request" do
147
+ logoutrequest = Onelogin::Saml::Logoutrequest.new
148
+ logout_url = logoutrequest.create(@settings, "demo")
149
+
150
+ expected_url = "http://slotarget.com/?SAMLRequest=nZFbS8QwEIXf91eEea%2FWUmQbtgVhEQqroILv03S6BJqkZqbgz7cX0fW2D57H%0Aycl35jBKrdoxun7Qh3AMozzSy0gs6tX1nvXyUsIYvQ7IlrVHR6zF6Kebu4PO%0ALlI9xCDBhB426kT1voQWr%2FOGmjTJ0ORJepVRgoUpknzbGLOlosvzFtQzRbbB%0AlzDBQNXMI9WeBb2UIHZKE3QNVF%2FoH1vrxR9Ptj2%2FLDJTlCkOKrv83F2eYP4K%0AuZ849f4fIT94s2baw4i97SzFEnhQM%2BJ3722IDuV83DyxbdItVi0RPVvyAlVL%0ALrz3Wxt89lvH325ebd4A%0A"
151
+ assert_equal expected_url, logout_url
152
+ end
153
+
154
+ end
155
+
156
+ context "Logoutresponse" do
157
+ should "validate the response" do
158
+
159
+ issuer = "https://test-idp.test.no:443/issuer"
160
+ transaction_id = "adflkjalkfjalsdfjlaskjdf"
161
+ params = {}
162
+ logout_xml = "<samlp:LogoutResponse xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"
163
+ ID=\"sedf164edc2121a23d4ca4b31d35261e5563dc352\" Version=\"2.0\"
164
+ IssueInstant=\"2011-03-07T14:43:34Z\" Destination=\"http://localhost:3000/saml/consume_logout\"
165
+ InResponseTo=\"#{transaction_id}\">
166
+ <saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">#{issuer}</saml:Issuer>
167
+ <samlp:Status xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\">
168
+ <samlp:StatusCode xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"
169
+ Value=\"urn:oasis:names:tc:SAML:2.0:status:Requester\">
170
+ </samlp:StatusCode>
171
+ </samlp:Status>
172
+ </samlp:LogoutResponse>"
173
+
174
+
175
+ params["SAMLResponse"] = encode(deflate(logout_xml))
176
+ logoutresponse = Onelogin::Saml::Logoutresponse.new(params["SAMLResponse"])
177
+
178
+ assert_equal logoutresponse.issuer, issuer
179
+ assert_equal logoutresponse.in_response_to, transaction_id
180
+ end
181
+ end
182
+
111
183
  context "EntityDescription" do
112
184
  should "generate a correct entity descriptor" do
113
185
  descriptor = Onelogin::Saml::EntityDescription.new
@@ -117,10 +189,11 @@ class RubySamlTest < Test::Unit::TestCase
117
189
  "assertion_consumer_service_location" => "http://localhost:3000/saml/consume"
118
190
  })
119
191
 
120
- assert_equal xml, "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>
192
+ expected_xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>
121
193
 
122
194
  <EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" entityID=\"http://test.no/\">
123
195
  <SPSSODescriptor AuthnRequestsSigned=\"false\" WantAssertionsSigned=\"true\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">
196
+
124
197
  <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
125
198
  <AssertionConsumerService
126
199
  isDefault=\"true\"
@@ -128,9 +201,35 @@ class RubySamlTest < Test::Unit::TestCase
128
201
  Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"
129
202
  Location=\"http://localhost:3000/saml/consume\"/>
130
203
  </SPSSODescriptor>
131
- <RoleDescriptor xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:query=\"urn:oasis:names:tc:SAML:metadata:ext:query\" xsi:type=\"query:AttributeQueryDescriptorType\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\"/>
132
- <XACMLAuthzDecisionQueryDescriptor WantAssertionsSigned=\"false\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\"/>
204
+ <RoleDescriptor xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:query=\"urn:oasis:names:tc:SAML:metadata:ext:query\" xsi:type=\"query:AttributeQueryDescriptorType\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\"/>\n <XACMLAuthzDecisionQueryDescriptor WantAssertionsSigned=\"false\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\"/>
133
205
  </EntityDescriptor>"
206
+
207
+ assert_equal expected_xml.gsub(" ", ""), xml.gsub(" ", "")
208
+ end
209
+
210
+ context "with slo" do
211
+ should "generate correct xml part" do
212
+ descriptor = Onelogin::Saml::EntityDescription.new
213
+ xml = descriptor.generate({
214
+ "entity_id" => "http://test.no/",
215
+ "name_id_format" => "urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
216
+ "assertion_consumer_service_location" => "http://localhost:3000/saml/consume",
217
+
218
+ "single_logout_service_location" => "slo_location",
219
+ "single_logout_service_response_location" => "response_location"
220
+ })
221
+
222
+ expected_xml = " <SingleLogoutService
223
+ Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\"
224
+ Location=\"slo_location\"
225
+ ResponseLocation=\"response_location\"/>"
226
+
227
+ assert xml.include?(expected_xml), "Xml does not include\nfull:#{xml}\n\nincluded: #{expected_xml}"
228
+ end
134
229
  end
135
230
  end
231
+
232
+
233
+
234
+
136
235
  end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 2
8
- - 4
9
- version: 0.2.4
7
+ - 3
8
+ - 1
9
+ version: 0.3.1
10
10
  platform: ruby
11
11
  authors:
12
12
  - OneLogin LLC
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2011-03-04 00:00:00 +01:00
17
+ date: 2011-03-07 00:00:00 +01:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -90,8 +90,11 @@ files:
90
90
  - VERSION
91
91
  - lib/onelogin/saml.rb
92
92
  - lib/onelogin/saml/authrequest.rb
93
+ - lib/onelogin/saml/codeing.rb
93
94
  - lib/onelogin/saml/entity_descriptor.rb
94
95
  - lib/onelogin/saml/entity_descriptor.xml.erb
96
+ - lib/onelogin/saml/logoutrequest.rb
97
+ - lib/onelogin/saml/logoutresponse.rb
95
98
  - lib/onelogin/saml/response.rb
96
99
  - lib/onelogin/saml/settings.rb
97
100
  - lib/ruby-saml.rb