cf-uaa-lib 3.6.0 → 3.14.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +3 -4
- data/NOTICE +12 -0
- data/README.md +1 -1
- data/cf-uaa-lib.gemspec +16 -15
- data/lib/uaa/http.rb +62 -42
- data/lib/uaa/info.rb +6 -7
- data/lib/uaa/scim.rb +79 -25
- data/lib/uaa/token_coder.rb +19 -1
- data/lib/uaa/token_issuer.rb +1 -5
- data/lib/uaa/version.rb +1 -1
- data/spec/http_spec.rb +99 -54
- data/spec/info_spec.rb +36 -38
- data/spec/integration_spec.rb +197 -106
- data/spec/scim_spec.rb +89 -81
- data/spec/token_issuer_spec.rb +130 -135
- metadata +61 -12
- data/NOTICE.TXT +0 -10
- data/lib/uaa/proxy_options.rb +0 -30
- /data/{LICENSE.TXT → LICENSE} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 903c34141ef900eafea69aa67eac6e998ff8ed4d
|
4
|
+
data.tar.gz: e994669699594790f50be50a10d7c8a1b1c38dc7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a8c4455646d340f50b5338481bf51f8d00daf6bf8bcce5c189761bb7f43f3b5c44a93e925266632b04aed9cdd0abc6a04df53110805ab94942410e503b68a49d
|
7
|
+
data.tar.gz: 98340c72c9201bea70d77f1dd3df5fc0a7f5ca2138e0534107cdda121dd26b6f512b127ed6c628ae10e02e1257e91c0027973390c83a132c5d0de746608c7877
|
data/.travis.yml
CHANGED
data/NOTICE
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
|
2
|
+
Copyright (c) 2015-Present CloudFoundry.org Foundation, Inc. All Rights Reserved.
|
3
|
+
|
4
|
+
This project contains software that is Copyright (c) 2012-2015 Pivotal Software, Inc.
|
5
|
+
|
6
|
+
This project is licensed to you under the Apache License, Version 2.0 (the "License").
|
7
|
+
|
8
|
+
You may not use this project except in compliance with the License.
|
9
|
+
|
10
|
+
This project may include a number of subcomponents with separate copyright notices
|
11
|
+
and license terms. Your use of these subcomponents is subject to the terms and
|
12
|
+
conditions of the subcomponent's license, as noted in the LICENSE file.
|
data/README.md
CHANGED
@@ -23,7 +23,7 @@ For documentation see: https://rubygems.org/gems/cf-uaa-lib
|
|
23
23
|
token_issuer = CF::UAA::TokenIssuer.new("https://uaa.cloudfoundry.com", "vmc")
|
24
24
|
puts token_issuer.prompts.inspect
|
25
25
|
token = token_issuer.implicit_grant_with_creds(username: "<your_username>", password: "<your_password>")
|
26
|
-
token_info = TokenCoder.decode(token.info["access_token"], nil, nil, false) #token signature not verified
|
26
|
+
token_info = CF::UAA::TokenCoder.decode(token.info["access_token"], nil, nil, false) #token signature not verified
|
27
27
|
puts token_info["user_name"]
|
28
28
|
|
29
29
|
## Tests
|
data/cf-uaa-lib.gemspec
CHANGED
@@ -16,31 +16,32 @@ $:.push File.expand_path("../lib", __FILE__)
|
|
16
16
|
require "uaa/version"
|
17
17
|
|
18
18
|
Gem::Specification.new do |s|
|
19
|
-
s.name =
|
19
|
+
s.name = 'cf-uaa-lib'
|
20
20
|
s.version = CF::UAA::VERSION
|
21
|
-
s.authors = [
|
22
|
-
s.email = [
|
23
|
-
s.homepage =
|
21
|
+
s.authors = ['Dave Syer', 'Dale Olds', 'Joel D\'sa', 'Vidya Valmikinathan', 'Luke Taylor']
|
22
|
+
s.email = ['dsyer@vmware.com', 'olds@vmware.com', 'jdsa@vmware.com', 'vidya@vmware.com', 'ltaylor@vmware.com']
|
23
|
+
s.homepage = 'https://github.com/cloudfoundry/cf-uaa-lib'
|
24
24
|
s.summary = %q{Client library for CloudFoundry UAA}
|
25
25
|
s.description = %q{Client library for interacting with the CloudFoundry User Account and Authorization (UAA) server. The UAA is an OAuth2 Authorization Server so it can be used by webapps and command line apps to obtain access tokens to act on behalf of users. The tokens can then be used to access protected resources in a Resource Server. This library is for use by UAA client applications or resource servers.}
|
26
26
|
|
27
27
|
s.rubyforge_project = "cf-uaa-lib"
|
28
28
|
|
29
|
-
s.license = "Apache
|
29
|
+
s.license = "Apache-2.0"
|
30
30
|
s.files = `git ls-files`.split("\n")
|
31
31
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
32
|
-
s.executables = `git ls-files -- bin/*`.split(
|
33
|
-
s.require_paths = [
|
32
|
+
s.executables = `git ls-files -- bin/*`.split('\n').map{ |f| File.basename(f) }
|
33
|
+
s.require_paths = ['lib']
|
34
34
|
|
35
35
|
# dependencies
|
36
|
-
s.add_dependency
|
36
|
+
s.add_dependency 'multi_json', '~> 1.12.0', '>= 1.12.1'
|
37
|
+
s.add_dependency 'httpclient', '~> 2.8', '>= 2.8.2.4'
|
37
38
|
|
38
|
-
s.add_development_dependency
|
39
|
-
s.add_development_dependency
|
40
|
-
s.add_development_dependency
|
41
|
-
s.add_development_dependency
|
42
|
-
s.add_development_dependency
|
43
|
-
s.add_development_dependency
|
44
|
-
s.add_development_dependency
|
39
|
+
s.add_development_dependency 'bundler', '~> 1.14'
|
40
|
+
s.add_development_dependency 'rake', '~> 10.3', '>= 10.3.2'
|
41
|
+
s.add_development_dependency 'rspec', '~> 2.14', '>= 2.14.1'
|
42
|
+
s.add_development_dependency 'simplecov', '~> 0.8.2'
|
43
|
+
s.add_development_dependency 'simplecov-rcov', '~> 0.2.3'
|
44
|
+
s.add_development_dependency 'ci_reporter', '~> 1.9', '>= 1.9.2'
|
45
|
+
s.add_development_dependency 'json_pure', '~> 1.8', '>= 1.8.1'
|
45
46
|
|
46
47
|
end
|
data/lib/uaa/http.rb
CHANGED
@@ -12,9 +12,8 @@
|
|
12
12
|
#++
|
13
13
|
|
14
14
|
require 'base64'
|
15
|
-
require 'net/http'
|
16
15
|
require 'uaa/util'
|
17
|
-
require '
|
16
|
+
require 'httpclient'
|
18
17
|
|
19
18
|
module CF::UAA
|
20
19
|
|
@@ -46,14 +45,20 @@ class InvalidToken < TargetError; end
|
|
46
45
|
|
47
46
|
# Utility accessors and methods for objects that want to access JSON web APIs.
|
48
47
|
module Http
|
49
|
-
include ProxyOptions
|
50
48
|
|
51
49
|
def self.included(base)
|
52
50
|
base.class_eval do
|
53
|
-
|
51
|
+
attr_reader :skip_ssl_validation, :ssl_ca_file, :ssl_cert_store, :http_timeout
|
54
52
|
end
|
55
53
|
end
|
56
54
|
|
55
|
+
def initialize_http_options(options)
|
56
|
+
@skip_ssl_validation = options[:skip_ssl_validation]
|
57
|
+
@ssl_ca_file = options[:ssl_ca_file]
|
58
|
+
@ssl_cert_store = options[:ssl_cert_store]
|
59
|
+
@http_timeout = options[:http_timeout]
|
60
|
+
end
|
61
|
+
|
57
62
|
# Sets the current logger instance to recieve error messages.
|
58
63
|
# @param [Logger] logr
|
59
64
|
# @return [Logger]
|
@@ -78,30 +83,30 @@ module Http
|
|
78
83
|
# @return [String]
|
79
84
|
def self.basic_auth(name, password)
|
80
85
|
str = "#{name}:#{password}"
|
81
|
-
|
82
|
-
Base64.strict_encode64(str): [str].pack(
|
86
|
+
'Basic ' + (Base64.respond_to?(:strict_encode64)?
|
87
|
+
Base64.strict_encode64(str): [str].pack('m').gsub(/\n/, ''))
|
83
88
|
end
|
84
89
|
|
85
|
-
JSON_UTF8 =
|
86
|
-
FORM_UTF8 =
|
90
|
+
JSON_UTF8 = 'application/json;charset=utf-8'
|
91
|
+
FORM_UTF8 = 'application/x-www-form-urlencoded;charset=utf-8'
|
87
92
|
|
88
93
|
private
|
89
94
|
|
90
95
|
def json_get(target, path = nil, style = nil, headers = {})
|
91
96
|
raise ArgumentError unless style.nil? || style.is_a?(Symbol)
|
92
|
-
json_parse_reply(style, *http_get(target, path, headers.merge(
|
97
|
+
json_parse_reply(style, *http_get(target, path, headers.merge('accept' => JSON_UTF8)))
|
93
98
|
end
|
94
99
|
|
95
100
|
def json_post(target, path, body, headers = {})
|
96
|
-
http_post(target, path, Util.json(body), headers.merge(
|
101
|
+
http_post(target, path, Util.json(body), headers.merge('content-type' => JSON_UTF8))
|
97
102
|
end
|
98
103
|
|
99
104
|
def json_put(target, path, body, headers = {})
|
100
|
-
http_put(target, path, Util.json(body), headers.merge(
|
105
|
+
http_put(target, path, Util.json(body), headers.merge('content-type' => JSON_UTF8))
|
101
106
|
end
|
102
107
|
|
103
108
|
def json_patch(target, path, body, headers = {})
|
104
|
-
http_patch(target, path, Util.json(body), headers.merge(
|
109
|
+
http_patch(target, path, Util.json(body), headers.merge('content-type' => JSON_UTF8))
|
105
110
|
end
|
106
111
|
|
107
112
|
def json_parse_reply(style, status, body, headers)
|
@@ -110,17 +115,17 @@ module Http
|
|
110
115
|
raise (status == 404 ? NotFound : BadResponse), "invalid status response: #{status}"
|
111
116
|
end
|
112
117
|
if body && !body.empty? && (status == 204 || headers.nil? ||
|
113
|
-
headers[
|
114
|
-
raise BadResponse,
|
118
|
+
headers['content-type'] !~ /application\/json/i)
|
119
|
+
raise BadResponse, 'received invalid response content or type'
|
115
120
|
end
|
116
121
|
parsed_reply = Util.json_parse(body, style)
|
117
122
|
if status >= 400
|
118
|
-
raise parsed_reply && parsed_reply[
|
119
|
-
InvalidToken.new(parsed_reply) : TargetError.new(parsed_reply),
|
123
|
+
raise parsed_reply && parsed_reply['error'] == 'invalid_token' ?
|
124
|
+
InvalidToken.new(parsed_reply) : TargetError.new(parsed_reply), 'error response'
|
120
125
|
end
|
121
126
|
parsed_reply
|
122
127
|
rescue DecodeError
|
123
|
-
raise BadResponse,
|
128
|
+
raise BadResponse, 'invalid JSON response'
|
124
129
|
end
|
125
130
|
|
126
131
|
def http_get(target, path = nil, headers = {}) request(target, :get, path, nil, headers) end
|
@@ -129,7 +134,7 @@ module Http
|
|
129
134
|
def http_patch(target, path, body, headers = {}) request(target, :patch, path, body, headers) end
|
130
135
|
|
131
136
|
def http_delete(target, path, authorization, zone = nil)
|
132
|
-
hdrs = {
|
137
|
+
hdrs = { 'authorization' => authorization }
|
133
138
|
hdrs['X-Identity-Zone-Subdomain'] = zone if zone
|
134
139
|
status = request(target, :delete, path, nil, hdrs)[0]
|
135
140
|
unless [200, 204].include?(status)
|
@@ -138,7 +143,7 @@ module Http
|
|
138
143
|
end
|
139
144
|
|
140
145
|
def request(target, method, path, body = nil, headers = {})
|
141
|
-
headers[
|
146
|
+
headers['accept'] = headers['content-type'] if headers['content-type'] && !headers['accept']
|
142
147
|
url = "#{target}#{path}"
|
143
148
|
|
144
149
|
logger.debug { "--->\nrequest: #{method} #{url}\n" +
|
@@ -156,44 +161,59 @@ module Http
|
|
156
161
|
end
|
157
162
|
|
158
163
|
def net_http_request(url, method, body, headers)
|
159
|
-
raise ArgumentError unless reqtype = {:delete => Net::HTTP::Delete,
|
160
|
-
:get => Net::HTTP::Get, :post => Net::HTTP::Post, :put => Net::HTTP::Put, :patch => Net::HTTP::Patch}[method]
|
161
|
-
headers["content-length"] = body.length if body
|
162
164
|
uri = URI.parse(url)
|
163
|
-
req = reqtype.new(uri.request_uri)
|
164
|
-
headers.each { |k, v| req[k] = v }
|
165
165
|
http = http_request(uri)
|
166
|
-
|
167
|
-
|
168
|
-
|
166
|
+
headers['content-length'] = body.length.to_s if body
|
167
|
+
case method
|
168
|
+
when :get, :delete
|
169
|
+
response = http.send(method, uri, nil, headers)
|
170
|
+
when :post, :put, :patch
|
171
|
+
response = http.send(method, uri, body, headers)
|
172
|
+
else
|
173
|
+
raise ArgumentError
|
174
|
+
end
|
169
175
|
|
176
|
+
unless response.status
|
177
|
+
raise HTTPException.new "Can't parse response from the server #{response.content}"
|
178
|
+
end
|
179
|
+
response_headers = {}
|
180
|
+
response.header.all.each { |k, v| response_headers[k.downcase] = v }
|
181
|
+
return [response.status.to_i, response.content, response_headers]
|
170
182
|
rescue OpenSSL::SSL::SSLError => e
|
171
183
|
raise SSLException, "Invalid SSL Cert for #{url}. Use '--skip-ssl-validation' to continue with an insecure target"
|
172
184
|
rescue URI::Error, SocketError, SystemCallError => e
|
173
185
|
raise BadTarget, "error: #{e.message}"
|
174
|
-
rescue
|
175
|
-
raise HTTPException
|
186
|
+
rescue HTTPClient::ConnectTimeoutError => e
|
187
|
+
raise HTTPException.new "http timeout"
|
176
188
|
end
|
177
189
|
|
178
190
|
def http_request(uri)
|
179
|
-
cache_key = URI.join(uri.to_s,
|
191
|
+
cache_key = URI.join(uri.to_s, '/')
|
180
192
|
@http_cache ||= {}
|
181
193
|
return @http_cache[cache_key] if @http_cache[cache_key]
|
182
194
|
|
183
|
-
http = Net::HTTP.new(uri.host, uri.port, *proxy_options_for(uri))
|
184
|
-
|
185
195
|
if uri.is_a?(URI::HTTPS)
|
186
|
-
http
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
+
http = HTTPClient.new.tap do |c|
|
197
|
+
if skip_ssl_validation
|
198
|
+
c.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
199
|
+
elsif ssl_ca_file
|
200
|
+
c.ssl_config.set_trust_ca File.expand_path(ssl_ca_file)
|
201
|
+
c.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
202
|
+
elsif ssl_cert_store
|
203
|
+
c.ssl_config.cert_store = ssl_cert_store
|
204
|
+
c.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
205
|
+
else
|
206
|
+
c.ssl_config.set_default_paths
|
207
|
+
end
|
196
208
|
end
|
209
|
+
else
|
210
|
+
http = HTTPClient.new
|
211
|
+
end
|
212
|
+
|
213
|
+
if http_timeout
|
214
|
+
http.connect_timeout = http_timeout
|
215
|
+
http.send_timeout = http_timeout
|
216
|
+
http.receive_timeout = http_timeout
|
197
217
|
end
|
198
218
|
|
199
219
|
@http_cache[cache_key] = http
|
data/lib/uaa/info.rb
CHANGED
@@ -31,12 +31,8 @@ class Info
|
|
31
31
|
# string keys are returned.
|
32
32
|
def initialize(target, options = {})
|
33
33
|
self.target = target
|
34
|
-
self.skip_ssl_validation = options[:skip_ssl_validation]
|
35
|
-
self.ssl_ca_file = options[:ssl_ca_file]
|
36
|
-
self.ssl_cert_store = options[:ssl_cert_store]
|
37
34
|
self.symbolize_keys = options[:symbolize_keys]
|
38
|
-
|
39
|
-
self.https_proxy = options[:https_proxy]
|
35
|
+
initialize_http_options(options)
|
40
36
|
end
|
41
37
|
|
42
38
|
# sets whether the keys in returned hashes should be symbols.
|
@@ -132,8 +128,11 @@ class Info
|
|
132
128
|
# @param [String] token_type as retrieved by {TokenIssuer}. See {TokenInfo}.
|
133
129
|
# @return [Hash] contents of the token
|
134
130
|
def decode_token(client_id, client_secret, token, token_type = "bearer", audience_ids = nil)
|
135
|
-
reply =
|
136
|
-
|
131
|
+
reply = json_parse_reply(key_style, *request(target, :post, '/check_token',
|
132
|
+
Util.encode_form(:token => token),
|
133
|
+
"authorization" => Http.basic_auth(client_id, client_secret),
|
134
|
+
"content-type" => Http::FORM_UTF8,"accept" => Http::JSON_UTF8))
|
135
|
+
|
137
136
|
auds = Util.arglist(reply[:aud] || reply['aud'])
|
138
137
|
if audience_ids && (!auds || (auds & audience_ids).empty?)
|
139
138
|
raise AuthError, "invalid audience: #{auds.join(' ')}"
|
data/lib/uaa/scim.rb
CHANGED
@@ -42,16 +42,28 @@ class Scim
|
|
42
42
|
|
43
43
|
def force_attr(k)
|
44
44
|
kd = k.to_s.downcase
|
45
|
-
kc = {
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
45
|
+
kc = {
|
46
|
+
'username' => 'userName',
|
47
|
+
'familyname' => 'familyName',
|
48
|
+
'givenname' => 'givenName',
|
49
|
+
'middlename' => 'middleName',
|
50
|
+
'honorificprefix' => 'honorificPrefix',
|
51
|
+
'honorificsuffix' => 'honorificSuffix',
|
52
|
+
'displayname' => 'displayName',
|
53
|
+
'nickname' => 'nickName',
|
54
|
+
'profileurl' => 'profileUrl',
|
55
|
+
'streetaddress' => 'streetAddress',
|
56
|
+
'postalcode' => 'postalCode',
|
57
|
+
'usertype' => 'userType',
|
58
|
+
'preferredlanguage' => 'preferredLanguage',
|
59
|
+
'x509certificates' => 'x509Certificates',
|
60
|
+
'lastmodified' => 'lastModified',
|
61
|
+
'externalid' => 'externalId',
|
62
|
+
'phonenumbers' => 'phoneNumbers',
|
63
|
+
'startindex' => 'startIndex',
|
64
|
+
'zoneid' => 'zoneId',
|
65
|
+
'includeinactive' => 'includeInactive'
|
66
|
+
}[kd]
|
55
67
|
kc || kd
|
56
68
|
end
|
57
69
|
|
@@ -76,15 +88,46 @@ class Scim
|
|
76
88
|
|
77
89
|
# an attempt to hide some scim and uaa oddities
|
78
90
|
def type_info(type, elem)
|
79
|
-
scimfo = {
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
91
|
+
scimfo = {
|
92
|
+
user: {
|
93
|
+
path: '/Users',
|
94
|
+
name_attr: 'userName',
|
95
|
+
origin_attr: 'origin'
|
96
|
+
},
|
97
|
+
group: {
|
98
|
+
path: '/Groups',
|
99
|
+
name_attr: 'displayName',
|
100
|
+
origin_attr: 'zoneid'
|
101
|
+
},
|
102
|
+
client: {
|
103
|
+
path: '/oauth/clients',
|
104
|
+
name_attr: 'client_id'
|
105
|
+
},
|
106
|
+
user_id: {
|
107
|
+
path: '/ids/Users',
|
108
|
+
name_attr: 'userName',
|
109
|
+
origin_attr: 'origin',
|
110
|
+
},
|
111
|
+
group_mapping: {
|
112
|
+
path: '/Groups/External',
|
113
|
+
name_attr: 'externalGroup',
|
114
|
+
origin_attr: 'origin'
|
115
|
+
}
|
116
|
+
}
|
117
|
+
|
118
|
+
type_info = scimfo[type]
|
119
|
+
|
120
|
+
unless type_info
|
85
121
|
raise ArgumentError, "scim resource type must be one of #{scimfo.keys.inspect}"
|
86
122
|
end
|
87
|
-
|
123
|
+
|
124
|
+
value = type_info[elem]
|
125
|
+
|
126
|
+
unless value
|
127
|
+
raise ArgumentError, "scim schema element must be one of #{type_info.keys.inspect}"
|
128
|
+
end
|
129
|
+
|
130
|
+
value
|
88
131
|
end
|
89
132
|
|
90
133
|
def jkey(k) @key_style == :down ? k.to_s : k end
|
@@ -106,12 +149,8 @@ class Scim
|
|
106
149
|
def initialize(target, auth_header, options = {})
|
107
150
|
@target, @auth_header = target, auth_header
|
108
151
|
@key_style = options[:symbolize_keys] ? :downsym : :down
|
109
|
-
self.skip_ssl_validation = options[:skip_ssl_validation]
|
110
|
-
self.ssl_ca_file = options[:ssl_ca_file]
|
111
|
-
self.ssl_cert_store = options[:ssl_cert_store]
|
112
|
-
self.http_proxy = options[:http_proxy]
|
113
|
-
self.https_proxy = options[:https_proxy]
|
114
152
|
@zone = options[:zone]
|
153
|
+
initialize_http_options(options)
|
115
154
|
end
|
116
155
|
|
117
156
|
# Convenience method to get the naming attribute, e.g. userName for user,
|
@@ -226,6 +265,14 @@ class Scim
|
|
226
265
|
info
|
227
266
|
end
|
228
267
|
|
268
|
+
# Get meta information about client
|
269
|
+
# @param client_id
|
270
|
+
# @return (client meta)
|
271
|
+
def get_client_meta(client_id)
|
272
|
+
path = type_info(:client, :path)
|
273
|
+
json_get(@target, "#{path}/#{URI.encode(client_id)}/meta", @key_style, headers)
|
274
|
+
end
|
275
|
+
|
229
276
|
# Collects all pages of entries from a query
|
230
277
|
# @param type (see #query)
|
231
278
|
# @param [Hash] query may contain the following keys:
|
@@ -256,9 +303,16 @@ class Scim
|
|
256
303
|
# @param type (see #add)
|
257
304
|
# @return [Array] array of name/id hashes for each object found
|
258
305
|
def ids(type, *names)
|
259
|
-
|
260
|
-
|
261
|
-
|
306
|
+
name_attr = type_info(type, :name_attr)
|
307
|
+
origin_attr = type_info(type, :origin_attr)
|
308
|
+
|
309
|
+
filter = names.map do |n|
|
310
|
+
"#{name_attr} eq \"#{n}\""
|
311
|
+
end
|
312
|
+
|
313
|
+
attributes = ['id', name_attr, origin_attr]
|
314
|
+
|
315
|
+
all_pages(type, attributes: attributes.join(','), filter: filter.join(' or '))
|
262
316
|
end
|
263
317
|
|
264
318
|
# Convenience method to query for single object by name.
|
data/lib/uaa/token_coder.rb
CHANGED
@@ -113,7 +113,7 @@ class TokenCoder
|
|
113
113
|
signature = Util.decode64(crypto_segment)
|
114
114
|
if ["HS256", "HS384", "HS512"].include?(algo)
|
115
115
|
raise InvalidSignature, "Signature verification failed" unless
|
116
|
-
options[:skey] && signature
|
116
|
+
options[:skey] && constant_time_compare(signature, OpenSSL::HMAC.digest(init_digest(algo), options[:skey], signing_input))
|
117
117
|
elsif ["RS256", "RS384", "RS512"].include?(algo)
|
118
118
|
raise InvalidSignature, "Signature verification failed" unless
|
119
119
|
options[:pkey] && options[:pkey].verify(init_digest(algo), signature, signing_input)
|
@@ -123,6 +123,24 @@ class TokenCoder
|
|
123
123
|
payload
|
124
124
|
end
|
125
125
|
|
126
|
+
# Takes constant time to compare 2 strings (HMAC digests in this case)
|
127
|
+
# to avoid timing attacks while comparing the HMAC digests
|
128
|
+
# @param [String] a: the first digest to compare
|
129
|
+
# @param [String] b: the second digest to compare
|
130
|
+
# @return [boolean] true if they are equal, false otherwise
|
131
|
+
def self.constant_time_compare(a, b)
|
132
|
+
if a.length != b.length
|
133
|
+
return false
|
134
|
+
end
|
135
|
+
|
136
|
+
result = 0
|
137
|
+
a.chars.zip(b.chars).each do |x, y|
|
138
|
+
result |= x.ord ^ y.ord
|
139
|
+
end
|
140
|
+
|
141
|
+
result == 0
|
142
|
+
end
|
143
|
+
|
126
144
|
# Creates a new token en/decoder for a service that is associated with
|
127
145
|
# the the audience_ids, the symmetrical token validation key, and the
|
128
146
|
# public and/or private keys.
|
data/lib/uaa/token_issuer.rb
CHANGED
@@ -109,11 +109,7 @@ class TokenIssuer
|
|
109
109
|
@target, @client_id, @client_secret = target, client_id, client_secret
|
110
110
|
@token_target = options[:token_target] || target
|
111
111
|
@key_style = options[:symbolize_keys] ? :sym : nil
|
112
|
-
|
113
|
-
self.ssl_ca_file = options[:ssl_ca_file]
|
114
|
-
self.ssl_cert_store = options[:ssl_cert_store]
|
115
|
-
self.http_proxy = options[:http_proxy]
|
116
|
-
self.https_proxy = options[:https_proxy]
|
112
|
+
initialize_http_options(options)
|
117
113
|
end
|
118
114
|
|
119
115
|
# Allows an app to discover what credentials are required for
|