cf-uaac 3.5.0 → 3.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -1
- data/cf-uaac.gemspec +1 -1
- data/lib/uaa/cli/client_reg.rb +50 -34
- data/lib/uaa/cli/user.rb +58 -33
- data/lib/uaa/cli/version.rb +1 -1
- data/lib/uaa/stub/scim.rb +44 -3
- data/lib/uaa/stub/uaa.rb +150 -132
- data/spec/client_reg_spec.rb +24 -24
- data/spec/spec_helper.rb +1 -0
- data/spec/user_spec.rb +44 -30
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7ad629a17b4992798f2f92eb7c4f6f658220579a
|
4
|
+
data.tar.gz: 69f7b40c269212155448de48f237484d86182486
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d6ed51ac1f2704d9c6c81f9aae8a05f2c37636697da90e6b7ed253503f6c15d1f7331b3d5b2e98d723cf93b21b6aef06b64d6297b014c98fe87c787ea6ef7bb3
|
7
|
+
data.tar.gz: 7250bf305e3ec5bc959f4b9b61af915b3ef5f3ad247dc44c9568a248b7ccfee6361b0533b324354fd96b303436bc76e1f14f35a15b239ae32437b3a98be90774
|
data/.travis.yml
CHANGED
data/cf-uaac.gemspec
CHANGED
@@ -39,7 +39,7 @@ Gem::Specification.new do |s|
|
|
39
39
|
s.add_development_dependency "simplecov", "~> 0.8.2"
|
40
40
|
s.add_development_dependency "simplecov-rcov", "~> 0.2.3"
|
41
41
|
s.add_development_dependency "ci_reporter", "~> 1.9.2"
|
42
|
-
s.add_runtime_dependency "cf-uaa-lib", "~> 3.
|
42
|
+
s.add_runtime_dependency "cf-uaa-lib", "~> 3.8.0"
|
43
43
|
s.add_runtime_dependency "highline", "~> 1.6.21"
|
44
44
|
s.add_runtime_dependency "eventmachine", "~> 1.0.3"
|
45
45
|
s.add_runtime_dependency "launchy", "~> 2.4.2"
|
data/lib/uaa/cli/client_reg.rb
CHANGED
@@ -17,18 +17,18 @@ module CF::UAA
|
|
17
17
|
|
18
18
|
class ClientCli < CommonCli
|
19
19
|
|
20
|
-
topic
|
20
|
+
topic 'Client Application Registrations', 'reg'
|
21
21
|
|
22
22
|
CLIENT_SCHEMA = {
|
23
|
-
:name =>
|
24
|
-
:scope =>
|
25
|
-
:authorized_grant_types =>
|
26
|
-
:authorities =>
|
27
|
-
:access_token_validity =>
|
28
|
-
:refresh_token_validity =>
|
29
|
-
:redirect_uri =>
|
30
|
-
:autoapprove =>
|
31
|
-
:'signup_redirect_url' =>
|
23
|
+
:name => 'string',
|
24
|
+
:scope => 'list',
|
25
|
+
:authorized_grant_types => 'list',
|
26
|
+
:authorities => 'list',
|
27
|
+
:access_token_validity => 'seconds',
|
28
|
+
:refresh_token_validity => 'seconds',
|
29
|
+
:redirect_uri => 'list',
|
30
|
+
:autoapprove => 'list',
|
31
|
+
:'signup_redirect_url' => 'url'
|
32
32
|
}
|
33
33
|
CLIENT_SCHEMA.each { |k, v| define_option(k, "--#{k} <#{v}>") }
|
34
34
|
|
@@ -45,72 +45,88 @@ class ClientCli < CommonCli
|
|
45
45
|
info[k] = opts[:interact] ?
|
46
46
|
info[k] = askd("#{k.to_s.gsub('_', ' ')} (#{p})", default): default
|
47
47
|
end
|
48
|
-
if k == :autoapprove && (info[k] ==
|
49
|
-
info[k] = !!(info[k] ==
|
48
|
+
if k == :autoapprove && (info[k] == 'true' || info[k] == 'false')
|
49
|
+
info[k] = !!(info[k] == 'true')
|
50
50
|
else
|
51
|
-
info[k] = Util.arglist(info[k]) if p ==
|
51
|
+
info[k] = Util.arglist(info[k]) if p == 'list'
|
52
52
|
info.delete(k) unless info[k]
|
53
53
|
end
|
54
54
|
end
|
55
55
|
end
|
56
56
|
|
57
|
-
desc
|
57
|
+
desc 'clients [filter]', 'List client registrations', :attrs, :start, :count do |filter|
|
58
58
|
scim_common_list(:client, filter)
|
59
59
|
end
|
60
60
|
|
61
61
|
desc "client get [id]", "Get specific client registration", :attrs do |id|
|
62
|
-
pp
|
62
|
+
pp(scim_request do |sr|
|
63
|
+
client = scim_get_object(sr, :client, clientid(id), opts[:attrs])
|
64
|
+
add_meta_fields_to_client(sr, client)
|
65
|
+
end)
|
63
66
|
end
|
64
67
|
|
65
|
-
define_option :clone,
|
66
|
-
define_option :interact,
|
67
|
-
desc
|
68
|
+
define_option :clone, '--clone <other>', 'get default settings from other'
|
69
|
+
define_option :interact, '--[no-]interactive', '-i', 'interactively verify all values'
|
70
|
+
desc 'client add [id]', 'Add client registration',
|
68
71
|
*CLIENT_SCHEMA.keys, :clone, :secret, :interact do |id|
|
69
72
|
pp scim_request { |cr|
|
70
73
|
opts[:client_id] = clientid(id)
|
71
74
|
opts[:name] = clientname() || opts[:client_id]
|
72
|
-
opts[:secret] = verified_pwd(
|
75
|
+
opts[:secret] = verified_pwd('New client secret', opts[:secret])
|
73
76
|
defaults = opts[:clone] ? Util.hash_keys!(cr.get(:client, opts[:clone]), :sym) : {}
|
74
77
|
defaults.delete(:client_id)
|
75
|
-
cr.add(:client, client_info(defaults))
|
78
|
+
client = cr.add(:client, client_info(defaults))
|
79
|
+
add_meta_fields_to_client(cr, client)
|
76
80
|
}
|
77
81
|
end
|
78
82
|
|
79
|
-
desc
|
83
|
+
desc 'client update [id]', 'Update client registration', *CLIENT_SCHEMA.keys,
|
80
84
|
:del_attrs, :interact do |id|
|
81
85
|
pp scim_request { |cr|
|
82
86
|
opts[:client_id] = clientid(id)
|
83
87
|
orig = Util.hash_keys!(cr.get(:client, opts[:client_id]), :sym)
|
84
88
|
info = client_info(orig)
|
85
|
-
info.any? { |k, v| v != orig[k] } ?
|
86
|
-
|
89
|
+
info.any? { |k, v| v != orig[k] } ?
|
90
|
+
update_client(cr, info) :
|
91
|
+
gripe('Nothing to update. Use -i for interactive update.')
|
87
92
|
}
|
88
93
|
end
|
89
94
|
|
90
|
-
desc
|
95
|
+
desc 'client delete [id]', 'Delete client registration' do |id|
|
91
96
|
pp scim_request { |cr|
|
92
97
|
cr.delete(:client, clientid(id))
|
93
|
-
|
98
|
+
'client registration deleted'
|
94
99
|
}
|
95
100
|
end
|
96
101
|
|
97
|
-
desc
|
102
|
+
desc 'secret set [id]', 'Set client secret', :secret do |id|
|
98
103
|
pp scim_request { |cr|
|
99
|
-
cr.change_secret(clientid(id), verified_pwd(
|
100
|
-
|
104
|
+
cr.change_secret(clientid(id), verified_pwd('New secret', opts[:secret]))
|
105
|
+
'client secret successfully set'
|
101
106
|
}
|
102
107
|
end
|
103
108
|
|
104
|
-
define_option :old_secret,
|
105
|
-
desc
|
106
|
-
return gripe
|
109
|
+
define_option :old_secret, '--old_secret <secret>', 'current secret'
|
110
|
+
desc 'secret change', 'Change secret for authenticated client in current context', :old_secret, :secret do
|
111
|
+
return gripe 'context not set' unless client_id = Config.context.to_s
|
107
112
|
scim_request { |cr|
|
108
|
-
old = opts[:old_secret] || ask_pwd(
|
109
|
-
cr.change_secret(client_id, verified_pwd(
|
110
|
-
|
113
|
+
old = opts[:old_secret] || ask_pwd('Current secret')
|
114
|
+
cr.change_secret(client_id, verified_pwd('New secret', opts[:secret]), old)
|
115
|
+
'client secret successfully changed'
|
111
116
|
}
|
112
117
|
end
|
113
118
|
|
119
|
+
private
|
120
|
+
|
121
|
+
def update_client(cr, info)
|
122
|
+
client = cr.put(:client, info)
|
123
|
+
add_meta_fields_to_client(cr, client)
|
124
|
+
end
|
125
|
+
|
126
|
+
def add_meta_fields_to_client(cr, client)
|
127
|
+
meta = cr.get_client_meta(client['client_id'])
|
128
|
+
client.merge({:created_by => meta['createdby']})
|
129
|
+
end
|
114
130
|
end
|
115
131
|
|
116
132
|
end
|
data/lib/uaa/cli/user.rb
CHANGED
@@ -17,12 +17,12 @@ module CF::UAA
|
|
17
17
|
|
18
18
|
class UserCli < CommonCli
|
19
19
|
|
20
|
-
topic
|
20
|
+
topic 'User Accounts', 'account'
|
21
21
|
|
22
|
-
define_option :givenName,
|
23
|
-
define_option :familyName,
|
24
|
-
define_option :emails,
|
25
|
-
define_option :phoneNumbers,
|
22
|
+
define_option :givenName, '--given_name <name>'
|
23
|
+
define_option :familyName, '--family_name <name>'
|
24
|
+
define_option :emails, '--emails <addresses>'
|
25
|
+
define_option :phoneNumbers, '--phones <phone_numbers>'
|
26
26
|
USER_INFO_OPTS = [:givenName, :familyName, :emails, :phoneNumbers]
|
27
27
|
|
28
28
|
def user_opts(info = {})
|
@@ -35,77 +35,102 @@ class UserCli < CommonCli
|
|
35
35
|
info
|
36
36
|
end
|
37
37
|
|
38
|
-
define_option :attrs,
|
39
|
-
define_option :start,
|
40
|
-
define_option :count,
|
41
|
-
desc
|
38
|
+
define_option :attrs, '-a', '--attributes <names>', 'output for each user'
|
39
|
+
define_option :start, '--start <number>', 'start of output page'
|
40
|
+
define_option :count, '--count <number>', 'max number per page'
|
41
|
+
desc 'users [filter]', 'List user accounts', :attrs, :start, :count do |filter|
|
42
42
|
scim_common_list(:user, filter)
|
43
43
|
end
|
44
44
|
|
45
|
-
desc
|
45
|
+
desc 'user get [name]', 'Get specific user account', :attrs do |name|
|
46
46
|
pp scim_request { |sr| scim_get_object(sr, :user, username(name), opts[:attrs]) }
|
47
47
|
end
|
48
48
|
|
49
|
-
desc
|
50
|
-
info = {userName: username(name), password: verified_pwd(
|
49
|
+
desc 'user add [name]', 'Add a user account', *USER_INFO_OPTS, :password do |name|
|
50
|
+
info = {userName: username(name), password: verified_pwd('Password', opts[:password])}
|
51
51
|
pp scim_request { |ua|
|
52
52
|
ua.add(:user, user_opts(info))
|
53
|
-
|
53
|
+
'user account successfully added'
|
54
54
|
}
|
55
55
|
end
|
56
56
|
|
57
|
-
define_option :del_attrs,
|
58
|
-
desc
|
57
|
+
define_option :del_attrs, '--del_attrs <attr_names>', 'list of attributes to delete'
|
58
|
+
desc 'user update [name]', 'Update a user account with specified options',
|
59
59
|
*USER_INFO_OPTS, :del_attrs do |name|
|
60
|
-
return say
|
60
|
+
return say 'no user updates specified' if (updates = user_opts).empty?
|
61
61
|
pp scim_request { |ua|
|
62
62
|
info = ua.get(:user, ua.id(:user, username(name)))
|
63
63
|
opts[:del_attrs].each { |a| info.delete(a.to_s) } if opts[:del_attrs]
|
64
64
|
ua.put(:user, info.merge(updates))
|
65
|
-
|
65
|
+
'user account successfully updated'
|
66
66
|
}
|
67
67
|
end
|
68
68
|
|
69
|
-
desc
|
69
|
+
desc 'user delete [name]', 'Delete user account' do |name|
|
70
70
|
pp scim_request { |ua|
|
71
71
|
ua.delete(:user, ua.id(:user, username(name)))
|
72
|
-
|
72
|
+
'user account successfully deleted'
|
73
73
|
}
|
74
74
|
end
|
75
75
|
|
76
|
-
desc
|
76
|
+
desc 'user ids [username|id...]', 'Gets user names and ids for the given users' do |*users|
|
77
77
|
pp scim_request { |ua|
|
78
|
-
users = Util.arglist(ask(
|
78
|
+
users = Util.arglist(ask('names or ids of users')) if !users || users.empty?
|
79
79
|
ids = ua.ids(:user_id, *users)
|
80
|
-
raise NotFound,
|
80
|
+
raise NotFound, 'no users found' unless ids && ids.length > 0
|
81
81
|
ids
|
82
82
|
}
|
83
83
|
end
|
84
84
|
|
85
|
-
desc
|
85
|
+
desc 'user unlock [name]', 'Unlocks the user account' do |name|
|
86
86
|
pp scim_request { |ua|
|
87
87
|
ua.unlock_user( ua.id(:user, username(name)))
|
88
|
-
|
88
|
+
'user account successfully unlocked'
|
89
89
|
}
|
90
90
|
end
|
91
91
|
|
92
|
-
desc
|
92
|
+
desc 'user deactivate [name]', 'Deactivates user' do |name|
|
93
93
|
pp scim_request { |ua|
|
94
|
-
|
95
|
-
|
94
|
+
change_activation(ua, name, false)
|
95
|
+
'user account successfully deactivated'
|
96
96
|
}
|
97
97
|
end
|
98
98
|
|
99
|
-
|
100
|
-
desc "password change", "Change password for authenticated user in current context", :old_password, :password do
|
99
|
+
desc 'user activate [name]', 'Activates user' do |name|
|
101
100
|
pp scim_request { |ua|
|
102
|
-
|
103
|
-
|
104
|
-
ua.change_password(Config.value(:user_id), verified_pwd("New password", opts[:password]), oldpwd)
|
105
|
-
"password successfully changed"
|
101
|
+
change_activation(ua, name, true)
|
102
|
+
'user account successfully activated'
|
106
103
|
}
|
107
104
|
end
|
108
105
|
|
106
|
+
desc 'password set [name]', 'Set password', :password do |name|
|
107
|
+
pp scim_request { |ua|
|
108
|
+
ua.change_password(ua.id(:user, username(name)), verified_pwd('New password', opts[:password]))
|
109
|
+
'password successfully set'
|
110
|
+
}
|
111
|
+
end
|
112
|
+
|
113
|
+
define_option :old_password, '-o', '--old_password <password>', 'current password'
|
114
|
+
desc 'password change', 'Change password for authenticated user in current context', :old_password, :password do
|
115
|
+
pp scim_request { |ua|
|
116
|
+
raise 'no user_id in current context' unless Config.value(:user_id)
|
117
|
+
oldpwd = opts[:old_password] || ask_pwd('Current password')
|
118
|
+
ua.change_password(Config.value(:user_id), verified_pwd('New password', opts[:password]), oldpwd)
|
119
|
+
'password successfully changed'
|
120
|
+
}
|
121
|
+
end
|
122
|
+
|
123
|
+
def change_activation(ua, name, activate)
|
124
|
+
info = ua.get(:user, ua.id(:user, username(name)))
|
125
|
+
|
126
|
+
required_info = ['id', 'username', 'name', 'emails', 'meta'].inject({}) do |res, required_param|
|
127
|
+
res[required_param] = info[required_param]
|
128
|
+
res
|
129
|
+
end
|
130
|
+
|
131
|
+
required_info['active'] = activate
|
132
|
+
ua.patch(:user, required_info)
|
133
|
+
end
|
109
134
|
end
|
110
135
|
|
111
136
|
end
|
data/lib/uaa/cli/version.rb
CHANGED
data/lib/uaa/stub/scim.rb
CHANGED
@@ -27,6 +27,8 @@ class StubScim
|
|
27
27
|
|
28
28
|
private
|
29
29
|
|
30
|
+
CREATOR = 'Stalin'
|
31
|
+
|
30
32
|
# attribute types. Anything not listed is case-ignore string
|
31
33
|
HIDDEN_ATTRS = [:rtype, :password, :client_secret].to_set
|
32
34
|
READ_ONLY_ATTRS = [:rtype, :id, :meta, :groups].to_set
|
@@ -163,7 +165,7 @@ class StubScim
|
|
163
165
|
def output(thing, attrs = nil)
|
164
166
|
attrs = thing.keys if attrs.nil? || attrs.empty?
|
165
167
|
attrs.each_with_object({}) {|a, o|
|
166
|
-
next
|
168
|
+
next if thing[a].nil?
|
167
169
|
case a
|
168
170
|
when *MEMBERSHIP
|
169
171
|
o[a] = thing[a].each_with_object([]) { |v, a|
|
@@ -192,7 +194,10 @@ class StubScim
|
|
192
194
|
|
193
195
|
public
|
194
196
|
|
195
|
-
def initialize
|
197
|
+
def initialize
|
198
|
+
@things_by_id, @things_by_name, @clients_metadata = {}, {}, {}
|
199
|
+
end
|
200
|
+
|
196
201
|
def name(id, rtype = nil) (t = ref_by_id(id, rtype))? t[NAME_ATTR[t[:rtype]]]: nil end
|
197
202
|
def id(name, rtype) (t = ref_by_name(name, rtype))? t[:id] : nil end
|
198
203
|
|
@@ -203,7 +208,10 @@ class StubScim
|
|
203
208
|
raise AlreadyExists if @things_by_name.key?(name = rtype.to_s + name.downcase)
|
204
209
|
enforce_schema(rtype, stuff)
|
205
210
|
thing = input(stuff).merge!(rtype: rtype, id: (id = SecureRandom.uuid),
|
206
|
-
meta: { created: Time.now.iso8601, last_modified: Time.now.iso8601, version: 1
|
211
|
+
meta: { created: Time.now.iso8601, last_modified: Time.now.iso8601, version: 1})
|
212
|
+
if rtype == :client
|
213
|
+
@clients_metadata[stuff['client_id']] = {:createdby => CREATOR}
|
214
|
+
end
|
207
215
|
add_user_groups(id, thing[:members])
|
208
216
|
@things_by_id[id] = @things_by_name[name] = thing
|
209
217
|
id
|
@@ -236,6 +244,35 @@ class StubScim
|
|
236
244
|
id
|
237
245
|
end
|
238
246
|
|
247
|
+
def patch(id, stuff, match_version = nil, match_type = nil)
|
248
|
+
raise NotFound unless thing = ref_by_id(id, match_type)
|
249
|
+
raise BadVersion if match_version && match_version != thing[:meta][:version]
|
250
|
+
enforce_schema(rtype = thing[:rtype], remove_attrs(stuff, READ_ONLY_ATTRS))
|
251
|
+
new_thing = input(stuff)
|
252
|
+
if newname = new_thing[NAME_ATTR[rtype]]
|
253
|
+
oldname = rtype.to_s + thing[NAME_ATTR[rtype]].downcase
|
254
|
+
unless (newname = rtype.to_s + newname.downcase) == oldname
|
255
|
+
raise AlreadyExists if @things_by_name.key?(newname)
|
256
|
+
@things_by_name.delete(oldname)
|
257
|
+
@things_by_name[newname] = thing
|
258
|
+
end
|
259
|
+
end
|
260
|
+
if new_thing[:members] || thing[:members]
|
261
|
+
old_members = thing[:members] || Set.new
|
262
|
+
new_members = new_thing[:members] || Set.new
|
263
|
+
delete_user_groups(id, old_members - new_members)
|
264
|
+
add_user_groups(id, new_members - old_members)
|
265
|
+
end
|
266
|
+
READ_ONLY_ATTRS.each { |a| new_thing[a] = thing[a] if thing[a] }
|
267
|
+
HIDDEN_ATTRS.each { |a| new_thing[a] = thing[a] if thing[a] }
|
268
|
+
new_thing.each do |key, value|
|
269
|
+
thing[key] = value
|
270
|
+
end
|
271
|
+
thing[:meta][:version] += 1
|
272
|
+
thing[:meta][:lastmodified] == Time.now.iso8601
|
273
|
+
id
|
274
|
+
end
|
275
|
+
|
239
276
|
def add_member(gid, member)
|
240
277
|
return unless g = ref_by_id(gid, :group)
|
241
278
|
(g[:members] ||= Set.new) << member
|
@@ -267,6 +304,10 @@ class StubScim
|
|
267
304
|
output(thing, attrs)
|
268
305
|
end
|
269
306
|
|
307
|
+
def get_client_meta(client_id)
|
308
|
+
@clients_metadata[client_id]
|
309
|
+
end
|
310
|
+
|
270
311
|
def get_by_name(name, rtype, *attrs)
|
271
312
|
return unless thing = ref_by_name(name, rtype)
|
272
313
|
output(thing, attrs)
|
data/lib/uaa/stub/uaa.rb
CHANGED
@@ -23,32 +23,33 @@ class StubUAAConn < Stub::Base
|
|
23
23
|
|
24
24
|
def inject_error(input = nil)
|
25
25
|
case server.reply_badly
|
26
|
-
when :non_json then reply.text(
|
27
|
-
when :bad_json then reply.body =
|
28
|
-
when :bad_state then input[:state] =
|
26
|
+
when :non_json then reply.text('non-json reply')
|
27
|
+
when :bad_json then reply.body = '{"access_token":"good.access.token" "missed a comma":"there"}'
|
28
|
+
when :bad_state then input[:state] = 'badstate'
|
29
29
|
when :no_token_type then input.delete(:token_type)
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
33
|
def bad_request(msg = nil); reply_in_kind(400, error: "bad request#{msg ? ',' : ''} #{msg}") end
|
34
34
|
def not_found(name = nil); reply_in_kind(404, error: "#{name} not found") end
|
35
|
-
def access_denied(msg =
|
35
|
+
def access_denied(msg = 'access denied') reply_in_kind(403, error: 'access_denied', error_description: msg) end
|
36
36
|
def ids_to_names(ids); ids ? ids.map { |id| server.scim.name(id) } : [] end
|
37
37
|
def names_to_ids(names, rtype); names ? names.map { |name| server.scim.id(name, rtype) } : [] end
|
38
38
|
def encode_cookie(obj = {}) Util.json_encode64(obj) end
|
39
39
|
def decode_cookie(str) Util.json.decode64(str) end
|
40
40
|
|
41
41
|
def valid_token(accepted_scope)
|
42
|
-
return nil unless (ah = request.headers[
|
43
|
-
contents = TokenCoder.decode(ah[1], accept_algorithms:
|
44
|
-
contents[
|
45
|
-
return contents if accepted_scope.nil? || !(accepted_scope & contents[
|
42
|
+
return nil unless (ah = request.headers['authorization']) && (ah = ah.split(' '))[0] =~ /^bearer$/i
|
43
|
+
contents = TokenCoder.decode(ah[1], accept_algorithms: 'none')
|
44
|
+
contents['scope'], accepted_scope = Util.arglist(contents['scope']), Util.arglist(accepted_scope)
|
45
|
+
return contents if accepted_scope.nil? || !(accepted_scope & contents['scope']).empty?
|
46
46
|
access_denied("accepted scope #{Util.strlist(accepted_scope)}")
|
47
47
|
end
|
48
48
|
|
49
49
|
def primary_email(emails)
|
50
50
|
return unless emails
|
51
|
-
emails.each {|e| return e[:value] if e[:type] && e[:type] ==
|
51
|
+
emails.each {|e| return e[:value] if e[:type] && e[:type] == 'primary'
|
52
|
+
}
|
52
53
|
emails[0][:value]
|
53
54
|
end
|
54
55
|
|
@@ -61,52 +62,52 @@ class StubUAAConn < Stub::Base
|
|
61
62
|
# miscellaneous endpoints
|
62
63
|
#
|
63
64
|
|
64
|
-
def default_route; reply_in_kind(404, error:
|
65
|
+
def default_route; reply_in_kind(404, error: 'not found', error_description: "unknown path #{request.path}") end
|
65
66
|
|
66
67
|
route :get, '/favicon.ico' do
|
67
|
-
reply.headers[:content_type] =
|
68
|
+
reply.headers[:content_type] = 'image/vnd.microsoft.icon'
|
68
69
|
reply.body = File.read File.expand_path(File.join(__FILE__, '..', '..', 'lib', 'cli', 'favicon.ico'))
|
69
70
|
end
|
70
71
|
|
71
72
|
route :put, '/another-fake-endpoint' do
|
72
|
-
return unless valid_token(
|
73
|
+
return unless valid_token('clients.read')
|
73
74
|
parsed = JSON.parse(request.body)
|
74
75
|
reply_in_kind(202, parsed.merge(:updated => 42))
|
75
76
|
end
|
76
77
|
|
77
78
|
route :put, '/fake-endpoint-empty-response' do
|
78
|
-
return unless valid_token(
|
79
|
+
return unless valid_token('clients.read')
|
79
80
|
reply.empty()
|
80
81
|
end
|
81
82
|
|
82
83
|
route :get, '/my-fake-endpoint' do
|
83
|
-
return unless valid_token(
|
84
|
-
reply_in_kind(200,
|
84
|
+
return unless valid_token('clients.read')
|
85
|
+
reply_in_kind(200, 'some fake response text')
|
85
86
|
end
|
86
87
|
|
87
88
|
route :get, '/' do reply_in_kind "welcome to stub UAA, version #{VERSION}" end
|
88
89
|
route :get, '/varz' do reply_in_kind(mem: 0, type: 'UAA', app: { version: VERSION } ) end
|
89
|
-
route :get, '/token_key' do reply_in_kind(alg:
|
90
|
+
route :get, '/token_key' do reply_in_kind(alg: 'none', value: 'none') end
|
90
91
|
|
91
|
-
route :post, '/password/score',
|
92
|
+
route :post, '/password/score', 'content-type' => %r{application/x-www-form-urlencoded} do
|
92
93
|
info = Util.decode_form(request.body)
|
93
|
-
return bad_request
|
94
|
+
return bad_request 'no password to score' unless pwd = info['password']
|
94
95
|
score = pwd.length > 10 || pwd.length < 0 ? 10 : pwd.length
|
95
96
|
reply_in_kind(score: score, requiredScore: 0)
|
96
97
|
end
|
97
98
|
|
98
99
|
route :get, %r{^/userinfo(\?|$)(.*)} do
|
99
|
-
return not_found unless (tokn = valid_token(
|
100
|
-
(info = server.scim.get(tokn[
|
100
|
+
return not_found unless (tokn = valid_token('openid')) &&
|
101
|
+
(info = server.scim.get(tokn['user_id'], :user, :username, :id, :emails)) && info[:username]
|
101
102
|
reply_in_kind(user_id: info[:id], user_name: info[:username], email: primary_email(info[:emails]))
|
102
103
|
end
|
103
104
|
|
104
105
|
route :get, '/login' do
|
105
|
-
return reply_in_kind(server.info) unless request.headers[
|
106
|
-
session = decode_cookie(request.cookies[
|
107
|
-
if session[
|
106
|
+
return reply_in_kind(server.info) unless request.headers['accept'] =~ /text\/html/
|
107
|
+
session = decode_cookie(request.cookies['stubsession']) || {}
|
108
|
+
if session['username']
|
108
109
|
page = <<-DATA.gsub(/^ +/, '')
|
109
|
-
you are logged in as #{session[
|
110
|
+
you are logged in as #{session['username']}
|
110
111
|
<form id='logout' action='login.do' method='get' accept-charset='UTF-8'>
|
111
112
|
<input type='submit' name='submit' value='Logout' /></form>
|
112
113
|
DATA
|
@@ -124,17 +125,17 @@ class StubUAAConn < Stub::Base
|
|
124
125
|
#reply.set_cookie(:stubsession, encode_cookie(session), httponly: nil)
|
125
126
|
end
|
126
127
|
|
127
|
-
route :post, '/login.do',
|
128
|
+
route :post, '/login.do', 'content-type' => %r{application/x-www-form-urlencoded} do
|
128
129
|
creds = Util.decode_form(request.body)
|
129
130
|
user = find_user(creds['username'], creds['password'])
|
130
|
-
reply.headers[:location] =
|
131
|
+
reply.headers[:location] = 'login'
|
131
132
|
reply.status = 302
|
132
133
|
reply.set_cookie(:stubsession, encode_cookie(username: user[:username], httponly: nil))
|
133
134
|
end
|
134
135
|
|
135
136
|
route :get, %r{^/logout.do(\?|$)(.*)} do
|
136
137
|
query = Util.decode_form(match[2])
|
137
|
-
reply.headers[:location] = query['redirect_uri'] ||
|
138
|
+
reply.headers[:location] = query['redirect_uri'] || 'login'
|
138
139
|
reply.status = 302
|
139
140
|
reply.set_cookie(:stubsession, encode_cookie, max_age: -1)
|
140
141
|
end
|
@@ -156,9 +157,9 @@ class StubUAAConn < Stub::Base
|
|
156
157
|
token_body[:user_name] = user[:username]
|
157
158
|
end
|
158
159
|
info = { access_token: TokenCoder.encode(token_body, :algorithm => 'none'),
|
159
|
-
token_type:
|
160
|
+
token_type: 'bearer', expires_in: interval, scope: scope}
|
160
161
|
info[:state] = state if state
|
161
|
-
info[:refresh_token] =
|
162
|
+
info[:refresh_token] = 'universal_refresh_token' if refresh
|
162
163
|
inject_error(info)
|
163
164
|
info
|
164
165
|
end
|
@@ -211,45 +212,45 @@ class StubUAAConn < Stub::Base
|
|
211
212
|
|
212
213
|
route [:post, :get], %r{^/oauth/authorize\?(.*)} do
|
213
214
|
query = Util.decode_form(match[1])
|
214
|
-
client = server.scim.get_by_name(query[
|
215
|
-
cburi, state = query[
|
215
|
+
client = server.scim.get_by_name(query['client_id'], :client)
|
216
|
+
cburi, state = query['redirect_uri'], query['state']
|
216
217
|
|
217
218
|
# if invalid client_id or redir_uri: inform resource owner, do not redirect
|
218
219
|
unless client && valid_redir_uri?(client, cburi)
|
219
|
-
return bad_request
|
220
|
+
return bad_request 'invalid client_id or redirect_uri'
|
220
221
|
end
|
221
|
-
if query[
|
222
|
-
unless client[:authorized_grant_types].include?(
|
223
|
-
return redir_err_f(cburi, state,
|
222
|
+
if query['response_type'] == 'token'
|
223
|
+
unless client[:authorized_grant_types].include?('implicit')
|
224
|
+
return redir_err_f(cburi, state, 'unauthorized_client')
|
224
225
|
end
|
225
|
-
if request.method ==
|
226
|
-
unless request.headers[
|
226
|
+
if request.method == 'post'
|
227
|
+
unless request.headers['content-type'] =~ %r{application/x-www-form-urlencoded} &&
|
227
228
|
(creds = Util.decode_form(request.body)) &&
|
228
|
-
creds[
|
229
|
-
return redir_err_f(cburi, state,
|
229
|
+
creds['source'] && creds['source'] == 'credentials'
|
230
|
+
return redir_err_f(cburi, state, 'invalid_request')
|
230
231
|
end
|
231
|
-
unless user = find_user(creds[
|
232
|
-
return redir_err_f(cburi, state,
|
232
|
+
unless user = find_user(creds['username'], creds['password'])
|
233
|
+
return redir_err_f(cburi, state, 'access_denied')
|
233
234
|
end
|
234
235
|
else
|
235
236
|
return reply.status = 501 # TODO: how to authN user and ask for authorizations?
|
236
237
|
end
|
237
|
-
unless (granted_scope = calc_scope(client, user, query[
|
238
|
-
return redir_err_f(cburi, state,
|
238
|
+
unless (granted_scope = calc_scope(client, user, query['scope']))
|
239
|
+
return redir_err_f(cburi, state, 'invalid_scope')
|
239
240
|
end
|
240
241
|
# TODO: how to stub any remaining scopes that are not auto-approve?
|
241
|
-
token_reply_info = token_reply_info(client, granted_scope, user, query[
|
242
|
-
token_reply_info.delete(:scope) if query[
|
242
|
+
token_reply_info = token_reply_info(client, granted_scope, user, query['state'])
|
243
|
+
token_reply_info.delete(:scope) if query['scope']
|
243
244
|
return redir_with_fragment(cburi, token_reply_info)
|
244
245
|
end
|
245
|
-
return redir_err_q(cburi, state,
|
246
|
-
return redir_err_q(cburi, state,
|
247
|
-
unless client[:authorized_grant_types].include?(
|
248
|
-
return redir_err_f(cburi, state,
|
246
|
+
return redir_err_q(cburi, state, 'invalid_request') unless request.method == 'get'
|
247
|
+
return redir_err_q(cburi, state, 'unsupported_response_type') unless query['response_type'] == 'code'
|
248
|
+
unless client[:authorized_grant_types].include?('authorization_code')
|
249
|
+
return redir_err_f(cburi, state, 'unauthorized_client')
|
249
250
|
end
|
250
|
-
return reply.status = 501 unless query[
|
251
|
-
return redir_err_f(cburi, state,
|
252
|
-
scope = calc_scope(client, user, query[
|
251
|
+
return reply.status = 501 unless query['emphatic_user'] # TODO: how to authN user and ask for authorizations?
|
252
|
+
return redir_err_f(cburi, state, 'access_denied') unless user = find_user(query['emphatic_user'])
|
253
|
+
scope = calc_scope(client, user, query['scope'])
|
253
254
|
redir_with_query(cburi, state: state, code: assign_auth_code(client[:id], user[:id], scope, cburi))
|
254
255
|
end
|
255
256
|
|
@@ -257,13 +258,13 @@ class StubUAAConn < Stub::Base
|
|
257
258
|
def bad_params?(params, required, optional = nil)
|
258
259
|
required.each {|r|
|
259
260
|
next if params[r]
|
260
|
-
reply.json(400, error:
|
261
|
+
reply.json(400, error: 'invalid_request', error_description: "no #{r} in request")
|
261
262
|
return true
|
262
263
|
}
|
263
264
|
return false unless optional
|
264
265
|
params.each {|k, v|
|
265
266
|
next if required.include?(k) || optional.include?(k)
|
266
|
-
reply.json(400, error:
|
267
|
+
reply.json(400, error: 'invalid_request', error_description: "#{k} not allowed")
|
267
268
|
return true
|
268
269
|
}
|
269
270
|
false
|
@@ -275,7 +276,7 @@ class StubUAAConn < Stub::Base
|
|
275
276
|
class << self; attr_accessor :authcode_store end
|
276
277
|
def assign_auth_code(client_id, user_id, scope, redir_uri)
|
277
278
|
code = SecureRandom.base64(8)
|
278
|
-
raise
|
279
|
+
raise 'authcode collision' if self.class.authcode_store[code]
|
279
280
|
self.class.authcode_store[code] = {client_id: client_id, user_id: user_id,
|
280
281
|
scope: scope, redir_uri: redir_uri}
|
281
282
|
code
|
@@ -286,25 +287,25 @@ class StubUAAConn < Stub::Base
|
|
286
287
|
[info[:user_id], info[:scope]]
|
287
288
|
end
|
288
289
|
|
289
|
-
route :post,
|
290
|
-
|
291
|
-
unless client = auth_client(request.headers[
|
292
|
-
reply.headers[:www_authenticate] =
|
293
|
-
return reply.json(401, error:
|
290
|
+
route :post, '/oauth/token', 'content-type' => %r{application/x-www-form-urlencoded},
|
291
|
+
'accept' => %r{application/json} do
|
292
|
+
unless client = auth_client(request.headers['authorization'])
|
293
|
+
reply.headers[:www_authenticate] = 'basic'
|
294
|
+
return reply.json(401, error: 'invalid_client')
|
294
295
|
end
|
295
296
|
return if bad_params?(params = Util.decode_form(request.body), ['grant_type'])
|
296
297
|
unless client[:authorized_grant_types].include?(params['grant_type'])
|
297
|
-
return reply.json(400, error:
|
298
|
+
return reply.json(400, error: 'unauthorized_client')
|
298
299
|
end
|
299
300
|
case params.delete('grant_type')
|
300
|
-
when
|
301
|
+
when 'authorization_code'
|
301
302
|
# TODO: need authcode store with requested scope, redir_uri must match
|
302
303
|
return if bad_params?(params, ['code', 'redirect_uri'], [])
|
303
304
|
user_id, scope = redeem_auth_code(client[:id], params['redirect_uri'], params['code'])
|
304
|
-
return reply.json(400, error:
|
305
|
+
return reply.json(400, error: 'invalid_grant') unless user_id && scope
|
305
306
|
user = server.scim.get(user, :user, :id, :emails, :username)
|
306
307
|
reply.json(token_reply_info(client, scope, user, nil, true))
|
307
|
-
when
|
308
|
+
when 'password'
|
308
309
|
notPassword = bad_params?(params, ['username', 'password'], ['scope'])
|
309
310
|
notPasscode = bad_params?(params, ['passcode'], ['scope'])
|
310
311
|
return if notPasscode && notPassword
|
@@ -316,33 +317,33 @@ class StubUAAConn < Stub::Base
|
|
316
317
|
username, password = Base64::urlsafe_decode64(params['passcode']).split
|
317
318
|
end
|
318
319
|
user = find_user(username, password)
|
319
|
-
return reply.json(400, error:
|
320
|
+
return reply.json(400, error: 'invalid_grant') unless user
|
320
321
|
scope = calc_scope(client, user, params['scope'])
|
321
|
-
return reply.json(400, error:
|
322
|
+
return reply.json(400, error: 'invalid_scope') unless scope
|
322
323
|
reply.json(200, token_reply_info(client, scope, user))
|
323
|
-
when
|
324
|
+
when 'client_credentials'
|
324
325
|
return if bad_params?(params, [], ['scope'])
|
325
326
|
scope = calc_scope(client, nil, params['scope'])
|
326
|
-
return reply.json(400, error:
|
327
|
+
return reply.json(400, error: 'invalid_scope') unless scope
|
327
328
|
reply.json(token_reply_info(client, scope))
|
328
|
-
when
|
329
|
+
when 'refresh_token'
|
329
330
|
return if bad_params?(params, ['refresh_token'], ['scope'])
|
330
|
-
return reply.json(400, error:
|
331
|
+
return reply.json(400, error: 'invalid_grant') unless params['refresh_token'] == 'universal_refresh_token'
|
331
332
|
# TODO: max scope should come from refresh token, or user from refresh token
|
332
333
|
# this should use calc_scope when we know the user
|
333
334
|
scope = ids_to_names(client[:scope])
|
334
335
|
scope = Util.strlist(Util.arglist(params['scope'], scope) & scope)
|
335
|
-
return reply.json(400, error:
|
336
|
+
return reply.json(400, error: 'invalid_scope') if scope.empty?
|
336
337
|
reply.json(token_reply_info(client, scope))
|
337
338
|
else
|
338
|
-
reply.json(400, error:
|
339
|
+
reply.json(400, error: 'unsupported_grant_type')
|
339
340
|
end
|
340
341
|
inject_error
|
341
342
|
end
|
342
343
|
|
343
|
-
route :post,
|
344
|
-
|
345
|
-
request.path.replace(
|
344
|
+
route :post, '/alternate/oauth/token', 'content-type' => %r{application/x-www-form-urlencoded},
|
345
|
+
'accept' => %r{application/json} do
|
346
|
+
request.path.replace('/oauth/token')
|
346
347
|
server.info.delete(:token_endpoint) # this indicates this was executed for a unit test
|
347
348
|
process
|
348
349
|
end
|
@@ -362,101 +363,106 @@ class StubUAAConn < Stub::Base
|
|
362
363
|
end
|
363
364
|
|
364
365
|
route :get, %r{^/oauth/clients(\?|$)(.*)} do
|
365
|
-
return unless valid_token(
|
366
|
+
return unless valid_token('clients.read')
|
366
367
|
info, _ = server.scim.find(:client)
|
367
368
|
reply_in_kind(info.each_with_object({}) {|c, o| o[c[:client_id]] = scim_to_client(c)})
|
368
369
|
end
|
369
370
|
|
370
|
-
route :post, '/oauth/clients',
|
371
|
-
return unless valid_token(
|
371
|
+
route :post, '/oauth/clients', 'content-type' => %r{application/json} do
|
372
|
+
return unless valid_token('clients.write')
|
372
373
|
id = server.scim.add(:client, client_to_scim(Util.json_parse(request.body, :down)))
|
373
374
|
reply_in_kind scim_to_client(server.scim.get(id, :client, *StubScim::VISIBLE_ATTRS[:client]))
|
374
375
|
end
|
375
376
|
|
376
|
-
route :put, %r{^/oauth/clients/([^/]+)$},
|
377
|
-
return unless valid_token(
|
377
|
+
route :put, %r{^/oauth/clients/([^/]+)$}, 'content-type' => %r{application/json} do
|
378
|
+
return unless valid_token('clients.write')
|
378
379
|
info = client_to_scim(Util.json_parse(request.body, :down))
|
379
380
|
server.scim.update(server.scim.id(match[1], :client), info)
|
380
381
|
reply.json(scim_to_client(info))
|
381
382
|
end
|
382
383
|
|
383
384
|
route :get, %r{^/oauth/clients/([^/]+)$} do
|
384
|
-
return unless valid_token(
|
385
|
+
return unless valid_token('clients.read')
|
385
386
|
return not_found(match[1]) unless client = server.scim.get_by_name(match[1], :client, *StubScim::VISIBLE_ATTRS[:client])
|
386
387
|
reply_in_kind(scim_to_client(client))
|
387
388
|
end
|
388
389
|
|
390
|
+
route :get, %r{^/oauth/clients/([^/]+)/meta$} do
|
391
|
+
return unless valid_token('clients.read')
|
392
|
+
reply_in_kind(server.scim.get_client_meta(match[1]))
|
393
|
+
end
|
394
|
+
|
389
395
|
route :delete, %r{^/oauth/clients/([^/]+)$} do
|
390
|
-
return unless valid_token(
|
396
|
+
return unless valid_token('clients.write')
|
391
397
|
return not_found(match[1]) unless server.scim.delete(server.scim.id(match[1], :client))
|
392
398
|
end
|
393
399
|
|
394
|
-
route :put, %r{^/oauth/clients/([^/]+)/secret$},
|
400
|
+
route :put, %r{^/oauth/clients/([^/]+)/secret$}, 'content-type' => %r{application/json} do
|
395
401
|
info = Util.json_parse(request.body, :down)
|
396
402
|
return not_found(match[1]) unless id = server.scim.id(match[1], :client)
|
397
|
-
return bad_request(
|
403
|
+
return bad_request('no new secret given') unless info['secret']
|
398
404
|
if oldsecret = info['oldsecret']
|
399
|
-
return unless valid_token(
|
405
|
+
return unless valid_token('clients.secret')
|
400
406
|
return not_found(match[1]) unless client = server.scim.get(id, :client, :client_secret)
|
401
|
-
return bad_request(
|
407
|
+
return bad_request('old secret does not match') unless oldsecret == client[:client_secret]
|
402
408
|
else
|
403
|
-
return unless valid_token(
|
409
|
+
return unless valid_token('uaa.admin')
|
404
410
|
end
|
405
411
|
server.scim.set_hidden_attr(id, :client_secret, info['secret'])
|
406
|
-
reply.json(status:
|
412
|
+
reply.json(status: 'ok', message: 'secret updated')
|
407
413
|
end
|
408
414
|
|
409
415
|
#----------------------------------------------------------------------------
|
410
416
|
# users and groups endpoints
|
411
417
|
#
|
412
|
-
route :post, %r{^/(Users|Groups)$},
|
413
|
-
return unless valid_token(
|
414
|
-
rtype = match[1] ==
|
418
|
+
route :post, %r{^/(Users|Groups)$}, 'content-type' => %r{application/json} do
|
419
|
+
return unless valid_token('scim.write')
|
420
|
+
rtype = match[1] == 'Users' ? :user : :group
|
415
421
|
id = server.scim.add(rtype, Util.json_parse(request.body, :down))
|
416
422
|
server.auto_groups.each {|g| server.scim.add_member(g, id)} if rtype == :user && server.auto_groups
|
417
423
|
reply_in_kind server.scim.get(id, rtype, *StubScim::VISIBLE_ATTRS[rtype])
|
418
424
|
end
|
419
425
|
|
420
426
|
def obj_access?(rtype, oid, perm)
|
421
|
-
major_scope = perm == :writers ?
|
427
|
+
major_scope = perm == :writers ? 'scim.write' : 'scim.read'
|
422
428
|
return unless tkn = valid_token("#{major_scope} scim.me")
|
423
|
-
return tkn if tkn[
|
424
|
-
rtype == :group && server.scim.is_member(oid, tkn[
|
429
|
+
return tkn if tkn['scope'].include?(major_scope) ||
|
430
|
+
rtype == :group && server.scim.is_member(oid, tkn['user_id'], perm)
|
425
431
|
access_denied
|
426
432
|
end
|
427
433
|
|
428
|
-
route :put, %r{^/(Users|Groups)/([^/]+)$},
|
429
|
-
rtype = match[1] ==
|
434
|
+
route :put, %r{^/(Users|Groups)/([^/]+)$}, 'content-type' => %r{application/json} do
|
435
|
+
rtype = match[1] == 'Users' ? :user : :group
|
430
436
|
return unless obj_access?(rtype, match[2], :writers)
|
431
437
|
version = request.headers['if-match']
|
432
438
|
version = version.to_i if version.to_i.to_s == version
|
433
439
|
begin
|
434
440
|
id = server.scim.update(match[2], Util.json_parse(request.body, :down), version, rtype)
|
435
441
|
reply_in_kind server.scim.get(id, rtype, *StubScim::VISIBLE_ATTRS[rtype])
|
436
|
-
rescue BadVersion; reply_in_kind(409, error:
|
442
|
+
rescue BadVersion; reply_in_kind(409, error: 'invalid object version')
|
437
443
|
rescue NotFound; not_found(match[2])
|
438
444
|
end
|
439
445
|
end
|
440
446
|
|
441
|
-
route :post, %r{^/Groups/External$},
|
447
|
+
route :post, %r{^/Groups/External$}, 'content-type' => %r{application/json} do
|
442
448
|
json = Util.json_parse(request.body, :down)
|
443
|
-
external_group = json[
|
444
|
-
group_name = json[
|
445
|
-
group_id = json[
|
446
|
-
origin = json[
|
449
|
+
external_group = json['externalgroup']
|
450
|
+
group_name = json['displayname']
|
451
|
+
group_id = json['groupid']
|
452
|
+
origin = json['origin']
|
447
453
|
group = server.scim.add_group_mapping(external_group, group_id, group_name, origin)
|
448
454
|
reply_in_kind(displayName: group[:displayname], externalGroup: external_group, groupId: group[:id], origin: origin)
|
449
455
|
end
|
450
456
|
|
451
457
|
route :get, %r{^/Groups/External/list(\?|$)(.*)} do
|
452
|
-
return unless valid_token(
|
458
|
+
return unless valid_token('scim.read')
|
453
459
|
|
454
460
|
query_params = CGI::parse(match[2])
|
455
461
|
|
456
|
-
start_index_param = query_params[
|
462
|
+
start_index_param = query_params['startIndex'].first
|
457
463
|
start_index = start_index_param.empty? ? 1 : start_index_param.to_i
|
458
464
|
|
459
|
-
count_param = query_params[
|
465
|
+
count_param = query_params['count'].first
|
460
466
|
count = count_param.empty? ? 100 : count_param.to_i
|
461
467
|
|
462
468
|
group_mappings = server.scim.get_group_mappings
|
@@ -466,7 +472,7 @@ class StubUAAConn < Stub::Base
|
|
466
472
|
end
|
467
473
|
|
468
474
|
route :delete, %r{^/Groups/External/groupId/([^/]+)/externalGroup/([^/]+)/origin/([^/]+)$} do
|
469
|
-
return unless valid_token(
|
475
|
+
return unless valid_token('scim.write')
|
470
476
|
|
471
477
|
group_id = match[1]
|
472
478
|
external_group = match[2]
|
@@ -492,18 +498,18 @@ class StubUAAConn < Stub::Base
|
|
492
498
|
end
|
493
499
|
start = sanitize_int(query['startindex'], 1, 1)
|
494
500
|
count = sanitize_int(query['count'], 15, 1, 3000)
|
495
|
-
return bad_request(
|
501
|
+
return bad_request('invalid startIndex or count') unless start && count
|
496
502
|
info, total = server.scim.find(rtype, start: start - 1, count: count,
|
497
503
|
filter: query['filter'], attrs: attrs, acl: acl, acl_id: acl_id)
|
498
504
|
reply_in_kind(resources: info, itemsPerPage: info.length, startIndex: start, totalResults: total)
|
499
505
|
end
|
500
506
|
|
501
507
|
route :get, %r{^/(Users|Groups)(\?|$)(.*)} do
|
502
|
-
rtype = match[1] ==
|
503
|
-
return unless tkn = valid_token(
|
508
|
+
rtype = match[1] == 'Users' ? :user : :group
|
509
|
+
return unless tkn = valid_token('scim.read scim.me')
|
504
510
|
acl = acl_id = nil
|
505
|
-
unless tkn[
|
506
|
-
acl, acl_id = :readers, tkn[
|
511
|
+
unless tkn['scope'].include?('scim.read')
|
512
|
+
acl, acl_id = :readers, tkn['user_id']
|
507
513
|
return access_denied unless rtype == :group && acl_id
|
508
514
|
end
|
509
515
|
page_query(rtype, Util.decode_form(match[3], :down),
|
@@ -511,7 +517,7 @@ class StubUAAConn < Stub::Base
|
|
511
517
|
end
|
512
518
|
|
513
519
|
route :get, %r{^/(Users|Groups)/([^/]+)$} do
|
514
|
-
rtype = match[1] ==
|
520
|
+
rtype = match[1] == 'Users' ? :user : :group
|
515
521
|
return unless obj_access?(rtype, match[2], :readers)
|
516
522
|
return not_found(match[2]) unless obj = server.scim.get(match[2], rtype, *StubScim::VISIBLE_ATTRS[rtype])
|
517
523
|
reply_in_kind(obj)
|
@@ -521,23 +527,35 @@ class StubUAAConn < Stub::Base
|
|
521
527
|
reply_in_kind('"locked":false')
|
522
528
|
end
|
523
529
|
|
530
|
+
route :patch, %r{^/(Users)/([^/]+)$}, 'content-type' => %r{application/json} do
|
531
|
+
return unless obj_access?(:user, match[2], :writers)
|
532
|
+
version = request.headers['if-match']
|
533
|
+
version = version.to_i if version.to_i.to_s == version
|
534
|
+
begin
|
535
|
+
id = server.scim.patch(match[2], Util.json_parse(request.body, :down), version, :user)
|
536
|
+
reply_in_kind server.scim.get(id, :user, *StubScim::VISIBLE_ATTRS[:user])
|
537
|
+
rescue BadVersion; reply_in_kind(409, error: 'invalid object version')
|
538
|
+
rescue NotFound; not_found(match[2])
|
539
|
+
end
|
540
|
+
end
|
541
|
+
|
524
542
|
route :delete, %r{^/(Users|Groups)/([^/]+)$} do
|
525
|
-
return unless valid_token(
|
526
|
-
not_found(match[2]) unless server.scim.delete(match[2], match[1] ==
|
543
|
+
return unless valid_token('scim.write')
|
544
|
+
not_found(match[2]) unless server.scim.delete(match[2], match[1] == 'Users' ? :user : :group)
|
527
545
|
end
|
528
546
|
|
529
|
-
route :put, %r{^/Users/([^/]+)/password$},
|
547
|
+
route :put, %r{^/Users/([^/]+)/password$}, 'content-type' => %r{application/json} do
|
530
548
|
info = Util.json_parse(request.body, :down)
|
531
549
|
if oldpwd = info['oldpassword']
|
532
|
-
return unless valid_token(
|
550
|
+
return unless valid_token('password.write')
|
533
551
|
return not_found(match[1]) unless user = server.scim.get(match[1], :user, :password)
|
534
|
-
return bad_request(
|
552
|
+
return bad_request('old password does not match') unless oldpwd == user[:password]
|
535
553
|
else
|
536
|
-
return unless valid_token(
|
554
|
+
return unless valid_token('scim.write')
|
537
555
|
end
|
538
|
-
return bad_request(
|
556
|
+
return bad_request('no new password given') unless newpwd = info['password']
|
539
557
|
server.scim.set_hidden_attr(match[1], :password, newpwd)
|
540
|
-
reply.json(status:
|
558
|
+
reply.json(status: 'ok', message: 'password updated')
|
541
559
|
end
|
542
560
|
|
543
561
|
route :get, %r{^/ids/Users(\?|$)(.*)} do
|
@@ -552,25 +570,25 @@ class StubUAA < Stub::Server
|
|
552
570
|
attr_reader :scim, :auto_groups
|
553
571
|
|
554
572
|
def initialize(options = {})
|
555
|
-
client = options[:boot_client] ||
|
556
|
-
secret = options[:boot_secret] ||
|
573
|
+
client = options[:boot_client] || 'admin'
|
574
|
+
secret = options[:boot_secret] || 'adminsecret'
|
557
575
|
@scim = StubScim.new
|
558
|
-
@auto_groups = [
|
576
|
+
@auto_groups = ['password.write', 'openid']
|
559
577
|
.each_with_object([]) { |g, o| o << @scim.add(:group, 'displayname' => g) }
|
560
|
-
[
|
578
|
+
['scim.read', 'scim.write', 'scim.me', 'uaa.resource']
|
561
579
|
.each { |g| @scim.add(:group, 'displayname' => g) }
|
562
|
-
gids = [
|
580
|
+
gids = ['clients.write', 'clients.read', 'clients.secret', 'uaa.admin']
|
563
581
|
.each_with_object([]) { |s, o| o << @scim.add(:group, 'displayname' => s) }
|
564
582
|
@scim.add(:client, 'client_id' => client, 'client_secret' => secret,
|
565
|
-
'authorized_grant_types' => [
|
583
|
+
'authorized_grant_types' => ['client_credentials'], 'authorities' => gids,
|
566
584
|
'access_token_validity' => 60 * 60 * 24 * 7)
|
567
|
-
@scim.add(:client, 'client_id' =>
|
568
|
-
'scope' => [@scim.id(
|
585
|
+
@scim.add(:client, 'client_id' => 'cf', 'authorized_grant_types' => ['implicit'],
|
586
|
+
'scope' => [@scim.id('openid', :group), @scim.id('password.write', :group)],
|
569
587
|
'access_token_validity' => 5 * 60 )
|
570
|
-
info = { commit_id:
|
571
|
-
app: {name:
|
572
|
-
description:
|
573
|
-
prompts: {username: [
|
588
|
+
info = { commit_id: 'not implemented',
|
589
|
+
app: {name: 'Stub UAA', version: CLI_VERSION,
|
590
|
+
description: 'User Account and Authentication Service, test server'},
|
591
|
+
prompts: {username: ['text', 'Username'], password: ['password', 'Password']} }
|
574
592
|
super(StubUAAConn, options.merge(info: info, logger: options[:logger] || Util.default_logger))
|
575
593
|
end
|
576
594
|
|