openlogic-saml-sp 3.1.3 → 4.0.0.alpha.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.
data/README.md CHANGED
@@ -117,7 +117,7 @@ this resolutions service regardless of it's realm.
117
117
 
118
118
  ## INSTALL:
119
119
 
120
- * sudo gem install openlogic-saml-sp
120
+ * sudo gem install saml-sp
121
121
 
122
122
  ## LICENSE:
123
123
 
data/Rakefile CHANGED
@@ -3,12 +3,15 @@ begin
3
3
  Jeweler::Tasks.new do |gemspec|
4
4
  gemspec.name = 'openlogic-saml-sp'
5
5
  gemspec.summary = 'SAML 2.0 SSO Sevice Provider Library'
6
- gemspec.email = 'gbettridge@openlogic.com'
7
- gemspec.authors = ["OpenLogic", "Peter Williams","Glen Aultman-Bettridge"]
6
+ gemspec.email = ['gbettridge@openlogic.com', 'todd.thomas@openlogic.com']
7
+ gemspec.authors = ["OpenLogic", "Peter Williams", "Glen Aultman-Bettridge", "Todd Thomas"]
8
+ gemspec.homepage = 'https://github.com/openlogic/saml-sp'
8
9
  gemspec.add_dependency 'nokogiri'
10
+ gemspec.add_dependency 'signed_xml', '~> 1.0'
9
11
  gemspec.add_dependency 'openlogic-resourceful'
10
12
  gemspec.add_dependency 'uuidtools'
11
- gemspec.add_development_dependency 'rspec'
13
+ gemspec.add_development_dependency 'rspec', '~> 2.12'
14
+ gemspec.add_development_dependency 'fakeweb'
12
15
  gemspec.files = FileList["[A-Z]*", "{bin,generators,lib,test,spec,rails}/**/*"]
13
16
  end
14
17
  Jeweler::GemcutterTasks.new
@@ -16,10 +19,10 @@ rescue LoadError
16
19
  puts "Jeweler not available. Install it with: gem install jeweler"
17
20
  end
18
21
 
19
- # require 'spec/rake/spectask'
20
- # Spec::Rake::SpecTask.new
21
- #
22
- # task 'test:run' => :spec
22
+ require "rspec/core/rake_task"
23
+ RSpec::Core::RakeTask.new(:spec)
24
+
25
+ task :default => :spec
23
26
  # EOF
24
27
 
25
28
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.1.3
1
+ 4.0.0.alpha.1
data/lib/saml-sp.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'digest'
1
2
  require 'logger'
2
3
 
3
4
  module SamlSp
@@ -57,6 +58,26 @@ module SamlSp
57
58
  end
58
59
  end
59
60
 
61
+ CertificateStore = Class.new(Hash) do
62
+ include Logging
63
+
64
+ def load_certificates(glob)
65
+ logger.info "loading certificates from #{glob}"
66
+ Dir[glob].each do |file|
67
+ begin
68
+ next unless File.file?(file)
69
+ logger.info "reading #{file}"
70
+ cert = OpenSSL::X509::Certificate.new(File.read(file))
71
+ fingerprint = Digest::SHA1.hexdigest(cert.to_der)
72
+ self[fingerprint] = cert
73
+ logger.info "loaded certificate #{cert.inspect} with fingerprint #{fingerprint}"
74
+ rescue StandardError => e
75
+ logger.warn "unable to read X.509 cert from #{file}: #{e.message}"
76
+ end
77
+ end
78
+ end
79
+ end.new
80
+
60
81
  autoload :Config, 'saml_sp/config'
61
82
  end # module SamlSp
62
83
 
@@ -23,7 +23,7 @@ module Saml2
23
23
 
24
24
  # Initialize and register a new artifact resolver.
25
25
  #
26
- # @param [string] source_id An opaque identifier used by the IDP
26
+ # @param [string] source_id An opacque identifier used by the IDP
27
27
  # to identify artifact that can be resolved by this service.
28
28
  #
29
29
  # @param [string] resolution_service_uri The URI that will resolve
@@ -80,12 +80,11 @@ module Saml2
80
80
  # response do not match the idp_id for this source.
81
81
  def resolve(artifact)
82
82
  soap_body = request_document_for(artifact)
83
- logger.info{"ArtifactResolve request body:\n#{soap_body.gsub(/^/, "\t")}"}
83
+ logger.debug{"ArtifactResolve request body:\n#{soap_body.gsub(/^/, "\t")}"}
84
84
  resp = http.resource(resolution_service_uri).post(soap_body,
85
85
  'Accept' => 'application/soap+xml',
86
86
  'Content-Type' => 'application/soap+xml')
87
87
 
88
- logger.info{"ArtifactResolve response body:\n#{resp.gsub(/^/, "\t")}"}
89
88
  doc = Nokogiri::XML.parse(resp.body)
90
89
  assert_successful_response(doc)
91
90
 
@@ -1,5 +1,6 @@
1
1
  require 'nokogiri'
2
2
  require 'saml2/artifact_resolver'
3
+ require 'signed_xml'
3
4
 
4
5
  module Saml2
5
6
  class InvalidAssertionError < ArgumentError
@@ -60,8 +61,13 @@ module Saml2
60
61
  else
61
62
  Nokogiri::XML.parse(xml_assertion)
62
63
  end
63
- logger.debug {"Parsing assertion: \n" + doc.to_xml(:indent => 2).gsub(/^/, "\t")}
64
+ logger.info {"Parsing assertion: \n" + doc.to_xml(:indent => 2).gsub(/^/, "\t")}
64
65
 
66
+ # We can't use the helpful #issuer_from until the 'asrt' namespace is defined,
67
+ # but we can't add that definition without breaking signature verification.
68
+ # This is sad.
69
+ issuer = doc.at_xpath('//saml2:Assertion/saml2:Issuer', saml2: "urn:oasis:names:tc:SAML:2.0:assertion").text.strip
70
+ verify(doc) if Saml2::Issuer(issuer).verify_signatures?
65
71
 
66
72
  doc.root.add_namespace_definition('asrt', 'urn:oasis:names:tc:SAML:2.0:assertion')
67
73
 
@@ -86,6 +92,11 @@ module Saml2
86
92
  attributes[attr_name.to_s]
87
93
  end
88
94
 
95
+ def self.verify(doc)
96
+ signed_doc = SignedXml::Document(doc)
97
+ raise "SAML assertion failed verification" unless signed_doc.is_verified?(SamlSp::CertificateStore)
98
+ end
99
+
89
100
  protected
90
101
 
91
102
  attr_reader :attributes
@@ -0,0 +1,26 @@
1
+ module Saml2
2
+ class AuthnRequest
3
+ attr_reader :issuer, :id, :issue_instant, :assertion_consumer_service_url
4
+
5
+ def initialize(attrs = {})
6
+ @issuer = attrs.fetch(:issuer)
7
+ @id = SecureRandom.uuid
8
+ @issue_instant = Time.now.utc.strftime('%FT%TZ')
9
+ @assertion_consumer_service_url = attrs[:assertion_consumer_service_url]
10
+ end
11
+
12
+ def to_xml
13
+ Nokogiri::XML::Builder.new do |xml|
14
+ xml.AuthnRequest('xmlns:samlp' => 'urn:oasis:names:tc:SAML:2.0:protocol',
15
+ 'xmlns:saml' => 'urn:oasis:names:tc:SAML:2.0:assertion',
16
+ 'ID' => id,
17
+ 'Version' => '2.0',
18
+ 'IssueInstant' => issue_instant,
19
+ 'AssertionConsumerServiceURL' => assertion_consumer_service_url) do
20
+ xml.parent.namespace = xml.parent.namespace_definitions.find {|ns| ns.prefix == 'samlp'} # Necessary to output namespace prefix on root element.
21
+ xml['saml'].Issuer issuer
22
+ end
23
+ end.to_xml
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,35 @@
1
+ module Saml2
2
+ NoSuchIssuerError = Class.new(StandardError)
3
+
4
+ class Issuer
5
+ attr_reader :id, :verify_signatures
6
+
7
+ def initialize(id, verify_signatures)
8
+ @id = id
9
+ @verify_signatures = verify_signatures
10
+
11
+ IssuerRegistry.register self
12
+ end
13
+
14
+ def verify_signatures?
15
+ verify_signatures
16
+ end
17
+ end
18
+
19
+ def self.Issuer(id)
20
+ if IssuerRegistry.has_key? id
21
+ IssuerRegistry[id]
22
+ else
23
+ raise NoSuchIssuerError, "No Issuer registered with id [#{id}]"
24
+ end
25
+ end
26
+
27
+ IssuerRegistry = Class.new(Hash) do
28
+ include SamlSp::Logging
29
+
30
+ def register(issuer)
31
+ self[issuer.id] = issuer
32
+ logger.info "saml-sp: #{issuer.inspect}' registered"
33
+ end
34
+ end.new
35
+ end
data/lib/saml2.rb CHANGED
@@ -2,6 +2,8 @@ module Saml2
2
2
  autoload :Type4Artifact, 'saml2/type4_artifact'
3
3
  autoload :Assertion, 'saml2/assertion'
4
4
  autoload :ArtifactResolver, 'saml2/artifact_resolver'
5
+ autoload :AuthnRequest, 'saml2/authn_request'
6
+ autoload :Issuer, 'saml2/issuer'
5
7
  end
6
8
 
7
9
 
@@ -60,6 +60,11 @@ METHOD
60
60
  dsl = ResolutionSerivceConfig.new
61
61
  dsl.interpret(blk)
62
62
  end
63
+
64
+ def issuer(&blk)
65
+ dsl = IssuerConfig.new
66
+ dsl.interpret(blk)
67
+ end
63
68
  end
64
69
 
65
70
 
@@ -111,6 +116,23 @@ METHOD
111
116
  (@realm || @promiscuous) && @user_id && @password
112
117
  end
113
118
  end
119
+
120
+ class IssuerConfig < ConfigBlock
121
+ config_item :id
122
+ config_item :verify_signatures
123
+
124
+ def interpret(blk, filename = nil)
125
+ super
126
+
127
+ raise ConfigurationError, "Incomplete Issuer configuration" unless valid?
128
+
129
+ Saml2::Issuer.new(@id, @verify_signatures)
130
+ end
131
+
132
+ def valid?
133
+ @id && !@verify_signatures.nil?
134
+ end
135
+ end
114
136
  end
115
137
 
116
138
  # Copyright (c) 2010 OpenLogic
@@ -22,6 +22,8 @@ describe Saml2::ArtifactResolver do
22
22
  before do
23
23
  @resolver = Saml2::ArtifactResolver.new('a-source-id', 'https://idp.invalid/resolution-service', 'http://idp.invalid', 'http://sp.invalid')
24
24
  @resolver.basic_auth_credentials('myuserid', 'mypasswd', 'myrealm')
25
+ # register issuer which doesn't require verification
26
+ Saml2::Issuer.new(@resolver.idp_id, false)
25
27
 
26
28
  @artifact = Saml2::Type4Artifact.new(0, '01234567890123456789', 'abcdefghijklmnopqrst')
27
29
  FakeWeb.register_uri(:post, 'https://idp.invalid/resolution-service', :body => SUCCESSFUL_SAML_RESP)
@@ -146,6 +146,9 @@ describe Saml2::Assertion do
146
146
  </SOAP-ENV:Body>
147
147
  </SOAP-ENV:Envelope>
148
148
  XML
149
+
150
+ # register issuer which doesn't require verification
151
+ Saml2::Issuer.new('https://idp.invalid', false)
149
152
  end
150
153
 
151
154
  def self.it_should_extract(prop, expected_value)
@@ -201,6 +201,30 @@ describe SamlSp::Config do
201
201
  end
202
202
  end
203
203
 
204
+ describe "valid issuer description" do
205
+ before do
206
+ @dsl = SamlSp::Config.new
207
+ @issuer = @dsl.interpret(<<-CONFIG)
208
+ issuer {
209
+ id "http://sso.example.org"
210
+ verify_signatures true
211
+ }
212
+ CONFIG
213
+ end
214
+
215
+ it "should build an issuer" do
216
+ @issuer.should be_kind_of(Saml2::Issuer)
217
+ end
218
+
219
+ it "should build an issuer with correct id" do
220
+ @issuer.id.should == 'http://sso.example.org'
221
+ end
222
+
223
+ it "should build an issuer with correct verify_signatures setting" do
224
+ @issuer.verify_signatures?.should be true
225
+ end
226
+ end
227
+
204
228
  it "should raise error on missing source_id" do
205
229
  lambda {
206
230
  @dsl.interpret(<<-CONFIG)
@@ -290,6 +314,26 @@ describe SamlSp::Config do
290
314
  }.should raise_error SamlSp::ConfigurationError
291
315
  end
292
316
 
317
+ it "should raise error on missing issuer id" do
318
+ lambda {
319
+ @dsl.interpret(<<-CONFIG)
320
+ issuer {
321
+ verify_signatures true
322
+ }
323
+ CONFIG
324
+ }.should raise_error SamlSp::ConfigurationError
325
+ end
326
+
327
+ it "should raise error on missing verify_signatures setting" do
328
+ lambda {
329
+ @dsl.interpret(<<-CONFIG)
330
+ issuer {
331
+ id "http://sso.example.org"
332
+ }
333
+ CONFIG
334
+ }.should raise_error SamlSp::ConfigurationError
335
+ end
336
+
293
337
  end
294
338
 
295
339
 
metadata CHANGED
@@ -1,17 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openlogic-saml-sp
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.3
5
- prerelease:
4
+ version: 4.0.0.alpha.1
5
+ prerelease: 6
6
6
  platform: ruby
7
7
  authors:
8
8
  - OpenLogic
9
9
  - Peter Williams
10
10
  - Glen Aultman-Bettridge
11
+ - Todd Thomas
11
12
  autorequire:
12
13
  bindir: bin
13
14
  cert_chain: []
14
- date: 2012-11-27 00:00:00.000000000 Z
15
+ date: 2013-04-17 00:00:00.000000000 Z
15
16
  dependencies:
16
17
  - !ruby/object:Gem::Dependency
17
18
  name: nokogiri
@@ -29,6 +30,22 @@ dependencies:
29
30
  - - ! '>='
30
31
  - !ruby/object:Gem::Version
31
32
  version: '0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: signed_xml
35
+ requirement: !ruby/object:Gem::Requirement
36
+ none: false
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
41
+ type: :runtime
42
+ prerelease: false
43
+ version_requirements: !ruby/object:Gem::Requirement
44
+ none: false
45
+ requirements:
46
+ - - ~>
47
+ - !ruby/object:Gem::Version
48
+ version: '1.0'
32
49
  - !ruby/object:Gem::Dependency
33
50
  name: openlogic-resourceful
34
51
  requirement: !ruby/object:Gem::Requirement
@@ -63,6 +80,22 @@ dependencies:
63
80
  version: '0'
64
81
  - !ruby/object:Gem::Dependency
65
82
  name: rspec
83
+ requirement: !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ~>
87
+ - !ruby/object:Gem::Version
88
+ version: '2.12'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ none: false
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: '2.12'
97
+ - !ruby/object:Gem::Dependency
98
+ name: fakeweb
66
99
  requirement: !ruby/object:Gem::Requirement
67
100
  none: false
68
101
  requirements:
@@ -78,7 +111,9 @@ dependencies:
78
111
  - !ruby/object:Gem::Version
79
112
  version: '0'
80
113
  description:
81
- email: gbettridge@openlogic.com
114
+ email:
115
+ - gbettridge@openlogic.com
116
+ - todd.thomas@openlogic.com
82
117
  executables: []
83
118
  extensions: []
84
119
  extra_rdoc_files:
@@ -94,6 +129,8 @@ files:
94
129
  - lib/saml2.rb
95
130
  - lib/saml2/artifact_resolver.rb
96
131
  - lib/saml2/assertion.rb
132
+ - lib/saml2/authn_request.rb
133
+ - lib/saml2/issuer.rb
97
134
  - lib/saml2/type4_artifact.rb
98
135
  - lib/saml2/unexpected_type_code_error.rb
99
136
  - lib/saml_sp/config.rb
@@ -103,7 +140,7 @@ files:
103
140
  - spec/saml2/type4_artifact_spec.rb
104
141
  - spec/saml_sp/config_spec.rb
105
142
  - spec/spec_helper.rb
106
- homepage:
143
+ homepage: https://github.com/openlogic/saml-sp
107
144
  licenses: []
108
145
  post_install_message:
109
146
  rdoc_options: []
@@ -118,12 +155,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
118
155
  required_rubygems_version: !ruby/object:Gem::Requirement
119
156
  none: false
120
157
  requirements:
121
- - - ! '>='
158
+ - - ! '>'
122
159
  - !ruby/object:Gem::Version
123
- version: '0'
160
+ version: 1.3.1
124
161
  requirements: []
125
162
  rubyforge_project:
126
- rubygems_version: 1.8.24
163
+ rubygems_version: 1.8.25
127
164
  signing_key:
128
165
  specification_version: 3
129
166
  summary: SAML 2.0 SSO Sevice Provider Library