openlogic-saml-sp 3.1.3 → 4.0.0.alpha.1

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