oa-enterprise 0.2.6 → 0.3.0.rc3

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/Gemfile CHANGED
@@ -1,5 +1,9 @@
1
+ require File.expand_path('../lib/omniauth/version', __FILE__)
2
+
1
3
  source 'http://rubygems.org'
2
4
 
5
+ gem 'oa-core', OmniAuth::Version::STRING, :path => '../oa-core'
6
+
3
7
  platforms :jruby do
4
8
  gem 'jruby-openssl', '~> 0.7'
5
9
  end
@@ -66,7 +66,40 @@ are not familiar with these authentication methods, please just avoid them.
66
66
 
67
67
  Direct users to '/auth/ldap' to have them authenticated via your
68
68
  company's LDAP server.
69
-
69
+
70
+ == SAML
71
+
72
+ Use the SAML strategy as a middleware in your application:
73
+
74
+ require 'omniauth/enterprise'
75
+ use OmniAuth::Strategies::SAML,
76
+ :assertion_consumer_service_url => "consumer_service_url",
77
+ :issuer => "issuer",
78
+ :idp_sso_target_url => "idp_sso_target_url",
79
+ :idp_cert_fingerprint => "E7:91:B2:E1:...",
80
+ :name_identifier_format => "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
81
+
82
+ :assertion_consumer_service_url
83
+ The URL at which the SAML assertion should be received.
84
+
85
+ :issuer
86
+ The name of your application. Some identity providers might need this to establish the
87
+ identity of the service provider requesting the login.
88
+
89
+ :idp_sso_target_url
90
+ The URL to which the authentication request should be sent. This would be on the identity provider.
91
+
92
+ :idp_cert_fingerprint
93
+ The certificate fingerprint, e.g. "90:CC:16:F0:8D:A6:D1:C6:BB:27:2D:BA:93:80:1A:1F:16:8E:4E:08".
94
+ This is provided from the identity provider when setting up the relationship.
95
+
96
+ :name_identifier_format
97
+ Describes the format of the username required by this application.
98
+ If you need the email address, use "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress".
99
+ See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf section 8.3 for
100
+ other options. Note that the identity provider might not support all options.
101
+
102
+
70
103
  == Multiple Strategies
71
104
 
72
105
  If you're using multiple strategies together, use OmniAuth's Builder. That's
@@ -4,5 +4,6 @@ module OmniAuth
4
4
  module Strategies
5
5
  autoload :CAS, 'omniauth/strategies/cas'
6
6
  autoload :LDAP, 'omniauth/strategies/ldap'
7
+ autoload :SAML, 'omniauth/strategies/saml'
7
8
  end
8
9
  end
@@ -9,19 +9,21 @@ module OmniAuth
9
9
  include OmniAuth::Strategy
10
10
 
11
11
  autoload :Adaptor, 'omniauth/strategies/ldap/adaptor'
12
- @@config = {'name' => 'cn',
13
- 'first_name' => 'givenName',
14
- 'last_name' => 'sn',
15
- 'email' => ['mail', "email", 'userPrincipalName'],
16
- 'phone' => ['telephoneNumber', 'homePhone', 'facsimileTelephoneNumber'],
17
- 'mobile_number' => ['mobile', 'mobileTelephoneNumber'],
18
- 'nickname' => ['uid', 'userid', 'sAMAccountName'],
19
- 'title' => 'title',
20
- 'location' => {"%0, %1, %2, %3 %4" => [['address', 'postalAddress', 'homePostalAddress', 'street', 'streetAddress'], ['l'], ['st'],['co'],['postOfficeBox']]},
21
- 'uid' => 'dn',
22
- 'url' => ['wwwhomepage'],
23
- 'image' => 'jpegPhoto',
24
- 'description' => 'description'}
12
+ @@config = {
13
+ 'name' => 'cn',
14
+ 'first_name' => 'givenName',
15
+ 'last_name' => 'sn',
16
+ 'email' => ['mail', "email", 'userPrincipalName'],
17
+ 'phone' => ['telephoneNumber', 'homePhone', 'facsimileTelephoneNumber'],
18
+ 'mobile_number' => ['mobile', 'mobileTelephoneNumber'],
19
+ 'nickname' => ['uid', 'userid', 'sAMAccountName'],
20
+ 'title' => 'title',
21
+ 'location' => {"%0, %1, %2, %3 %4" => [['address', 'postalAddress', 'homePostalAddress', 'street', 'streetAddress'], ['l'], ['st'],['co'],['postOfficeBox']]},
22
+ 'uid' => 'dn',
23
+ 'url' => ['wwwhomepage'],
24
+ 'image' => 'jpegPhoto',
25
+ 'description' => 'description'
26
+ }
25
27
 
26
28
  # Initialize the LDAP Middleware
27
29
  #
@@ -44,7 +46,7 @@ module OmniAuth
44
46
  end
45
47
  end
46
48
 
47
- def get_credentials
49
+ def get_credentials
48
50
  OmniAuth::Form.build(:title => (options[:title] || "LDAP Authentication")) do
49
51
  text_field 'Login', 'username'
50
52
  password_field 'Password', 'password'
@@ -52,28 +54,28 @@ module OmniAuth
52
54
  end
53
55
 
54
56
  def callback_phase
55
- begin
57
+ begin
56
58
  creds = session['omniauth.ldap']
57
59
  session.delete 'omniauth.ldap'
58
- @ldap_user_info = {}
60
+ @ldap_user_info = {}
59
61
  begin
60
- (@adaptor.bind(:allow_anonymous => true) unless @adaptor.bound?)
62
+ (@adaptor.bind(:allow_anonymous => true) unless @adaptor.bound?)
61
63
  rescue Exception => e
62
- puts "failed to bind with the default credentials: " + e.message
63
- end
64
+ puts "failed to bind with the default credentials: " + e.message
65
+ end
64
66
  @ldap_user_info = @adaptor.search(:filter => Net::LDAP::Filter.eq(@adaptor.uid, @name_proc.call(creds['username'])),:limit => 1) if @adaptor.bound?
65
- bind_dn = creds['username']
66
- bind_dn = @ldap_user_info[:dn].to_a.first if @ldap_user_info[:dn]
67
+ bind_dn = creds['username']
68
+ bind_dn = @ldap_user_info[:dn].to_a.first if @ldap_user_info[:dn]
67
69
  @adaptor.bind(:bind_dn => bind_dn, :password => creds['password'])
68
70
  @ldap_user_info = @adaptor.search(:filter => Net::LDAP::Filter.eq(@adaptor.uid, @name_proc.call(creds['username'])),:limit => 1) if @ldap_user_info.empty?
69
- @user_info = self.class.map_user(@@config, @ldap_user_info)
71
+ @user_info = self.class.map_user(@@config, @ldap_user_info)
70
72
 
71
73
  @env['omniauth.auth'] = auth_hash
72
74
 
73
- rescue Exception => e
74
- return fail!(:invalid_credentials, e)
75
- end
76
- call_app!
75
+ rescue Exception => e
76
+ return fail!(:invalid_credentials, e)
77
+ end
78
+ call_app!
77
79
  end
78
80
 
79
81
  def auth_hash
@@ -84,28 +86,28 @@ module OmniAuth
84
86
  })
85
87
  end
86
88
 
87
- def self.map_user(mapper, object)
88
- user = {}
89
- mapper.each do |key, value|
90
- case value
91
- when String
92
- user[key] = object[value.downcase.to_sym].to_s if object[value.downcase.to_sym]
93
- when Array
94
- value.each {|v| (user[key] = object[v.downcase.to_sym].to_s; break;) if object[v.downcase.to_sym]}
95
- when Hash
96
- value.map do |key1, value1|
97
- pattern = key1.dup
98
- value1.each_with_index do |v,i|
99
- part = '';
100
- v.each {|v1| (part = object[v1.downcase.to_sym].to_s; break;) if object[v1.downcase.to_sym]}
101
- pattern.gsub!("%#{i}",part||'')
102
- end
103
- user[key] = pattern
104
- end
105
- end
106
- end
107
- user
108
- end
89
+ def self.map_user(mapper, object)
90
+ user = {}
91
+ mapper.each do |key, value|
92
+ case value
93
+ when String
94
+ user[key] = object[value.downcase.to_sym].to_s if object[value.downcase.to_sym]
95
+ when Array
96
+ value.each {|v| (user[key] = object[v.downcase.to_sym].to_s; break;) if object[v.downcase.to_sym]}
97
+ when Hash
98
+ value.map do |key1, value1|
99
+ pattern = key1.dup
100
+ value1.each_with_index do |v,i|
101
+ part = '';
102
+ v.each {|v1| (part = object[v1.downcase.to_sym].to_s; break;) if object[v1.downcase.to_sym]}
103
+ pattern.gsub!("%#{i}",part||'')
104
+ end
105
+ user[key] = pattern
106
+ end
107
+ end
108
+ end
109
+ user
110
+ end
109
111
  end
110
112
  end
111
113
  end
@@ -14,14 +14,13 @@ module OmniAuth
14
14
  class AuthenticationError < StandardError; end
15
15
  class ConnectionError < StandardError; end
16
16
 
17
- VALID_ADAPTER_CONFIGURATION_KEYS = [:host, :port, :method, :bind_dn, :password,
18
- :try_sasl, :sasl_mechanisms, :uid, :base, :allow_anonymous]
17
+ VALID_ADAPTER_CONFIGURATION_KEYS = [:host, :port, :method, :bind_dn, :password, :try_sasl, :sasl_mechanisms, :uid, :base, :allow_anonymous]
19
18
 
20
19
  MUST_HAVE_KEYS = [:host, :port, :method, :uid, :base]
21
20
 
22
21
  METHOD = {
23
- :ssl => :simple_tls,
24
- :tls => :start_tls,
22
+ :ssl => :simple_tls,
23
+ :tls => :start_tls,
25
24
  :plain => nil,
26
25
  }
27
26
 
@@ -63,7 +62,6 @@ module OmniAuth
63
62
  @connection, @uri, @with_start_tls = begin
64
63
  uri = construct_uri(host, port, method == :simple_tls)
65
64
  with_start_tls = method == :start_tls
66
- puts ({:uri => uri, :with_start_tls => with_start_tls}).inspect
67
65
  [Net::LDAP::Connection.new(config), uri, with_start_tls]
68
66
  rescue Net::LDAP::LdapError
69
67
  raise ConnectionError, $!.message
@@ -91,9 +89,9 @@ module OmniAuth
91
89
  # Attempt 2: SIMPLE with credentials if password block
92
90
  # Attempt 3: SIMPLE ANONYMOUS if 1 and 2 fail and allow anonymous is set to true
93
91
  if try_sasl and sasl_bind(bind_dn, options)
94
- puts "bound with sasl"
92
+ puts "bound with sasl"
95
93
  elsif simple_bind(bind_dn, options)
96
- puts "bound with simple"
94
+ puts "bound with simple"
97
95
  elsif allow_anonymous and bind_as_anonymous(options)
98
96
  puts "bound as anonymous"
99
97
  else
@@ -127,12 +125,12 @@ module OmniAuth
127
125
  end
128
126
 
129
127
  def search(options={}, &block)
130
- base = options[:base]
128
+ base = options[:base] || @base
131
129
  filter = options[:filter]
132
130
  limit = options[:limit]
133
131
 
134
132
  args = {
135
- :base => @base,
133
+ :base => base,
136
134
  :filter => filter,
137
135
  :size => limit
138
136
  }
@@ -226,7 +224,6 @@ module OmniAuth
226
224
  end
227
225
 
228
226
  def sasl_bind_setup_gss_spnego(bind_dn, options)
229
- puts options.inspect
230
227
  user,psw = [bind_dn, options[:password]||@password]
231
228
  raise LdapError.new( "invalid binding information" ) unless (user && psw)
232
229
 
@@ -0,0 +1,50 @@
1
+ require 'omniauth/enterprise'
2
+
3
+ module OmniAuth
4
+ module Strategies
5
+ class SAML
6
+ include OmniAuth::Strategy
7
+ autoload :AuthRequest, 'omniauth/strategies/saml/auth_request'
8
+ autoload :AuthResponse, 'omniauth/strategies/saml/auth_response'
9
+ autoload :ValidationError, 'omniauth/strategies/saml/validation_error'
10
+ autoload :XMLSecurity, 'omniauth/strategies/saml/xml_security'
11
+
12
+ @@settings = {}
13
+
14
+ def initialize(app, options={})
15
+ super(app, :saml)
16
+ @@settings = {
17
+ :assertion_consumer_service_url => options[:assertion_consumer_service_url],
18
+ :issuer => options[:issuer],
19
+ :idp_sso_target_url => options[:idp_sso_target_url],
20
+ :idp_cert_fingerprint => options[:idp_cert_fingerprint],
21
+ :name_identifier_format => options[:name_identifier_format] || "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
22
+ }
23
+ end
24
+
25
+ def request_phase
26
+ request = OmniAuth::Strategies::SAML::AuthRequest.new
27
+ redirect(request.create(@@settings))
28
+ end
29
+
30
+ def callback_phase
31
+ begin
32
+ response = OmniAuth::Strategies::SAML::AuthResponse.new(request.params['SAMLResponse'])
33
+ response.settings = @@settings
34
+ @name_id = response.name_id
35
+ return fail!(:invalid_ticket, 'Invalid SAML Ticket') if @name_id.nil? || @name_id.empty?
36
+ super
37
+ rescue ArgumentError => e
38
+ fail!(:invalid_ticket, 'Invalid SAML Response')
39
+ end
40
+ end
41
+
42
+ def auth_hash
43
+ OmniAuth::Utils.deep_merge(super, {
44
+ 'uid' => @name_id
45
+ })
46
+ end
47
+
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,38 @@
1
+ require "base64"
2
+ require "uuid"
3
+ require "zlib"
4
+ require "cgi"
5
+
6
+ module OmniAuth
7
+ module Strategies
8
+ class SAML
9
+ class AuthRequest
10
+
11
+ def create(settings, params = {})
12
+ uuid = "_" + UUID.new.generate
13
+ time = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
14
+
15
+ request =
16
+ "<samlp:AuthnRequest xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" ID=\"#{uuid}\" Version=\"2.0\" IssueInstant=\"#{time}\" ProtocolBinding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" AssertionConsumerServiceURL=\"#{settings[:assertion_consumer_service_url]}\">" +
17
+ "<saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">#{settings[:issuer]}</saml:Issuer>\n" +
18
+ "<samlp:NameIDPolicy xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" Format=\"#{settings[:name_identifier_format]}\" AllowCreate=\"true\"></samlp:NameIDPolicy>\n" +
19
+ "<samlp:RequestedAuthnContext xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" Comparison=\"exact\">" +
20
+ "<saml:AuthnContextClassRef xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef></samlp:RequestedAuthnContext>\n" +
21
+ "</samlp:AuthnRequest>"
22
+
23
+ deflated_request = Zlib::Deflate.deflate(request, 9)[2..-5]
24
+ base64_request = Base64.encode64(deflated_request)
25
+ encoded_request = CGI.escape(base64_request)
26
+ request_params = "?SAMLRequest=" + encoded_request
27
+
28
+ params.each_pair do |key, value|
29
+ request_params << "&#{key}=#{CGI.escape(value.to_s)}"
30
+ end
31
+
32
+ settings[:idp_sso_target_url] + request_params
33
+ end
34
+
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,141 @@
1
+ require "time"
2
+
3
+ module OmniAuth
4
+ module Strategies
5
+ class SAML
6
+ class AuthResponse
7
+
8
+ ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
9
+ PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
10
+ DSIG = "http://www.w3.org/2000/09/xmldsig#"
11
+
12
+ attr_accessor :options, :response, :document, :settings
13
+
14
+ def initialize(response, options = {})
15
+ raise ArgumentError.new("Response cannot be nil") if response.nil?
16
+ self.options = options
17
+ self.response = response
18
+ self.document = OmniAuth::Strategies::SAML::XMLSecurity::SignedDocument.new(Base64.decode64(response))
19
+ end
20
+
21
+ def is_valid?
22
+ validate(soft = true)
23
+ end
24
+
25
+ def validate!
26
+ validate(soft = false)
27
+ end
28
+
29
+ # The value of the user identifier as designated by the initialization request response
30
+ def name_id
31
+ @name_id ||= begin
32
+ node = REXML::XPath.first(document, "/p:Response/a:Assertion[@ID='#{document.signed_element_id[1,document.signed_element_id.size]}']/a:Subject/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
33
+ node ||= REXML::XPath.first(document, "/p:Response[@ID='#{document.signed_element_id[1,document.signed_element_id.size]}']/a:Assertion/a:Subject/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
34
+ node.nil? ? nil : node.text
35
+ end
36
+ end
37
+
38
+ # A hash of alle the attributes with the response. Assuming there is only one value for each key
39
+ def attributes
40
+ @attr_statements ||= begin
41
+ result = {}
42
+
43
+ stmt_element = REXML::XPath.first(document, "/p:Response/a:Assertion/a:AttributeStatement", { "p" => PROTOCOL, "a" => ASSERTION })
44
+ return {} if stmt_element.nil?
45
+
46
+ stmt_element.elements.each do |attr_element|
47
+ name = attr_element.attributes["Name"]
48
+ value = attr_element.elements.first.text
49
+
50
+ result[name] = value
51
+ end
52
+
53
+ result.keys.each do |key|
54
+ result[key.intern] = result[key]
55
+ end
56
+
57
+ result
58
+ end
59
+ end
60
+
61
+ # When this user session should expire at latest
62
+ def session_expires_at
63
+ @expires_at ||= begin
64
+ node = REXML::XPath.first(document, "/p:Response/a:Assertion/a:AuthnStatement", { "p" => PROTOCOL, "a" => ASSERTION })
65
+ parse_time(node, "SessionNotOnOrAfter")
66
+ end
67
+ end
68
+
69
+ # Conditions (if any) for the assertion to run
70
+ def conditions
71
+ @conditions ||= begin
72
+ REXML::XPath.first(document, "/p:Response/a:Assertion[@ID='#{document.signed_element_id[1,document.signed_element_id.size]}']/a:Conditions", { "p" => PROTOCOL, "a" => ASSERTION })
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def validation_error(message)
79
+ raise OmniAuth::Strategies::SAML::ValidationError.new(message)
80
+ end
81
+
82
+ def validate(soft = true)
83
+ validate_response_state(soft) &&
84
+ validate_conditions(soft) &&
85
+ document.validate(get_fingerprint, soft)
86
+ end
87
+
88
+ def validate_response_state(soft = true)
89
+ if response.empty?
90
+ return soft ? false : validation_error("Blank response")
91
+ end
92
+
93
+ if settings.nil?
94
+ return soft ? false : validation_error("No settings on response")
95
+ end
96
+
97
+ if settings.idp_cert_fingerprint.nil? && settings.idp_cert.nil?
98
+ return soft ? false : validation_error("No fingerprint or certificate on settings")
99
+ end
100
+
101
+ true
102
+ end
103
+
104
+ def get_fingerprint
105
+ if settings.idp_cert
106
+ cert = OpenSSL::X509::Certificate.new(settings.idp_cert)
107
+ Digest::SHA1.hexdigest(cert.to_der).upcase.scan(/../).join(":")
108
+ else
109
+ settings.idp_cert_fingerprint
110
+ end
111
+ end
112
+
113
+ def validate_conditions(soft = true)
114
+ return true if conditions.nil?
115
+ return true if options[:skip_conditions]
116
+
117
+ if not_before = parse_time(conditions, "NotBefore")
118
+ if Time.now.utc < not_before
119
+ return soft ? false : validation_error("Current time is earlier than NotBefore condition")
120
+ end
121
+ end
122
+
123
+ if not_on_or_after = parse_time(conditions, "NotOnOrAfter")
124
+ if Time.now.utc >= not_on_or_after
125
+ return soft ? false : validation_error("Current time is on or after NotOnOrAfter condition")
126
+ end
127
+ end
128
+
129
+ true
130
+ end
131
+
132
+ def parse_time(node, attribute)
133
+ if node && node.attributes[attribute]
134
+ Time.parse(node.attributes[attribute])
135
+ end
136
+ end
137
+
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,8 @@
1
+ module OmniAuth
2
+ module Strategies
3
+ class SAML
4
+ class ValidationError < Exception
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,126 @@
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 'rubygems'
26
+ require "rexml/document"
27
+ require "rexml/xpath"
28
+ require "openssl"
29
+ require "xmlcanonicalizer"
30
+ require "digest/sha1"
31
+
32
+ module OmniAuth
33
+ module Strategies
34
+ class SAML
35
+
36
+ module XMLSecurity
37
+
38
+ class SignedDocument < REXML::Document
39
+ DSIG = "http://www.w3.org/2000/09/xmldsig#"
40
+
41
+ attr_accessor :signed_element_id
42
+
43
+ def initialize(response)
44
+ super(response)
45
+ extract_signed_element_id
46
+ end
47
+
48
+ def validate(idp_cert_fingerprint, soft = true)
49
+ # get cert from response
50
+ base64_cert = self.elements["//ds:X509Certificate"].text
51
+ cert_text = Base64.decode64(base64_cert)
52
+ cert = OpenSSL::X509::Certificate.new(cert_text)
53
+
54
+ # check cert matches registered idp cert
55
+ fingerprint = Digest::SHA1.hexdigest(cert.to_der)
56
+
57
+ if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
58
+ return soft ? false : (raise OmniAuth::Strategies::SAML::ValidationError.new("Fingerprint mismatch"))
59
+ end
60
+
61
+ validate_doc(base64_cert, soft)
62
+ end
63
+
64
+ def validate_doc(base64_cert, soft = true)
65
+ # validate references
66
+
67
+ # check for inclusive namespaces
68
+
69
+ inclusive_namespaces = []
70
+ inclusive_namespace_element = REXML::XPath.first(self, "//ec:InclusiveNamespaces")
71
+
72
+ if inclusive_namespace_element
73
+ prefix_list = inclusive_namespace_element.attributes.get_attribute('PrefixList').value
74
+ inclusive_namespaces = prefix_list.split(" ")
75
+ end
76
+
77
+ # remove signature node
78
+ sig_element = REXML::XPath.first(self, "//ds:Signature", {"ds"=>"http://www.w3.org/2000/09/xmldsig#"})
79
+ sig_element.remove
80
+
81
+ # check digests
82
+ REXML::XPath.each(sig_element, "//ds:Reference", {"ds"=>"http://www.w3.org/2000/09/xmldsig#"}) do |ref|
83
+ uri = ref.attributes.get_attribute("URI").value
84
+ hashed_element = REXML::XPath.first(self, "//[@ID='#{uri[1,uri.size]}']")
85
+ canoner = XML::Util::XmlCanonicalizer.new(false, true)
86
+ canoner.inclusive_namespaces = inclusive_namespaces if canoner.respond_to?(:inclusive_namespaces) && !inclusive_namespaces.empty?
87
+ canon_hashed_element = canoner.canonicalize(hashed_element)
88
+ hash = Base64.encode64(Digest::SHA1.digest(canon_hashed_element)).chomp
89
+ digest_value = REXML::XPath.first(ref, "//ds:DigestValue", {"ds"=>"http://www.w3.org/2000/09/xmldsig#"}).text
90
+
91
+ if hash != digest_value
92
+ return soft ? false : (raise OmniAuth::Strategies::SAML::ValidationError.new("Digest mismatch"))
93
+ end
94
+ end
95
+
96
+ # verify signature
97
+ canoner = XML::Util::XmlCanonicalizer.new(false, true)
98
+ signed_info_element = REXML::XPath.first(sig_element, "//ds:SignedInfo", {"ds"=>"http://www.w3.org/2000/09/xmldsig#"})
99
+ canon_string = canoner.canonicalize(signed_info_element)
100
+
101
+ base64_signature = REXML::XPath.first(sig_element, "//ds:SignatureValue", {"ds"=>"http://www.w3.org/2000/09/xmldsig#"}).text
102
+ signature = Base64.decode64(base64_signature)
103
+
104
+ # get certificate object
105
+ cert_text = Base64.decode64(base64_cert)
106
+ cert = OpenSSL::X509::Certificate.new(cert_text)
107
+
108
+ if !cert.public_key.verify(OpenSSL::Digest::SHA1.new, signature, canon_string)
109
+ return soft ? false : (raise OmniAuth::Strategies::SAML::ValidationError.new("Key validation error"))
110
+ end
111
+
112
+ return true
113
+ end
114
+
115
+ private
116
+
117
+ def extract_signed_element_id
118
+ reference_element = REXML::XPath.first(self, "//ds:Signature/ds:SignedInfo/ds:Reference", {"ds"=>DSIG})
119
+ self.signed_element_id = reference_element.attribute("URI").value unless reference_element.nil?
120
+ end
121
+ end
122
+ end
123
+
124
+ end
125
+ end
126
+ end
@@ -4,13 +4,13 @@ module OmniAuth
4
4
  MAJOR = 0
5
5
  end
6
6
  unless defined?(::OmniAuth::Version::MINOR)
7
- MINOR = 2
7
+ MINOR = 3
8
8
  end
9
9
  unless defined?(::OmniAuth::Version::PATCH)
10
- PATCH = 6
10
+ PATCH = 0
11
11
  end
12
12
  unless defined?(::OmniAuth::Version::PRE)
13
- PRE = nil
13
+ PRE = "rc3"
14
14
  end
15
15
  unless defined?(::OmniAuth::Version::STRING)
16
16
  STRING = [MAJOR, MINOR, PATCH, PRE].compact.join('.')
@@ -1,31 +1,31 @@
1
- # -*- encoding: utf-8 -*-
1
+ # encoding: utf-8
2
2
  require File.expand_path('../lib/omniauth/version', __FILE__)
3
3
 
4
4
  Gem::Specification.new do |gem|
5
- gem.add_runtime_dependency 'addressable', '2.2.4'
6
- gem.add_runtime_dependency 'nokogiri', '~> 1.4.2'
7
- gem.add_runtime_dependency 'net-ldap', '~> 0.2.2'
8
- gem.add_runtime_dependency 'oa-core', OmniAuth::Version::STRING
9
- gem.add_runtime_dependency 'pyu-ruby-sasl', '~> 0.0.3.1'
10
- gem.add_runtime_dependency 'rubyntlm', '~> 0.1.1'
11
- gem.add_development_dependency 'maruku', '~> 0.6'
12
- gem.add_development_dependency 'simplecov', '~> 0.4'
5
+ gem.add_dependency 'addressable', '~> 2.2.6'
6
+ gem.add_dependency 'net-ldap', '~> 0.2.2'
7
+ gem.add_dependency 'nokogiri', '~> 1.5.0'
8
+ gem.add_dependency 'oa-core', OmniAuth::Version::STRING
9
+ gem.add_dependency 'pyu-ruby-sasl', '~> 0.0.3.1'
10
+ gem.add_dependency 'rubyntlm', '~> 0.1.1'
11
+ gem.add_dependency 'uuid'
12
+ gem.add_dependency 'XMLCanonicalizer', '~> 1.0.1'
13
13
  gem.add_development_dependency 'rack-test', '~> 0.5'
14
14
  gem.add_development_dependency 'rake', '~> 0.8'
15
+ gem.add_development_dependency 'rdiscount', '~> 1.6'
15
16
  gem.add_development_dependency 'rspec', '~> 2.5'
16
- gem.add_development_dependency 'webmock', '~> 1.6'
17
+ gem.add_development_dependency 'simplecov', '~> 0.4'
18
+ gem.add_development_dependency 'webmock', '~> 1.7'
17
19
  gem.add_development_dependency 'yard', '~> 0.7'
18
- gem.add_development_dependency 'ZenTest', '~> 4.5'
19
- gem.name = 'oa-enterprise'
20
- gem.version = OmniAuth::Version::STRING
20
+ gem.authors = ['James A. Rosen', 'Ping Yu', 'Michael Bleigh', 'Erik Michaels-Ober', 'Raecoo Cao']
21
21
  gem.description = %q{Enterprise strategies for OmniAuth.}
22
- gem.summary = gem.description
23
- gem.email = ['james.a.rosen@gmail.com', 'ping@intridea.com', 'michael@intridea.com', 'sferik@gmail.com']
24
- gem.homepage = 'http://github.com/intridea/omniauth'
25
- gem.authors = ['James A. Rosen', 'Ping Yu', 'Michael Bleigh', 'Erik Michaels-Ober']
26
- gem.executables = `git ls-files -- bin/*`.split("\n").map{|f| File.basename(f)}
22
+ gem.email = ['james.a.rosen@gmail.com', 'ping@intridea.com', 'michael@intridea.com', 'sferik@gmail.com', 'raecoo@intridea.com']
27
23
  gem.files = `git ls-files`.split("\n")
28
- gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
24
+ gem.homepage = 'http://github.com/intridea/omniauth'
25
+ gem.name = 'oa-enterprise'
29
26
  gem.require_paths = ['lib']
30
27
  gem.required_rubygems_version = Gem::Requirement.new('>= 1.3.6') if gem.respond_to? :required_rubygems_version=
28
+ gem.summary = gem.description
29
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
30
+ gem.version = OmniAuth::Version::STRING
31
31
  end
@@ -0,0 +1,37 @@
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
+
3
+ describe OmniAuth::Strategies::SAML, :type => :strategy do
4
+
5
+ include OmniAuth::Test::StrategyTestCase
6
+
7
+ def strategy
8
+ [OmniAuth::Strategies::SAML, {
9
+ :assertion_consumer_service_url => "http://consumer.service.url/auth/saml/callback",
10
+ :issuer => "https://saml.issuer.url/issuers/29490",
11
+ :idp_sso_target_url => "https://idp.sso.target_url/signon/29490",
12
+ :idp_cert_fingerprint => "E7:91:B2:E1:4C:65:2C:49:F3:33:74:0A:58:5A:7E:55:F7:15:7A:33",
13
+ :name_identifier_format => "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
14
+ }]
15
+ end
16
+
17
+ describe 'GET /auth/saml' do
18
+ before do
19
+ get '/auth/saml'
20
+ end
21
+
22
+ it 'should get authentication page' do
23
+ last_response.should be_redirect
24
+ end
25
+ end
26
+
27
+ describe 'POST /auth/saml/callback' do
28
+
29
+ it 'should raise ArgumentError exception without the SAMLResponse parameter' do
30
+ post '/auth/saml/callback'
31
+ last_response.should be_redirect
32
+ last_response.location.should == '/auth/failure?message=invalid_ticket'
33
+ end
34
+
35
+ end
36
+
37
+ end
metadata CHANGED
@@ -1,19 +1,27 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oa-enterprise
3
3
  version: !ruby/object:Gem::Version
4
- prerelease:
5
- version: 0.2.6
4
+ hash: 15424035
5
+ prerelease: 6
6
+ segments:
7
+ - 0
8
+ - 3
9
+ - 0
10
+ - rc
11
+ - 3
12
+ version: 0.3.0.rc3
6
13
  platform: ruby
7
14
  authors:
8
15
  - James A. Rosen
9
16
  - Ping Yu
10
17
  - Michael Bleigh
11
18
  - Erik Michaels-Ober
19
+ - Raecoo Cao
12
20
  autorequire:
13
21
  bindir: bin
14
22
  cert_chain: []
15
23
 
16
- date: 2011-05-20 00:00:00 Z
24
+ date: 2011-09-03 00:00:00 Z
17
25
  dependencies:
18
26
  - !ruby/object:Gem::Dependency
19
27
  name: addressable
@@ -21,31 +29,46 @@ dependencies:
21
29
  requirement: &id001 !ruby/object:Gem::Requirement
22
30
  none: false
23
31
  requirements:
24
- - - "="
32
+ - - ~>
25
33
  - !ruby/object:Gem::Version
26
- version: 2.2.4
34
+ hash: 11
35
+ segments:
36
+ - 2
37
+ - 2
38
+ - 6
39
+ version: 2.2.6
27
40
  type: :runtime
28
41
  version_requirements: *id001
29
42
  - !ruby/object:Gem::Dependency
30
- name: nokogiri
43
+ name: net-ldap
31
44
  prerelease: false
32
45
  requirement: &id002 !ruby/object:Gem::Requirement
33
46
  none: false
34
47
  requirements:
35
48
  - - ~>
36
49
  - !ruby/object:Gem::Version
37
- version: 1.4.2
50
+ hash: 19
51
+ segments:
52
+ - 0
53
+ - 2
54
+ - 2
55
+ version: 0.2.2
38
56
  type: :runtime
39
57
  version_requirements: *id002
40
58
  - !ruby/object:Gem::Dependency
41
- name: net-ldap
59
+ name: nokogiri
42
60
  prerelease: false
43
61
  requirement: &id003 !ruby/object:Gem::Requirement
44
62
  none: false
45
63
  requirements:
46
64
  - - ~>
47
65
  - !ruby/object:Gem::Version
48
- version: 0.2.2
66
+ hash: 3
67
+ segments:
68
+ - 1
69
+ - 5
70
+ - 0
71
+ version: 1.5.0
49
72
  type: :runtime
50
73
  version_requirements: *id003
51
74
  - !ruby/object:Gem::Dependency
@@ -56,7 +79,14 @@ dependencies:
56
79
  requirements:
57
80
  - - "="
58
81
  - !ruby/object:Gem::Version
59
- version: 0.2.6
82
+ hash: 15424035
83
+ segments:
84
+ - 0
85
+ - 3
86
+ - 0
87
+ - rc
88
+ - 3
89
+ version: 0.3.0.rc3
60
90
  type: :runtime
61
91
  version_requirements: *id004
62
92
  - !ruby/object:Gem::Dependency
@@ -67,6 +97,12 @@ dependencies:
67
97
  requirements:
68
98
  - - ~>
69
99
  - !ruby/object:Gem::Version
100
+ hash: 65
101
+ segments:
102
+ - 0
103
+ - 0
104
+ - 3
105
+ - 1
70
106
  version: 0.0.3.1
71
107
  type: :runtime
72
108
  version_requirements: *id005
@@ -78,30 +114,43 @@ dependencies:
78
114
  requirements:
79
115
  - - ~>
80
116
  - !ruby/object:Gem::Version
117
+ hash: 25
118
+ segments:
119
+ - 0
120
+ - 1
121
+ - 1
81
122
  version: 0.1.1
82
123
  type: :runtime
83
124
  version_requirements: *id006
84
125
  - !ruby/object:Gem::Dependency
85
- name: maruku
126
+ name: uuid
86
127
  prerelease: false
87
128
  requirement: &id007 !ruby/object:Gem::Requirement
88
129
  none: false
89
130
  requirements:
90
- - - ~>
131
+ - - ">="
91
132
  - !ruby/object:Gem::Version
92
- version: "0.6"
93
- type: :development
133
+ hash: 3
134
+ segments:
135
+ - 0
136
+ version: "0"
137
+ type: :runtime
94
138
  version_requirements: *id007
95
139
  - !ruby/object:Gem::Dependency
96
- name: simplecov
140
+ name: XMLCanonicalizer
97
141
  prerelease: false
98
142
  requirement: &id008 !ruby/object:Gem::Requirement
99
143
  none: false
100
144
  requirements:
101
145
  - - ~>
102
146
  - !ruby/object:Gem::Version
103
- version: "0.4"
104
- type: :development
147
+ hash: 21
148
+ segments:
149
+ - 1
150
+ - 0
151
+ - 1
152
+ version: 1.0.1
153
+ type: :runtime
105
154
  version_requirements: *id008
106
155
  - !ruby/object:Gem::Dependency
107
156
  name: rack-test
@@ -111,6 +160,10 @@ dependencies:
111
160
  requirements:
112
161
  - - ~>
113
162
  - !ruby/object:Gem::Version
163
+ hash: 1
164
+ segments:
165
+ - 0
166
+ - 5
114
167
  version: "0.5"
115
168
  type: :development
116
169
  version_requirements: *id009
@@ -122,59 +175,95 @@ dependencies:
122
175
  requirements:
123
176
  - - ~>
124
177
  - !ruby/object:Gem::Version
178
+ hash: 27
179
+ segments:
180
+ - 0
181
+ - 8
125
182
  version: "0.8"
126
183
  type: :development
127
184
  version_requirements: *id010
128
185
  - !ruby/object:Gem::Dependency
129
- name: rspec
186
+ name: rdiscount
130
187
  prerelease: false
131
188
  requirement: &id011 !ruby/object:Gem::Requirement
132
189
  none: false
133
190
  requirements:
134
191
  - - ~>
135
192
  - !ruby/object:Gem::Version
136
- version: "2.5"
193
+ hash: 3
194
+ segments:
195
+ - 1
196
+ - 6
197
+ version: "1.6"
137
198
  type: :development
138
199
  version_requirements: *id011
139
200
  - !ruby/object:Gem::Dependency
140
- name: webmock
201
+ name: rspec
141
202
  prerelease: false
142
203
  requirement: &id012 !ruby/object:Gem::Requirement
143
204
  none: false
144
205
  requirements:
145
206
  - - ~>
146
207
  - !ruby/object:Gem::Version
147
- version: "1.6"
208
+ hash: 9
209
+ segments:
210
+ - 2
211
+ - 5
212
+ version: "2.5"
148
213
  type: :development
149
214
  version_requirements: *id012
150
215
  - !ruby/object:Gem::Dependency
151
- name: yard
216
+ name: simplecov
152
217
  prerelease: false
153
218
  requirement: &id013 !ruby/object:Gem::Requirement
154
219
  none: false
155
220
  requirements:
156
221
  - - ~>
157
222
  - !ruby/object:Gem::Version
158
- version: "0.7"
223
+ hash: 3
224
+ segments:
225
+ - 0
226
+ - 4
227
+ version: "0.4"
159
228
  type: :development
160
229
  version_requirements: *id013
161
230
  - !ruby/object:Gem::Dependency
162
- name: ZenTest
231
+ name: webmock
163
232
  prerelease: false
164
233
  requirement: &id014 !ruby/object:Gem::Requirement
165
234
  none: false
166
235
  requirements:
167
236
  - - ~>
168
237
  - !ruby/object:Gem::Version
169
- version: "4.5"
238
+ hash: 1
239
+ segments:
240
+ - 1
241
+ - 7
242
+ version: "1.7"
170
243
  type: :development
171
244
  version_requirements: *id014
245
+ - !ruby/object:Gem::Dependency
246
+ name: yard
247
+ prerelease: false
248
+ requirement: &id015 !ruby/object:Gem::Requirement
249
+ none: false
250
+ requirements:
251
+ - - ~>
252
+ - !ruby/object:Gem::Version
253
+ hash: 5
254
+ segments:
255
+ - 0
256
+ - 7
257
+ version: "0.7"
258
+ type: :development
259
+ version_requirements: *id015
172
260
  description: Enterprise strategies for OmniAuth.
173
261
  email:
174
262
  - james.a.rosen@gmail.com
175
263
  - ping@intridea.com
176
264
  - michael@intridea.com
177
265
  - sferik@gmail.com
266
+ - raecoo@intridea.com
178
267
  executables: []
179
268
 
180
269
  extensions: []
@@ -196,12 +285,18 @@ files:
196
285
  - lib/omniauth/strategies/cas/service_ticket_validator.rb
197
286
  - lib/omniauth/strategies/ldap.rb
198
287
  - lib/omniauth/strategies/ldap/adaptor.rb
288
+ - lib/omniauth/strategies/saml.rb
289
+ - lib/omniauth/strategies/saml/auth_request.rb
290
+ - lib/omniauth/strategies/saml/auth_response.rb
291
+ - lib/omniauth/strategies/saml/validation_error.rb
292
+ - lib/omniauth/strategies/saml/xml_security.rb
199
293
  - lib/omniauth/version.rb
200
294
  - oa-enterprise.gemspec
201
295
  - spec/fixtures/cas_failure.xml
202
296
  - spec/fixtures/cas_success.xml
203
297
  - spec/omniauth/strategies/cas_spec.rb
204
298
  - spec/omniauth/strategies/ldap_spec.rb
299
+ - spec/omniauth/strategies/saml_spec.rb
205
300
  - spec/spec_helper.rb
206
301
  homepage: http://github.com/intridea/omniauth
207
302
  licenses: []
@@ -216,17 +311,25 @@ required_ruby_version: !ruby/object:Gem::Requirement
216
311
  requirements:
217
312
  - - ">="
218
313
  - !ruby/object:Gem::Version
314
+ hash: 3
315
+ segments:
316
+ - 0
219
317
  version: "0"
220
318
  required_rubygems_version: !ruby/object:Gem::Requirement
221
319
  none: false
222
320
  requirements:
223
- - - ">="
321
+ - - ">"
224
322
  - !ruby/object:Gem::Version
225
- version: 1.3.6
323
+ hash: 25
324
+ segments:
325
+ - 1
326
+ - 3
327
+ - 1
328
+ version: 1.3.1
226
329
  requirements: []
227
330
 
228
331
  rubyforge_project:
229
- rubygems_version: 1.8.2
332
+ rubygems_version: 1.8.10
230
333
  signing_key:
231
334
  specification_version: 3
232
335
  summary: Enterprise strategies for OmniAuth.
@@ -235,4 +338,5 @@ test_files:
235
338
  - spec/fixtures/cas_success.xml
236
339
  - spec/omniauth/strategies/cas_spec.rb
237
340
  - spec/omniauth/strategies/ldap_spec.rb
341
+ - spec/omniauth/strategies/saml_spec.rb
238
342
  - spec/spec_helper.rb