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 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