ruby-saml-bekk 0.2.4 → 0.3.1

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