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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3c37b2ebe3caa99e54b161ddf16ae7621e067753
4
- data.tar.gz: 63e43ef4fed0eff9f9dab075a3863589a40ae3f6
3
+ metadata.gz: 7ad629a17b4992798f2f92eb7c4f6f658220579a
4
+ data.tar.gz: 69f7b40c269212155448de48f237484d86182486
5
5
  SHA512:
6
- metadata.gz: be007e6e3dc344260d7248588efbb73a7fbd716c8ed68c082bf7e372cffb1c535d8636db83ce01a7365ec2b33e81e6ca6f127fef28ecbdfd420a0c1f5aaafef0
7
- data.tar.gz: c149ebf7f4dfc0ef9324d28d8bd59505fe38af4f41f6a3d49ef1a844b6b239f98489eb4fd73be4a4654b645a4c1b0308c20ad67777f4e1db18831b1899a22f6c
6
+ metadata.gz: d6ed51ac1f2704d9c6c81f9aae8a05f2c37636697da90e6b7ed253503f6c15d1f7331b3d5b2e98d723cf93b21b6aef06b64d6297b014c98fe87c787ea6ef7bb3
7
+ data.tar.gz: 7250bf305e3ec5bc959f4b9b61af915b3ef5f3ad247dc44c9568a248b7ccfee6361b0533b324354fd96b303436bc76e1f14f35a15b239ae32437b3a98be90774
data/.travis.yml CHANGED
@@ -5,7 +5,7 @@ before_install:
5
5
  - gem install bundler
6
6
 
7
7
  rvm:
8
- - 1.9.3
8
+ - 2.2.1
9
9
 
10
10
 
11
11
 
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.7.0"
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"
@@ -17,18 +17,18 @@ module CF::UAA
17
17
 
18
18
  class ClientCli < CommonCli
19
19
 
20
- topic "Client Application Registrations", "reg"
20
+ topic 'Client Application Registrations', 'reg'
21
21
 
22
22
  CLIENT_SCHEMA = {
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"
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] == "true" || info[k] == "false")
49
- info[k] = !!(info[k] == "true")
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 == "list"
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 "clients [filter]", "List client registrations", :attrs, :start, :count do |filter|
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 scim_request { |sr| scim_get_object(sr, :client, clientid(id), opts[:attrs]) }
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, "--clone <other>", "get default settings from other"
66
- define_option :interact, "--[no-]interactive", "-i", "interactively verify all values"
67
- desc "client add [id]", "Add client registration",
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("New client secret", opts[:secret])
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 "client update [id]", "Update client registration", *CLIENT_SCHEMA.keys,
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] } ? cr.put(:client, info) :
86
- gripe("Nothing to update. Use -i for interactive update.")
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 "client delete [id]", "Delete client registration" do |id|
95
+ desc 'client delete [id]', 'Delete client registration' do |id|
91
96
  pp scim_request { |cr|
92
97
  cr.delete(:client, clientid(id))
93
- "client registration deleted"
98
+ 'client registration deleted'
94
99
  }
95
100
  end
96
101
 
97
- desc "secret set [id]", "Set client secret", :secret do |id|
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("New secret", opts[:secret]))
100
- "client secret successfully set"
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, "--old_secret <secret>", "current secret"
105
- desc "secret change", "Change secret for authenticated client in current context", :old_secret, :secret do
106
- return gripe "context not set" unless client_id = Config.context.to_s
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("Current secret")
109
- cr.change_secret(client_id, verified_pwd("New secret", opts[:secret]), old)
110
- "client secret successfully changed"
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 "User Accounts", "account"
20
+ topic 'User Accounts', 'account'
21
21
 
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>"
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, "-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|
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 "user get [name]", "Get specific user account", :attrs do |name|
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 "user add [name]", "Add a user account", *USER_INFO_OPTS, :password do |name|
50
- info = {userName: username(name), password: verified_pwd("Password", opts[:password])}
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
- "user account successfully added"
53
+ 'user account successfully added'
54
54
  }
55
55
  end
56
56
 
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",
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 "no user updates specified" if (updates = user_opts).empty?
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
- "user account successfully updated"
65
+ 'user account successfully updated'
66
66
  }
67
67
  end
68
68
 
69
- desc "user delete [name]", "Delete user account" do |name|
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
- "user account successfully deleted"
72
+ 'user account successfully deleted'
73
73
  }
74
74
  end
75
75
 
76
- desc "user ids [username|id...]", "Gets user names and ids for the given users" do |*users|
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("names or ids of users")) if !users || users.empty?
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, "no users found" unless ids && ids.length > 0
80
+ raise NotFound, 'no users found' unless ids && ids.length > 0
81
81
  ids
82
82
  }
83
83
  end
84
84
 
85
- desc "user unlock [name]", "Unlocks the user account" do |name|
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
- "user account successfully unlocked"
88
+ 'user account successfully unlocked'
89
89
  }
90
90
  end
91
91
 
92
- desc "password set [name]", "Set password", :password do |name|
92
+ desc 'user deactivate [name]', 'Deactivates user' do |name|
93
93
  pp scim_request { |ua|
94
- ua.change_password(ua.id(:user, username(name)), verified_pwd("New password", opts[:password]))
95
- "password successfully set"
94
+ change_activation(ua, name, false)
95
+ 'user account successfully deactivated'
96
96
  }
97
97
  end
98
98
 
99
- define_option :old_password, "-o", "--old_password <password>", "current password"
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
- raise "no user_id in current context" unless Config.value(:user_id)
103
- oldpwd = opts[:old_password] || ask_pwd("Current password")
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
@@ -14,6 +14,6 @@
14
14
  # Cloud Foundry namespace
15
15
  module CF
16
16
  module UAA
17
- CLI_VERSION = "3.5.0"
17
+ CLI_VERSION = "3.6.0"
18
18
  end
19
19
  end
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 unless thing[a]
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; @things_by_id, @things_by_name = {}, {} end
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("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"
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 = "access denied") reply_in_kind(403, error: "access_denied", error_description: msg) end
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["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?
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] == "primary"}
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: "not found", error_description: "unknown path #{request.path}") end
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] = "image/vnd.microsoft.icon"
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("clients.read")
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("clients.read")
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("clients.read")
84
- reply_in_kind(200, "some fake response text")
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: "none", value: "none") end
90
+ route :get, '/token_key' do reply_in_kind(alg: 'none', value: 'none') end
90
91
 
91
- route :post, '/password/score', "content-type" => %r{application/x-www-form-urlencoded} do
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 "no password to score" unless pwd = info["password"]
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("openid")) &&
100
- (info = server.scim.get(tokn["user_id"], :user, :username, :id, :emails)) && info[:username]
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["accept"] =~ /text\/html/
106
- session = decode_cookie(request.cookies["stubsession"]) || {}
107
- if session["username"]
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["username"]}
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', "content-type" => %r{application/x-www-form-urlencoded} 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] = "login"
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'] || "login"
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: "bearer", expires_in: interval, scope: scope}
160
+ token_type: 'bearer', expires_in: interval, scope: scope}
160
161
  info[:state] = state if state
161
- info[:refresh_token] = "universal_refresh_token" if refresh
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["client_id"], :client)
215
- cburi, state = query["redirect_uri"], query["state"]
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 "invalid client_id or redirect_uri"
220
+ return bad_request 'invalid client_id or redirect_uri'
220
221
  end
221
- if query["response_type"] == 'token'
222
- unless client[:authorized_grant_types].include?("implicit")
223
- return redir_err_f(cburi, state, "unauthorized_client")
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 == "post"
226
- unless request.headers["content-type"] =~ %r{application/x-www-form-urlencoded} &&
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["source"] && creds["source"] == "credentials"
229
- return redir_err_f(cburi, state, "invalid_request")
229
+ creds['source'] && creds['source'] == 'credentials'
230
+ return redir_err_f(cburi, state, 'invalid_request')
230
231
  end
231
- unless user = find_user(creds["username"], creds["password"])
232
- return redir_err_f(cburi, state, "access_denied")
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["scope"]))
238
- return redir_err_f(cburi, state, "invalid_scope")
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["state"])
242
- token_reply_info.delete(:scope) if query["scope"]
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, "invalid_request") unless request.method == "get"
246
- return redir_err_q(cburi, state, "unsupported_response_type") unless query["response_type"] == 'code'
247
- unless client[:authorized_grant_types].include?("authorization_code")
248
- return redir_err_f(cburi, state, "unauthorized_client")
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["emphatic_user"] # TODO: how to authN user and ask for authorizations?
251
- return redir_err_f(cburi, state, "access_denied") unless user = find_user(query["emphatic_user"])
252
- scope = calc_scope(client, user, query["scope"])
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: "invalid_request", error_description: "no #{r} in request")
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: "invalid_request", error_description: "#{k} not allowed")
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 "authcode collision" if self.class.authcode_store[code]
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, "/oauth/token", "content-type" => %r{application/x-www-form-urlencoded},
290
- "accept" => %r{application/json} do
291
- unless client = auth_client(request.headers["authorization"])
292
- reply.headers[:www_authenticate] = "basic"
293
- return reply.json(401, error: "invalid_client")
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: "unauthorized_client")
298
+ return reply.json(400, error: 'unauthorized_client')
298
299
  end
299
300
  case params.delete('grant_type')
300
- when "authorization_code"
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: "invalid_grant") unless user_id && scope
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 "password"
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: "invalid_grant") unless user
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: "invalid_scope") unless scope
322
+ return reply.json(400, error: 'invalid_scope') unless scope
322
323
  reply.json(200, token_reply_info(client, scope, user))
323
- when "client_credentials"
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: "invalid_scope") unless scope
327
+ return reply.json(400, error: 'invalid_scope') unless scope
327
328
  reply.json(token_reply_info(client, scope))
328
- when "refresh_token"
329
+ when 'refresh_token'
329
330
  return if bad_params?(params, ['refresh_token'], ['scope'])
330
- return reply.json(400, error: "invalid_grant") unless params['refresh_token'] == "universal_refresh_token"
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: "invalid_scope") if scope.empty?
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: "unsupported_grant_type")
339
+ reply.json(400, error: 'unsupported_grant_type')
339
340
  end
340
341
  inject_error
341
342
  end
342
343
 
343
- route :post, "/alternate/oauth/token", "content-type" => %r{application/x-www-form-urlencoded},
344
- "accept" => %r{application/json} do
345
- request.path.replace("/oauth/token")
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("clients.read")
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', "content-type" => %r{application/json} do
371
- return unless valid_token("clients.write")
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/([^/]+)$}, "content-type" => %r{application/json} do
377
- return unless valid_token("clients.write")
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("clients.read")
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("clients.write")
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$}, "content-type" => %r{application/json} do
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("no new secret given") unless info['secret']
403
+ return bad_request('no new secret given') unless info['secret']
398
404
  if oldsecret = info['oldsecret']
399
- return unless valid_token("clients.secret")
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("old secret does not match") unless oldsecret == client[:client_secret]
407
+ return bad_request('old secret does not match') unless oldsecret == client[:client_secret]
402
408
  else
403
- return unless valid_token("uaa.admin")
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: "ok", message: "secret updated")
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)$}, "content-type" => %r{application/json} do
413
- return unless valid_token("scim.write")
414
- rtype = match[1] == "Users"? :user : :group
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 ? "scim.write" : "scim.read"
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["scope"].include?(major_scope) ||
424
- rtype == :group && server.scim.is_member(oid, tkn["user_id"], perm)
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)/([^/]+)$}, "content-type" => %r{application/json} do
429
- rtype = match[1] == "Users"? :user : :group
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: "invalid object version")
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$}, "content-type" => %r{application/json} do
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["externalgroup"]
444
- group_name = json["displayname"]
445
- group_id = json["groupid"]
446
- origin = json["origin"]
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("scim.read")
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["startIndex"].first
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["count"].first
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("scim.write")
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("invalid startIndex or count") unless start && count
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] == "Users"? :user : :group
503
- return unless tkn = valid_token("scim.read scim.me")
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["scope"].include?("scim.read")
506
- acl, acl_id = :readers, tkn["user_id"]
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] == "Users"? :user : :group
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("scim.write")
526
- not_found(match[2]) unless server.scim.delete(match[2], match[1] == "Users"? :user : :group)
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$}, "content-type" => %r{application/json} do
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("password.write")
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("old password does not match") unless oldpwd == user[:password]
552
+ return bad_request('old password does not match') unless oldpwd == user[:password]
535
553
  else
536
- return unless valid_token("scim.write")
554
+ return unless valid_token('scim.write')
537
555
  end
538
- return bad_request("no new password given") unless newpwd = info['password']
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: "ok", message: "password updated")
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] || "admin"
556
- secret = options[:boot_secret] || "adminsecret"
573
+ client = options[:boot_client] || 'admin'
574
+ secret = options[:boot_secret] || 'adminsecret'
557
575
  @scim = StubScim.new
558
- @auto_groups = ["password.write", "openid"]
576
+ @auto_groups = ['password.write', 'openid']
559
577
  .each_with_object([]) { |g, o| o << @scim.add(:group, 'displayname' => g) }
560
- ["scim.read", "scim.write", "scim.me", "uaa.resource"]
578
+ ['scim.read', 'scim.write', 'scim.me', 'uaa.resource']
561
579
  .each { |g| @scim.add(:group, 'displayname' => g) }
562
- gids = ["clients.write", "clients.read", "clients.secret", "uaa.admin"]
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' => ["client_credentials"], 'authorities' => gids,
583
+ 'authorized_grant_types' => ['client_credentials'], 'authorities' => gids,
566
584
  'access_token_validity' => 60 * 60 * 24 * 7)
567
- @scim.add(:client, 'client_id' => "cf", 'authorized_grant_types' => ["implicit"],
568
- 'scope' => [@scim.id("openid", :group), @scim.id("password.write", :group)],
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: "not implemented",
571
- app: {name: "Stub UAA", version: CLI_VERSION,
572
- description: "User Account and Authentication Service, test server"},
573
- prompts: {username: ["text", "Username"], password: ["password","Password"]} }
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