Skip to content

Commit 646d8d3

Browse files
committed
Update branch
2 parents 9b37250 + a6fa4b5 commit 646d8d3

7 files changed

Lines changed: 82 additions & 16 deletions

File tree

README.md

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,37 @@ The Ruby SAML library is for implementing the client side of a SAML authorizatio
99

1010
SAML authorization is a two step process and you are expected to implement support for both.
1111

12-
## The initialization phase
12+
## Getting Started
13+
In order to use the toolkit you will need to install the gem (either manually or using Bundler), and require the library in your Ruby application:
14+
15+
Using `Gemfile`
16+
17+
```ruby
18+
# latest stable
19+
gem 'ruby-saml', '~> 0.8.1'
20+
21+
# or track master for bleeding-edge
22+
gem 'ruby-saml', :github => 'onelogin/ruby-saml'
23+
```
24+
25+
Using Bundler
26+
27+
```sh
28+
gem install ruby-saml
29+
```
30+
31+
When requiring the gem, you can add the whole toolkit
32+
```ruby
33+
require 'onelogin/ruby-saml'
34+
```
35+
36+
or just the required components individually:
37+
38+
```ruby
39+
require 'onelogin/ruby-saml/authrequest'
40+
```
41+
42+
## The Initialization Phase
1343

1444
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):
1545

@@ -46,6 +76,7 @@ def saml_settings
4676
settings.idp_sso_target_url = "https://app.onelogin.com/saml/signon/#{OneLoginAppId}"
4777
settings.idp_cert_fingerprint = OneLoginAppCertFingerPrint
4878
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
79+
4980
# Optional for most SAML IdPs
5081
settings.authn_context = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
5182

@@ -84,8 +115,10 @@ class SamlController < ApplicationController
84115
settings.idp_sso_target_url = "https://app.onelogin.com/saml/signon/#{OneLoginAppId}"
85116
settings.idp_cert_fingerprint = OneLoginAppCertFingerPrint
86117
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
118+
87119
# Optional for most SAML IdPs
88120
settings.authn_context = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
121+
89122
# Optional. Describe according to IdP specification (if supported) which attributes the SP desires to receive in SAMLResponse.
90123
settings.attributes_index = 30
91124

@@ -94,8 +127,8 @@ class SamlController < ApplicationController
94127
end
95128
```
96129

97-
If are using saml:AttributeStatement to transfare metadata, like the user name, you can access all the attributes through `response.attributes`. It
98-
contains all the saml:AttributeStatement with its 'Name' as a indifferent key and the one saml:AttributeValue as value.
130+
131+
If are using saml:AttributeStatement to transfare metadata, like the user name, you can access all the attributes through `response.attributes`. It contains all the saml:AttributeStatement with its 'Name' as a indifferent key and the one saml:AttributeValue as value.
99132

100133
```ruby
101134
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse])
@@ -113,8 +146,8 @@ If we want to add a saml:AuthnContextDeclRef, define a `settings.authn_context_d
113146
To form a trusted pair relationship with the IdP, the SP (you) need to provide metadata XML
114147
to the IdP for various good reasons. (Caching, certificate lookups, relaying party permissions, etc)
115148

116-
The class OneLogin::RubySaml::Metadata takes care of this by reading the Settings and returning XML. All
117-
you have to do is add a controller to return the data, then give this URL to the IdP administrator.
149+
The class `OneLogin::RubySaml::Metadata` takes care of this by reading the Settings and returning XML. All you have to do is add a controller to return the data, then give this URL to the IdP administrator.
150+
118151
The metdata will be polled by the IdP every few minutes, so updating your settings should propagate
119152
to the IdP settings.
120153

@@ -143,11 +176,11 @@ response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], :allowed_cloc
143176

144177
Make sure to keep the value as comfortably small as possible to keep security risks to a minimum.
145178

146-
## Note on Patches/Pull Requests
179+
## Adding Features, Pull Requests
147180

148-
* Fork the project.
149-
* Make your feature addition or bug fix.
150-
* Add tests for it. This is important so I don't break it in a
151-
future version unintentionally.
152-
* Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
153-
* Send me a pull request. Bonus points for topic branches.
181+
* Fork the repository
182+
* Make your feature addition or bug fix
183+
* Add tests for your new features. This is important so we don't break any features in a future version unintentionally.
184+
* Ensure all tests pass.
185+
* Do not change rakefile, version, or history.
186+
* Open a pull request, following [this template](https://gist.github.com/Lordnibbler/11002759).

lib/onelogin/ruby-saml/authrequest.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
require "rexml/document"
66
require "rexml/xpath"
77

8+
require "onelogin/ruby-saml/logging"
9+
810
module OneLogin
911
module RubySaml
1012
include REXML

lib/onelogin/ruby-saml/metadata.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
require "rexml/xpath"
33
require "uri"
44

5+
require "onelogin/ruby-saml/logging"
6+
57
# Class to return SP metadata based on the settings requested.
68
# Return this XML in a controller, then give that URL to the the
79
# IdP administrator. The IdP will poll the URL and your settings

lib/onelogin/ruby-saml/response.rb

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,18 @@ class Response
1313

1414
# TODO: This should probably be ctor initialized too... WDYT?
1515
attr_accessor :settings
16+
attr_accessor :errors
1617

1718
attr_reader :options
1819
attr_reader :response
1920
attr_reader :document
2021

2122
def initialize(response, options = {})
23+
@errors = []
2224
raise ArgumentError.new("Response cannot be nil") if response.nil?
2325
@options = options
2426
@response = (response =~ /^</) ? response : Base64.decode64(response)
25-
@document = XMLSecurity::SignedDocument.new(@response)
27+
@document = XMLSecurity::SignedDocument.new(@response, @errors)
2628
end
2729

2830
def is_valid?
@@ -33,6 +35,10 @@ def validate!
3335
validate(false)
3436
end
3537

38+
def errors
39+
@errors
40+
end
41+
3642
# The value of the user identifier as designated by the initialization request response
3743
def name_id
3844
@name_id ||= begin
@@ -153,9 +159,14 @@ def validate_structure(soft = true)
153159
@xml = Nokogiri::XML(self.document.to_s)
154160
end
155161
if soft
156-
@schema.validate(@xml).map{ return false }
162+
@schema.validate(@xml).map{
163+
@errors << "Schema validation failed";
164+
return false
165+
}
157166
else
158-
@schema.validate(@xml).map{ |error| validation_error("#{error.message}\n\n#{@xml.to_s}") }
167+
@schema.validate(@xml).map{ |error| @errors << "#{error.message}\n\n#{@xml.to_s}";
168+
validation_error("#{error.message}\n\n#{@xml.to_s}")
169+
}
159170
end
160171
end
161172

@@ -197,10 +208,12 @@ def validate_conditions(soft = true)
197208
now = Time.now.utc
198209

199210
if not_before && (now + (options[:allowed_clock_drift] || 0)) < not_before
211+
@errors << "Current time is earlier than NotBefore condition #{(now + (options[:allowed_clock_drift] || 0))} < #{not_before})"
200212
return soft ? false : validation_error("Current time is earlier than NotBefore condition")
201213
end
202214

203215
if not_on_or_after && now >= not_on_or_after
216+
@errors << "Current time is on or after NotOnOrAfter condition (#{now} >= #{not_on_or_after})"
204217
return soft ? false : validation_error("Current time is on or after NotOnOrAfter condition")
205218
end
206219

lib/xml_security.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@ class SignedDocument < REXML::Document
3838
DSIG = "http://www.w3.org/2000/09/xmldsig#"
3939

4040
attr_accessor :signed_element_id
41+
attr_accessor :errors
4142

42-
def initialize(response)
43+
def initialize(response, errors = [])
4344
super(response)
45+
@errors = errors
4446
extract_signed_element_id
4547
end
4648

@@ -62,6 +64,7 @@ def validate_document(idp_cert_fingerprint, soft = true)
6264
fingerprint = Digest::SHA1.hexdigest(cert.to_der)
6365

6466
if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
67+
@errors << "Fingerprint mismatch"
6568
return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Fingerprint mismatch"))
6669
end
6770

@@ -108,6 +111,7 @@ def validate_signature(base64_cert, soft = true)
108111
digest_value = Base64.decode64(REXML::XPath.first(ref, "//ds:DigestValue", {"ds"=>DSIG}).text)
109112

110113
unless digests_match?(hash, digest_value)
114+
@errors << "Digest mismatch"
111115
return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Digest mismatch"))
112116
end
113117
end
@@ -123,6 +127,7 @@ def validate_signature(base64_cert, soft = true)
123127
signature_algorithm = algorithm(REXML::XPath.first(signed_info_element, "//ds:SignatureMethod", {"ds"=>DSIG}))
124128

125129
unless cert.public_key.verify(signature_algorithm.new, signature, canon_string)
130+
@errors << "Key validation error"
126131
return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Key validation error"))
127132
end
128133

test/response_test.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,14 @@ class RubySamlTest < Test::Unit::TestCase
5353
end
5454
end
5555

56+
context "#validate_structure" do
57+
should "raise when encountering a condition that prevents the document from being valid" do
58+
response = OneLogin::RubySaml::Response.new(response_document_2)
59+
response.send(:validate_structure)
60+
assert response.errors.include? "Schema validation failed"
61+
end
62+
end
63+
5664
context "#is_valid?" do
5765
should "return false when response is initialized with blank data" do
5866
response = OneLogin::RubySaml::Response.new('')

test/xml_security_test.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,15 @@ class XmlSecurityTest < Test::Unit::TestCase
4040
@document.validate_document("no:fi:ng:er:pr:in:t", false)
4141
end
4242
assert_equal("Fingerprint mismatch", exception.message)
43+
assert @document.errors.include? "Fingerprint mismatch"
4344
end
4445

4546
should "should raise Digest mismatch" do
4647
exception = assert_raise(OneLogin::RubySaml::ValidationError) do
4748
@document.validate_signature(@base64cert, false)
4849
end
4950
assert_equal("Digest mismatch", exception.message)
51+
assert @document.errors.include? "Digest mismatch"
5052
end
5153

5254
should "should raise Key validation error" do
@@ -59,6 +61,7 @@ class XmlSecurityTest < Test::Unit::TestCase
5961
document.validate_signature(base64cert, false)
6062
end
6163
assert_equal("Key validation error", exception.message)
64+
assert document.errors.include? "Key validation error"
6265
end
6366

6467
should "correctly obtain the digest method with alternate namespace declaration" do

0 commit comments

Comments
 (0)