oa-enterprise 0.2.6 → 0.3.0.rc3

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