saml2ruby 1.1.0

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 ADDED
@@ -0,0 +1,19 @@
1
+ =Ruby SAML2
2
+
3
+ Grabbed from https://opensso.dev.java.net/source/browse/opensso/extensions/saml2ruby/source/
4
+
5
+ A Ruby library for SAML 2.0
6
+
7
+ ==Features
8
+ * Easy to use API for acting as a SAML 2.0 relying party.
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
@@ -0,0 +1,111 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+ require 'rake/packagetask'
5
+ require 'rake/gempackagetask'
6
+
7
+ require File.join(File.dirname(__FILE__), '/lib/saml_2_ruby/version')
8
+
9
+ PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
10
+ PKG_NAME = 'saml2ruby'
11
+ PKG_VERSION = SAML::VERSION::STRING + PKG_BUILD
12
+ PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
13
+ PKG_DESTINATION = ENV["PKG_DESTINATION"] || "../#{PKG_NAME}"
14
+
15
+ RELEASE_NAME = "REL #{PKG_VERSION}"
16
+
17
+ PKG_FILES = FileList[
18
+ #'CHANGELOG',
19
+ #'LICENSE',
20
+ 'README',
21
+ #'TODO',
22
+ 'Rakefile',
23
+ 'bin/**/*',
24
+ 'doc/**/*',
25
+ 'lib/**/*',
26
+ ] - [ 'test' ]
27
+
28
+ require 'rubygems'
29
+ begin
30
+ require 'jeweler'
31
+ Jeweler::Tasks.new do |gemspec|
32
+ gemspec.name = "saml2ruby"
33
+ gemspec.summary = "Ruby implementation of the SAML 2.0 Specification"
34
+ gemspec.description = %Q{Part of the OpenSSO extension set. Writting by the wonderful guys over at Sun. Moved it over to a github repo and turned it into a rubygem.}
35
+ gemspec.email = ["scashin133@gmail.com"]
36
+ gemspec.homepage = "http://github.com/scashin133/saml2ruby"
37
+ gemspec.authors = ["Sean Cashin"]
38
+ gemspec.add_dependency('XMLCanonicalizer', '>=1.0.1')
39
+ gemspec.version = PKG_VERSION
40
+ gemspec.files = PKG_FILES.to_a
41
+ end
42
+ Jeweler::GemcutterTasks.new
43
+ rescue LoadError
44
+ puts "Jeweler not available. Install it with: gem install jeweler"
45
+ end
46
+
47
+
48
+ desc 'Default: run unit tests.'
49
+ task :default => :test
50
+
51
+ desc 'Test the library.'
52
+ Rake::TestTask.new(:test) do |t|
53
+ t.libs << 'lib'
54
+ test_files = FileList['test/**/*_test.rb']
55
+ t.test_files = test_files
56
+ t.verbose = true
57
+ end
58
+
59
+ desc 'Generate documentation for the library.'
60
+ Rake::RDocTask.new(:rdoc) do |rdoc|
61
+ rdoc.rdoc_dir = 'rdoc'
62
+ rdoc.title = 'RSAML'
63
+ rdoc.options << '--line-numbers' << '--inline-source'
64
+ rdoc.rdoc_files.include('README')
65
+ rdoc.rdoc_files.include('lib/**/*.rb')
66
+ end
67
+
68
+ namespace :rcov do
69
+ desc 'Measures test coverage'
70
+ task :test do
71
+ rm_f 'coverage.data'
72
+ mkdir 'coverage' unless File.exist?('coverage')
73
+ rcov = "rcov --aggregate coverage.data --text-summary -Ilib"
74
+ system("#{rcov} test/*_test.rb")
75
+ #system("open coverage/index.html") if PLATFORM['darwin']
76
+ end
77
+ end
78
+
79
+ desc "Generate code statistics"
80
+ task :lines do
81
+ lines, codelines, total_lines, total_codelines = 0, 0, 0, 0
82
+
83
+ for file_name in FileList["lib/**/*.rb"]
84
+ next if file_name =~ /vendor/
85
+ f = File.open(file_name)
86
+
87
+ while line = f.gets
88
+ lines += 1
89
+ next if line =~ /^\s*$/
90
+ next if line =~ /^\s*#/
91
+ codelines += 1
92
+ end
93
+ puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}"
94
+
95
+ total_lines += lines
96
+ total_codelines += codelines
97
+
98
+ lines, codelines = 0, 0
99
+ end
100
+
101
+ puts "Total: Lines #{total_lines}, LOC #{total_codelines}"
102
+ end
103
+
104
+ desc "Reinstall the gem from a local package copy"
105
+ task :reinstall => [:package] do
106
+ windows = RUBY_PLATFORM =~ /mswin/
107
+ sudo = windows ? '' : 'sudo'
108
+ gem = windows ? 'gem.bat' : 'gem'
109
+ `#{sudo} #{gem} uninstall -x -i #{PKG_NAME}`
110
+ `#{sudo} #{gem} install pkg/#{PKG_NAME}-#{PKG_VERSION}`
111
+ end
data/TODO ADDED
@@ -0,0 +1,3 @@
1
+
2
+
3
+
@@ -0,0 +1,74 @@
1
+ # The contents of this file are subject to the terms
2
+ # of the Common Development and Distribution License
3
+ # (the License). You may not use this file except in
4
+ # compliance with the License.
5
+ #
6
+ # You can obtain a copy of the License at
7
+ # https://opensso.dev.java.net/public/CDDLv1.0.html or
8
+ # opensso/legal/CDDLv1.0.txt
9
+ # See the License for the specific language governing
10
+ # permission and limitations under the License.
11
+ #
12
+ # When distributing Covered Code, include this CDDL
13
+ # Header Notice in each file and include the License file
14
+ # at opensso/legal/CDDLv1.0.txt.
15
+ # If applicable, add the following below the CDDL Header,
16
+ # with the fields enclosed by brackets [] replaced by
17
+ # your own identifying information:
18
+ # "Portions Copyrighted [year] [name of copyright owner]"
19
+ #
20
+ # $Id: account_controller.rb,v 1.1 2007/03/20 05:26:56 todddd Exp $
21
+ #
22
+ # Copyright 2007 Sun Microsystems Inc. All Rights Reserved
23
+ # Portions Copyrighted 2007 Todd W Saxton.
24
+
25
+ require "pathname"
26
+ require "cgi"
27
+ require "saml2ruby"
28
+
29
+
30
+ class AccountController < ApplicationController
31
+
32
+ def login
33
+ relying_party = SAML::RelyingParty.new(
34
+ :assertion_consumer_service_URL => "http://localhost:3008/account/complete",
35
+ :issuer => "localhost_ruby",
36
+ :sp_name_qualifier => "localhost_ruby",
37
+ :idp_sso_target_url => "http://localhost:8080/openfm-samples-ip/SSORedirect/metaAlias/ip_meta_alias",
38
+ :idp_slo_target_url => "http://localhost:8080/openfm-samples-ip/IDPSloRedirect/metaAlias/ip_meta_alias",
39
+ :idp_cert_fingerprint => "93:bd:43:a4:60:65:a4:05:95:98:a9:d8:f4:8b:4c:c8:5f:31:87:e9"
40
+ )
41
+ relying_party.logger = logger
42
+ session[:relying_party] = relying_party
43
+ request = relying_party.create_auth_request
44
+ redirect_to(request)
45
+ end
46
+
47
+ def complete
48
+
49
+ if params[:SAMLResponse].empty?
50
+ flash[:notice] = 'Unknown response from SAML server.'
51
+ redirect_to :action => 'index'
52
+ end
53
+
54
+ valid_flag = session[:relying_party].process_auth_response(params[:SAMLResponse])
55
+
56
+ if valid_flag
57
+ redirect_to :action => "welcome"
58
+ else
59
+ flash[:notice] = 'invalid SAML Response.'
60
+ redirect_to :action => "index"
61
+ end
62
+
63
+ end
64
+
65
+ def logout
66
+
67
+ request = session[:relying_party].create_logout_request
68
+ session[:relying_party] = nil
69
+
70
+ redirect_to(request)
71
+
72
+ end
73
+
74
+ end
@@ -0,0 +1,7 @@
1
+ # Filters added to this controller apply to all controllers in the application.
2
+ # Likewise, all the methods added will be available for all controllers.
3
+
4
+ class ApplicationController < ActionController::Base
5
+ # Pick a unique cookie name to distinguish our session data from others'
6
+ session :session_key => '_SimpleSAMLRP_session_id'
7
+ end
@@ -0,0 +1,2 @@
1
+ module AccountHelper
2
+ end
@@ -0,0 +1,3 @@
1
+ # Methods added to this helper will be available to all templates in the application.
2
+ module ApplicationHelper
3
+ end
@@ -0,0 +1,45 @@
1
+ # Don't change this file. Configuration is done in config/environment.rb and config/environments/*.rb
2
+
3
+ unless defined?(RAILS_ROOT)
4
+ root_path = File.join(File.dirname(__FILE__), '..')
5
+
6
+ unless RUBY_PLATFORM =~ /mswin32/
7
+ require 'pathname'
8
+ root_path = Pathname.new(root_path).cleanpath(true).to_s
9
+ end
10
+
11
+ RAILS_ROOT = root_path
12
+ end
13
+
14
+ unless defined?(Rails::Initializer)
15
+ if File.directory?("#{RAILS_ROOT}/vendor/rails")
16
+ require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
17
+ else
18
+ require 'rubygems'
19
+
20
+ environment_without_comments = IO.readlines(File.dirname(__FILE__) + '/environment.rb').reject { |l| l =~ /^#/ }.join
21
+ environment_without_comments =~ /[^#]RAILS_GEM_VERSION = '([\d.]+)'/
22
+ rails_gem_version = $1
23
+
24
+ if version = defined?(RAILS_GEM_VERSION) ? RAILS_GEM_VERSION : rails_gem_version
25
+ # Asking for 1.1.6 will give you 1.1.6.5206, if available -- makes it easier to use beta gems
26
+ rails_gem = Gem.cache.search('rails', "~>#{version}.0").sort_by { |g| g.version.version }.last
27
+
28
+ if rails_gem
29
+ require_gem "rails", "=#{rails_gem.version.version}"
30
+ require rails_gem.full_gem_path + '/lib/initializer'
31
+ else
32
+ STDERR.puts %(Cannot find gem for Rails ~>#{version}.0:
33
+ Install the missing gem with 'gem install -v=#{version} rails', or
34
+ change environment.rb to define RAILS_GEM_VERSION with your desired version.
35
+ )
36
+ exit 1
37
+ end
38
+ else
39
+ require_gem "rails"
40
+ require 'initializer'
41
+ end
42
+ end
43
+
44
+ Rails::Initializer.run(:set_load_path)
45
+ end
@@ -0,0 +1,56 @@
1
+ # Be sure to restart your web server when you modify this file.
2
+
3
+ # Uncomment below to force Rails into production mode when
4
+ # you don't control web/app server and can't set it the proper way
5
+ # ENV['RAILS_ENV'] ||= 'production'
6
+
7
+ # Specifies gem version of Rails to use when vendor/rails is not present
8
+ RAILS_GEM_VERSION = '1.1.6' unless defined? RAILS_GEM_VERSION
9
+
10
+ # Bootstrap the Rails environment, frameworks, and default configuration
11
+ require File.join(File.dirname(__FILE__), 'boot')
12
+
13
+ Rails::Initializer.run do |config|
14
+ # Settings in config/environments/* take precedence over those specified here
15
+
16
+ # Skip frameworks you're not going to use (only works if using vendor/rails)
17
+ # config.frameworks -= [ :action_web_service, :action_mailer ]
18
+
19
+ # Only load the plugins named here, by default all plugins in vendor/plugins are loaded
20
+ # config.plugins = %W( exception_notification ssl_requirement )
21
+
22
+ # Add additional load paths for your own custom dirs
23
+ # config.load_paths += %W( #{RAILS_ROOT}/extras )
24
+
25
+ # Force all environments to use the same logger level
26
+ # (by default production uses :info, the others :debug)
27
+ # config.log_level = :debug
28
+
29
+ # Use the database for sessions instead of the file system
30
+ # (create the session table with 'rake db:sessions:create')
31
+ # config.action_controller.session_store = :active_record_store
32
+
33
+ # Use SQL instead of Active Record's schema dumper when creating the test database.
34
+ # This is necessary if your schema can't be completely dumped by the schema dumper,
35
+ # like if you have constraints or database-specific column types
36
+ # config.active_record.schema_format = :sql
37
+
38
+ # Activate observers that should always be running
39
+ # config.active_record.observers = :cacher, :garbage_collector
40
+
41
+ # Make Active Record use UTC-base instead of local time
42
+ # config.active_record.default_timezone = :utc
43
+
44
+ # See Rails::Configuration for more options
45
+ end
46
+
47
+ # Add new inflection rules using the following format
48
+ # (all these examples are active by default):
49
+ # Inflector.inflections do |inflect|
50
+ # inflect.plural /^(ox)$/i, '\1en'
51
+ # inflect.singular /^(ox)en/i, '\1'
52
+ # inflect.irregular 'person', 'people'
53
+ # inflect.uncountable %w( fish sheep )
54
+ # end
55
+
56
+ # Include your application configuration below
@@ -0,0 +1,21 @@
1
+ # Settings specified here will take precedence over those in config/environment.rb
2
+
3
+ # In the development environment your application's code is reloaded on
4
+ # every request. This slows down response time but is perfect for development
5
+ # since you don't have to restart the webserver when you make code changes.
6
+ config.cache_classes = false
7
+
8
+ # Log error messages when you accidentally call methods on nil.
9
+ config.whiny_nils = true
10
+
11
+ # Enable the breakpoint server that script/breakpointer connects to
12
+ config.breakpoint_server = true
13
+
14
+ # Show full error reports and disable caching
15
+ config.action_controller.consider_all_requests_local = true
16
+ config.action_controller.perform_caching = false
17
+ config.action_view.cache_template_extensions = false
18
+ config.action_view.debug_rjs = true
19
+
20
+ # Don't care if the mailer can't send
21
+ config.action_mailer.raise_delivery_errors = false
@@ -0,0 +1,18 @@
1
+ # Settings specified here will take precedence over those in config/environment.rb
2
+
3
+ # The production environment is meant for finished, "live" apps.
4
+ # Code is not reloaded between requests
5
+ config.cache_classes = true
6
+
7
+ # Use a different logger for distributed setups
8
+ # config.logger = SyslogLogger.new
9
+
10
+ # Full error reports are disabled and caching is turned on
11
+ config.action_controller.consider_all_requests_local = false
12
+ config.action_controller.perform_caching = true
13
+
14
+ # Enable serving of images, stylesheets, and javascripts from an asset server
15
+ # config.action_controller.asset_host = "http://assets.example.com"
16
+
17
+ # Disable delivery errors, bad email addresses will be ignored
18
+ # config.action_mailer.raise_delivery_errors = false
@@ -0,0 +1,19 @@
1
+ # Settings specified here will take precedence over those in config/environment.rb
2
+
3
+ # The test environment is used exclusively to run your application's
4
+ # test suite. You never need to work with it otherwise. Remember that
5
+ # your test database is "scratch space" for the test suite and is wiped
6
+ # and recreated between test runs. Don't rely on the data there!
7
+ config.cache_classes = true
8
+
9
+ # Log error messages when you accidentally call methods on nil.
10
+ config.whiny_nils = true
11
+
12
+ # Show full error reports and disable caching
13
+ config.action_controller.consider_all_requests_local = true
14
+ config.action_controller.perform_caching = false
15
+
16
+ # Tell ActionMailer not to deliver emails to the real world.
17
+ # The :test delivery method accumulates sent emails in the
18
+ # ActionMailer::Base.deliveries array.
19
+ config.action_mailer.delivery_method = :test
@@ -0,0 +1,23 @@
1
+ ActionController::Routing::Routes.draw do |map|
2
+ # The priority is based upon order of creation: first created -> highest priority.
3
+
4
+ # Sample of regular route:
5
+ # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
6
+ # Keep in mind you can assign values other than :controller and :action
7
+
8
+ # Sample of named route:
9
+ # map.purchase 'products/:id/purchase', :controller => 'catalog', :action => 'purchase'
10
+ # This route can be invoked with purchase_url(:id => product.id)
11
+
12
+ # You can have the root of your site routed by hooking up ''
13
+ # -- just remember to delete public/index.html.
14
+ # map.connect '', :controller => "welcome"
15
+
16
+ # Allow downloading Web Service WSDL as a file with an extension
17
+ # instead of a file named 'wsdl'
18
+ map.connect ':controller/service.wsdl', :action => 'wsdl'
19
+
20
+ # Install the default route as the lowest priority.
21
+ map.connect ':controller/:action/:id.:format'
22
+ map.connect ':controller/:action/:id'
23
+ end
@@ -0,0 +1,10 @@
1
+ #!c:/ruby/bin/ruby
2
+
3
+ require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT)
4
+
5
+ # If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like:
6
+ # "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired
7
+ require "dispatcher"
8
+
9
+ ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun)
10
+ Dispatcher.dispatch
@@ -0,0 +1,18 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+ require 'account_controller'
3
+
4
+ # Re-raise errors caught by the controller.
5
+ class AccountController; def rescue_action(e) raise e end; end
6
+
7
+ class AccountControllerTest < Test::Unit::TestCase
8
+ def setup
9
+ @controller = AccountController.new
10
+ @request = ActionController::TestRequest.new
11
+ @response = ActionController::TestResponse.new
12
+ end
13
+
14
+ # Replace this with your real tests.
15
+ def test_truth
16
+ assert true
17
+ end
18
+ end
@@ -0,0 +1,28 @@
1
+ ENV["RAILS_ENV"] = "test"
2
+ require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
3
+ require 'test_help'
4
+
5
+ class Test::Unit::TestCase
6
+ # Transactional fixtures accelerate your tests by wrapping each test method
7
+ # in a transaction that's rolled back on completion. This ensures that the
8
+ # test database remains unchanged so your fixtures don't have to be reloaded
9
+ # between every test method. Fewer database queries means faster tests.
10
+ #
11
+ # Read Mike Clark's excellent walkthrough at
12
+ # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting
13
+ #
14
+ # Every Active Record database supports transactions except MyISAM tables
15
+ # in MySQL. Turn off transactional fixtures in this case; however, if you
16
+ # don't care one way or the other, switching from MyISAM to InnoDB tables
17
+ # is recommended.
18
+ self.use_transactional_fixtures = true
19
+
20
+ # Instantiated fixtures are slow, but give you @david where otherwise you
21
+ # would need people(:david). If you don't want to migrate your existing
22
+ # test cases which use the @david style and don't mind the speed hit (each
23
+ # instantiated fixtures translates to a database query per test method),
24
+ # then set this back to true.
25
+ self.use_instantiated_fixtures = false
26
+
27
+ # Add more helper methods to be used by all tests here...
28
+ end
@@ -0,0 +1 @@
1
+ require 'saml_2_ruby'
@@ -0,0 +1,5 @@
1
+
2
+ $:.unshift(File.dirname(__FILE__))
3
+
4
+ require "saml_2_ruby/xml_sec"
5
+ require 'saml_2_ruby/relying_party'
@@ -0,0 +1,170 @@
1
+ # The contents of this file are subject to the terms
2
+ # of the Common Development and Distribution License
3
+ # (the License). You may not use this file except in
4
+ # compliance with the License.
5
+ #
6
+ # You can obtain a copy of the License at
7
+ # https://opensso.dev.java.net/public/CDDLv1.0.html or
8
+ # opensso/legal/CDDLv1.0.txt
9
+ # See the License for the specific language governing
10
+ # permission and limitations under the License.
11
+ #
12
+ # When distributing Covered Code, include this CDDL
13
+ # Header Notice in each file and include the License file
14
+ # at opensso/legal/CDDLv1.0.txt.
15
+ # If applicable, add the following below the CDDL Header,
16
+ # with the fields enclosed by brackets [] replaced by
17
+ # your own identifying information:
18
+ # "Portions Copyrighted [year] [name of copyright owner]"
19
+ #
20
+ # $Id: relying_party.rb,v 1.1 2007/03/19 22:45:54 todddd Exp $
21
+ #
22
+ # Copyright 2007 Sun Microsystems Inc. All Rights Reserved
23
+ # Portions Copyrighted 2007 Todd W Saxton.
24
+
25
+
26
+ require "base64"
27
+ require "rexml/document"
28
+
29
+ module SAML
30
+
31
+ class RelyingParty
32
+
33
+ cattr_accessor :logger
34
+ attr_accessor :name_id
35
+
36
+ def initialize(metadata)
37
+ @assertion_consumer_service_URL = metadata[:assertion_consumer_service_URL]
38
+ @issuer = metadata[:issuer]
39
+ @sp_name_qualifier = metadata[:sp_name_qualifier]
40
+ @idp_sso_target_url = metadata[:idp_sso_target_url]
41
+ @idp_slo_target_url = metadata[:idp_slo_target_url]
42
+ @idp_cert_fingerprint = metadata[:idp_cert_fingerprint]
43
+ end
44
+
45
+ def create_auth_request
46
+
47
+ id = generateUniqueHexCode(42)
48
+ issue_instant = Time.new().strftime("%Y-%m-%dT%H:%M:%SZ")
49
+
50
+ auth_request = "<samlp:AuthnRequest " +
51
+ "xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"\n" +
52
+ "ID=\"" + id + "\" " +
53
+ "Version=\"2.0\" " +
54
+ "IssueInstant=\"" + issue_instant + "\" " +
55
+ "ForceAuthn=\"false\" " +
56
+ "isPassive=\"false\" " +
57
+ "ProtocolBinding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" " +
58
+ "AssertionConsumerServiceURL=\"" + @assertion_consumer_service_URL + "\">\n" +
59
+ "<saml:Issuer " +
60
+ "xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">" +
61
+ @issuer +
62
+ "</saml:Issuer>\n" +
63
+ "<samlp:NameIDPolicy " +
64
+ "xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" " +
65
+ "Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:transient\" " +
66
+ "SPNameQualifier=\"" + @sp_name_qualifier + "\" " +
67
+ "AllowCreate=\"true\">\n" +
68
+ "</samlp:NameIDPolicy>\n" +
69
+ "<samlp:RequestedAuthnContext " +
70
+ "xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" " +
71
+ "Comparison=\"exact\">" +
72
+ "<saml:AuthnContextClassRef " +
73
+ "xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">" +
74
+ "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport" +
75
+ "</saml:AuthnContextClassRef>" +
76
+ "</samlp:RequestedAuthnContext>\n" +
77
+ "</samlp:AuthnRequest>"
78
+
79
+ logger.info(auth_request) if !logger.nil?
80
+
81
+ deflated_auth_request = Zlib::Deflate.deflate(auth_request, 9)[2..-5]
82
+ base64_auth_request = Base64.encode64(deflated_auth_request)
83
+ encoded_auth_request = CGI.escape(base64_auth_request)
84
+
85
+
86
+ redirect_url = @idp_sso_target_url + "?SAMLRequest=" + encoded_auth_request
87
+
88
+ return redirect_url
89
+
90
+ end
91
+
92
+ def process_auth_response(raw_response)
93
+
94
+ logger.info("Raw Response: " + raw_response ) if !logger.nil?
95
+
96
+ # raw_response is all ready URL decoded...
97
+ @saml_response = Base64.decode64( raw_response )
98
+ logger.info("Authn response = " + @saml_response) if !logger.nil?
99
+
100
+ saml_response_doc = XMLSecurity::SignedDocument.new @saml_response
101
+
102
+ if valid_flag = saml_response_doc.validate(@idp_cert_fingerprint, logger)
103
+ self.name_id = saml_response_doc.elements["/samlp:Response/saml:Assertion/saml:Subject/saml:NameID"].text
104
+ else
105
+ # error
106
+ logger.error("Invalid SAML response")
107
+ end
108
+
109
+ return valid_flag
110
+ end
111
+
112
+
113
+
114
+ def create_logout_request
115
+
116
+ saml_response_doc = REXML::Document.new @saml_response
117
+ id = generateUniqueHexCode(42)
118
+ issue_instant = Time.new().strftime("%Y-%m-%dT%H:%M:%SZ")
119
+ name_id = saml_response_doc.elements["/samlp:Response/saml:Assertion/saml:Subject/saml:NameID"].text
120
+ name_qualifier = saml_response_doc.elements["/samlp:Response/saml:Assertion/saml:Subject/saml:NameID"].attributes["NameQualifier"]
121
+ session_index = saml_response_doc.elements["/samlp:Response/saml:Assertion/saml:AuthnStatement"].attributes["SessionIndex"]
122
+
123
+ logout_request = "<samlp:LogoutRequest " +
124
+ "xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" " +
125
+ "ID=\"" + id + "\" " +
126
+ "Version=\"2.0\" " +
127
+ "IssueInstant=\"" + issue_instant + "\"> " +
128
+ "<saml:Issuer " +
129
+ "xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">" +
130
+ @issuer +
131
+ "</saml:Issuer>" +
132
+ "<saml:NameID " +
133
+ "xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" " +
134
+ "NameQualifier=\"" + name_qualifier + "\" " +
135
+ "SPNameQualifier=\"" + @sp_name_qualifier + "\" " +
136
+ "Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:transient\">" +
137
+ name_id +
138
+ "</saml:NameID>" +
139
+ "<samlp:SessionIndex " +
140
+ "xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\">" +
141
+ session_index +
142
+ "</samlp:SessionIndex>" +
143
+ "</samlp:LogoutRequest>";
144
+
145
+ logger.info("Logout request = " + logout_request) if !logger.nil?
146
+
147
+ deflated_logout_request = Zlib::Deflate.deflate(logout_request, 9)[2..-5]
148
+ base64_logout_request = Base64.encode64(deflated_logout_request)
149
+ encoded_logout_request = CGI.escape(base64_logout_request)
150
+
151
+ redirect_url = @idp_slo_target_url + "?SAMLRequest=" + encoded_logout_request
152
+
153
+ return redirect_url
154
+
155
+ end
156
+
157
+ private
158
+
159
+ def generateUniqueHexCode( codeLength )
160
+ validChars = ("A".."F").to_a + ("0".."9").to_a
161
+ length = validChars.size
162
+
163
+ hexCode = ""
164
+ 1.upto(codeLength) { |i| hexCode << validChars[rand(length-1)] }
165
+
166
+ hexCode
167
+ end
168
+ end
169
+
170
+ end
@@ -0,0 +1,9 @@
1
+ module SAML#:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 1
4
+ MINOR = 1
5
+ TINY = 0
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -0,0 +1,111 @@
1
+ # The contents of this file are subject to the terms
2
+ # of the Common Development and Distribution License
3
+ # (the License). You may not use this file except in
4
+ # compliance with the License.
5
+ #
6
+ # You can obtain a copy of the License at
7
+ # https://opensso.dev.java.net/public/CDDLv1.0.html or
8
+ # opensso/legal/CDDLv1.0.txt
9
+ # See the License for the specific language governing
10
+ # permission and limitations under the License.
11
+ #
12
+ # When distributing Covered Code, include this CDDL
13
+ # Header Notice in each file and include the License file
14
+ # at opensso/legal/CDDLv1.0.txt.
15
+ # If applicable, add the following below the CDDL Header,
16
+ # with the fields enclosed by brackets [] replaced by
17
+ # your own identifying information:
18
+ # "Portions Copyrighted [year] [name of copyright owner]"
19
+ #
20
+ # $Id: xml_sec.rb,v 1.6 2007/10/24 00:28:41 todddd Exp $
21
+ #
22
+ # Copyright 2007 Sun Microsystems Inc. All Rights Reserved
23
+ # Portions Copyrighted 2007 Todd W Saxton.
24
+
25
+ require "rexml/document"
26
+ require "rexml/xpath"
27
+ require "openssl"
28
+ require "xmlcanonicalizer"
29
+ require "digest/sha1"
30
+
31
+ #
32
+ # WARNING, WARNING: VERY rudimentary
33
+ #
34
+ #
35
+ module XMLSecurity
36
+
37
+ class SignedDocument < REXML::Document
38
+
39
+ def validate (idp_cert_fingerprint, logger)
40
+
41
+ # get cert from response
42
+ base64_cert = self.elements["//X509Certificate"].text
43
+ cert_text = Base64.decode64(base64_cert)
44
+ cert = OpenSSL::X509::Certificate.new(cert_text)
45
+
46
+ # check cert matches registered idp cert
47
+ fingerprint = Digest::SHA1.hexdigest(cert.to_der)
48
+ logger.info("fingerprint = " + fingerprint) if !logger.nil?
49
+ valid_flag = fingerprint == idp_cert_fingerprint.gsub(":", "").downcase
50
+
51
+ return valid_flag if !valid_flag
52
+
53
+ validate_doc(base64_cert, logger)
54
+ end
55
+
56
+ def validate_doc(base64_cert, logger)
57
+
58
+ #
59
+ #validate references
60
+ #
61
+
62
+ # remove signature node
63
+ sig_element = XPath.first(self, "//ds:Signature", {"ds"=>"http://www.w3.org/2000/09/xmldsig#"})
64
+ sig_element.remove
65
+
66
+ #check digests
67
+ logger.info("checking digests") if !logger.nil?
68
+ XPath.each(sig_element, "//ds:Reference", {"ds"=>"http://www.w3.org/2000/09/xmldsig#"}) do | ref |
69
+ uri = ref.attributes.get_attribute("URI").value
70
+ logger.info("URI = " + uri[1,uri.size]) if !logger.nil?
71
+ hashed_element = XPath.first(self, "//[@ID='#{uri[1,uri.size]}']")
72
+ logger.info("hashed element = " + hashed_element.to_s) if !logger.nil?
73
+ canoner = XML::Util::XmlCanonicalizer.new(false, true)
74
+ canon_hashed_element = canoner.canonicalize_element(hashed_element)
75
+ logger.info("canon hashed element = " + canon_hashed_element) if !logger.nil?
76
+ hash = Base64.encode64(Digest::SHA1.digest(canon_hashed_element)).chomp
77
+ digest_value = XPath.first(ref, "//ds:DigestValue", {"ds"=>"http://www.w3.org/2000/09/xmldsig#"}).text
78
+ logger.info("hashed_element_hash = " + hash) if !logger.nil?
79
+ logger.info("digest_value_element = " + digest_value) if !logger.nil?
80
+
81
+ valid_flag = hash == digest_value
82
+ return valid_flag if !valid_flag
83
+ end
84
+
85
+ #
86
+ # verify dig sig
87
+ #
88
+ logger.info("checking dig sig") if !logger.nil?
89
+
90
+ # signed_info_element.add_namespace("http://www.w3.org/2000/09/xmldsig#") if signed_info_element.namespace.nil? || signed_info_element.namespace.empty?
91
+ canoner = XML::Util::XmlCanonicalizer.new(false, true)
92
+ signed_info_element = XPath.first(sig_element, "//ds:SignedInfo", {"ds"=>"http://www.w3.org/2000/09/xmldsig#"})
93
+ canon_string = canoner.canonicalize_element(signed_info_element)
94
+ logger.info("canon INFO = " + canon_string) if !logger.nil?
95
+
96
+ base64_signature = XPath.first(sig_element, "//ds:SignatureValue", {"ds"=>"http://www.w3.org/2000/09/xmldsig#"}).text
97
+ logger.info("base 64 SIG = " + base64_signature) if !logger.nil?
98
+ signature = Base64.decode64(base64_signature)
99
+ logger.info("SIG = " + signature) if !logger.nil?
100
+
101
+ # get cert object
102
+ cert_text = Base64.decode64(base64_cert)
103
+ cert = OpenSSL::X509::Certificate.new(cert_text)
104
+
105
+ valid_flag = cert.public_key.verify(OpenSSL::Digest::SHA1.new, signature, canon_string)
106
+
107
+ return valid_flag
108
+ end
109
+
110
+ end
111
+ end
@@ -0,0 +1,62 @@
1
+ $LOAD_PATH.insert(0, File.expand_path(File.dirname(__FILE__)) + "/../lib")
2
+ load "xml_sec.rb"
3
+ require "logger"
4
+ require "test/unit"
5
+
6
+ class XMLSecurityTest < Test::Unit::TestCase
7
+
8
+
9
+ def setup
10
+ @log = Logger.new("test_output.log")
11
+ @log.level = Logger::DEBUG
12
+ @log.info "starting"
13
+ end
14
+
15
+ def test_open_fed_response
16
+ puts "Processing OpenFederation file"
17
+ @log.info "Processing OpenFederation file"
18
+ File.open("test/authNResponseOpenFed.xml", aModeString="r") {|file|
19
+
20
+ saml_response_doc = XMLSecurity::SignedDocument.new(file)
21
+
22
+ base64_cert = saml_response_doc.elements["//X509Certificate"].text
23
+ assert saml_response_doc.validate_doc(base64_cert, @log)
24
+ }
25
+ end
26
+
27
+ def test_rsa_fim_response
28
+ puts "Processing RSA file"
29
+ @log.info "Processing RSA file"
30
+ File.open("test/authNResponseRSAFIM.xml", aModeString="r") {|file|
31
+
32
+ saml_response_doc = XMLSecurity::SignedDocument.new(file)
33
+
34
+ base64_cert = "MIICODCCAaECBEatbN8wDQYJKoZIhvcNAQEEBQAwYzELMAkGA1UEBhMCTloxEDAOBgNVBAgTB1Vua25vd24xEzARBgNVBAcTCldlbGxpbmd0b24xDDAKBgNVBAoTA1NTQzEMMAoGA1UECxMDU1NDMREwDwYDVQQDEwhHTFMgU0FNTDAeFw0wNzA3MzAwNDQ1MTlaFw0xMjA3MjgwNDQ1MTlaMGMxCzAJBgNVBAYTAk5aMRAwDgYDVQQIEwdVbmtub3duMRMwEQYDVQQHEwpXZWxsaW5ndG9uMQwwCgYDVQQKEwNTU0MxDDAKBgNVBAsTA1NTQzERMA8GA1UEAxMIR0xTIFNBTUwwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOy62p6wMNRRVCSs7/4fnPrHqFehVBaYeg9yayD1I/yqYXvmOgbYX/OnuIE+RylVDwkzmKHgNsLpw8jfw1Ee2f0qmZY1rs6x+jmE4yDLDR1eKNGCkB4hSep2cVknWCBBzvmrk1nKet8Aw460FU2+C5H67Iwj5sVqshi5noLXSckTAgMBAAEwDQYJKoZIhvcNAQEEBQADgYEAL2ebLISFR6F0RzeEpLOjv4kSfDhsELSzEi4vVqlmCm+YcRWH0ASik3Ynl1B/K05cosqD5RMJG71t6ZWNf/s5F4NX0blU0ZAWewQXIUS4CUZPcQT3K/WXbpWBjRDIY0Cj6Dim/yBdmYSxZV51sDAIfOq4FXb5bEPSCopKK1YQRLE="
35
+ assert saml_response_doc.validate_doc(base64_cert, @log)
36
+ }
37
+ end
38
+
39
+ def test_rsa_fim_response_after_enc
40
+ puts "Processing RSA file after encryption"
41
+ @log.info "Processing RSA file after encryption"
42
+ File.open("test/authNResponseRSAFIMAfterEncryption.xml", aModeString="r") {|file|
43
+
44
+ saml_response_doc = XMLSecurity::SignedDocument.new(file)
45
+
46
+ base64_cert = "MIICODCCAaECBEatbN8wDQYJKoZIhvcNAQEEBQAwYzELMAkGA1UEBhMCTloxEDAOBgNVBAgTB1Vua25vd24xEzARBgNVBAcTCldlbGxpbmd0b24xDDAKBgNVBAoTA1NTQzEMMAoGA1UECxMDU1NDMREwDwYDVQQDEwhHTFMgU0FNTDAeFw0wNzA3MzAwNDQ1MTlaFw0xMjA3MjgwNDQ1MTlaMGMxCzAJBgNVBAYTAk5aMRAwDgYDVQQIEwdVbmtub3duMRMwEQYDVQQHEwpXZWxsaW5ndG9uMQwwCgYDVQQKEwNTU0MxDDAKBgNVBAsTA1NTQzERMA8GA1UEAxMIR0xTIFNBTUwwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOy62p6wMNRRVCSs7/4fnPrHqFehVBaYeg9yayD1I/yqYXvmOgbYX/OnuIE+RylVDwkzmKHgNsLpw8jfw1Ee2f0qmZY1rs6x+jmE4yDLDR1eKNGCkB4hSep2cVknWCBBzvmrk1nKet8Aw460FU2+C5H67Iwj5sVqshi5noLXSckTAgMBAAEwDQYJKoZIhvcNAQEEBQADgYEAL2ebLISFR6F0RzeEpLOjv4kSfDhsELSzEi4vVqlmCm+YcRWH0ASik3Ynl1B/K05cosqD5RMJG71t6ZWNf/s5F4NX0blU0ZAWewQXIUS4CUZPcQT3K/WXbpWBjRDIY0Cj6Dim/yBdmYSxZV51sDAIfOq4FXb5bEPSCopKK1YQRLE="
47
+ assert saml_response_doc.validate_doc(base64_cert, @log)
48
+ }
49
+ end
50
+
51
+ def test_ak_assertion
52
+ puts "Processing AK file"
53
+ @log.info "Processing AK file"
54
+ File.open("test/assertion_from_AK.xml", aModeString="r") {|file|
55
+
56
+ saml_response_doc = XMLSecurity::SignedDocument.new(file)
57
+
58
+ base64_cert = saml_response_doc.elements["//X509Certificate"].text
59
+ assert saml_response_doc.validate_doc(base64_cert, @log)
60
+ }
61
+ end
62
+ end
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: saml2ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sean Cashin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-02-16 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: XMLCanonicalizer
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.0.1
24
+ version:
25
+ description: Part of the OpenSSO extension set. Writting by the wonderful guys over at Sun. Moved it over to a github repo and turned it into a rubygem.
26
+ email:
27
+ - scashin133@gmail.com
28
+ executables: []
29
+
30
+ extensions: []
31
+
32
+ extra_rdoc_files:
33
+ - README
34
+ - TODO
35
+ files:
36
+ - README
37
+ - Rakefile
38
+ - lib/saml2ruby.rb
39
+ - lib/saml_2_ruby.rb
40
+ - lib/saml_2_ruby/relying_party.rb
41
+ - lib/saml_2_ruby/version.rb
42
+ - lib/saml_2_ruby/xml_sec.rb
43
+ - TODO
44
+ has_rdoc: true
45
+ homepage: http://github.com/scashin133/saml2ruby
46
+ licenses: []
47
+
48
+ post_install_message:
49
+ rdoc_options:
50
+ - --charset=UTF-8
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ requirements: []
66
+
67
+ rubyforge_project:
68
+ rubygems_version: 1.3.5
69
+ signing_key:
70
+ specification_version: 3
71
+ summary: Ruby implementation of the SAML 2.0 Specification
72
+ test_files:
73
+ - test/xml_sec_test.rb
74
+ - examples/rails/SimpleSAMLRP/app/controllers/account_controller.rb
75
+ - examples/rails/SimpleSAMLRP/app/controllers/application.rb
76
+ - examples/rails/SimpleSAMLRP/app/helpers/account_helper.rb
77
+ - examples/rails/SimpleSAMLRP/app/helpers/application_helper.rb
78
+ - examples/rails/SimpleSAMLRP/config/boot.rb
79
+ - examples/rails/SimpleSAMLRP/config/environment.rb
80
+ - examples/rails/SimpleSAMLRP/config/environments/development.rb
81
+ - examples/rails/SimpleSAMLRP/config/environments/production.rb
82
+ - examples/rails/SimpleSAMLRP/config/environments/test.rb
83
+ - examples/rails/SimpleSAMLRP/config/routes.rb
84
+ - examples/rails/SimpleSAMLRP/public/dispatch.rb
85
+ - examples/rails/SimpleSAMLRP/test/functional/account_controller_test.rb
86
+ - examples/rails/SimpleSAMLRP/test/test_helper.rb