saml2ruby 1.1.0

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