cf-uaac 3.5.0 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
|