cf-uaa-lib 3.13.0 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/dependabot.yml +11 -0
- data/.github/workflows/gem-push.yml +29 -0
- data/.github/workflows/ruby.yml +27 -0
- data/README.md +1 -1
- data/cf-uaa-lib.gemspec +9 -9
- data/lib/uaa/http.rb +16 -1
- data/lib/uaa/info.rb +3 -13
- data/lib/uaa/scim.rb +12 -14
- data/lib/uaa/token_coder.rb +4 -4
- data/lib/uaa/token_issuer.rb +21 -16
- data/lib/uaa/util.rb +1 -1
- data/lib/uaa/version.rb +1 -1
- data/spec/http_spec.rb +28 -4
- data/spec/info_spec.rb +3 -3
- data/spec/integration_spec.rb +45 -17
- data/spec/scim_spec.rb +11 -11
- data/spec/spec_helper.rb +7 -1
- data/spec/token_coder_spec.rb +14 -14
- data/spec/token_issuer_spec.rb +29 -25
- metadata +74 -46
- data/.travis.yml +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e8a741ef102742885eff043d198628e3bc05f0bcc98c752a57ce5f6b3c18bf43
|
4
|
+
data.tar.gz: 6ffc61520e49b04d65bd0404f577727986e3f625e6ef095fc8a0d361c50be5ab
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 75d8062f4bdc94bc7375a18124f72b3664a2d4074a63699c4e55f52ae085375ae587e7c113859b345bd7476878d744ceb66320cd42ccc9ef3e02f0315e591f78
|
7
|
+
data.tar.gz: 223a89a483dfe638062240e4326e8c8e485b62a1e07108ed87feb5fff67ce1f5a705923939f23d6135bd4eeb6f8638cd60ae7638cc62ff2e1376aafe94720051
|
@@ -0,0 +1,29 @@
|
|
1
|
+
name: Ruby Gem
|
2
|
+
|
3
|
+
on: workflow_dispatch
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
build:
|
7
|
+
name: Build + Publish
|
8
|
+
runs-on: ubuntu-latest
|
9
|
+
permissions:
|
10
|
+
contents: read
|
11
|
+
packages: write
|
12
|
+
|
13
|
+
steps:
|
14
|
+
- uses: actions/checkout@v2
|
15
|
+
- name: Set up Ruby 2.6
|
16
|
+
uses: actions/setup-ruby@v1
|
17
|
+
with:
|
18
|
+
ruby-version: 2.6.x
|
19
|
+
|
20
|
+
- name: Publish to RubyGems
|
21
|
+
run: |
|
22
|
+
mkdir -p $HOME/.gem
|
23
|
+
touch $HOME/.gem/credentials
|
24
|
+
chmod 0600 $HOME/.gem/credentials
|
25
|
+
printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
|
26
|
+
gem build *.gemspec
|
27
|
+
gem push *.gem
|
28
|
+
env:
|
29
|
+
GEM_HOST_API_KEY: "${{ secrets.RUBYGEMS_AUTH_TOKEN }}"
|
@@ -0,0 +1,27 @@
|
|
1
|
+
name: Ruby
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches: [ master ]
|
6
|
+
pull_request:
|
7
|
+
branches: [ master ]
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
test:
|
11
|
+
|
12
|
+
runs-on: ubuntu-latest
|
13
|
+
strategy:
|
14
|
+
matrix:
|
15
|
+
ruby-version: ['2.5', '2.7', '3.0', '3.1']
|
16
|
+
|
17
|
+
steps:
|
18
|
+
- uses: actions/checkout@v2
|
19
|
+
- name: Set up Ruby
|
20
|
+
uses: ruby/setup-ruby@v1
|
21
|
+
with:
|
22
|
+
ruby-version: ${{ matrix.ruby-version }}
|
23
|
+
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
24
|
+
- name: Run tests
|
25
|
+
run: bundle exec rake
|
26
|
+
- name: Run coverage
|
27
|
+
run: bundle exec rake cov
|
data/README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# CloudFoundry UAA Gem
|
2
|
-
|
2
|
+
![Build status](https://github.com/cloudfoundry/cf-uaa-lib/actions/workflows/ruby.yml/badge.svg?branch=master)
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/cf-uaa-lib.png)](http://badge.fury.io/rb/cf-uaa-lib)
|
4
4
|
|
5
5
|
Client gem for interacting with the [CloudFoundry UAA server](https://github.com/cloudfoundry/uaa)
|
data/cf-uaa-lib.gemspec
CHANGED
@@ -24,8 +24,6 @@ Gem::Specification.new do |s|
|
|
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
|
-
s.rubyforge_project = "cf-uaa-lib"
|
28
|
-
|
29
27
|
s.license = "Apache-2.0"
|
30
28
|
s.files = `git ls-files`.split("\n")
|
31
29
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
@@ -33,15 +31,17 @@ Gem::Specification.new do |s|
|
|
33
31
|
s.require_paths = ['lib']
|
34
32
|
|
35
33
|
# dependencies
|
36
|
-
s.add_dependency 'multi_json', '
|
34
|
+
s.add_dependency 'multi_json', '>= 1.12.1', '< 1.16'
|
37
35
|
s.add_dependency 'httpclient', '~> 2.8', '>= 2.8.2.4'
|
36
|
+
s.add_dependency 'addressable', '~> 2.8', '>= 2.8.0'
|
38
37
|
|
39
|
-
s.add_development_dependency 'bundler', '~>
|
40
|
-
s.add_development_dependency 'rake', '
|
41
|
-
s.add_development_dependency 'rspec', '
|
42
|
-
s.add_development_dependency 'simplecov', '~> 0.
|
38
|
+
s.add_development_dependency 'bundler', '~> 2.2'
|
39
|
+
s.add_development_dependency 'rake', '>= 10.3.2', '~> 13.0'
|
40
|
+
s.add_development_dependency 'rspec', '>= 2.14.1', '~> 3.9'
|
41
|
+
s.add_development_dependency 'simplecov', '~> 0.21.2'
|
43
42
|
s.add_development_dependency 'simplecov-rcov', '~> 0.2.3'
|
44
|
-
s.add_development_dependency 'ci_reporter', '
|
45
|
-
s.add_development_dependency 'json_pure', '
|
43
|
+
s.add_development_dependency 'ci_reporter', '>= 1.9.2', '~> 2.0'
|
44
|
+
s.add_development_dependency 'json_pure', '>= 1.8.1', '~> 2.5'
|
45
|
+
s.add_development_dependency 'ci_reporter_rspec', '~> 1.0'
|
46
46
|
|
47
47
|
end
|
data/lib/uaa/http.rb
CHANGED
@@ -48,10 +48,17 @@ module Http
|
|
48
48
|
|
49
49
|
def self.included(base)
|
50
50
|
base.class_eval do
|
51
|
-
|
51
|
+
attr_reader :skip_ssl_validation, :ssl_ca_file, :ssl_cert_store, :http_timeout
|
52
52
|
end
|
53
53
|
end
|
54
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
|
+
|
55
62
|
# Sets the current logger instance to recieve error messages.
|
56
63
|
# @param [Logger] logr
|
57
64
|
# @return [Logger]
|
@@ -176,6 +183,8 @@ module Http
|
|
176
183
|
raise SSLException, "Invalid SSL Cert for #{url}. Use '--skip-ssl-validation' to continue with an insecure target"
|
177
184
|
rescue URI::Error, SocketError, SystemCallError => e
|
178
185
|
raise BadTarget, "error: #{e.message}"
|
186
|
+
rescue HTTPClient::ConnectTimeoutError => e
|
187
|
+
raise HTTPException.new "http timeout"
|
179
188
|
end
|
180
189
|
|
181
190
|
def http_request(uri)
|
@@ -201,6 +210,12 @@ module Http
|
|
201
210
|
http = HTTPClient.new
|
202
211
|
end
|
203
212
|
|
213
|
+
if http_timeout
|
214
|
+
http.connect_timeout = http_timeout
|
215
|
+
http.send_timeout = http_timeout
|
216
|
+
http.receive_timeout = http_timeout
|
217
|
+
end
|
218
|
+
|
204
219
|
@http_cache[cache_key] = http
|
205
220
|
end
|
206
221
|
|
data/lib/uaa/info.rb
CHANGED
@@ -31,10 +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]
|
35
|
+
initialize_http_options(options)
|
38
36
|
end
|
39
37
|
|
40
38
|
# sets whether the keys in returned hashes should be symbols.
|
@@ -56,14 +54,6 @@ class Info
|
|
56
54
|
json_get(target, "/userinfo?schema=openid", key_style, "authorization" => auth_header)
|
57
55
|
end
|
58
56
|
|
59
|
-
# Gets various monitoring and status variables from the server.
|
60
|
-
# Authenticates using +name+ and +pwd+ for basic authentication.
|
61
|
-
# @param (see Misc.server)
|
62
|
-
# @return [Hash]
|
63
|
-
def varz(name, pwd)
|
64
|
-
json_get(target, "/varz", key_style, "authorization" => Http.basic_auth(name, pwd))
|
65
|
-
end
|
66
|
-
|
67
57
|
# Gets basic information about the target server, including version number,
|
68
58
|
# commit ID, and links to API endpoints.
|
69
59
|
# @return [Hash]
|
@@ -131,7 +121,7 @@ class Info
|
|
131
121
|
# @return [Hash] contents of the token
|
132
122
|
def decode_token(client_id, client_secret, token, token_type = "bearer", audience_ids = nil)
|
133
123
|
reply = json_parse_reply(key_style, *request(target, :post, '/check_token',
|
134
|
-
Util.encode_form(:
|
124
|
+
Util.encode_form(token: token),
|
135
125
|
"authorization" => Http.basic_auth(client_id, client_secret),
|
136
126
|
"content-type" => Http::FORM_UTF8,"accept" => Http::JSON_UTF8))
|
137
127
|
|
@@ -148,7 +138,7 @@ class Info
|
|
148
138
|
# @return [Hash]
|
149
139
|
def password_strength(password)
|
150
140
|
json_parse_reply(key_style, *request(target, :post, '/password/score',
|
151
|
-
Util.encode_form(:
|
141
|
+
Util.encode_form(password: password), "content-type" => Http::FORM_UTF8,
|
152
142
|
"accept" => Http::JSON_UTF8))
|
153
143
|
end
|
154
144
|
|
data/lib/uaa/scim.rb
CHANGED
@@ -12,7 +12,7 @@
|
|
12
12
|
#++
|
13
13
|
|
14
14
|
require 'uaa/http'
|
15
|
-
require 'uri'
|
15
|
+
require 'addressable/uri'
|
16
16
|
|
17
17
|
module CF::UAA
|
18
18
|
|
@@ -149,10 +149,8 @@ class Scim
|
|
149
149
|
def initialize(target, auth_header, options = {})
|
150
150
|
@target, @auth_header = target, auth_header
|
151
151
|
@key_style = options[:symbolize_keys] ? :downsym : :down
|
152
|
-
self.skip_ssl_validation = options[:skip_ssl_validation]
|
153
|
-
self.ssl_ca_file = options[:ssl_ca_file]
|
154
|
-
self.ssl_cert_store = options[:ssl_cert_store]
|
155
152
|
@zone = options[:zone]
|
153
|
+
initialize_http_options(options)
|
156
154
|
end
|
157
155
|
|
158
156
|
# Convenience method to get the naming attribute, e.g. userName for user,
|
@@ -179,7 +177,7 @@ class Scim
|
|
179
177
|
# @param [String] id the id attribute of the SCIM object
|
180
178
|
# @return [nil]
|
181
179
|
def delete(type, id)
|
182
|
-
http_delete @target, "#{type_info(type, :path)}/#{URI.encode(id)}", @auth_header, @zone
|
180
|
+
http_delete @target, "#{type_info(type, :path)}/#{Addressable::URI.encode(id)}", @auth_header, @zone
|
183
181
|
end
|
184
182
|
|
185
183
|
# Replaces the contents of a SCIM object.
|
@@ -194,7 +192,7 @@ class Scim
|
|
194
192
|
hdrs.merge!('if-match' => etag)
|
195
193
|
end
|
196
194
|
reply = json_parse_reply(@key_style,
|
197
|
-
*json_put(@target, "#{path}/#{URI.encode(id)}", info, hdrs))
|
195
|
+
*json_put(@target, "#{path}/#{Addressable::URI.encode(id)}", info, hdrs))
|
198
196
|
|
199
197
|
# hide client endpoints that are not quite scim compatible
|
200
198
|
type == :client && !reply ? get(type, info['client_id']): reply
|
@@ -212,7 +210,7 @@ class Scim
|
|
212
210
|
hdrs.merge!('if-match' => etag)
|
213
211
|
end
|
214
212
|
reply = json_parse_reply(@key_style,
|
215
|
-
*json_patch(@target, "#{path}/#{URI.encode(id)}", info, hdrs))
|
213
|
+
*json_patch(@target, "#{path}/#{Addressable::URI.encode(id)}", info, hdrs))
|
216
214
|
|
217
215
|
# hide client endpoints that are not quite scim compatible
|
218
216
|
type == :client && !reply ? get(type, info['client_id']): reply
|
@@ -260,7 +258,7 @@ class Scim
|
|
260
258
|
# @param (see #delete)
|
261
259
|
# @return (see #add)
|
262
260
|
def get(type, id)
|
263
|
-
info = json_get(@target, "#{type_info(type, :path)}/#{URI.encode(id)}",
|
261
|
+
info = json_get(@target, "#{type_info(type, :path)}/#{Addressable::URI.encode(id)}",
|
264
262
|
@key_style, headers)
|
265
263
|
|
266
264
|
fake_client_id(info) if type == :client # hide client reply, not quite scim
|
@@ -272,7 +270,7 @@ class Scim
|
|
272
270
|
# @return (client meta)
|
273
271
|
def get_client_meta(client_id)
|
274
272
|
path = type_info(:client, :path)
|
275
|
-
json_get(@target, "#{path}/#{URI.encode(client_id)}/meta", @key_style, headers)
|
273
|
+
json_get(@target, "#{path}/#{Addressable::URI.encode(client_id)}/meta", @key_style, headers)
|
276
274
|
end
|
277
275
|
|
278
276
|
# Collects all pages of entries from a query
|
@@ -352,7 +350,7 @@ class Scim
|
|
352
350
|
req = {"password" => new_password}
|
353
351
|
req["oldPassword"] = old_password if old_password
|
354
352
|
json_parse_reply(@key_style, *json_put(@target,
|
355
|
-
"#{type_info(:user, :path)}/#{URI.encode(user_id)}/password", req, headers))
|
353
|
+
"#{type_info(:user, :path)}/#{Addressable::URI.encode(user_id)}/password", req, headers))
|
356
354
|
end
|
357
355
|
|
358
356
|
# Change client secret.
|
@@ -368,18 +366,18 @@ class Scim
|
|
368
366
|
req = {"secret" => new_secret }
|
369
367
|
req["oldSecret"] = old_secret if old_secret
|
370
368
|
json_parse_reply(@key_style, *json_put(@target,
|
371
|
-
"#{type_info(:client, :path)}/#{URI.encode(client_id)}/secret", req, headers))
|
369
|
+
"#{type_info(:client, :path)}/#{Addressable::URI.encode(client_id)}/secret", req, headers))
|
372
370
|
end
|
373
371
|
|
374
372
|
def unlock_user(user_id)
|
375
373
|
req = {"locked" => false}
|
376
374
|
json_parse_reply(@key_style, *json_patch(@target,
|
377
|
-
"#{type_info(:user, :path)}/#{URI.encode(user_id)}/status", req, headers))
|
375
|
+
"#{type_info(:user, :path)}/#{Addressable::URI.encode(user_id)}/status", req, headers))
|
378
376
|
end
|
379
377
|
|
380
378
|
def map_group(group, is_id, external_group, origin = "ldap")
|
381
379
|
key_name = is_id ? :groupId : :displayName
|
382
|
-
request = {key_name => group, :
|
380
|
+
request = {key_name => group, externalGroup: external_group, schemas: ["urn:scim:schemas:core:1.0"], origin: origin }
|
383
381
|
result = json_parse_reply(@key_style, *json_post(@target,
|
384
382
|
"#{type_info(:group_mapping, :path)}", request,
|
385
383
|
headers))
|
@@ -387,7 +385,7 @@ class Scim
|
|
387
385
|
end
|
388
386
|
|
389
387
|
def unmap_group(group_id, external_group, origin = "ldap")
|
390
|
-
http_delete(@target, "#{type_info(:group_mapping, :path)}/groupId/#{group_id}/externalGroup/#{URI.encode(external_group)}/origin/#{origin}",
|
388
|
+
http_delete(@target, "#{type_info(:group_mapping, :path)}/groupId/#{group_id}/externalGroup/#{Addressable::URI.encode(external_group)}/origin/#{origin}",
|
391
389
|
@auth_header, @zone)
|
392
390
|
end
|
393
391
|
|
data/lib/uaa/token_coder.rb
CHANGED
@@ -65,7 +65,7 @@ class TokenCoder
|
|
65
65
|
unless options.is_a?(Hash) && obsolete1.nil? && obsolete2.nil?
|
66
66
|
# deprecated: def self.encode(token_body, skey, pkey = nil, algo = 'HS256')
|
67
67
|
warn "#{self.class}##{__method__} is deprecated with these parameters. Please use options hash."
|
68
|
-
options = {:
|
68
|
+
options = {skey: options }
|
69
69
|
options[:pkey], options[:algorithm] = obsolete1, obsolete2
|
70
70
|
end
|
71
71
|
options = normalize_options(options)
|
@@ -96,7 +96,7 @@ class TokenCoder
|
|
96
96
|
unless options.is_a?(Hash) && obsolete1.nil? && obsolete2.nil?
|
97
97
|
# deprecated: def self.decode(token, skey = nil, pkey = nil, verify = true)
|
98
98
|
warn "#{self.class}##{__method__} is deprecated with these parameters. Please use options hash."
|
99
|
-
options = {:
|
99
|
+
options = {skey: options }
|
100
100
|
options[:pkey], options[:verify] = obsolete1, obsolete2
|
101
101
|
end
|
102
102
|
options = normalize_options(options)
|
@@ -170,7 +170,7 @@ class TokenCoder
|
|
170
170
|
unless options.is_a?(Hash) && obsolete1.nil? && obsolete2.nil?
|
171
171
|
# deprecated: def initialize(audience_ids, skey, pkey = nil)
|
172
172
|
warn "#{self.class}##{__method__} is deprecated with these parameters. Please use options hash."
|
173
|
-
options = {:
|
173
|
+
options = {audience_ids: options }
|
174
174
|
options[:skey], options[:pkey] = obsolete1, obsolete2
|
175
175
|
end
|
176
176
|
@options = self.class.normalize_options(options)
|
@@ -184,7 +184,7 @@ class TokenCoder
|
|
184
184
|
def encode(token_body = {}, algorithm = nil)
|
185
185
|
token_body[:aud] = @options[:audience_ids] if @options[:audience_ids] && !token_body[:aud] && !token_body['aud']
|
186
186
|
token_body[:exp] = Time.now.to_i + 7 * 24 * 60 * 60 unless token_body[:exp] || token_body['exp']
|
187
|
-
self.class.encode(token_body, algorithm ? @options.merge(:
|
187
|
+
self.class.encode(token_body, algorithm ? @options.merge(algorithm: algorithm) : @options)
|
188
188
|
end
|
189
189
|
|
190
190
|
# Returns hash of values decoded from the token contents. If the
|
data/lib/uaa/token_issuer.rb
CHANGED
@@ -13,6 +13,7 @@
|
|
13
13
|
|
14
14
|
require 'securerandom'
|
15
15
|
require 'uaa/http'
|
16
|
+
require 'cgi'
|
16
17
|
|
17
18
|
module CF::UAA
|
18
19
|
|
@@ -72,8 +73,13 @@ class TokenIssuer
|
|
72
73
|
if scope = Util.arglist(params.delete(:scope))
|
73
74
|
params[:scope] = Util.strlist(scope)
|
74
75
|
end
|
75
|
-
headers = {'content-type' => FORM_UTF8, 'accept' => JSON_UTF8
|
76
|
-
|
76
|
+
headers = {'content-type' => FORM_UTF8, 'accept' => JSON_UTF8}
|
77
|
+
if @basic_auth
|
78
|
+
headers['authorization'] = Http.basic_auth(@client_id, @client_secret)
|
79
|
+
else
|
80
|
+
headers['X-CF-ENCODED-CREDENTIALS'] = 'true'
|
81
|
+
headers['authorization'] = Http.basic_auth(CGI.escape(@client_id), CGI.escape(@client_secret))
|
82
|
+
end
|
77
83
|
reply = json_parse_reply(@key_style, *request(@token_target, :post,
|
78
84
|
'/oauth/token', Util.encode_form(params), headers))
|
79
85
|
raise BadResponse unless reply[jkey :token_type] && reply[jkey :access_token]
|
@@ -81,8 +87,8 @@ class TokenIssuer
|
|
81
87
|
end
|
82
88
|
|
83
89
|
def authorize_path_args(response_type, redirect_uri, scope, state = random_state, args = {})
|
84
|
-
params = args.merge(:
|
85
|
-
:
|
90
|
+
params = args.merge(client_id: @client_id, response_type: response_type,
|
91
|
+
redirect_uri: redirect_uri, state: state)
|
86
92
|
params[:scope] = scope = Util.strlist(scope) if scope = Util.arglist(scope)
|
87
93
|
params[:nonce] = state
|
88
94
|
"/oauth/authorize?#{Util.encode_form(params)}"
|
@@ -109,9 +115,8 @@ class TokenIssuer
|
|
109
115
|
@target, @client_id, @client_secret = target, client_id, client_secret
|
110
116
|
@token_target = options[:token_target] || target
|
111
117
|
@key_style = options[:symbolize_keys] ? :sym : nil
|
112
|
-
|
113
|
-
|
114
|
-
self.ssl_cert_store = options[:ssl_cert_store]
|
118
|
+
@basic_auth = options[:basic_auth] == true ? true : false
|
119
|
+
initialize_http_options(options)
|
115
120
|
end
|
116
121
|
|
117
122
|
# Allows an app to discover what credentials are required for
|
@@ -139,7 +144,7 @@ class TokenIssuer
|
|
139
144
|
|
140
145
|
# the accept header is only here so the uaa will issue error replies in json to aid debugging
|
141
146
|
headers = {'content-type' => FORM_UTF8, 'accept' => JSON_UTF8 }
|
142
|
-
body = Util.encode_form(credentials.merge(:
|
147
|
+
body = Util.encode_form(credentials.merge(source: 'credentials'))
|
143
148
|
status, body, headers = request(@target, :post, uri, body, headers)
|
144
149
|
raise BadResponse, "status #{status}" unless status == 302
|
145
150
|
req_uri, reply_uri = URI.parse(redir_uri), URI.parse(headers['location'])
|
@@ -196,7 +201,7 @@ class TokenIssuer
|
|
196
201
|
reply = json_parse_reply(nil, *request(@target, :post, "/autologin", body, headers))
|
197
202
|
raise BadResponse, "no autologin code in reply" unless reply['code']
|
198
203
|
@target + authorize_path_args('code', redirect_uri, scope,
|
199
|
-
random_state, :
|
204
|
+
random_state, code: reply['code'])
|
200
205
|
end
|
201
206
|
|
202
207
|
# Constructs a uri that the client is to return to the browser to direct
|
@@ -230,8 +235,8 @@ class TokenIssuer
|
|
230
235
|
rescue URI::InvalidURIError, ArgumentError, BadResponse
|
231
236
|
raise BadResponse, "received invalid response from target #{@target}"
|
232
237
|
end
|
233
|
-
request_token(:
|
234
|
-
:
|
238
|
+
request_token(grant_type: 'authorization_code', code: authcode,
|
239
|
+
redirect_uri: ac_params['redirect_uri'])
|
235
240
|
end
|
236
241
|
|
237
242
|
# Uses the instance client credentials in addition to the +username+
|
@@ -239,15 +244,15 @@ class TokenIssuer
|
|
239
244
|
# See {http://tools.ietf.org/html/rfc6749#section-4.3}.
|
240
245
|
# @return [TokenInfo]
|
241
246
|
def owner_password_grant(username, password, scope = nil)
|
242
|
-
request_token(:
|
243
|
-
:
|
247
|
+
request_token(grant_type: 'password', username: username,
|
248
|
+
password: password, scope: scope)
|
244
249
|
end
|
245
250
|
|
246
251
|
# Uses a one-time passcode obtained from the UAA to get a
|
247
252
|
# token.
|
248
253
|
# @return [TokenInfo]
|
249
254
|
def passcode_grant(passcode, scope = nil)
|
250
|
-
request_token(:
|
255
|
+
request_token(grant_type: 'password', passcode: passcode, scope: scope)
|
251
256
|
end
|
252
257
|
|
253
258
|
# Gets an access token with the user credentials used for authentication
|
@@ -266,14 +271,14 @@ class TokenIssuer
|
|
266
271
|
# credentials grant. See http://tools.ietf.org/html/rfc6749#section-4.4
|
267
272
|
# @return [TokenInfo]
|
268
273
|
def client_credentials_grant(scope = nil)
|
269
|
-
request_token(:
|
274
|
+
request_token(grant_type: 'client_credentials', scope: scope)
|
270
275
|
end
|
271
276
|
|
272
277
|
# Uses the instance client credentials and the given +refresh_token+ to get
|
273
278
|
# a new access token. See http://tools.ietf.org/html/rfc6749#section-6
|
274
279
|
# @return [TokenInfo] which may include a new refresh token as well as an access token.
|
275
280
|
def refresh_token_grant(refresh_token, scope = nil)
|
276
|
-
request_token(:
|
281
|
+
request_token(grant_type: 'refresh_token', refresh_token: refresh_token, scope: scope)
|
277
282
|
end
|
278
283
|
|
279
284
|
end
|
data/lib/uaa/util.rb
CHANGED
@@ -145,7 +145,7 @@ class Util
|
|
145
145
|
|
146
146
|
# Converts +obj+ to nicely formatted JSON
|
147
147
|
# @return [String] obj in formatted json
|
148
|
-
def self.json_pretty(obj) MultiJson.dump(obj, :
|
148
|
+
def self.json_pretty(obj) MultiJson.dump(obj, pretty: true) end
|
149
149
|
|
150
150
|
# Converts +obj+ to a URL-safe base 64 encoded string
|
151
151
|
# @return [String]
|
data/lib/uaa/version.rb
CHANGED
data/spec/http_spec.rb
CHANGED
@@ -69,7 +69,7 @@ describe CF::UAA::Http do
|
|
69
69
|
let(:ssl_config) { double('ssl_config') }
|
70
70
|
|
71
71
|
it 'sets verify mode to VERIFY_NONE' do
|
72
|
-
http_instance.skip_ssl_validation
|
72
|
+
http_instance.initialize_http_options({skip_ssl_validation: true})
|
73
73
|
|
74
74
|
expect(http_double).to receive(:ssl_config).and_return(ssl_config)
|
75
75
|
expect(ssl_config).to receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE)
|
@@ -91,7 +91,7 @@ describe CF::UAA::Http do
|
|
91
91
|
let(:ssl_config) { double('ssl_config') }
|
92
92
|
|
93
93
|
it 'passes it' do
|
94
|
-
http_instance.ssl_ca_file
|
94
|
+
http_instance.initialize_http_options({ssl_ca_file: '/fake-ca-file'})
|
95
95
|
|
96
96
|
expect(http_double).to receive(:ssl_config).and_return(ssl_config).twice
|
97
97
|
expect(ssl_config).to receive(:set_trust_ca).with('/fake-ca-file')
|
@@ -105,7 +105,7 @@ describe CF::UAA::Http do
|
|
105
105
|
let(:ssl_config) { double('ssl_config') }
|
106
106
|
|
107
107
|
it 'passes it' do
|
108
|
-
http_instance.ssl_cert_store
|
108
|
+
http_instance.initialize_http_options({ssl_cert_store: cert_store})
|
109
109
|
|
110
110
|
expect(http_double).to receive(:ssl_config).and_return(ssl_config).twice
|
111
111
|
expect(ssl_config).to receive(:cert_store=).with(cert_store)
|
@@ -114,5 +114,29 @@ describe CF::UAA::Http do
|
|
114
114
|
http_instance.http_get('https://uncached.example.com')
|
115
115
|
end
|
116
116
|
end
|
117
|
+
|
118
|
+
context 'when an http request timeout is provided' do
|
119
|
+
it 'sets all timeouts on the http clien to the http_timeout' do
|
120
|
+
http_instance.initialize_http_options({http_timeout: 10})
|
121
|
+
|
122
|
+
expect(http_double).to receive(:connect_timeout=).with(10)
|
123
|
+
expect(http_double).to receive(:send_timeout=).with(10)
|
124
|
+
expect(http_double).to receive(:receive_timeout=).with(10)
|
125
|
+
|
126
|
+
http_instance.http_get('https://uncached.example.com')
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
context 'when an http request timeout is not provided' do
|
131
|
+
it 'does not override the default' do
|
132
|
+
http_instance.initialize_http_options({})
|
133
|
+
|
134
|
+
expect(http_double).not_to receive(:connect_timeout=)
|
135
|
+
expect(http_double).not_to receive(:send_timeout=)
|
136
|
+
expect(http_double).not_to receive(:receive_timeout=)
|
137
|
+
|
138
|
+
http_instance.http_get('https://uncached.example.com')
|
139
|
+
end
|
140
|
+
end
|
117
141
|
end
|
118
|
-
end
|
142
|
+
end
|
data/spec/info_spec.rb
CHANGED
@@ -34,7 +34,7 @@ module CF::UAA
|
|
34
34
|
end
|
35
35
|
|
36
36
|
describe 'initialize' do
|
37
|
-
let(:options) { {:
|
37
|
+
let(:options) { {skip_ssl_validation: true} }
|
38
38
|
|
39
39
|
it 'sets proxy information' do
|
40
40
|
uaa_info.skip_ssl_validation == true
|
@@ -52,7 +52,7 @@ module CF::UAA
|
|
52
52
|
end
|
53
53
|
|
54
54
|
context 'with symbolize_keys keys true' do
|
55
|
-
let(:options) { {:
|
55
|
+
let(:options) { {symbolize_keys: true} }
|
56
56
|
|
57
57
|
it 'gets server info' do
|
58
58
|
result = uaa_info.server
|
@@ -84,7 +84,7 @@ module CF::UAA
|
|
84
84
|
end
|
85
85
|
|
86
86
|
context 'with symbolize_keys keys true' do
|
87
|
-
let(:options) { {:
|
87
|
+
let(:options) { {symbolize_keys: true} }
|
88
88
|
|
89
89
|
it 'gets UAA target' do
|
90
90
|
result = uaa_info.discover_uaa
|
data/spec/integration_spec.rb
CHANGED
@@ -41,7 +41,35 @@ module CF::UAA
|
|
41
41
|
target = ENV['UAA_CLIENT_TARGET']
|
42
42
|
|
43
43
|
admin_token_issuer = TokenIssuer.new(target, admin_client, admin_secret, options)
|
44
|
-
Scim.new(target, admin_token_issuer.client_credentials_grant.auth_header, options.merge(:
|
44
|
+
Scim.new(target, admin_token_issuer.client_credentials_grant.auth_header, options.merge(symbolize_keys: true))
|
45
|
+
end
|
46
|
+
|
47
|
+
describe 'when UAA does not respond' do
|
48
|
+
let(:http_timeout) { 0.01 }
|
49
|
+
let(:default_http_client_timeout) { 60 }
|
50
|
+
let(:scim) { Scim.new(@target, "", {http_timeout: http_timeout}) }
|
51
|
+
let(:token_issuer) { TokenIssuer.new(@target, "", "", {http_timeout: http_timeout}) }
|
52
|
+
let(:blackhole_ip) { '10.255.255.1'}
|
53
|
+
|
54
|
+
before do
|
55
|
+
@target = "http://#{blackhole_ip}"
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'times out the connection at the configured time for the scim' do
|
59
|
+
expect {
|
60
|
+
Timeout.timeout(default_http_client_timeout - 1) do
|
61
|
+
scim.get(:user, "admin")
|
62
|
+
end
|
63
|
+
}.to raise_error HTTPException
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'times out the connection at the configured time for the token issuer' do
|
67
|
+
expect {
|
68
|
+
Timeout.timeout(default_http_client_timeout - 1) do
|
69
|
+
token_issuer.client_credentials_grant
|
70
|
+
end
|
71
|
+
}.to raise_error HTTPException
|
72
|
+
end
|
45
73
|
end
|
46
74
|
|
47
75
|
if ENV['UAA_CLIENT_TARGET']
|
@@ -49,20 +77,20 @@ module CF::UAA
|
|
49
77
|
|
50
78
|
let(:options) { @options }
|
51
79
|
let(:token_issuer) { TokenIssuer.new(@target, @test_client, @test_secret, options) }
|
52
|
-
let(:scim) { Scim.new(@target, token_issuer.client_credentials_grant.auth_header, options.merge(:
|
80
|
+
let(:scim) { Scim.new(@target, token_issuer.client_credentials_grant.auth_header, options.merge(symbolize_keys: true)) }
|
53
81
|
|
54
82
|
before :all do
|
55
83
|
@options = {}
|
56
84
|
if ENV['SKIP_SSL_VALIDATION']
|
57
|
-
@options = {:
|
85
|
+
@options = {skip_ssl_validation: true}
|
58
86
|
end
|
59
87
|
@target = ENV['UAA_CLIENT_TARGET']
|
60
88
|
@test_client = "test_client_#{Time.now.to_i}"
|
61
89
|
@test_secret = '+=tEsTsEcRet~!@'
|
62
90
|
gids = ['clients.read', 'scim.read', 'scim.write', 'uaa.resource', 'password.write']
|
63
|
-
test_client = CF::UAA::admin_scim(@options).add(:client, :
|
64
|
-
:
|
65
|
-
:
|
91
|
+
test_client = CF::UAA::admin_scim(@options).add(:client, client_id: @test_client, client_secret: @test_secret,
|
92
|
+
authorities: gids, authorized_grant_types: ['client_credentials', 'password'],
|
93
|
+
scope: ['openid', 'password.write'])
|
66
94
|
expect(test_client[:client_id]).to eq(@test_client)
|
67
95
|
end
|
68
96
|
|
@@ -74,7 +102,7 @@ module CF::UAA
|
|
74
102
|
|
75
103
|
if ENV['SKIP_SSL_VALIDATION']
|
76
104
|
context 'when ssl certificate is self-signed' do
|
77
|
-
let(:options) { {:
|
105
|
+
let(:options) { {skip_ssl_validation: false} }
|
78
106
|
|
79
107
|
it 'fails if skip_ssl_validation is false' do
|
80
108
|
expect{ scim }.to raise_exception(CF::UAA::SSLException)
|
@@ -85,7 +113,7 @@ module CF::UAA
|
|
85
113
|
if ENV['SSL_CA_FILE']
|
86
114
|
context 'when you do not skip SSL validation' do
|
87
115
|
context 'when you provide cert' do
|
88
|
-
let(:options) { {:
|
116
|
+
let(:options) { {ssl_ca_file: ENV['SSL_CA_FILE']} }
|
89
117
|
|
90
118
|
it 'works' do
|
91
119
|
expect(token_issuer.prompts).to_not be_nil
|
@@ -111,7 +139,7 @@ module CF::UAA
|
|
111
139
|
cert_store
|
112
140
|
end
|
113
141
|
|
114
|
-
let(:options) { {:
|
142
|
+
let(:options) { {ssl_cert_store: cert_store} }
|
115
143
|
it 'works' do
|
116
144
|
expect(token_issuer.prompts).to_not be_nil
|
117
145
|
end
|
@@ -128,7 +156,7 @@ module CF::UAA
|
|
128
156
|
end
|
129
157
|
|
130
158
|
it 'should report the uaa client version' do
|
131
|
-
expect(VERSION).to match(/\d
|
159
|
+
expect(VERSION).to match(/\d+.\d+.\d+/)
|
132
160
|
end
|
133
161
|
|
134
162
|
it 'makes sure the server is there by getting the prompts for an implicit grant' do
|
@@ -138,7 +166,7 @@ module CF::UAA
|
|
138
166
|
it 'gets a token with client credentials' do
|
139
167
|
tkn = token_issuer.client_credentials_grant
|
140
168
|
expect(tkn.auth_header).to match(/^bearer\s/i)
|
141
|
-
info = TokenCoder.decode(tkn.info['access_token'], :
|
169
|
+
info = TokenCoder.decode(tkn.info['access_token'], verify: false, symbolize_keys: true)
|
142
170
|
expect(info[:exp]).to be
|
143
171
|
expect(info[:jti]).to be
|
144
172
|
end
|
@@ -151,9 +179,9 @@ module CF::UAA
|
|
151
179
|
before :each do
|
152
180
|
@username = "sam_#{Time.now.to_i}"
|
153
181
|
@user_pwd = "sam's P@55w0rd~!`@\#\$%^&*()_/{}[]\\|:\";',.<>?/"
|
154
|
-
usr = scim.add(:user, :
|
155
|
-
:
|
156
|
-
:
|
182
|
+
usr = scim.add(:user, username: @username, password: @user_pwd,
|
183
|
+
emails: [{value: 'sam@example.com'}],
|
184
|
+
name: {givenname: 'none', familyname: 'none'})
|
157
185
|
@user_id = usr[:id]
|
158
186
|
end
|
159
187
|
|
@@ -194,8 +222,8 @@ module CF::UAA
|
|
194
222
|
|
195
223
|
it 'should get a uri to be sent to the user agent to initiate autologin' do
|
196
224
|
redir_uri = 'http://call.back/uri_path'
|
197
|
-
uri_parts = token_issuer.autologin_uri(redir_uri, :
|
198
|
-
:
|
225
|
+
uri_parts = token_issuer.autologin_uri(redir_uri, username: @username,
|
226
|
+
password: @user_pwd ).split('?')
|
199
227
|
expect(uri_parts[0]).to eq("#{ENV['UAA_CLIENT_TARGET']}/oauth/authorize")
|
200
228
|
params = Util.decode_form(uri_parts[1], :sym)
|
201
229
|
expect(params[:response_type]).to eq('code')
|
@@ -209,4 +237,4 @@ module CF::UAA
|
|
209
237
|
end
|
210
238
|
end
|
211
239
|
end
|
212
|
-
end
|
240
|
+
end
|
data/spec/scim_spec.rb
CHANGED
@@ -39,7 +39,7 @@ describe Scim do
|
|
39
39
|
end
|
40
40
|
|
41
41
|
describe 'initialize' do
|
42
|
-
let(:options) { {:
|
42
|
+
let(:options) { {http_proxy: 'http-proxy.com', https_proxy: 'https-proxy.com', skip_ssl_validation: true} }
|
43
43
|
|
44
44
|
it 'sets skip_ssl_validation' do
|
45
45
|
subject.skip_ssl_validation == true
|
@@ -53,8 +53,8 @@ describe Scim do
|
|
53
53
|
check_headers(headers, :json, :json, nil)
|
54
54
|
[200, '{"ID":"id12345"}', {'content-type' => 'application/json'}]
|
55
55
|
end
|
56
|
-
result = subject.add(:user, :
|
57
|
-
:
|
56
|
+
result = subject.add(:user, hair: 'brown', shoe_size: 'large',
|
57
|
+
eye_color: ['blue', 'green'], name: 'fred')
|
58
58
|
result['id'].should == 'id12345'
|
59
59
|
end
|
60
60
|
|
@@ -71,8 +71,8 @@ describe Scim do
|
|
71
71
|
end
|
72
72
|
|
73
73
|
it 'replaces an object' do
|
74
|
-
obj = {:
|
75
|
-
:
|
74
|
+
obj = {hair: 'black', shoe_size: 'medium', eye_color: ['hazel', 'brown'],
|
75
|
+
name: 'fredrick', meta: {version: 'v567'}, id: 'id12345'}
|
76
76
|
subject.set_request_handler do |url, method, body, headers|
|
77
77
|
url.should == "#{@target}/Users/id12345"
|
78
78
|
method.should == :put
|
@@ -85,8 +85,8 @@ describe Scim do
|
|
85
85
|
end
|
86
86
|
|
87
87
|
it 'modifies an object' do
|
88
|
-
obj = {:
|
89
|
-
:
|
88
|
+
obj = {hair: 'black', shoe_size: 'medium', eye_color: ['hazel', 'brown'],
|
89
|
+
name: 'fredrick', meta: {version: 'v567'}, id: 'id12345'}
|
90
90
|
subject.set_request_handler do |url, method, body, headers|
|
91
91
|
url.should == "#{@target}/Users/id12345"
|
92
92
|
method.should == :patch
|
@@ -122,7 +122,7 @@ describe Scim do
|
|
122
122
|
'{"TotalResults":2,"ItemsPerPage":1,"StartIndex":2,"RESOURCES":[{"id":"id67890"}]}'
|
123
123
|
[200, reply, {'content-type' => 'application/json'}]
|
124
124
|
end
|
125
|
-
result = subject.all_pages(:user, :
|
125
|
+
result = subject.all_pages(:user, attributes: 'id', includeInactive: true)
|
126
126
|
[result[0]['id'], result[1]['id']].to_set.should == ['id12345', 'id67890'].to_set
|
127
127
|
end
|
128
128
|
|
@@ -221,7 +221,7 @@ describe Scim do
|
|
221
221
|
end
|
222
222
|
|
223
223
|
describe 'users in a zone' do
|
224
|
-
let(:options) { {:
|
224
|
+
let(:options) { {http_proxy: 'http-proxy.com', https_proxy: 'https-proxy.com', skip_ssl_validation: true, zone: 'derpzone'} }
|
225
225
|
|
226
226
|
it 'sends zone header' do
|
227
227
|
subject.set_request_handler do |url, method, body, headers|
|
@@ -230,8 +230,8 @@ describe Scim do
|
|
230
230
|
check_headers(headers, :json, :json, 'derpzone')
|
231
231
|
[200, '{"ID":"id12345"}', {'content-type' => 'application/json'}]
|
232
232
|
end
|
233
|
-
result = subject.add(:user, :
|
234
|
-
:
|
233
|
+
result = subject.add(:user, hair: 'brown', shoe_size: 'large',
|
234
|
+
eye_color: ['blue', 'green'], name: 'fred')
|
235
235
|
result['id'].should == 'id12345'
|
236
236
|
end
|
237
237
|
end
|
data/spec/spec_helper.rb
CHANGED
data/spec/token_coder_spec.rb
CHANGED
@@ -18,8 +18,8 @@ module CF::UAA
|
|
18
18
|
|
19
19
|
describe TokenCoder do
|
20
20
|
|
21
|
-
subject { TokenCoder.new(:
|
22
|
-
:
|
21
|
+
subject { TokenCoder.new(audience_ids: "test_resource",
|
22
|
+
skey: "test_secret", pkey: OpenSSL::PKey::RSA.generate(512) ) }
|
23
23
|
|
24
24
|
before :each do
|
25
25
|
@tkn_body = {'foo' => "bar"}
|
@@ -57,7 +57,7 @@ describe TokenCoder do
|
|
57
57
|
2yrlT5h164jGCxqe7++1kIl4ollFCgz6QJ8lcmb/2Q==
|
58
58
|
-----END RSA PRIVATE KEY-----
|
59
59
|
DATA
|
60
|
-
coder = TokenCoder.new(:
|
60
|
+
coder = TokenCoder.new(audience_ids: "test_resource", pkey: pem)
|
61
61
|
tkn = coder.encode(@tkn_body, 'RS256')
|
62
62
|
result = coder.decode("bEaReR #{tkn}")
|
63
63
|
result.should_not be_nil
|
@@ -66,7 +66,7 @@ describe TokenCoder do
|
|
66
66
|
|
67
67
|
it "encodes/decodes with 'none' signature if explicitly accepted" do
|
68
68
|
tkn = subject.encode(@tkn_body, 'none')
|
69
|
-
result = TokenCoder.decode(tkn, :
|
69
|
+
result = TokenCoder.decode(tkn, accept_algorithms: "none")
|
70
70
|
result.should_not be_nil
|
71
71
|
result["foo"].should == "bar"
|
72
72
|
end
|
@@ -86,7 +86,7 @@ describe TokenCoder do
|
|
86
86
|
end
|
87
87
|
|
88
88
|
it "raises an error if the token is signed by an unknown signing key" do
|
89
|
-
other = TokenCoder.new(:
|
89
|
+
other = TokenCoder.new(audience_ids: "test_resource", skey: "other_secret")
|
90
90
|
tkn = other.encode(@tkn_body)
|
91
91
|
expect { subject.decode("bEaReR #{tkn}") }.to raise_exception(InvalidSignature)
|
92
92
|
end
|
@@ -103,8 +103,8 @@ describe TokenCoder do
|
|
103
103
|
2yrlT5h164jGCxqe7++1kIl4ollFCgz6QJ8lcmb/2Q==
|
104
104
|
-----END RSA PRIVATE KEY-----
|
105
105
|
DATA
|
106
|
-
coder = TokenCoder.new(:
|
107
|
-
coder2 = TokenCoder.new(:
|
106
|
+
coder = TokenCoder.new(audience_ids: "test_resource", pkey: pem)
|
107
|
+
coder2 = TokenCoder.new(audience_ids: "test_resource", skey: 'randomness')
|
108
108
|
|
109
109
|
tkn = coder.encode(@tkn_body, 'RS256')
|
110
110
|
|
@@ -123,21 +123,21 @@ describe TokenCoder do
|
|
123
123
|
2yrlT5h164jGCxqe7++1kIl4ollFCgz6QJ8lcmb/2Q==
|
124
124
|
-----END RSA PRIVATE KEY-----
|
125
125
|
DATA
|
126
|
-
coder = TokenCoder.new(:
|
127
|
-
coder2 = TokenCoder.new(:
|
126
|
+
coder = TokenCoder.new(audience_ids: "test_resource", pkey: pem)
|
127
|
+
coder2 = TokenCoder.new(audience_ids: "test_resource", skey: 'randomness')
|
128
128
|
tkn = coder2.encode(@tkn_body)
|
129
129
|
|
130
130
|
expect { coder.decode("bEaReR #{tkn}") }.to raise_exception(InvalidSignature)
|
131
131
|
end
|
132
132
|
|
133
133
|
it "raises an error if the token is an unknown signing algorithm" do
|
134
|
-
segments = [Util.json_encode64(:
|
134
|
+
segments = [Util.json_encode64(typ: "JWT", alg:"BADALGO")]
|
135
135
|
segments << Util.json_encode64(@tkn_body)
|
136
136
|
segments << Util.encode64("BADSIG")
|
137
137
|
tkn = segments.join('.')
|
138
|
-
tc = TokenCoder.new(:
|
139
|
-
:
|
140
|
-
:
|
138
|
+
tc = TokenCoder.new(audience_ids: "test_resource",
|
139
|
+
skey: "test_secret", pkey: OpenSSL::PKey::RSA.generate(512),
|
140
|
+
accept_algorithms: "BADALGO")
|
141
141
|
expect { tc.decode("bEaReR #{tkn}") }.to raise_exception(SignatureNotSupported)
|
142
142
|
end
|
143
143
|
|
@@ -179,7 +179,7 @@ describe TokenCoder do
|
|
179
179
|
|
180
180
|
it "decodes a token without validation" do
|
181
181
|
token = "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6ImY1MTgwMjExLWVkYjItNGQ4OS1hNmQwLThmNGVjMTE0NTE4YSIsInJlc291cmNlX2lkcyI6WyJjbG91ZF9jb250cm9sbGVyIiwicGFzc3dvcmQiXSwiZXhwaXJlc19hdCI6MTMzNjU1MTc2Niwic2NvcGUiOlsicmVhZCJdLCJlbWFpbCI6Im9sZHNAdm13YXJlLmNvbSIsImNsaWVudF9hdXRob3JpdGllcyI6WyJST0xFX1VOVFJVU1RFRCJdLCJleHBpcmVzX2luIjo0MzIwMCwidXNlcl9hdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwidXNlcl9pZCI6Im9sZHNAdm13YXJlLmNvbSIsImNsaWVudF9pZCI6InZtYyIsInRva2VuX2lkIjoiZWRlYmYzMTctNWU2Yi00YmYwLWFmM2ItMTA0OWRjNmFlYjc1In0.XoirrePfEujnZ9Vm7SRRnj3vZEfRp2tkjkS_OCVz5Bs"
|
182
|
-
info = TokenCoder.decode(token, :
|
182
|
+
info = TokenCoder.decode(token, verify: false)
|
183
183
|
info["id"].should_not be_nil
|
184
184
|
info["email"].should == "olds@vmware.com"
|
185
185
|
end
|
data/spec/token_issuer_spec.rb
CHANGED
@@ -23,13 +23,13 @@ describe TokenIssuer do
|
|
23
23
|
|
24
24
|
before do
|
25
25
|
#Util.default_logger(:trace)
|
26
|
-
@issuer = TokenIssuer.new('http://test.uaa.target', 'test_client', '
|
26
|
+
@issuer = TokenIssuer.new('http://test.uaa.target', 'test_client', 'test!secret', options)
|
27
27
|
end
|
28
28
|
|
29
29
|
subject { @issuer }
|
30
30
|
|
31
31
|
describe 'initialize' do
|
32
|
-
let(:options) { {:
|
32
|
+
let(:options) { {http_proxy: 'http-proxy.com', https_proxy: 'https-proxy.com', skip_ssl_validation: true, basic_auth: false} }
|
33
33
|
|
34
34
|
it 'sets skip_ssl_validation' do
|
35
35
|
subject.skip_ssl_validation == true
|
@@ -42,11 +42,12 @@ describe TokenIssuer do
|
|
42
42
|
subject.set_request_handler do |url, method, body, headers|
|
43
43
|
headers['content-type'].should =~ /application\/x-www-form-urlencoded/
|
44
44
|
headers['accept'].should =~ /application\/json/
|
45
|
-
|
45
|
+
headers['X-CF-ENCODED-CREDENTIALS'].should == 'true'
|
46
|
+
headers['authorization'].should == 'Basic dGVzdF9jbGllbnQ6dGVzdCUyMXNlY3JldA=='
|
46
47
|
url.should == 'http://test.uaa.target/oauth/token'
|
47
48
|
method.should == :post
|
48
|
-
reply = {:
|
49
|
-
:
|
49
|
+
reply = {access_token: 'test_access_token', token_type: 'BEARER',
|
50
|
+
scope: 'logs.read', expires_in: 98765}
|
50
51
|
[200, Util.json(reply), {'content-type' => 'application/json'}]
|
51
52
|
end
|
52
53
|
token = subject.client_credentials_grant('logs.read')
|
@@ -59,8 +60,8 @@ describe TokenIssuer do
|
|
59
60
|
|
60
61
|
it 'gets all granted scopes if none specified' do
|
61
62
|
subject.set_request_handler do |url, method, body, headers|
|
62
|
-
reply = {:
|
63
|
-
:
|
63
|
+
reply = {access_token: 'test_access_token', token_type: 'BEARER',
|
64
|
+
scope: 'openid logs.read', expires_in: 98765}
|
64
65
|
[200, Util.json(reply), {'content-type' => 'application/json'}]
|
65
66
|
end
|
66
67
|
token = subject.client_credentials_grant
|
@@ -89,11 +90,12 @@ describe TokenIssuer do
|
|
89
90
|
subject.set_request_handler do |url, method, body, headers|
|
90
91
|
headers['content-type'].should =~ /application\/x-www-form-urlencoded/
|
91
92
|
headers['accept'].should =~ /application\/json/
|
92
|
-
|
93
|
+
headers['X-CF-ENCODED-CREDENTIALS'].should == 'true'
|
94
|
+
headers['authorization'].should == 'Basic dGVzdF9jbGllbnQ6dGVzdCUyMXNlY3JldA=='
|
93
95
|
url.should == 'http://test.uaa.target/oauth/token'
|
94
96
|
method.should == :post
|
95
|
-
reply = {:
|
96
|
-
:
|
97
|
+
reply = {access_token: 'test_access_token', token_type: 'BEARER',
|
98
|
+
scope: 'openid', expires_in: 98765}
|
97
99
|
[200, Util.json(reply), {'content-type' => 'application/json'}]
|
98
100
|
end
|
99
101
|
token = subject.owner_password_grant('joe+admin', "?joe's%password$@ ", 'openid')
|
@@ -108,13 +110,14 @@ describe TokenIssuer do
|
|
108
110
|
subject.set_request_handler do |url, method, body, headers|
|
109
111
|
headers['content-type'].should =~ /application\/x-www-form-urlencoded/
|
110
112
|
headers['accept'].should =~ /application\/json/
|
111
|
-
|
113
|
+
headers['X-CF-ENCODED-CREDENTIALS'].should == 'true'
|
114
|
+
headers['authorization'].should == 'Basic dGVzdF9jbGllbnQ6dGVzdCUyMXNlY3JldA=='
|
112
115
|
url.should == 'http://test.uaa.target/oauth/token'
|
113
116
|
body.should =~ /(^|&)passcode=12345($|&)/
|
114
117
|
body.should =~ /(^|&)grant_type=password($|&)/
|
115
118
|
method.should == :post
|
116
|
-
reply = {:
|
117
|
-
:
|
119
|
+
reply = {access_token: 'test_access_token', token_type: 'BEARER',
|
120
|
+
scope: 'openid', expires_in: 98765}
|
118
121
|
[200, Util.json(reply), {'content-type' => 'application/json'}]
|
119
122
|
end
|
120
123
|
token = subject.passcode_grant('12345')
|
@@ -135,8 +138,8 @@ describe TokenIssuer do
|
|
135
138
|
url.should == 'http://test.uaa.target/oauth/token'
|
136
139
|
method.should == :post
|
137
140
|
body.split('&').should =~ ['passcode=fake-passcode', 'grant_type=password']
|
138
|
-
reply = {:
|
139
|
-
:
|
141
|
+
reply = {access_token: 'test_access_token', token_type: 'BEARER',
|
142
|
+
scope: 'openid', expires_in: 98765}
|
140
143
|
[200, Util.json(reply), {'content-type' => 'application/json'}]
|
141
144
|
end
|
142
145
|
token = subject.owner_password_credentials_grant({passcode: 'fake-passcode'})
|
@@ -153,7 +156,7 @@ describe TokenIssuer do
|
|
153
156
|
|
154
157
|
it 'gets the prompts for credentials used to authenticate implicit grant' do
|
155
158
|
subject.set_request_handler do |url, method, body, headers|
|
156
|
-
info = { :
|
159
|
+
info = { prompts: {username: ['text', 'Username'], password: ['password', 'Password']} }
|
157
160
|
[200, Util.json(info), {'content-type' => 'application/json'}]
|
158
161
|
end
|
159
162
|
result = subject.prompts
|
@@ -182,10 +185,10 @@ describe TokenIssuer do
|
|
182
185
|
end
|
183
186
|
|
184
187
|
expect(subject).to receive(:authorize_path_args).with('token', 'https://uaa.cloudfoundry.com/redirect/test_client', 'logs.read', anything)
|
185
|
-
subject.
|
186
|
-
subject.
|
188
|
+
allow(subject).to receive(:random_state).and_return('1234')
|
189
|
+
allow(subject).to receive(:authorize_path_args).and_return('/oauth/authorize?state=1234&scope=logs.read')
|
187
190
|
|
188
|
-
token = subject.implicit_grant_with_creds({:
|
191
|
+
token = subject.implicit_grant_with_creds({username: 'joe+admin', password: "?joe's%password$@ "}, 'logs.read')
|
189
192
|
token.should be_an_instance_of TokenInfo
|
190
193
|
token.info['access_token'].should == 'test_access_token'
|
191
194
|
token.info['token_type'].should =~ /^bearer$/i
|
@@ -202,7 +205,7 @@ describe TokenIssuer do
|
|
202
205
|
end
|
203
206
|
|
204
207
|
expect(subject).to receive(:authorize_path_args).with('token id_token', 'https://uaa.cloudfoundry.com/redirect/test_client', 'openid logs.read', anything)
|
205
|
-
subject.
|
208
|
+
allow(subject).to receive(:random_state).and_return('1234')
|
206
209
|
subject.implicit_grant_with_creds({:username => 'joe+admin', :password => "?joe's%password$@ "}, 'openid logs.read')
|
207
210
|
end
|
208
211
|
end
|
@@ -214,8 +217,8 @@ describe TokenIssuer do
|
|
214
217
|
'expires_in=98765&scope=openid+logs.read&state=bad_state'
|
215
218
|
[302, nil, {'content-type' => 'application/json', 'location' => location}]
|
216
219
|
end
|
217
|
-
expect {token = subject.implicit_grant_with_creds(:
|
218
|
-
:
|
220
|
+
expect {token = subject.implicit_grant_with_creds(username: 'joe+admin',
|
221
|
+
password: "?joe's%password$@ ")}.to raise_exception BadResponse
|
219
222
|
end
|
220
223
|
|
221
224
|
it 'asks for an id_token with openid scope' do
|
@@ -250,11 +253,12 @@ describe TokenIssuer do
|
|
250
253
|
subject.set_request_handler do |url, method, body, headers|
|
251
254
|
headers['content-type'].should =~ /application\/x-www-form-urlencoded/
|
252
255
|
headers['accept'].should =~ /application\/json/
|
253
|
-
|
256
|
+
headers['X-CF-ENCODED-CREDENTIALS'].should == 'true'
|
257
|
+
headers['authorization'].should == 'Basic dGVzdF9jbGllbnQ6dGVzdCUyMXNlY3JldA=='
|
254
258
|
url.should match 'http://test.uaa.target/oauth/token'
|
255
259
|
method.should == :post
|
256
|
-
reply = {:
|
257
|
-
:
|
260
|
+
reply = {access_token: 'test_access_token', token_type: 'BEARER',
|
261
|
+
scope: 'openid', expires_in: 98765}
|
258
262
|
[200, Util.json(reply), {'content-type' => 'application/json'}]
|
259
263
|
end
|
260
264
|
cburi = 'http://call.back/uri_path'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cf-uaa-lib
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dave Syer
|
@@ -12,28 +12,28 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
|
-
date:
|
15
|
+
date: 2022-01-21 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: multi_json
|
19
19
|
requirement: !ruby/object:Gem::Requirement
|
20
20
|
requirements:
|
21
|
-
- - "~>"
|
22
|
-
- !ruby/object:Gem::Version
|
23
|
-
version: 1.12.0
|
24
21
|
- - ">="
|
25
22
|
- !ruby/object:Gem::Version
|
26
23
|
version: 1.12.1
|
24
|
+
- - "<"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.16'
|
27
27
|
type: :runtime
|
28
28
|
prerelease: false
|
29
29
|
version_requirements: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: 1.12.0
|
34
31
|
- - ">="
|
35
32
|
- !ruby/object:Gem::Version
|
36
33
|
version: 1.12.1
|
34
|
+
- - "<"
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '1.16'
|
37
37
|
- !ruby/object:Gem::Dependency
|
38
38
|
name: httpclient
|
39
39
|
requirement: !ruby/object:Gem::Requirement
|
@@ -54,74 +54,94 @@ dependencies:
|
|
54
54
|
- - ">="
|
55
55
|
- !ruby/object:Gem::Version
|
56
56
|
version: 2.8.2.4
|
57
|
+
- !ruby/object:Gem::Dependency
|
58
|
+
name: addressable
|
59
|
+
requirement: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: 2.8.0
|
64
|
+
- - "~>"
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '2.8'
|
67
|
+
type: :runtime
|
68
|
+
prerelease: false
|
69
|
+
version_requirements: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: 2.8.0
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '2.8'
|
57
77
|
- !ruby/object:Gem::Dependency
|
58
78
|
name: bundler
|
59
79
|
requirement: !ruby/object:Gem::Requirement
|
60
80
|
requirements:
|
61
81
|
- - "~>"
|
62
82
|
- !ruby/object:Gem::Version
|
63
|
-
version: '
|
83
|
+
version: '2.2'
|
64
84
|
type: :development
|
65
85
|
prerelease: false
|
66
86
|
version_requirements: !ruby/object:Gem::Requirement
|
67
87
|
requirements:
|
68
88
|
- - "~>"
|
69
89
|
- !ruby/object:Gem::Version
|
70
|
-
version: '
|
90
|
+
version: '2.2'
|
71
91
|
- !ruby/object:Gem::Dependency
|
72
92
|
name: rake
|
73
93
|
requirement: !ruby/object:Gem::Requirement
|
74
94
|
requirements:
|
75
|
-
- - "~>"
|
76
|
-
- !ruby/object:Gem::Version
|
77
|
-
version: '10.3'
|
78
95
|
- - ">="
|
79
96
|
- !ruby/object:Gem::Version
|
80
97
|
version: 10.3.2
|
98
|
+
- - "~>"
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '13.0'
|
81
101
|
type: :development
|
82
102
|
prerelease: false
|
83
103
|
version_requirements: !ruby/object:Gem::Requirement
|
84
104
|
requirements:
|
85
|
-
- - "~>"
|
86
|
-
- !ruby/object:Gem::Version
|
87
|
-
version: '10.3'
|
88
105
|
- - ">="
|
89
106
|
- !ruby/object:Gem::Version
|
90
107
|
version: 10.3.2
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '13.0'
|
91
111
|
- !ruby/object:Gem::Dependency
|
92
112
|
name: rspec
|
93
113
|
requirement: !ruby/object:Gem::Requirement
|
94
114
|
requirements:
|
95
|
-
- - "~>"
|
96
|
-
- !ruby/object:Gem::Version
|
97
|
-
version: '2.14'
|
98
115
|
- - ">="
|
99
116
|
- !ruby/object:Gem::Version
|
100
117
|
version: 2.14.1
|
118
|
+
- - "~>"
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '3.9'
|
101
121
|
type: :development
|
102
122
|
prerelease: false
|
103
123
|
version_requirements: !ruby/object:Gem::Requirement
|
104
124
|
requirements:
|
105
|
-
- - "~>"
|
106
|
-
- !ruby/object:Gem::Version
|
107
|
-
version: '2.14'
|
108
125
|
- - ">="
|
109
126
|
- !ruby/object:Gem::Version
|
110
127
|
version: 2.14.1
|
128
|
+
- - "~>"
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '3.9'
|
111
131
|
- !ruby/object:Gem::Dependency
|
112
132
|
name: simplecov
|
113
133
|
requirement: !ruby/object:Gem::Requirement
|
114
134
|
requirements:
|
115
135
|
- - "~>"
|
116
136
|
- !ruby/object:Gem::Version
|
117
|
-
version: 0.
|
137
|
+
version: 0.21.2
|
118
138
|
type: :development
|
119
139
|
prerelease: false
|
120
140
|
version_requirements: !ruby/object:Gem::Requirement
|
121
141
|
requirements:
|
122
142
|
- - "~>"
|
123
143
|
- !ruby/object:Gem::Version
|
124
|
-
version: 0.
|
144
|
+
version: 0.21.2
|
125
145
|
- !ruby/object:Gem::Dependency
|
126
146
|
name: simplecov-rcov
|
127
147
|
requirement: !ruby/object:Gem::Requirement
|
@@ -140,42 +160,56 @@ dependencies:
|
|
140
160
|
name: ci_reporter
|
141
161
|
requirement: !ruby/object:Gem::Requirement
|
142
162
|
requirements:
|
143
|
-
- - "~>"
|
144
|
-
- !ruby/object:Gem::Version
|
145
|
-
version: '1.9'
|
146
163
|
- - ">="
|
147
164
|
- !ruby/object:Gem::Version
|
148
165
|
version: 1.9.2
|
166
|
+
- - "~>"
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: '2.0'
|
149
169
|
type: :development
|
150
170
|
prerelease: false
|
151
171
|
version_requirements: !ruby/object:Gem::Requirement
|
152
172
|
requirements:
|
153
|
-
- - "~>"
|
154
|
-
- !ruby/object:Gem::Version
|
155
|
-
version: '1.9'
|
156
173
|
- - ">="
|
157
174
|
- !ruby/object:Gem::Version
|
158
175
|
version: 1.9.2
|
176
|
+
- - "~>"
|
177
|
+
- !ruby/object:Gem::Version
|
178
|
+
version: '2.0'
|
159
179
|
- !ruby/object:Gem::Dependency
|
160
180
|
name: json_pure
|
161
181
|
requirement: !ruby/object:Gem::Requirement
|
162
182
|
requirements:
|
183
|
+
- - ">="
|
184
|
+
- !ruby/object:Gem::Version
|
185
|
+
version: 1.8.1
|
163
186
|
- - "~>"
|
164
187
|
- !ruby/object:Gem::Version
|
165
|
-
version: '
|
188
|
+
version: '2.5'
|
189
|
+
type: :development
|
190
|
+
prerelease: false
|
191
|
+
version_requirements: !ruby/object:Gem::Requirement
|
192
|
+
requirements:
|
166
193
|
- - ">="
|
167
194
|
- !ruby/object:Gem::Version
|
168
195
|
version: 1.8.1
|
196
|
+
- - "~>"
|
197
|
+
- !ruby/object:Gem::Version
|
198
|
+
version: '2.5'
|
199
|
+
- !ruby/object:Gem::Dependency
|
200
|
+
name: ci_reporter_rspec
|
201
|
+
requirement: !ruby/object:Gem::Requirement
|
202
|
+
requirements:
|
203
|
+
- - "~>"
|
204
|
+
- !ruby/object:Gem::Version
|
205
|
+
version: '1.0'
|
169
206
|
type: :development
|
170
207
|
prerelease: false
|
171
208
|
version_requirements: !ruby/object:Gem::Requirement
|
172
209
|
requirements:
|
173
210
|
- - "~>"
|
174
211
|
- !ruby/object:Gem::Version
|
175
|
-
version: '1.
|
176
|
-
- - ">="
|
177
|
-
- !ruby/object:Gem::Version
|
178
|
-
version: 1.8.1
|
212
|
+
version: '1.0'
|
179
213
|
description: Client library for interacting with the CloudFoundry User Account and
|
180
214
|
Authorization (UAA) server. The UAA is an OAuth2 Authorization Server so it can
|
181
215
|
be used by webapps and command line apps to obtain access tokens to act on behalf
|
@@ -191,8 +225,10 @@ executables: []
|
|
191
225
|
extensions: []
|
192
226
|
extra_rdoc_files: []
|
193
227
|
files:
|
228
|
+
- ".github/dependabot.yml"
|
229
|
+
- ".github/workflows/gem-push.yml"
|
230
|
+
- ".github/workflows/ruby.yml"
|
194
231
|
- ".gitignore"
|
195
|
-
- ".travis.yml"
|
196
232
|
- ".yardopts"
|
197
233
|
- CHANGELOG.md
|
198
234
|
- Gemfile
|
@@ -235,16 +271,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
235
271
|
- !ruby/object:Gem::Version
|
236
272
|
version: '0'
|
237
273
|
requirements: []
|
238
|
-
|
239
|
-
rubygems_version: 2.6.13
|
274
|
+
rubygems_version: 3.0.3.1
|
240
275
|
signing_key:
|
241
276
|
specification_version: 4
|
242
277
|
summary: Client library for CloudFoundry UAA
|
243
|
-
test_files:
|
244
|
-
- spec/http_spec.rb
|
245
|
-
- spec/info_spec.rb
|
246
|
-
- spec/integration_spec.rb
|
247
|
-
- spec/scim_spec.rb
|
248
|
-
- spec/spec_helper.rb
|
249
|
-
- spec/token_coder_spec.rb
|
250
|
-
- spec/token_issuer_spec.rb
|
278
|
+
test_files: []
|