ruby-saml 0.0.8 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of ruby-saml might be problematic. Click here for more details.
- data/README.rdoc +80 -3
- data/Rakefile +5 -2
- data/VERSION +1 -1
- data/lib/onelogin/saml.rb +4 -4
- data/lib/onelogin/saml/authrequest.rb +23 -51
- data/lib/onelogin/saml/response.rb +19 -19
- data/lib/onelogin/saml/settings.rb +2 -41
- data/lib/xml_sec.rb +0 -1
- data/ruby-saml.gemspec +35 -28
- data/test/response.txt +68 -0
- data/test/ruby-saml_test.rb +77 -2
- data/test/test_helper.rb +4 -0
- metadata +46 -18
- data/.gitignore +0 -5
data/README.rdoc
CHANGED
@@ -1,7 +1,84 @@
|
|
1
|
-
=
|
1
|
+
= Ruby SAML
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
The Ruby SAML library is for implementing the client side of a SAML authorization, i.e. it provides a means for managing authorization initialization and confirmation requests from identity providers.
|
4
|
+
|
5
|
+
SAML authorization is a two step process and you are expected to implement support for both.
|
6
|
+
|
7
|
+
== The initialization phase
|
8
|
+
|
9
|
+
This is the first request you will get from the identity provider. It will hit your application at a specific URL (that you've announced as being your SAML initialization point). The response to this initialization, is a redirect back to the identity provider, which can look something like this (ignore the saml_settings method call for now):
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
request = Onelogin::Saml::Authrequest.new
|
13
|
+
redirect_to(request.create(saml_settings))
|
14
|
+
end
|
15
|
+
|
16
|
+
Once you've redirected back to the identity provider, it will ensure that the user has been authorized and redirect back to your application for final consumption, this is can look something like this (the authorize_success and authorize_failure methods are specific to your application):
|
17
|
+
|
18
|
+
def consume
|
19
|
+
response = Onelogin::Saml::Response.new(params[:SAMLResponse])
|
20
|
+
response.settings = saml_settings
|
21
|
+
|
22
|
+
if response.is_valid? && user = current_account.users.find_by_email(response.name_id)
|
23
|
+
authorize_success(user)
|
24
|
+
else
|
25
|
+
authorize_failure(user)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
In the above there are a few assumptions in place, one being that the response.name_id is an email address. This is all handled with how you specify the settings that are in play via the saml_settings method. That could be implemented along the lines of this:
|
30
|
+
|
31
|
+
def saml_settings
|
32
|
+
settings = Onelogin::Saml::Settings.new
|
33
|
+
|
34
|
+
settings.assertion_consumer_service_url = "http://#{request.host}/saml/finalize"
|
35
|
+
settings.issuer = request.host
|
36
|
+
settings.idp_sso_target_url = "https://app.onelogin.com/saml/signon/#{OneLoginAppId}"
|
37
|
+
settings.idp_cert_fingerprint = OneLoginAppCertFingerPrint
|
38
|
+
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
39
|
+
|
40
|
+
settings
|
41
|
+
end
|
42
|
+
|
43
|
+
What's left at this point, is to wrap it all up in a controller and point the initialization and consumption URLs in OneLogin at that. A full controller example could look like this:
|
44
|
+
|
45
|
+
# This controller expects you to use the URLs /saml/initialize and /saml/consume in your OneLogin application.
|
46
|
+
class SamlController < ApplicationController
|
47
|
+
def initialize
|
48
|
+
request = Onelogin::Saml::Authrequest.new
|
49
|
+
redirect_to(request.create(saml_settings))
|
50
|
+
end
|
51
|
+
|
52
|
+
def consume
|
53
|
+
response = Onelogin::Saml::Response.new(params[:SAMLResponse])
|
54
|
+
response.settings = saml_settings
|
55
|
+
|
56
|
+
if response.is_valid? && user = current_account.users.find_by_email(response.name_id)
|
57
|
+
authorize_success(user)
|
58
|
+
else
|
59
|
+
authorize_failure(user)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def saml_settings
|
66
|
+
settings = Onelogin::Saml::Settings.new
|
67
|
+
|
68
|
+
settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
|
69
|
+
settings.issuer = request.host
|
70
|
+
settings.idp_sso_target_url = "https://app.onelogin.com/saml/signon/#{OneLoginAppId}"
|
71
|
+
settings.idp_cert_fingerprint = OneLoginAppCertFingerPrint
|
72
|
+
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
73
|
+
|
74
|
+
settings
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
= Full Example
|
80
|
+
|
81
|
+
Please check https://github.com/onelogin/ruby-saml-example for a very basic sample Rails application using this gem.
|
5
82
|
|
6
83
|
== Note on Patches/Pull Requests
|
7
84
|
|
data/Rakefile
CHANGED
@@ -10,10 +10,13 @@ begin
|
|
10
10
|
gem.email = "support@onelogin.com"
|
11
11
|
gem.homepage = "http://github.com/onelogin/ruby-saml"
|
12
12
|
gem.authors = ["OneLogin LLC"]
|
13
|
-
gem.add_dependency("
|
14
|
-
|
13
|
+
gem.add_dependency("xmlcanonicalizer","= 0.1.0")
|
14
|
+
gem.add_dependency("uuid","= 2.3.1")
|
15
|
+
gem.add_development_dependency "shoulda"
|
16
|
+
gem.add_development_dependency "mocha"
|
15
17
|
#gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
16
18
|
end
|
19
|
+
Jeweler::GemcutterTasks.new
|
17
20
|
rescue LoadError
|
18
21
|
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
19
22
|
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.1
|
data/lib/onelogin/saml.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'onelogin/saml'
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
require 'onelogin/saml/authrequest'
|
2
|
+
require 'onelogin/saml/response'
|
3
|
+
require 'onelogin/saml/settings'
|
4
|
+
|
@@ -1,56 +1,28 @@
|
|
1
1
|
require "base64"
|
2
2
|
require "uuid"
|
3
|
+
require "zlib"
|
4
|
+
require "cgi"
|
3
5
|
|
4
6
|
module Onelogin::Saml
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
def generate_saml_request(settings, options = {})
|
28
|
-
options[:style] ||= :default
|
29
|
-
case options[:style]
|
30
|
-
when :default
|
31
|
-
standard_saml_request(settings)
|
32
|
-
when :google
|
33
|
-
google_saml_request(settings)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def standard_saml_request(settings)
|
38
|
-
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
39
|
-
<samlp:AuthnRequest xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" ID=\"#{@id}\" Version=\"2.0\" IssueInstant=\"#{@issue_instant}\" ProtocolBinding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" AssertionConsumerServiceURL=\"#{settings.assertion_consumer_service_url}\">" +
|
40
|
-
"<saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">#{settings.issuer}</saml:Issuer>\n" +
|
41
|
-
"<samlp:NameIDPolicy xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" Format=\"#{settings.name_identifier_format}\" AllowCreate=\"true\"></samlp:NameIDPolicy>\n" +
|
42
|
-
"<samlp:RequestedAuthnContext xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" Comparison=\"exact\">" +
|
43
|
-
"<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" +
|
44
|
-
"</samlp:AuthnRequest>"
|
45
|
-
end
|
46
|
-
|
47
|
-
def google_saml_request(settings)
|
48
|
-
%Q(<?xml version="1.0" encoding="UTF-8"?><samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="#{@id}" Version="2.0" IssueInstant="#{@issue_instant}" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" ProviderName="#{settings.issuer}" AssertionConsumerServiceURL="#{settings.assertion_consumer_service_url}"><saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">#{settings.issuer}</saml:Issuer><samlp:NameIDPolicy AllowCreate="true" Format="#{settings.name_identifier_format}" /></samlp:AuthnRequest>)
|
49
|
-
end
|
50
|
-
|
51
|
-
def self.getTimestamp
|
52
|
-
Time.new().strftime("%Y-%m-%dT%H:%M:%SZ")
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
7
|
+
class Authrequest
|
8
|
+
def create(settings)
|
9
|
+
uuid = UUID.new.generate
|
10
|
+
time = Time.now.strftime("%Y-%m-%dT%H:%M:%SZ")
|
11
|
+
|
12
|
+
request =
|
13
|
+
"<samlp:AuthnRequest xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" ID=\"#{uuid}\" Version=\"2.0\" IssueInstant=\"#{time}\" ProtocolBinding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" AssertionConsumerServiceURL=\"#{settings.assertion_consumer_service_url}\">" +
|
14
|
+
"<saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">#{settings.issuer}</saml:Issuer>\n" +
|
15
|
+
"<samlp:NameIDPolicy xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" Format=\"#{settings.name_identifier_format}\" AllowCreate=\"true\"></samlp:NameIDPolicy>\n" +
|
16
|
+
"<samlp:RequestedAuthnContext xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" Comparison=\"exact\">" +
|
17
|
+
"<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
|
+
"</samlp:AuthnRequest>"
|
19
|
+
|
20
|
+
deflated_request = Zlib::Deflate.deflate(request, 9)[2..-5]
|
21
|
+
base64_request = Base64.encode64(deflated_request)
|
22
|
+
encoded_request = CGI.escape(base64_request)
|
23
|
+
|
24
|
+
settings.idp_sso_target_url + "?SAMLRequest=" + encoded_request
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
56
28
|
end
|
@@ -1,33 +1,33 @@
|
|
1
1
|
require "rexml/document"
|
2
2
|
require "xml_sec"
|
3
|
+
require "time"
|
3
4
|
|
4
5
|
module Onelogin::Saml
|
5
6
|
class Response
|
7
|
+
attr_accessor :response, :document, :logger, :settings
|
8
|
+
|
6
9
|
def initialize(response)
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
@document = XMLSecurity::SignedDocument.new(@response) if @document.root.blank?
|
11
|
-
@document = REXML::Document.new(@response) if @document.root.blank?
|
12
|
-
end
|
13
|
-
|
14
|
-
def logger=(val)
|
15
|
-
@logger = val
|
10
|
+
raise ArgumentError.new("Response cannot be nil") if response.nil?
|
11
|
+
self.response = response
|
12
|
+
self.document = XMLSecurity::SignedDocument.new(Base64.decode64(response))
|
16
13
|
end
|
17
|
-
|
18
|
-
def settings=(_settings)
|
19
|
-
@settings = _settings
|
20
|
-
end
|
21
|
-
|
14
|
+
|
22
15
|
def is_valid?
|
23
|
-
|
24
|
-
|
25
|
-
|
16
|
+
return false if response.empty?
|
17
|
+
return false if settings.nil?
|
18
|
+
return false if settings.idp_cert_fingerprint.nil?
|
19
|
+
|
20
|
+
document.validate(settings.idp_cert_fingerprint, logger)
|
26
21
|
end
|
27
22
|
|
23
|
+
# The value of the user identifier as designated by the initialization request response
|
28
24
|
def name_id
|
29
|
-
|
30
|
-
|
25
|
+
@name_id ||= document.elements["/samlp:Response/saml:Assertion/saml:Subject/saml:NameID"].text
|
26
|
+
end
|
27
|
+
|
28
|
+
# When this user session should expire at latest
|
29
|
+
def session_expires_at
|
30
|
+
@expires_at ||= Time.parse(document.elements["/samlp:Response/saml:Assertion/saml:AuthnStatement"].attributes["SessionNotOnOrAfter"])
|
31
31
|
end
|
32
32
|
end
|
33
33
|
end
|
@@ -1,45 +1,6 @@
|
|
1
1
|
module Onelogin::Saml
|
2
2
|
class Settings
|
3
|
-
|
4
|
-
|
5
|
-
end
|
6
|
-
def assertion_consumer_service_url=(val)
|
7
|
-
@assertion_consumer_service_url = val
|
8
|
-
end
|
9
|
-
|
10
|
-
def issuer
|
11
|
-
@issuer
|
12
|
-
end
|
13
|
-
def issuer=(val)
|
14
|
-
@issuer = val
|
15
|
-
end
|
16
|
-
|
17
|
-
def sp_name_qualifier
|
18
|
-
@sp_name_qualifier
|
19
|
-
end
|
20
|
-
def sp_name_qualifier=(val)
|
21
|
-
@sp_name_qualifier = val
|
22
|
-
end
|
23
|
-
|
24
|
-
def idp_sso_target_url
|
25
|
-
@idp_sso_target_url
|
26
|
-
end
|
27
|
-
def idp_sso_target_url=(val)
|
28
|
-
@idp_sso_target_url = val
|
29
|
-
end
|
30
|
-
|
31
|
-
def idp_cert_fingerprint
|
32
|
-
@idp_cert_fingerprint
|
33
|
-
end
|
34
|
-
def idp_cert_fingerprint=(val)
|
35
|
-
@idp_cert_fingerprint = val
|
36
|
-
end
|
37
|
-
|
38
|
-
def name_identifier_format
|
39
|
-
@name_identifier_format
|
40
|
-
end
|
41
|
-
def name_identifier_format=(val)
|
42
|
-
@name_identifier_format = val
|
43
|
-
end
|
3
|
+
attr_accessor :assertion_consumer_service_url, :issuer, :sp_name_qualifier
|
4
|
+
attr_accessor :idp_sso_target_url, :idp_cert_fingerprint, :name_identifier_format
|
44
5
|
end
|
45
6
|
end
|
data/lib/xml_sec.rb
CHANGED
@@ -35,7 +35,6 @@ module XMLSecurity
|
|
35
35
|
|
36
36
|
def validate (idp_cert_fingerprint, logger = nil)
|
37
37
|
# get cert from response
|
38
|
-
return true if self.elements["//ds:X509Certificate"].blank?
|
39
38
|
base64_cert = self.elements["//ds:X509Certificate"].text
|
40
39
|
cert_text = Base64.decode64(base64_cert)
|
41
40
|
cert = OpenSSL::X509::Certificate.new(cert_text)
|
data/ruby-saml.gemspec
CHANGED
@@ -1,60 +1,67 @@
|
|
1
1
|
# Generated by jeweler
|
2
|
-
# DO NOT EDIT THIS FILE
|
3
|
-
# Instead, edit Jeweler::Tasks in Rakefile, and run
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{ruby-saml}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.2.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{2010-
|
12
|
+
s.date = %q{2010-12-01}
|
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 = [
|
16
16
|
"LICENSE",
|
17
|
-
|
17
|
+
"README.rdoc"
|
18
18
|
]
|
19
19
|
s.files = [
|
20
20
|
".document",
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
21
|
+
"LICENSE",
|
22
|
+
"README.rdoc",
|
23
|
+
"Rakefile",
|
24
|
+
"VERSION",
|
25
|
+
"lib/onelogin/saml.rb",
|
26
|
+
"lib/onelogin/saml/authrequest.rb",
|
27
|
+
"lib/onelogin/saml/response.rb",
|
28
|
+
"lib/onelogin/saml/settings.rb",
|
29
|
+
"lib/ruby-saml.rb",
|
30
|
+
"lib/xml_sec.rb",
|
31
|
+
"ruby-saml.gemspec",
|
32
|
+
"test/response.txt",
|
33
|
+
"test/ruby-saml_test.rb",
|
34
|
+
"test/test_helper.rb"
|
35
35
|
]
|
36
36
|
s.homepage = %q{http://github.com/onelogin/ruby-saml}
|
37
|
-
s.rdoc_options = ["--charset=UTF-8"]
|
38
37
|
s.require_paths = ["lib"]
|
39
|
-
s.rubygems_version = %q{1.3.
|
38
|
+
s.rubygems_version = %q{1.3.7}
|
40
39
|
s.summary = %q{SAML Ruby Tookit}
|
41
40
|
s.test_files = [
|
42
41
|
"test/ruby-saml_test.rb",
|
43
|
-
|
42
|
+
"test/test_helper.rb"
|
44
43
|
]
|
45
44
|
|
46
45
|
if s.respond_to? :specification_version then
|
47
46
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
48
47
|
s.specification_version = 3
|
49
48
|
|
50
|
-
if Gem::Version.new(Gem::
|
51
|
-
s.add_runtime_dependency(%q<
|
52
|
-
|
49
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
50
|
+
s.add_runtime_dependency(%q<xmlcanonicalizer>, ["= 0.1.0"])
|
51
|
+
s.add_runtime_dependency(%q<uuid>, ["= 2.3.1"])
|
52
|
+
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
53
|
+
s.add_development_dependency(%q<mocha>, [">= 0"])
|
53
54
|
else
|
54
|
-
s.add_dependency(%q<
|
55
|
-
|
55
|
+
s.add_dependency(%q<xmlcanonicalizer>, ["= 0.1.0"])
|
56
|
+
s.add_dependency(%q<uuid>, ["= 2.3.1"])
|
57
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
58
|
+
s.add_dependency(%q<mocha>, [">= 0"])
|
56
59
|
end
|
57
60
|
else
|
58
|
-
s.add_dependency(%q<
|
61
|
+
s.add_dependency(%q<xmlcanonicalizer>, ["= 0.1.0"])
|
62
|
+
s.add_dependency(%q<uuid>, ["= 2.3.1"])
|
63
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
64
|
+
s.add_dependency(%q<mocha>, [">= 0"])
|
59
65
|
end
|
60
66
|
end
|
67
|
+
|
data/test/response.txt
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0
|
2
|
+
YzpTQU1MOjIuMDphc3NlcnRpb24iIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6
|
3
|
+
bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIElEPSJHT1NBTUxSMTI5MDEx
|
4
|
+
NzQ1NzE3OTQiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDEwLTEx
|
5
|
+
LTE4VDIxOjU3OjM3WiIgRGVzdGluYXRpb249IntyZWNpcGllbnR9Ij48c2Ft
|
6
|
+
bHA6U3RhdHVzPjxzYW1scDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6
|
7
|
+
bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz48L3NhbWxwOlN0
|
8
|
+
YXR1cz48c2FtbDpBc3NlcnRpb24geG1sbnM6eHM9Imh0dHA6Ly93d3cudzMu
|
9
|
+
b3JnLzIwMDEvWE1MU2NoZW1hIiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMu
|
10
|
+
b3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiBWZXJzaW9uPSIyLjAiIElE
|
11
|
+
PSJwZnhhNDY1NzRkZi1iM2IwLWEwNmEtMjNjOC02MzY0MTMxOTg3NzIiIElz
|
12
|
+
c3VlSW5zdGFudD0iMjAxMC0xMS0xOFQyMTo1NzozN1oiPjxzYW1sOklzc3Vl
|
13
|
+
cj5odHRwczovL2FwcC5vbmVsb2dpbi5jb20vc2FtbC9tZXRhZGF0YS8xMzU5
|
14
|
+
MDwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDov
|
15
|
+
L3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+CiAgPGRzOlNpZ25lZElu
|
16
|
+
Zm8+PGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRw
|
17
|
+
Oi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz4KICAgIDxk
|
18
|
+
czpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9y
|
19
|
+
Zy8yMDAwLzA5L3htbGRzaWcjcnNhLXNoYTEiLz4KICA8ZHM6UmVmZXJlbmNl
|
20
|
+
IFVSST0iI3BmeGE0NjU3NGRmLWIzYjAtYTA2YS0yM2M4LTYzNjQxMzE5ODc3
|
21
|
+
MiI+PGRzOlRyYW5zZm9ybXM+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0
|
22
|
+
dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2ln
|
23
|
+
bmF0dXJlIi8+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cu
|
24
|
+
dzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvZHM6VHJhbnNmb3Jt
|
25
|
+
cz48ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5v
|
26
|
+
cmcvMjAwMC8wOS94bWxkc2lnI3NoYTEiLz48ZHM6RGlnZXN0VmFsdWU+cEpR
|
27
|
+
N01TL2VrNEtSUldHbXYvSDQzUmVIWU1zPTwvZHM6RGlnZXN0VmFsdWU+PC9k
|
28
|
+
czpSZWZlcmVuY2U+PC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1
|
29
|
+
ZT55aXZlS2NQZERwdUROajZzaHJRM0FCd3IvY0EzQ3J5RDJwaEcveExac3pL
|
30
|
+
V3hVNS9tbGFLdDhld2JaT2RLS3Z0T3MycEhCeTVEdWEzazk0QUYrenhHeWVs
|
31
|
+
NWdPb3dtb3lYSnIrQU9yK2tQTzB2bGkxVjhvM2hQUFVad1JnU1g2UTlwUzFD
|
32
|
+
cVFnaEtpRWFzUnl5bHFxSlVhUFl6bU96T0U4L1hsTWt3aVdtTzA9PC9kczpT
|
33
|
+
aWduYXR1cmVWYWx1ZT4KPGRzOktleUluZm8+PGRzOlg1MDlEYXRhPjxkczpY
|
34
|
+
NTA5Q2VydGlmaWNhdGU+TUlJQnJUQ0NBYUdnQXdJQkFnSUJBVEFEQmdFQU1H
|
35
|
+
Y3hDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJREFwRFlXeHBabTl5Ym1s
|
36
|
+
aE1SVXdFd1lEVlFRSERBeFRZVzUwWVNCTmIyNXBZMkV4RVRBUEJnTlZCQW9N
|
37
|
+
Q0U5dVpVeHZaMmx1TVJrd0Z3WURWUVFEREJCaGNIQXViMjVsYkc5bmFXNHVZ
|
38
|
+
Mjl0TUI0WERURXdNRE13T1RBNU5UZzBOVm9YRFRFMU1ETXdPVEE1TlRnME5W
|
39
|
+
b3daekVMTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnTUNrTmhiR2xtYjNK
|
40
|
+
dWFXRXhGVEFUQmdOVkJBY01ERk5oYm5SaElFMXZibWxqWVRFUk1BOEdBMVVF
|
41
|
+
Q2d3SVQyNWxURzluYVc0eEdUQVhCZ05WQkFNTUVHRndjQzV2Ym1Wc2IyZHBi
|
42
|
+
aTVqYjIwd2daOHdEUVlKS29aSWh2Y05BUUVCQlFBRGdZMEFNSUdKQW9HQkFP
|
43
|
+
alN1MWZqUHk4ZDV3NFF5TDEremQ0aEl3MU1ra2ZmNFdZL1RMRzhPWmtVNVlU
|
44
|
+
U1dtbUhQRDVrdllINXVvWFMvNnFRODFxWHBSMndWOENUb3daSlVMZzA5ZGRS
|
45
|
+
ZFJuOFFzcWoxRnlPQzVzbEUzeTJiWjJvRnVhNzJvZi80OWZwdWpuRlQ2S25R
|
46
|
+
NjFDQk1xbERvVFFxT1Q2MnZHSjhuUDZNWld2QTZzeHF1ZDVBZ01CQUFFd0F3
|
47
|
+
WUJBQU1CQUE9PTwvZHM6WDUwOUNlcnRpZmljYXRlPjwvZHM6WDUwOURhdGE+
|
48
|
+
PC9kczpLZXlJbmZvPjwvZHM6U2lnbmF0dXJlPjxzYW1sOlN1YmplY3Q+PHNh
|
49
|
+
bWw6TmFtZUlEIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4x
|
50
|
+
Om5hbWVpZC1mb3JtYXQ6ZW1haWxBZGRyZXNzIj5zdXBwb3J0QG9uZWxvZ2lu
|
51
|
+
LmNvbTwvc2FtbDpOYW1lSUQ+PHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbiBN
|
52
|
+
ZXRob2Q9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpjbTpiZWFyZXIi
|
53
|
+
PjxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0i
|
54
|
+
MjAxMC0xMS0xOFQyMjowMjozN1oiIFJlY2lwaWVudD0ie3JlY2lwaWVudH0i
|
55
|
+
Lz48L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj48L3NhbWw6U3ViamVjdD48
|
56
|
+
c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxMC0xMS0xOFQyMTo1Mjoz
|
57
|
+
N1oiIE5vdE9uT3JBZnRlcj0iMjAxMC0xMS0xOFQyMjowMjozN1oiPjxzYW1s
|
58
|
+
OkF1ZGllbmNlUmVzdHJpY3Rpb24+PHNhbWw6QXVkaWVuY2U+e2F1ZGllbmNl
|
59
|
+
fTwvc2FtbDpBdWRpZW5jZT48L3NhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj48
|
60
|
+
L3NhbWw6Q29uZGl0aW9ucz48c2FtbDpBdXRoblN0YXRlbWVudCBBdXRobklu
|
61
|
+
c3RhbnQ9IjIwMTAtMTEtMThUMjE6NTc6MzdaIiBTZXNzaW9uTm90T25PckFm
|
62
|
+
dGVyPSIyMDEwLTExLTE5VDIxOjU3OjM3WiIgU2Vzc2lvbkluZGV4PSJfNTMx
|
63
|
+
YzMyZDI4M2JkZmY3ZTA0ZTQ4N2JjZGJjNGRkOGQiPjxzYW1sOkF1dGhuQ29u
|
64
|
+
dGV4dD48c2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj51cm46b2FzaXM6bmFt
|
65
|
+
ZXM6dGM6U0FNTDoyLjA6YWM6Y2xhc3NlczpQYXNzd29yZDwvc2FtbDpBdXRo
|
66
|
+
bkNvbnRleHRDbGFzc1JlZj48L3NhbWw6QXV0aG5Db250ZXh0Pjwvc2FtbDpB
|
67
|
+
dXRoblN0YXRlbWVudD48L3NhbWw6QXNzZXJ0aW9uPjwvc2FtbHA6UmVzcG9u
|
68
|
+
c2U+Cg==
|
data/test/ruby-saml_test.rb
CHANGED
@@ -1,7 +1,82 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
3
|
class RubySamlTest < Test::Unit::TestCase
|
4
|
-
|
5
|
-
|
4
|
+
|
5
|
+
context "Settings" do
|
6
|
+
setup do
|
7
|
+
@settings = Onelogin::Saml::Settings.new
|
8
|
+
end
|
9
|
+
should "should provide getters and settings" do
|
10
|
+
accessors = [
|
11
|
+
:assertion_consumer_service_url, :issuer, :sp_name_qualifier, :sp_name_qualifier,
|
12
|
+
:idp_sso_target_url, :idp_cert_fingerprint, :name_identifier_format
|
13
|
+
]
|
14
|
+
|
15
|
+
accessors.each do |accessor|
|
16
|
+
value = Kernel.rand
|
17
|
+
@settings.send("#{accessor}=".to_sym, value)
|
18
|
+
assert_equal value, @settings.send(accessor)
|
19
|
+
end
|
20
|
+
end
|
6
21
|
end
|
22
|
+
|
23
|
+
context "Response" do
|
24
|
+
should "provide setter for a logger" do
|
25
|
+
response = Onelogin::Saml::Response.new('')
|
26
|
+
assert response.logger = 'hello'
|
27
|
+
end
|
28
|
+
|
29
|
+
should "raise an exception when response is initialized with nil" do
|
30
|
+
assert_raises(ArgumentError) { Onelogin::Saml::Response.new(nil) }
|
31
|
+
end
|
32
|
+
|
33
|
+
context "#is_valid?" do
|
34
|
+
should "return false when response is initialized with blank data" do
|
35
|
+
response = Onelogin::Saml::Response.new('')
|
36
|
+
assert !response.is_valid?
|
37
|
+
end
|
38
|
+
|
39
|
+
should "return false if settings have not been set" do
|
40
|
+
response = Onelogin::Saml::Response.new(response_document)
|
41
|
+
assert !response.is_valid?
|
42
|
+
end
|
43
|
+
|
44
|
+
should "return true when the response is initialized with valid data" do
|
45
|
+
response = Onelogin::Saml::Response.new(response_document)
|
46
|
+
settings = Onelogin::Saml::Settings.new
|
47
|
+
settings.idp_cert_fingerprint = 'hello'
|
48
|
+
response.settings = settings
|
49
|
+
assert !response.is_valid?
|
50
|
+
document = stub()
|
51
|
+
document.stubs(:validate).returns(true)
|
52
|
+
response.document = document
|
53
|
+
assert response.is_valid?
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context "#name_id" do
|
58
|
+
should "extract the value of the name id element" do
|
59
|
+
response = Onelogin::Saml::Response.new(response_document)
|
60
|
+
assert_equal "support@onelogin.com", response.name_id
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context "#session_expires_at" do
|
65
|
+
should "extract the value of the SessionNotOnOrAfter attribute" do
|
66
|
+
response = Onelogin::Saml::Response.new(response_document)
|
67
|
+
assert response.session_expires_at.is_a?(Time)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "Authrequest" do
|
73
|
+
should "create the SAMLRequest URL parameter" do
|
74
|
+
settings = Onelogin::Saml::Settings.new
|
75
|
+
settings.idp_sso_target_url = "http://stuff.com"
|
76
|
+
auth_url = Onelogin::Saml::Authrequest.new.create(settings)
|
77
|
+
assert auth_url =~ /^http:\/\/stuff\.com\?SAMLRequest=/
|
78
|
+
payload = CGI.unescape(auth_url.split("=").last)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
7
82
|
end
|
data/test/test_helper.rb
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'test/unit'
|
3
3
|
require 'shoulda'
|
4
|
+
require 'mocha'
|
4
5
|
|
5
6
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
6
7
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
7
8
|
require 'ruby-saml'
|
8
9
|
|
9
10
|
class Test::Unit::TestCase
|
11
|
+
def response_document
|
12
|
+
@response_document ||= File.read(File.join(File.dirname(__FILE__), 'response.txt'))
|
13
|
+
end
|
10
14
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-saml
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 21
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
8
|
+
- 2
|
9
|
+
- 1
|
10
|
+
version: 0.2.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- OneLogin LLC
|
@@ -15,23 +15,23 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-
|
18
|
+
date: 2010-12-01 00:00:00 -06:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
22
|
-
name:
|
22
|
+
name: xmlcanonicalizer
|
23
23
|
prerelease: false
|
24
24
|
requirement: &id001 !ruby/object:Gem::Requirement
|
25
25
|
none: false
|
26
26
|
requirements:
|
27
|
-
- - "
|
27
|
+
- - "="
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
hash:
|
29
|
+
hash: 27
|
30
30
|
segments:
|
31
|
-
- 1
|
32
31
|
- 0
|
33
32
|
- 1
|
34
|
-
|
33
|
+
- 0
|
34
|
+
version: 0.1.0
|
35
35
|
type: :runtime
|
36
36
|
version_requirements: *id001
|
37
37
|
- !ruby/object:Gem::Dependency
|
@@ -40,16 +40,44 @@ dependencies:
|
|
40
40
|
requirement: &id002 !ruby/object:Gem::Requirement
|
41
41
|
none: false
|
42
42
|
requirements:
|
43
|
-
- - "
|
43
|
+
- - "="
|
44
44
|
- !ruby/object:Gem::Version
|
45
|
-
hash:
|
45
|
+
hash: 1
|
46
46
|
segments:
|
47
47
|
- 2
|
48
|
-
-
|
49
|
-
-
|
50
|
-
version: 2.
|
48
|
+
- 3
|
49
|
+
- 1
|
50
|
+
version: 2.3.1
|
51
51
|
type: :runtime
|
52
52
|
version_requirements: *id002
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: shoulda
|
55
|
+
prerelease: false
|
56
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 3
|
62
|
+
segments:
|
63
|
+
- 0
|
64
|
+
version: "0"
|
65
|
+
type: :development
|
66
|
+
version_requirements: *id003
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: mocha
|
69
|
+
prerelease: false
|
70
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
hash: 3
|
76
|
+
segments:
|
77
|
+
- 0
|
78
|
+
version: "0"
|
79
|
+
type: :development
|
80
|
+
version_requirements: *id004
|
53
81
|
description: SAML toolkit for Ruby on Rails
|
54
82
|
email: support@onelogin.com
|
55
83
|
executables: []
|
@@ -61,7 +89,6 @@ extra_rdoc_files:
|
|
61
89
|
- README.rdoc
|
62
90
|
files:
|
63
91
|
- .document
|
64
|
-
- .gitignore
|
65
92
|
- LICENSE
|
66
93
|
- README.rdoc
|
67
94
|
- Rakefile
|
@@ -73,6 +100,7 @@ files:
|
|
73
100
|
- lib/ruby-saml.rb
|
74
101
|
- lib/xml_sec.rb
|
75
102
|
- ruby-saml.gemspec
|
103
|
+
- test/response.txt
|
76
104
|
- test/ruby-saml_test.rb
|
77
105
|
- test/test_helper.rb
|
78
106
|
has_rdoc: true
|
@@ -80,8 +108,8 @@ homepage: http://github.com/onelogin/ruby-saml
|
|
80
108
|
licenses: []
|
81
109
|
|
82
110
|
post_install_message:
|
83
|
-
rdoc_options:
|
84
|
-
|
111
|
+
rdoc_options: []
|
112
|
+
|
85
113
|
require_paths:
|
86
114
|
- lib
|
87
115
|
required_ruby_version: !ruby/object:Gem::Requirement
|