cf-uaa-lib 1.3.1 → 1.3.2
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.
- data/LICENSE.TXT +12737 -0
- data/NOTICE.TXT +10 -0
- data/README.md +3 -1
- data/Rakefile +7 -6
- data/cf-uaa-lib.gemspec +3 -1
- data/lib/uaa/http.rb +37 -32
- data/lib/uaa/misc.rb +59 -30
- data/lib/uaa/scim.rb +150 -110
- data/lib/uaa/token_coder.rb +84 -42
- data/lib/uaa/token_issuer.rb +137 -120
- data/lib/uaa/util.rb +113 -62
- data/lib/uaa/version.rb +2 -1
- data/spec/http_spec.rb +1 -1
- data/spec/integration_spec.rb +149 -0
- data/spec/scim_spec.rb +12 -11
- data/spec/token_coder_spec.rb +6 -6
- data/spec/token_issuer_spec.rb +17 -14
- metadata +42 -6
data/lib/uaa/util.rb
CHANGED
@@ -16,13 +16,13 @@ require "base64"
|
|
16
16
|
require 'logger'
|
17
17
|
require 'uri'
|
18
18
|
|
19
|
-
#
|
19
|
+
# Cloud Foundry namespace
|
20
20
|
module CF
|
21
|
-
# Namespace for
|
21
|
+
# Namespace for User Account and Authentication service
|
22
22
|
module UAA end
|
23
23
|
end
|
24
24
|
|
25
|
-
class Logger #
|
25
|
+
class Logger # @private
|
26
26
|
Severity::TRACE = Severity::DEBUG - 1
|
27
27
|
def trace(progname, &blk); add(Logger::Severity::TRACE, nil, progname, &blk) end
|
28
28
|
def trace? ; @level <= Logger::Severity::TRACE end
|
@@ -33,49 +33,44 @@ module CF::UAA
|
|
33
33
|
# Useful parent class. All CF::UAA exceptions are derived from this.
|
34
34
|
class UAAError < RuntimeError; end
|
35
35
|
|
36
|
-
# Indicates an authentication error
|
36
|
+
# Indicates an authentication error.
|
37
37
|
class AuthError < UAAError; end
|
38
38
|
|
39
|
-
# Indicates an error occurred decoding a token, base64 decoding, or JSON
|
39
|
+
# Indicates an error occurred decoding a token, base64 decoding, or JSON.
|
40
40
|
class DecodeError < UAAError; end
|
41
41
|
|
42
|
-
#
|
42
|
+
# Helper functions useful to the UAA client APIs
|
43
43
|
class Util
|
44
44
|
|
45
|
-
#
|
46
|
-
#
|
47
|
-
#
|
48
|
-
#
|
49
|
-
#
|
45
|
+
# General method to transform a hash key to a given style. Useful when
|
46
|
+
# dealing with HTTP headers and various protocol tags that tend to contain
|
47
|
+
# '-' characters and are case-insensitive and want to use them as keys in
|
48
|
+
# ruby hashes. Useful for dealing with {http://www.simplecloud.info/ SCIM}
|
49
|
+
# case-insensitive attribute names to normalize all attribute names (downcase).
|
50
50
|
#
|
51
|
-
#
|
52
|
-
# [
|
53
|
-
# [
|
54
|
-
|
55
|
-
# [+:tocamel+] reverse of +uncamel+
|
56
|
-
# [+:tosym+] to symbol
|
57
|
-
# [+:tostr+] to string
|
58
|
-
# [+:down+] to lowercase
|
59
|
-
# [+:none+] leave the damn key alone
|
60
|
-
#
|
61
|
-
# returns new key
|
62
|
-
def self.hash_key(k, style)
|
51
|
+
# @param [String, Symbol] key current key
|
52
|
+
# @param [Symbol] style can be sym, downsym, down, str, [un]dash, [un]camel, nil, none
|
53
|
+
# @return [String, Symbol] new key
|
54
|
+
def self.hash_key(key, style)
|
63
55
|
case style
|
64
|
-
when :
|
65
|
-
when :
|
66
|
-
when :
|
67
|
-
when :
|
68
|
-
when :
|
69
|
-
when :
|
70
|
-
when :
|
71
|
-
when :
|
56
|
+
when nil, :none then key
|
57
|
+
when :downsym then key.to_s.downcase.to_sym
|
58
|
+
when :sym then key.to_sym
|
59
|
+
when :str then key.to_s
|
60
|
+
when :down then key.to_s.downcase
|
61
|
+
when :dash then key.to_s.downcase.tr('_', '-')
|
62
|
+
when :undash then key.to_s.downcase.tr('-', '_').to_sym
|
63
|
+
when :uncamel then key.to_s.gsub(/([A-Z])([^A-Z]*)/,'_\1\2').downcase.to_sym
|
64
|
+
when :camel then key.to_s.gsub(/(_[a-z])([^_]*)/) { $1[1].upcase + $2 }
|
72
65
|
else raise ArgumentError, "unknown hash key style: #{style}"
|
73
66
|
end
|
74
67
|
end
|
75
68
|
|
76
|
-
# Modifies obj in place changing any hash keys to style
|
77
|
-
#
|
78
|
-
|
69
|
+
# Modifies obj in place changing any hash keys to style. Recursively modifies
|
70
|
+
# subordinate hashes.
|
71
|
+
# @param style (see Util.hash_key)
|
72
|
+
# @return modified obj
|
73
|
+
def self.hash_keys!(obj, style = nil)
|
79
74
|
return obj if style == :none
|
80
75
|
return obj.each {|o| hash_keys!(o, style)} if obj.is_a? Array
|
81
76
|
return obj unless obj.is_a? Hash
|
@@ -88,9 +83,11 @@ class Util
|
|
88
83
|
obj.merge!(newkeys)
|
89
84
|
end
|
90
85
|
|
91
|
-
# Makes a new copy of obj with hash keys to style
|
92
|
-
#
|
93
|
-
|
86
|
+
# Makes a new copy of obj with hash keys to style. Recursively modifies
|
87
|
+
# subordinate hashes.
|
88
|
+
# @param style (see Util.hash_key)
|
89
|
+
# @return obj or new object if hash keys were changed
|
90
|
+
def self.hash_keys(obj, style = nil)
|
94
91
|
return obj.collect {|o| hash_keys(o, style)} if obj.is_a? Array
|
95
92
|
return obj unless obj.is_a? Hash
|
96
93
|
obj.each_with_object({}) {|(k, v), h|
|
@@ -98,64 +95,112 @@ class Util
|
|
98
95
|
}
|
99
96
|
end
|
100
97
|
|
101
|
-
#
|
102
|
-
|
98
|
+
# handle ruby 1.8.7 compatibility for form encoding
|
99
|
+
if URI.respond_to?(:encode_www_form_component)
|
100
|
+
def self.encode_component(str) URI.encode_www_form_component(str) end #@private
|
101
|
+
def self.decode_component(str) URI.decode_www_form_component(str) end #@private
|
102
|
+
else
|
103
|
+
def self.encode_component(str) # @private
|
104
|
+
str.to_s.gsub(/([^ a-zA-Z0-9*_.-]+)/) {
|
105
|
+
'%' + $1.unpack('H2' * $1.size).join('%').upcase
|
106
|
+
}.tr(' ', '+')
|
107
|
+
end
|
108
|
+
def self.decode_component(str) # @private
|
109
|
+
str.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/) {[$1.delete('%')].pack('H*')}
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Takes an x-www-form-urlencoded string and returns a hash of utf-8 key/value
|
114
|
+
# pairs. Useful for OAuth parameters. Raises ArgumentError if a key occurs
|
103
115
|
# more than once, which is a restriction of OAuth query strings.
|
104
|
-
# OAuth parameters are case sensitive, scim parameters are case-insensitive
|
105
|
-
#
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
116
|
+
# OAuth parameters are case sensitive, scim parameters are case-insensitive.
|
117
|
+
# @see http://tools.ietf.org/html/rfc6749#section-3.1
|
118
|
+
# @param [String] url_encoded_pairs in an x-www-form-urlencoded string
|
119
|
+
# @param style (see Util.hash_key)
|
120
|
+
# @return [Hash] of key value pairs
|
121
|
+
def self.decode_form(url_encoded_pairs, style = nil)
|
122
|
+
pairs = {}
|
123
|
+
url_encoded_pairs.split(/[&;]/).each do |pair|
|
124
|
+
k, v = pair.split('=', 2).collect { |v| decode_component(v) }
|
125
|
+
raise "duplicate keys in form parameters" if pairs.key?(k = hash_key(k, style))
|
126
|
+
pairs[k] = v
|
111
127
|
end
|
128
|
+
pairs
|
112
129
|
rescue Exception => e
|
113
130
|
raise ArgumentError, e.message
|
114
131
|
end
|
115
132
|
|
133
|
+
# Encode an object into x-www-form-urlencoded string suitable for oauth2.
|
134
|
+
# @note that ruby 1.9.3 converts form components to utf-8. Ruby 1.8.7
|
135
|
+
# users must ensure all data is in utf-8 format before passing to form encode.
|
136
|
+
# @param [Hash] obj a hash of key/value pairs to be encoded.
|
137
|
+
# @see http://tools.ietf.org/html/rfc6749#section-3.1
|
138
|
+
def self.encode_form(obj)
|
139
|
+
obj.map {|k, v| encode_component(k) << '=' << encode_component(v)}.join('&')
|
140
|
+
end
|
141
|
+
|
116
142
|
# Converts +obj+ to JSON
|
143
|
+
# @return [String] obj in JSON form.
|
117
144
|
def self.json(obj) MultiJson.dump(obj) end
|
118
145
|
|
119
146
|
# Converts +obj+ to nicely formatted JSON
|
120
|
-
|
147
|
+
# @return [String] obj in formatted json
|
148
|
+
def self.json_pretty(obj) MultiJson.dump(obj, :pretty => true) end
|
121
149
|
|
122
150
|
# Converts +obj+ to a URL-safe base 64 encoded string
|
151
|
+
# @return [String]
|
123
152
|
def self.json_encode64(obj = {}) encode64(json(obj)) end
|
124
153
|
|
125
|
-
#
|
126
|
-
|
127
|
-
|
128
|
-
#
|
129
|
-
def self.
|
154
|
+
# Decodes base64 encoding of JSON data.
|
155
|
+
# @param [String] str
|
156
|
+
# @param style (see Util.hash_key)
|
157
|
+
# @return [Hash]
|
158
|
+
def self.json_decode64(str, style = nil) json_parse(decode64(str), style) end
|
159
|
+
|
160
|
+
# Encodes +obj+ as a URL-safe base 64 encoded string, with trailing padding removed.
|
161
|
+
# @return [String]
|
162
|
+
def self.encode64(obj)
|
163
|
+
str = Base64.respond_to?(:urlsafe_encode64)? Base64.urlsafe_encode64(obj):
|
164
|
+
[obj].pack("m").tr("+/", "-_")
|
165
|
+
str.gsub!(/(\n|=*$)/, '')
|
166
|
+
str
|
167
|
+
end
|
130
168
|
|
131
|
-
#
|
169
|
+
# Decodes a URL-safe base 64 encoded string. Adds padding if necessary.
|
170
|
+
# @return [String] decoded string
|
132
171
|
def self.decode64(str)
|
133
172
|
return unless str
|
134
173
|
pad = str.length % 4
|
135
|
-
str
|
136
|
-
Base64
|
174
|
+
str = str + '=' * (4 - pad) if pad > 0
|
175
|
+
Base64.respond_to?(:urlsafe_decode64) ?
|
176
|
+
Base64.urlsafe_decode64(str) : Base64.decode64(str.tr('-_', '+/'))
|
137
177
|
rescue ArgumentError
|
138
178
|
raise DecodeError, "invalid base64 encoding"
|
139
179
|
end
|
140
180
|
|
141
|
-
# Parses a JSON string
|
142
|
-
# see
|
143
|
-
|
181
|
+
# Parses a JSON string.
|
182
|
+
# @param style (see Util.hash_key)
|
183
|
+
# @return [Hash] parsed data
|
184
|
+
def self.json_parse(str, style = nil)
|
144
185
|
hash_keys!(MultiJson.load(str), style) if str && !str.empty?
|
145
186
|
rescue MultiJson::DecodeError
|
146
187
|
raise DecodeError, "json decoding error"
|
147
188
|
end
|
148
189
|
|
149
|
-
|
190
|
+
# Converts obj to a string and truncates if over limit.
|
191
|
+
# @return [String]
|
192
|
+
def self.truncate(obj, limit = 50)
|
150
193
|
return obj.to_s if limit == 0
|
151
194
|
limit = limit < 5 ? 1 : limit - 4
|
152
195
|
str = obj.to_s[0..limit]
|
153
196
|
str.length > limit ? str + '...': str
|
154
197
|
end
|
155
198
|
|
199
|
+
# Converts common input formats into array of strings.
|
156
200
|
# Many parameters in these classes can be given as arrays, or as a list of
|
157
201
|
# arguments separated by spaces or commas. This method handles the possible
|
158
|
-
# inputs and returns an array of
|
202
|
+
# inputs and returns an array of strings.
|
203
|
+
# @return [Array<String>]
|
159
204
|
def self.arglist(arg, default_arg = nil)
|
160
205
|
arg = default_arg unless arg
|
161
206
|
return arg if arg.nil? || arg.respond_to?(:join)
|
@@ -163,19 +208,25 @@ class Util
|
|
163
208
|
arg.split(/[\s\,]+/).reject { |e| e.empty? }
|
164
209
|
end
|
165
210
|
|
166
|
-
#
|
211
|
+
# Joins arrays of strings into a single string. Reverse of {Util.arglist}.
|
212
|
+
# @param [Object, #join] arg
|
213
|
+
# @param [String] delim delimiter to put between strings.
|
214
|
+
# @return [String]
|
167
215
|
def self.strlist(arg, delim = ' ')
|
168
216
|
arg.respond_to?(:join) ? arg.join(delim) : arg.to_s if arg
|
169
217
|
end
|
170
218
|
|
171
219
|
# Set the default logger used by the higher level classes.
|
220
|
+
# @param [String, Symbol] level such as info, debug trace.
|
221
|
+
# @param [IO] sink output for log messages, defaults to $stdout
|
222
|
+
# @return [Logger]
|
172
223
|
def self.default_logger(level = nil, sink = nil)
|
173
224
|
if sink || !@default_logger
|
174
225
|
@default_logger = Logger.new(sink || $stdout)
|
175
226
|
level = :info unless level
|
176
227
|
@default_logger.formatter = Proc.new { |severity, time, pname, msg| puts msg }
|
177
228
|
end
|
178
|
-
@default_logger.level = Logger::Severity.const_get(level.upcase) if level
|
229
|
+
@default_logger.level = Logger::Severity.const_get(level.to_s.upcase) if level
|
179
230
|
@default_logger
|
180
231
|
end
|
181
232
|
|
data/lib/uaa/version.rb
CHANGED
data/spec/http_spec.rb
CHANGED
@@ -23,7 +23,7 @@ describe Http do
|
|
23
23
|
include SpecHelper
|
24
24
|
|
25
25
|
it "sets a request handler" do
|
26
|
-
set_request_handler do |
|
26
|
+
set_request_handler do |url, method, body, headers|
|
27
27
|
[200, "body", {"content-type" => "text/plain"}]
|
28
28
|
end
|
29
29
|
status, body, resp_headers = http_get("http://example.com")
|
@@ -0,0 +1,149 @@
|
|
1
|
+
#--
|
2
|
+
# Cloud Foundry 2012.02.03 Beta
|
3
|
+
# Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved.
|
4
|
+
#
|
5
|
+
# This product is licensed to you under the Apache License, Version 2.0 (the "License").
|
6
|
+
# You may not use this product except in compliance with the License.
|
7
|
+
#
|
8
|
+
# This product includes a number of subcomponents with
|
9
|
+
# separate copyright notices and license terms. Your use of these
|
10
|
+
# subcomponents is subject to the terms and conditions of the
|
11
|
+
# subcomponent's license, as noted in the LICENSE file.
|
12
|
+
#++
|
13
|
+
|
14
|
+
require 'spec_helper'
|
15
|
+
require 'uaa'
|
16
|
+
require 'pp'
|
17
|
+
|
18
|
+
# Example config for integration tests with defaults:
|
19
|
+
# ENV["UAA_CLIENT_ID"] = "admin"
|
20
|
+
# ENV["UAA_CLIENT_SECRET"] = "adminsecret"
|
21
|
+
# ENV["UAA_CLIENT_TARGET"] = "http://localhost:8080/uaa"
|
22
|
+
|
23
|
+
module CF::UAA
|
24
|
+
|
25
|
+
if ENV["UAA_CLIENT_TARGET"]
|
26
|
+
|
27
|
+
describe "UAA Integration:" do
|
28
|
+
|
29
|
+
def create_test_client
|
30
|
+
toki = TokenIssuer.new(@target, @admin_client, @admin_secret)
|
31
|
+
cr = Scim.new(@target, toki.client_credentials_grant.auth_header, :symbolize_keys => true)
|
32
|
+
@test_client = "test_client_#{Time.now.to_i}"
|
33
|
+
@test_secret = "+=tEsTsEcRet~!@"
|
34
|
+
gids = ["clients.read", "scim.read", "scim.write", "uaa.resource", "password.write"]
|
35
|
+
new_client = cr.add(:client, :client_id => @test_client, :client_secret => @test_secret,
|
36
|
+
:authorities => gids, :authorized_grant_types => ["client_credentials", "password"],
|
37
|
+
:scope => ["openid", "password.write"])
|
38
|
+
new_client[:client_id].should == @test_client
|
39
|
+
@username = "sam_#{Time.now.to_i}"
|
40
|
+
end
|
41
|
+
|
42
|
+
before :all do
|
43
|
+
#Util.default_logger(:trace)
|
44
|
+
@admin_client = ENV["UAA_CLIENT_ID"] || "admin"
|
45
|
+
@admin_secret = ENV["UAA_CLIENT_SECRET"] || "adminsecret"
|
46
|
+
@target = ENV["UAA_CLIENT_TARGET"]
|
47
|
+
@username = "sam_#{Time.now.to_i}"
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should report the uaa client version" do
|
51
|
+
VERSION.should =~ /\d.\d.\d/
|
52
|
+
end
|
53
|
+
|
54
|
+
it "makes sure the server is there by getting the prompts for an implicit grant" do
|
55
|
+
prompts = TokenIssuer.new(@target, @admin_client, @admin_secret).prompts
|
56
|
+
prompts.should_not be_nil
|
57
|
+
end
|
58
|
+
|
59
|
+
it "gets a token with client credentials" do
|
60
|
+
tkn = TokenIssuer.new(@target, @admin_client, @admin_secret).client_credentials_grant
|
61
|
+
tkn.auth_header.should =~ /^bearer\s/i
|
62
|
+
info = TokenCoder.decode(tkn.info["access_token"], :verify => false, :symbolize_keys => true)
|
63
|
+
info[:exp].should be
|
64
|
+
info[:jti].should be
|
65
|
+
end
|
66
|
+
|
67
|
+
context "as a client," do
|
68
|
+
|
69
|
+
before :all do
|
70
|
+
create_test_client
|
71
|
+
toki = TokenIssuer.new(@target, @test_client, @test_secret)
|
72
|
+
@scim = Scim.new(@target, toki.client_credentials_grant.auth_header, :symbolize_keys => true)
|
73
|
+
@user_pwd = "sam's P@55w0rd~!`@\#\$%^&*()_/{}[]\\|:\";',.<>?/"
|
74
|
+
usr = @scim.add(:user, :username => @username, :password => @user_pwd,
|
75
|
+
:emails => [{:value => "sam@example.com"}],
|
76
|
+
:name => {:givenname => "none", :familyname => "none"})
|
77
|
+
@user_id = usr[:id]
|
78
|
+
end
|
79
|
+
|
80
|
+
after :all do
|
81
|
+
# TODO: delete user, delete test client
|
82
|
+
end
|
83
|
+
|
84
|
+
it "creates a user" do
|
85
|
+
@user_id.should be
|
86
|
+
end
|
87
|
+
|
88
|
+
it "finds the user by name" do
|
89
|
+
@scim.id(:user, @username).should == @user_id
|
90
|
+
end
|
91
|
+
|
92
|
+
it "gets the user by id" do
|
93
|
+
user_info = @scim.get(:user, @user_id)
|
94
|
+
user_info[:id].should == @user_id
|
95
|
+
user_info[:username].should == @username
|
96
|
+
end
|
97
|
+
|
98
|
+
it "gets a user token by an implicit grant" do
|
99
|
+
@toki = TokenIssuer.new(@target, "vmc")
|
100
|
+
token = @toki.implicit_grant_with_creds(:username => @username, :password => @user_pwd)
|
101
|
+
token.info["access_token"].should be
|
102
|
+
info = Misc.whoami(@target, token.auth_header)
|
103
|
+
info["user_name"].should == @username
|
104
|
+
contents = TokenCoder.decode(token.info["access_token"], :verify => false)
|
105
|
+
contents["user_name"].should == @username
|
106
|
+
end
|
107
|
+
|
108
|
+
it "changes the user's password by name" do
|
109
|
+
@scim.change_password(@scim.id(:user, @username), "newpassword")[:status].should == "ok"
|
110
|
+
end
|
111
|
+
|
112
|
+
it "lists all users" do
|
113
|
+
user_info = @scim.query(:user)
|
114
|
+
user_info.should_not be_nil
|
115
|
+
end
|
116
|
+
|
117
|
+
if ENV["UAA_CLIENT_LOGIN"]
|
118
|
+
it "should get a uri to be sent to the user agent to initiate autologin" do
|
119
|
+
logn = ENV["UAA_CLIENT_LOGIN"]
|
120
|
+
toki = TokenIssuer.new(logn, @test_client, @test_secret)
|
121
|
+
redir_uri = "http://call.back/uri_path"
|
122
|
+
uri_parts = toki.autologin_uri(redir_uri, :username => @username,
|
123
|
+
:password => "newpassword").split('?')
|
124
|
+
uri_parts[0].should == "#{logn}/oauth/authorize"
|
125
|
+
params = Util.decode_form(uri_parts[1], :sym)
|
126
|
+
params[:response_type].should == "code"
|
127
|
+
params[:client_id].should == @client_id
|
128
|
+
params[:scope].should be_nil
|
129
|
+
params[:redirect_uri].should == redir_uri
|
130
|
+
params[:state].should_not be_nil
|
131
|
+
params[:code].should_not be_nil
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
it "deletes the user" do
|
136
|
+
@scim.delete(:user, @user_id)
|
137
|
+
expect { @scim.id(:user, @username) }.to raise_exception(NotFound)
|
138
|
+
expect { @scim.get(:user, @user_id) }.to raise_exception(NotFound)
|
139
|
+
end
|
140
|
+
|
141
|
+
it "complains about an attempt to delete a non-existent user" do
|
142
|
+
expect { @scim.delete(:user, "non-existent-user") }.to raise_exception(NotFound)
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
end end
|
148
|
+
|
149
|
+
end
|
data/spec/scim_spec.rb
CHANGED
@@ -43,14 +43,14 @@ describe Scim do
|
|
43
43
|
check_headers(headers, :json, :json)
|
44
44
|
[200, '{"ID":"id12345"}', {"content-type" => "application/json"}]
|
45
45
|
end
|
46
|
-
result = subject.add(:user, hair
|
47
|
-
eye_color
|
46
|
+
result = subject.add(:user, :hair => "brown", :shoe_size => "large",
|
47
|
+
:eye_color => ["blue", "green"], :name => "fred")
|
48
48
|
result["id"].should == "id12345"
|
49
49
|
end
|
50
50
|
|
51
51
|
it "replaces an object" do
|
52
|
-
obj = {hair
|
53
|
-
name
|
52
|
+
obj = {:hair => "black", :shoe_size => "medium", :eye_color => ["hazel", "brown"],
|
53
|
+
:name => "fredrick", :meta => {:version => 'v567'}, :id => "id12345"}
|
54
54
|
subject.set_request_handler do |url, method, body, headers|
|
55
55
|
url.should == "#{@target}/Users/id12345"
|
56
56
|
method.should == :put
|
@@ -75,17 +75,18 @@ describe Scim do
|
|
75
75
|
|
76
76
|
it "pages through all objects" do
|
77
77
|
subject.set_request_handler do |url, method, body, headers|
|
78
|
-
url.should =~ %r{^#{@target}/Users\?
|
78
|
+
url.should =~ %r{^#{@target}/Users\?}
|
79
|
+
url.should =~ %r{[\?&]attributes=id(&|$)}
|
80
|
+
url.should =~ %r{[\?&]startIndex=[12](&|$)}
|
79
81
|
method.should == :get
|
80
82
|
check_headers(headers, nil, :json)
|
81
|
-
reply = url =~ /1
|
83
|
+
reply = url =~ /startIndex=1/ ?
|
82
84
|
'{"TotalResults":2,"ItemsPerPage":1,"StartIndex":1,"RESOURCES":[{"id":"id12345"}]}' :
|
83
85
|
'{"TotalResults":2,"ItemsPerPage":1,"StartIndex":2,"RESOURCES":[{"id":"id67890"}]}'
|
84
86
|
[200, reply, {"content-type" => "application/json"}]
|
85
87
|
end
|
86
|
-
result = subject.all_pages(:user, attributes
|
87
|
-
result[0]['id'].should == "id12345"
|
88
|
-
result[1]['id'].should == "id67890"
|
88
|
+
result = subject.all_pages(:user, :attributes => 'id')
|
89
|
+
[result[0]['id'], result[1]['id']].to_set.should == ["id12345", "id67890"].to_set
|
89
90
|
end
|
90
91
|
|
91
92
|
it "changes a user's password" do
|
@@ -93,7 +94,7 @@ describe Scim do
|
|
93
94
|
url.should == "#{@target}/Users/id12345/password"
|
94
95
|
method.should == :put
|
95
96
|
check_headers(headers, :json, :json)
|
96
|
-
body.should
|
97
|
+
body.should include('"password":"newpwd"', '"oldPassword":"oldpwd"')
|
97
98
|
[200, '{"id":"id12345"}', {"content-type" => "application/json"}]
|
98
99
|
end
|
99
100
|
result = subject.change_password("id12345", "newpwd", "oldpwd")
|
@@ -105,7 +106,7 @@ describe Scim do
|
|
105
106
|
url.should == "#{@target}/oauth/clients/id12345/secret"
|
106
107
|
method.should == :put
|
107
108
|
check_headers(headers, :json, :json)
|
108
|
-
body.should
|
109
|
+
body.should include('"secret":"newpwd"', '"oldSecret":"oldpwd"')
|
109
110
|
[200, '{"id":"id12345"}', {"content-type" => "application/json"}]
|
110
111
|
end
|
111
112
|
result = subject.change_secret("id12345", "newpwd", "oldpwd")
|